hysteria/extras/outbounds/interface.go

133 lines
3.8 KiB
Go

package outbounds
import (
"net"
"strconv"
"github.com/apernet/hysteria/core/v2/server"
)
// The PluggableOutbound system is designed to function in a chain-like manner.
// Not every outbound is an actual outbound; some are just wrappers around other
// outbounds, such as custom resolvers, ACL engine, etc. It is a pipeline where
// each stage can check (and optionally modify) the request before passing it
// on to the next stage. The last stage in the pipeline is always a real outbound
// that actually implements the logic of connecting to the remote server.
// There can also be instances of branching, where requests can be sent to
// different outbound sub-pipelines based on some criteria.
// PluggableOutbound differs from the built-in Outbound interface from Hysteria core
// in that it uses an AddrEx struct for addresses instead of a string. Because of this
// difference, we need a special PluggableOutboundAdapter to convert between the two
// for use in Hysteria core config.
type PluggableOutbound interface {
TCP(reqAddr *AddrEx) (net.Conn, error)
UDP(reqAddr *AddrEx) (UDPConn, error)
}
type UDPConn interface {
ReadFrom(b []byte) (int, *AddrEx, error)
WriteTo(b []byte, addr *AddrEx) (int, error)
Close() error
}
// AddrEx keeps both the original string representation of the address and
// the resolved IP addresses from the resolver, if any.
// The actual outbound implementations can choose to use either the string
// representation or the resolved IP addresses, depending on their capabilities.
// A SOCKS5 outbound, for example, should prefer the string representation
// because SOCKS5 protocol supports sending the hostname to the proxy server
// and let the proxy server do the DNS resolution.
type AddrEx struct {
Host string // String representation of the host, can be an IP or a domain name
Port uint16
ResolveInfo *ResolveInfo // Only set if there's a resolver in the pipeline
}
func (a *AddrEx) String() string {
return net.JoinHostPort(a.Host, strconv.Itoa(int(a.Port)))
}
// ResolveInfo contains the resolved IP addresses from the resolver, and any
// error that occurred during the resolution.
// Note that there could be no error but also no resolved IP addresses,
// or there could be an error but also some resolved IP addresses.
// It's up to the actual outbound implementation to decide how to handle
// these cases.
type ResolveInfo struct {
IPv4 net.IP
IPv6 net.IP
Err error
}
var _ server.Outbound = (*PluggableOutboundAdapter)(nil)
type PluggableOutboundAdapter struct {
PluggableOutbound
}
func (a *PluggableOutboundAdapter) TCP(reqAddr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(reqAddr)
if err != nil {
return nil, err
}
portInt, err := strconv.Atoi(port)
if err != nil {
return nil, err
}
return a.PluggableOutbound.TCP(&AddrEx{
Host: host,
Port: uint16(portInt),
})
}
func (a *PluggableOutboundAdapter) UDP(reqAddr string) (server.UDPConn, error) {
host, port, err := net.SplitHostPort(reqAddr)
if err != nil {
return nil, err
}
portInt, err := strconv.Atoi(port)
if err != nil {
return nil, err
}
conn, err := a.PluggableOutbound.UDP(&AddrEx{
Host: host,
Port: uint16(portInt),
})
if err != nil {
return nil, err
}
return &udpConnAdapter{conn}, nil
}
type udpConnAdapter struct {
UDPConn
}
func (u *udpConnAdapter) ReadFrom(b []byte) (int, string, error) {
n, addr, err := u.UDPConn.ReadFrom(b)
if addr != nil {
return n, addr.String(), err
} else {
return n, "", err
}
}
func (u *udpConnAdapter) WriteTo(b []byte, addr string) (int, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return 0, err
}
portInt, err := strconv.Atoi(port)
if err != nil {
return 0, err
}
return u.UDPConn.WriteTo(b, &AddrEx{
Host: host,
Port: uint16(portInt),
})
}
func (u *udpConnAdapter) Close() error {
return u.UDPConn.Close()
}