package outbound import ( "context" "crypto/tls" "errors" "fmt" "io" "net" "strconv" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/proxydialer" tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/socks5" ) type Socks5 struct { *Base option *Socks5Option user string pass string tls bool skipCertVerify bool tlsConfig *tls.Config } type Socks5Option struct { BasicOption Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` UserName string `proxy:"username,omitempty"` Password string `proxy:"password,omitempty"` TLS bool `proxy:"tls,omitempty"` UDP bool `proxy:"udp,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"` } // StreamConn implements C.ProxyAdapter func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { if ss.tls { cc := tls.Client(c, ss.tlsConfig) ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) defer cancel() err := cc.HandshakeContext(ctx) c = cc if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } } var user *socks5.User if ss.user != "" { user = &socks5.User{ Username: ss.user, Password: ss.pass, } } if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil { return nil, err } return c, nil } // DialContext implements C.ProxyAdapter func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.Base.DialOptions(opts...)...), metadata) } // DialContextWithDialer implements C.ProxyAdapter func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) { if len(ss.option.DialerProxy) > 0 { dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer) if err != nil { return nil, err } } c, err := dialer.DialContext(ctx, "tcp", ss.addr) if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } tcpKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) }(c) c, err = ss.StreamConn(c, metadata) if err != nil { return nil, err } return NewConn(c, ss), nil } // SupportWithDialer implements C.ProxyAdapter func (ss *Socks5) SupportWithDialer() bool { return true } // ListenPacketContext implements C.ProxyAdapter func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { var cDialer C.Dialer = dialer.NewDialer(ss.Base.DialOptions(opts...)...) if len(ss.option.DialerProxy) > 0 { cDialer, err = proxydialer.NewByName(ss.option.DialerProxy, cDialer) if err != nil { return nil, err } } c, err := cDialer.DialContext(ctx, "tcp", ss.addr) if err != nil { err = fmt.Errorf("%s connect error: %w", ss.addr, err) return } if ss.tls { cc := tls.Client(c, ss.tlsConfig) ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) defer cancel() err = cc.HandshakeContext(ctx) c = cc } defer func(c net.Conn) { safeConnClose(c, err) }(c) 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 } // Support unspecified UDP bind address. bindUDPAddr := bindAddr.UDPAddr() if bindUDPAddr == nil { err = errors.New("invalid UDP bind address") return } else if bindUDPAddr.IP.IsUnspecified() { serverAddr, err := resolveUDPAddr(ctx, "udp", ss.Addr()) if err != nil { return nil, err } bindUDPAddr.IP = serverAddr.IP } pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", bindUDPAddr.AddrPort().Addr()), "", ss.Base.DialOptions(opts...)...) if err != nil { return } 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() }() return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil } func NewSocks5(option Socks5Option) (*Socks5, error) { var tlsConfig *tls.Config if option.TLS { tlsConfig = &tls.Config{ InsecureSkipVerify: option.SkipCertVerify, ServerName: option.Server, } if len(option.Fingerprint) == 0 { tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { var err error if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil { return nil, err } } } return &Socks5{ Base: &Base{ name: option.Name, addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.Socks5, udp: option.UDP, tfo: option.TFO, iface: option.Interface, rmark: option.RoutingMark, prefer: C.NewDNSPrefer(option.IPVersion), }, option: &option, user: option.UserName, pass: option.Password, tls: option.TLS, skipCertVerify: option.SkipCertVerify, tlsConfig: tlsConfig, }, nil } type socksPacketConn struct { net.PacketConn rAddr net.Addr tcpConn net.Conn } func (uc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) if err != nil { return } return uc.PacketConn.WriteTo(packet, uc.rAddr) } func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { n, _, e := uc.PacketConn.ReadFrom(b) if e != nil { return 0, nil, e } addr, payload, err := socks5.DecodeUDPPacket(b) if err != nil { return 0, nil, err } udpAddr := addr.UDPAddr() if udpAddr == nil { return 0, nil, errors.New("parse udp addr error") } // due to DecodeUDPPacket is mutable, record addr length copy(b, payload) return n - len(addr) - 3, udpAddr, nil } func (uc *socksPacketConn) Close() error { uc.tcpConn.Close() return uc.PacketConn.Close() }