diff options
author | Shelikhoo <xiaokangwang@outlook.com> | 2023-10-18 16:23:31 +0100 |
---|---|---|
committer | Shelikhoo <xiaokangwang@outlook.com> | 2023-10-24 17:42:46 +0100 |
commit | 8b46e60553b9de34df12079906f72559361ea20d (patch) | |
tree | b50ebcb9965317ec21d01b1c3d3a20540dfa9c3a /common | |
parent | 6b0421db0d0f7f24fc0e1251a8a2803eeb06c5fe (diff) | |
download | snowflake-8b46e60553b9de34df12079906f72559361ea20d.tar.gz snowflake-8b46e60553b9de34df12079906f72559361ea20d.zip |
Add common proxy utilities
Diffstat (limited to 'common')
-rw-r--r-- | common/proxy/check.go | 18 | ||||
-rw-r--r-- | common/proxy/client.go | 253 |
2 files changed, 271 insertions, 0 deletions
diff --git a/common/proxy/check.go b/common/proxy/check.go new file mode 100644 index 0000000..797b3eb --- /dev/null +++ b/common/proxy/check.go @@ -0,0 +1,18 @@ +package proxy + +import ( + "errors" + "net/url" + "strings" +) + +var errUnsupportedProxyType = errors.New("unsupported proxy type") + +func CheckProxyProtocolSupport(proxy *url.URL) error { + switch strings.ToLower(proxy.Scheme) { + case "socks5": + return nil + default: + return errUnsupportedProxyType + } +} diff --git a/common/proxy/client.go b/common/proxy/client.go new file mode 100644 index 0000000..cd1bd98 --- /dev/null +++ b/common/proxy/client.go @@ -0,0 +1,253 @@ +package proxy + +import ( + "context" + "errors" + "log" + "net" + "net/url" + "strconv" + "time" + + "github.com/miekg/dns" + "github.com/pion/transport/v2" + "github.com/txthinking/socks5" +) + +func NewSocks5UDPClient(addr *url.URL) SocksClient { + return SocksClient{addr: addr} +} + +type SocksClient struct { + addr *url.URL +} + +type SocksConn struct { + net.Conn + socks5Client *socks5.Client +} + +func (s SocksConn) SetReadBuffer(bytes int) error { + return nil +} + +func (s SocksConn) SetWriteBuffer(bytes int) error { + return nil +} + +func (s SocksConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) { + var buf [2000]byte + n, err = s.Conn.Read(buf[:]) + if err != nil { + return 0, nil, err + } + Datagram, err := socks5.NewDatagramFromBytes(buf[:n]) + if err != nil { + return 0, nil, err + } + addr, err = net.ResolveUDPAddr("udp", Datagram.Address()) + if err != nil { + return 0, nil, err + } + n = copy(b, Datagram.Data) + if n < len(Datagram.Data) { + return 0, nil, errors.New("short buffer") + } + return len(Datagram.Data), addr, nil +} + +func (s SocksConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) { + panic("unimplemented") +} + +func (s SocksConn) WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) { + + a, addrb, portb, err := socks5.ParseAddress(addr.String()) + if err != nil { + return 0, err + } + packet := socks5.NewDatagram(a, addrb, portb, b) + _, err = s.Conn.Write(packet.Bytes()) + if err != nil { + return 0, err + } + return len(b), nil +} + +func (s SocksConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) { + panic("unimplemented") +} + +func (sc *SocksClient) ListenPacket(network string, locAddr *net.UDPAddr) (transport.UDPConn, error) { + conn, err := sc.listenPacket() + if err != nil { + log.Println("[SOCKS5 Client Error] cannot listen packet", err) + } + return conn, err +} + +func (sc *SocksClient) listenPacket() (transport.UDPConn, error) { + var username, password string + if sc.addr.User != nil { + username = sc.addr.User.Username() + password, _ = sc.addr.User.Password() + } + client, err := socks5.NewClient( + sc.addr.Host, + username, password, 300, 300) + if err != nil { + return nil, err + } + + err = client.Negotiate(nil) + if err != nil { + return nil, err + } + + udpRequest := socks5.NewRequest(socks5.CmdUDP, socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00}) + + reply, err := client.Request(udpRequest) + if err != nil { + return nil, err + } + + udpServerAddr := socks5.ToAddress(reply.Atyp, reply.BndAddr, reply.BndPort) + + conn, err := net.Dial("udp", udpServerAddr) + if err != nil { + return nil, err + } + + return &SocksConn{conn, client}, nil +} + +func (s SocksConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + return s.WriteToUDP(p, addr.(*net.UDPAddr)) +} + +func (s SocksConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + return s.ReadFromUDP(p) +} + +func (s SocksConn) Read(b []byte) (int, error) { + panic("implement me") +} + +func (s SocksConn) RemoteAddr() net.Addr { + panic("implement me") +} + +func (s SocksConn) Write(b []byte) (int, error) { + panic("implement me") +} + +func (sc *SocksClient) ResolveUDPAddr(network string, address string) (*net.UDPAddr, error) { + dnsServer, err := net.ResolveUDPAddr("udp", "1.1.1.1:53") + if err != nil { + return nil, err + } + proxiedResolver := newDnsResolver(sc, dnsServer) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + host, port, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + ip, err := proxiedResolver.lookupIPAddr(ctx, host, network == "udp6") + if err != nil { + return nil, err + } + if len(ip) <= 0 { + return nil, errors.New("cannot resolve hostname: NXDOMAIN") + } + switch network { + case "udp4": + var v4IPAddr []net.IPAddr + for _, v := range ip { + if v.IP.To4() != nil { + v4IPAddr = append(v4IPAddr, v) + } + } + ip = v4IPAddr + case "udp6": + var v6IPAddr []net.IPAddr + for _, v := range ip { + if v.IP.To4() == nil { + v6IPAddr = append(v6IPAddr, v) + } + } + ip = v6IPAddr + case "udp": + default: + return nil, errors.New("unknown network") + } + + if len(ip) <= 0 { + return nil, errors.New("cannot resolve hostname: so suitable address") + } + + portInInt, err := strconv.ParseInt(port, 10, 32) + return &net.UDPAddr{ + IP: ip[0].IP, + Port: int(portInInt), + Zone: "", + }, nil +} + +func newDnsResolver(sc *SocksClient, + serverAddress net.Addr) *dnsResolver { + return &dnsResolver{sc: sc, serverAddress: serverAddress} +} + +type dnsResolver struct { + sc *SocksClient + serverAddress net.Addr +} + +func (r *dnsResolver) lookupIPAddr(ctx context.Context, host string, ipv6 bool) ([]net.IPAddr, error) { + packetConn, err := r.sc.listenPacket() + if err != nil { + return nil, err + } + msg := new(dns.Msg) + if !ipv6 { + msg.SetQuestion(dns.Fqdn(host), dns.TypeA) + } else { + msg.SetQuestion(dns.Fqdn(host), dns.TypeAAAA) + } + encodedMsg, err := msg.Pack() + if err != nil { + log.Println(err.Error()) + } + for i := 2; i >= 0; i-- { + _, err := packetConn.WriteTo(encodedMsg, r.serverAddress) + if err != nil { + log.Println(err.Error()) + } + } + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + go func() { + <-ctx.Done() + packetConn.Close() + }() + var dataBuf [1600]byte + n, _, err := packetConn.ReadFrom(dataBuf[:]) + if err != nil { + return nil, err + } + err = msg.Unpack(dataBuf[:n]) + if err != nil { + return nil, err + } + var returnedIPs []net.IPAddr + for _, resp := range msg.Answer { + switch respTyped := resp.(type) { + case *dns.A: + returnedIPs = append(returnedIPs, net.IPAddr{IP: respTyped.A}) + case *dns.AAAA: + returnedIPs = append(returnedIPs, net.IPAddr{IP: respTyped.AAAA}) + } + } + return returnedIPs, nil +} |