Feature: support relay Socks5 UDP

supports relaying of all UDP traffic except the HTTP outbound.
This commit is contained in:
yaling888 2022-05-24 21:58:20 +08:00
parent 68cf94a866
commit 6ad2cde909
3 changed files with 195 additions and 114 deletions

View File

@ -37,12 +37,59 @@ type Socks5Option struct {
// StreamConn implements C.ProxyAdapter // StreamConn implements C.ProxyAdapter
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
c, _, err = ss.streamConn(c, metadata)
return c, err
}
func (ss *Socks5) StreamSocks5PacketConn(c net.Conn, pc net.PacketConn, metadata *C.Metadata) (net.PacketConn, error) {
if c == nil {
return pc, fmt.Errorf("%s connect error: parameter net.Conn is nil", ss.addr)
}
if pc == nil {
return pc, fmt.Errorf("%s connect error: parameter net.PacketConn is nil", ss.addr)
}
cc, bindAddr, err := ss.streamConn(c, metadata)
if err != nil {
return pc, err
}
c = cc
go func() {
_, _ = io.Copy(io.Discard, c)
_ = c.Close()
// A UDP association terminates when the TCP connection that the UDP
// ASSOCIATE request arrived on terminates. RFC1928
_ = pc.Close()
}()
// Support unspecified UDP bind address.
bindUDPAddr := bindAddr.UDPAddr()
if bindUDPAddr == nil {
return pc, errors.New("invalid UDP bind address")
} else if bindUDPAddr.IP.IsUnspecified() {
serverAddr, err := resolveUDPAddr("udp", ss.Addr())
if err != nil {
return pc, err
}
bindUDPAddr.IP = serverAddr.IP
}
return &socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, nil
}
func (ss *Socks5) streamConn(c net.Conn, metadata *C.Metadata) (_ net.Conn, bindAddr socks5.Addr, err error) {
if ss.tls { if ss.tls {
cc := tls.Client(c, ss.tlsConfig) cc := tls.Client(c, ss.tlsConfig)
err := cc.Handshake() err := cc.Handshake()
c = cc c = cc
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return c, nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
} }
@ -53,10 +100,14 @@ func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
Password: ss.pass, Password: ss.pass,
} }
} }
if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
return nil, err if metadata.NetWork == C.UDP {
bindAddr, err = socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user)
} else {
bindAddr, err = socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user)
} }
return c, nil
return c, bindAddr, err
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@ -81,61 +132,24 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...) c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
if err != nil { if err != nil {
err = fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
return
}
if ss.tls {
cc := tls.Client(c, ss.tlsConfig)
err = cc.Handshake()
c = cc
} }
defer safeConnClose(c, err) defer safeConnClose(c, err)
tcpKeepAlive(c)
var user *socks5.User
if ss.user != "" {
user = &socks5.User{
Username: ss.user,
Password: ss.pass,
}
}
bindAddr, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user)
if err != nil {
err = fmt.Errorf("client hanshake error: %w", err)
return
}
pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...) pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...)
if err != nil { if err != nil {
return return
} }
go func() { tcpKeepAlive(c)
_, _ = io.Copy(io.Discard, c)
_ = c.Close()
// A UDP association terminates when the TCP connection that the UDP
// ASSOCIATE request arrived on terminates. RFC1928
_ = pc.Close()
}()
// Support unspecified UDP bind address. pc, err = ss.StreamSocks5PacketConn(c, pc, metadata)
bindUDPAddr := bindAddr.UDPAddr() if err != nil {
if bindUDPAddr == nil {
err = errors.New("invalid UDP bind address")
return return
} else if bindUDPAddr.IP.IsUnspecified() {
serverAddr, err := resolveUDPAddr("udp", ss.Addr())
if err != nil {
return nil, err
}
bindUDPAddr.IP = serverAddr.IP
} }
return NewPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil return NewPacketConn(pc, ss), nil
} }
func NewSocks5(option Socks5Option) *Socks5 { func NewSocks5(option Socks5Option) *Socks5 {

View File

@ -7,6 +7,7 @@ import (
"net" "net"
"net/netip" "net/netip"
"github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
@ -37,30 +38,12 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...) return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
} }
first := proxies[0] c, err := r.streamContext(ctx, proxies, r.Base.DialOptions(opts...)...)
last := proxies[len(proxies)-1]
c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) return nil, err
}
tcpKeepAlive(c)
var currentMeta *C.Metadata
for _, proxy := range proxies[1:] {
currentMeta, err = addrToMetadata(proxy.Addr())
if err != nil {
return nil, err
}
c, err = first.StreamConn(c, currentMeta)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
first = proxy
} }
last := proxies[len(proxies)-1]
c, err = last.StreamConn(c, metadata) c, err = last.StreamConn(c, metadata)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err) return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
@ -78,7 +61,9 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
} }
} }
switch len(proxies) { length := len(proxies)
switch length {
case 0: case 0:
return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...) return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
case 1: case 1:
@ -90,12 +75,16 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
} }
var ( var (
first = proxies[0] firstIndex = 0
last = proxies[len(proxies)-1] nextIndex = 1
rawUDPRelay bool lastUDPOverTCPIndex = -1
udpOverTCPEndIndex = -1 rawUDPRelay = false
first = proxies[firstIndex]
last = proxies[length-1]
c net.Conn c net.Conn
cc net.Conn
err error err error
currentMeta *C.Metadata currentMeta *C.Metadata
) )
@ -104,38 +93,47 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported in relay chains", last.Addr(), last.Name()) return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported in relay chains", last.Addr(), last.Name())
} }
rawUDPRelay, udpOverTCPEndIndex = isRawUDPRelay(proxies) rawUDPRelay, lastUDPOverTCPIndex = isRawUDPRelay(proxies)
if rawUDPRelay { if first.Type() == C.Socks5 {
cc1, err1 := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
if err1 != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
cc = cc1
tcpKeepAlive(cc)
var pc net.PacketConn
pc, err = dialer.ListenPacket(ctx, "udp", "", r.Base.DialOptions(opts...)...)
c = outbound.WrapConn(pc)
} else if rawUDPRelay {
var pc net.PacketConn var pc net.PacketConn
pc, err = dialer.ListenPacket(ctx, "udp", "", r.Base.DialOptions(opts...)...) pc, err = dialer.ListenPacket(ctx, "udp", "", r.Base.DialOptions(opts...)...)
c = outbound.WrapConn(pc) c = outbound.WrapConn(pc)
} else { } else {
c, err = dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...) firstIndex = lastUDPOverTCPIndex
nextIndex = firstIndex + 1
first = proxies[firstIndex]
c, err = r.streamContext(ctx, proxies[:nextIndex], r.Base.DialOptions(opts...)...)
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
} }
defer func() { if nextIndex < length {
if err != nil && c != nil { for i, proxy := range proxies[nextIndex:] { // raw udp in loop
_ = c.Close() currentMeta, err = addrToMetadata(proxy.Addr())
} if err != nil {
}() return nil, err
}
currentMeta.NetWork = C.UDP
for i, proxy := range proxies[1:] {
currentMeta, err = addrToMetadata(proxy.Addr())
if err != nil {
return nil, err
}
if outbound.IsPacketConn(c) || udpOverTCPEndIndex == i {
if !isRawUDP(first) && !first.SupportUDP() { if !isRawUDP(first) && !first.SupportUDP() {
return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported in relay chains", first.Addr(), first.Name()) return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported in relay chains", first.Addr(), first.Name())
} }
if !currentMeta.Resolved() && needResolveIP(first) { if needResolveIP(first, currentMeta) {
var ip netip.Addr var ip netip.Addr
ip, err = resolver.ResolveProxyServerHost(currentMeta.Host) ip, err = resolver.ResolveProxyServerHost(currentMeta.Host)
if err != nil { if err != nil {
@ -144,28 +142,57 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
currentMeta.DstIP = ip currentMeta.DstIP = ip
} }
currentMeta.NetWork = C.UDP if cc != nil { // socks5
c, err = first.StreamPacketConn(c, currentMeta) c, err = streamSocks5PacketConn(first, cc, c, currentMeta)
} else { cc = nil
c, err = first.StreamConn(c, currentMeta) } else {
} c, err = first.StreamPacketConn(c, currentMeta)
}
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
} }
first = proxy if proxy.Type() == C.Socks5 {
endIndex := nextIndex + i + 1
cc, err = r.streamContext(ctx, proxies[:endIndex], r.Base.DialOptions(opts...)...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
}
first = proxy
}
} }
c, err = last.StreamPacketConn(c, metadata) if cc != nil {
c, err = streamSocks5PacketConn(last, cc, c, metadata)
} else {
c, err = last.StreamPacketConn(c, metadata)
}
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
} }
return outbound.NewPacketConn(c.(net.PacketConn), r), nil return outbound.NewPacketConn(c.(net.PacketConn), r), nil
} }
// SupportUDP implements C.ProxyAdapter
func (r *Relay) SupportUDP() bool {
proxies := r.rawProxies(true)
l := len(proxies)
if l == 0 {
return true
}
last := proxies[l-1]
return isRawUDP(last) || last.SupportUDP()
}
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (r *Relay) MarshalJSON() ([]byte, error) { func (r *Relay) MarshalJSON() ([]byte, error) {
var all []string var all []string
@ -200,12 +227,47 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy {
return proxies return proxies
} }
func (r *Relay) streamContext(ctx context.Context, proxies []C.Proxy, opts ...dialer.Option) (net.Conn, error) {
first := proxies[0]
c, err := dialer.DialContext(ctx, "tcp", first.Addr(), opts...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
tcpKeepAlive(c)
if len(proxies) > 1 {
var currentMeta *C.Metadata
for _, proxy := range proxies[1:] {
currentMeta, err = addrToMetadata(proxy.Addr())
if err != nil {
return nil, err
}
c, err = first.StreamConn(c, currentMeta)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
first = proxy
}
}
return c, nil
}
func streamSocks5PacketConn(proxy C.Proxy, cc, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
pc, err := proxy.(*adapter.Proxy).ProxyAdapter.(*outbound.Socks5).StreamSocks5PacketConn(cc, c.(net.PacketConn), metadata)
return outbound.WrapConn(pc), err
}
func isRawUDPRelay(proxies []C.Proxy) (bool, int) { func isRawUDPRelay(proxies []C.Proxy) (bool, int) {
var ( var (
lastIndex = len(proxies) - 1 lastIndex = len(proxies) - 1
isLastRawUDP = isRawUDP(proxies[lastIndex]) last = proxies[lastIndex]
isUDPOverTCP = false isLastRawUDP = isRawUDP(last)
udpOverTCPEndIndex = -1 isUDPOverTCP = false
lastUDPOverTCPIndex = -1
) )
for i := lastIndex; i >= 0; i-- { for i := lastIndex; i >= 0; i-- {
@ -213,26 +275,33 @@ func isRawUDPRelay(proxies []C.Proxy) (bool, int) {
isUDPOverTCP = isUDPOverTCP || !isRawUDP(p) isUDPOverTCP = isUDPOverTCP || !isRawUDP(p)
if isLastRawUDP && isUDPOverTCP && udpOverTCPEndIndex == -1 { if isLastRawUDP && isUDPOverTCP && lastUDPOverTCPIndex == -1 {
udpOverTCPEndIndex = i lastUDPOverTCPIndex = i
} }
} }
return !isUDPOverTCP, udpOverTCPEndIndex if !isLastRawUDP {
lastUDPOverTCPIndex = lastIndex
}
return !isUDPOverTCP, lastUDPOverTCPIndex
} }
func isRawUDP(proxy C.ProxyAdapter) bool { func isRawUDP(proxy C.ProxyAdapter) bool {
if proxy.Type() == C.Shadowsocks || proxy.Type() == C.ShadowsocksR { if proxy.Type() == C.Shadowsocks || proxy.Type() == C.ShadowsocksR || proxy.Type() == C.Socks5 {
return true return true
} }
return false return false
} }
func needResolveIP(proxy C.ProxyAdapter) bool { func needResolveIP(proxy C.ProxyAdapter, metadata *C.Metadata) bool {
if proxy.Type() == C.Vmess || proxy.Type() == C.Vless { if metadata.Resolved() {
return true return false
} }
return false if proxy.Type() != C.Vmess && proxy.Type() != C.Vless {
return false
}
return true
} }
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay { func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
@ -240,7 +309,6 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re
Base: outbound.NewBase(outbound.BaseOption{ Base: outbound.NewBase(outbound.BaseOption{
Name: option.Name, Name: option.Name,
Type: C.Relay, Type: C.Relay,
UDP: true,
Interface: option.Interface, Interface: option.Interface,
RoutingMark: option.RoutingMark, RoutingMark: option.RoutingMark,
}), }),

View File

@ -24,8 +24,7 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
DstIP: netip.Addr{}, DstIP: netip.Addr{},
DstPort: port, DstPort: port,
} }
err = nil return addr, nil
return
} else if ip.Is4() { } else if ip.Is4() {
addr = &C.Metadata{ addr = &C.Metadata{
AddrType: C.AtypIPv4, AddrType: C.AtypIPv4,