mirror of
https://github.com/apernet/hysteria.git
synced 2025-04-03 20:47:38 +03:00
133 lines
3.8 KiB
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()
|
|
}
|