diff --git a/cmd/main.go b/cmd/main.go index 8ad1cb9..3e07054 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -80,7 +80,7 @@ func init() { // Proxy args.ProxyServer = flag.String("proxyServer", "", "Proxy server address") - args.UdpTimeout = flag.Duration("udpTimeout", 60*time.Second, "UDP session timeout") + args.UdpTimeout = flag.Duration("udpTimeout", 30*time.Second, "UDP session timeout") } func main() { @@ -134,7 +134,7 @@ func main() { log.Fatalf("invalid proxy server address: %v", err) } proxyHost := proxyAddr.IP.String() - proxyPort := uint16(proxyAddr.Port) + proxyPort := proxyAddr.Port core.RegisterTCPConnHandler(proxy.NewTCPHandler(proxyHost, proxyPort, fakeDns, sessionStater)) core.RegisterUDPConnHandler(proxy.NewUDPHandler(proxyHost, proxyPort, *args.UdpTimeout, fakeDns, sessionStater)) diff --git a/proxy/socks.go b/proxy/socks.go deleted file mode 100644 index aa3d906..0000000 --- a/proxy/socks.go +++ /dev/null @@ -1,164 +0,0 @@ -// Code in this file are grabbed from https://github.com/nadoo/glider, which -// is also referencing another repo: https://github.com/shadowsocks/go-shadowsocks2 - -package proxy - -import ( - "errors" - "io" - "net" - "strconv" -) - -const socks5Version = 5 - -// SOCKS request commands as defined in RFC 1928 section 4. -const ( - // socks5Connect = 1 - // socks5Bind = 2 - socks5UDPAssociate = 3 -) - -// SOCKS address types as defined in RFC 1928 section 5. -const ( - socks5IP4 = 1 - socks5Domain = 3 - socks5IP6 = 4 -) - -var socks5Errors = []error{ - errors.New(""), - errors.New("general failure"), - errors.New("connection forbidden"), - errors.New("network unreachable"), - errors.New("host unreachable"), - errors.New("connection refused"), - errors.New("TTL expired"), - errors.New("command not supported"), - errors.New("address type not supported"), - errors.New("socks5UDPAssociate"), -} - -// MaxAddrLen is the maximum size of SOCKS address in bytes. -const MaxAddrLen = 1 + 1 + 255 + 2 - -// ATYP return the address type -func ATYP(b byte) int { - return int(b &^ 0x8) -} - -// Addr represents a SOCKS address as defined in RFC 1928 section 5. -type Addr []byte - -// String serializes SOCKS address a to string form. -func (a Addr) String() string { - var host, port string - - switch ATYP(a[0]) { // address type - case socks5Domain: - host = string(a[2 : 2+int(a[1])]) - port = strconv.Itoa((int(a[2+int(a[1])]) << 8) | int(a[2+int(a[1])+1])) - case socks5IP4: - host = net.IP(a[1 : 1+net.IPv4len]).String() - port = strconv.Itoa((int(a[1+net.IPv4len]) << 8) | int(a[1+net.IPv4len+1])) - case socks5IP6: - host = net.IP(a[1 : 1+net.IPv6len]).String() - port = strconv.Itoa((int(a[1+net.IPv6len]) << 8) | int(a[1+net.IPv6len+1])) - } - - return net.JoinHostPort(host, port) -} - -// ParseAddr parses the address in string s. Returns nil if failed. -func ParseAddr(s string) Addr { - var addr Addr - host, port, err := net.SplitHostPort(s) - if err != nil { - return nil - } - if ip := net.ParseIP(host); ip != nil { - if ip4 := ip.To4(); ip4 != nil { - addr = make([]byte, 1+net.IPv4len+2) - addr[0] = socks5IP4 - copy(addr[1:], ip4) - } else { - addr = make([]byte, 1+net.IPv6len+2) - addr[0] = socks5IP6 - copy(addr[1:], ip) - } - } else { - if len(host) > 255 { - return nil - } - addr = make([]byte, 1+1+len(host)+2) - addr[0] = socks5Domain - addr[1] = byte(len(host)) - copy(addr[2:], host) - } - - portUint, err := strconv.ParseUint(port, 10, 16) - if err != nil { - return nil - } - - addr[len(addr)-2], addr[len(addr)-1] = byte(portUint>>8), byte(portUint) - - return addr -} - -func readAddr(r io.Reader, b []byte) (Addr, error) { - if len(b) < MaxAddrLen { - return nil, io.ErrShortBuffer - } - _, err := io.ReadFull(r, b[:1]) // read 1st byte for address type - if err != nil { - return nil, err - } - - switch ATYP(b[0]) { - case socks5Domain: - _, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length - if err != nil { - return nil, err - } - _, err = io.ReadFull(r, b[2:2+int(b[1])+2]) - return b[:1+1+int(b[1])+2], err - case socks5IP4: - _, err = io.ReadFull(r, b[1:1+net.IPv4len+2]) - return b[:1+net.IPv4len+2], err - case socks5IP6: - _, err = io.ReadFull(r, b[1:1+net.IPv6len+2]) - return b[:1+net.IPv6len+2], err - } - - return nil, socks5Errors[8] -} - -// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed. -func SplitAddr(b []byte) Addr { - addrLen := 1 - if len(b) < addrLen { - return nil - } - - switch ATYP(b[0]) { - case socks5Domain: - if len(b) < 2 { - return nil - } - addrLen = 1 + 1 + int(b[1]) + 2 - case socks5IP4: - addrLen = 1 + net.IPv4len + 2 - case socks5IP6: - addrLen = 1 + net.IPv6len + 2 - default: - return nil - - } - - if len(b) < addrLen { - return nil - } - - return b[:addrLen] -} diff --git a/proxy/socks/client.go b/proxy/socks/client.go new file mode 100755 index 0000000..b4b5470 --- /dev/null +++ b/proxy/socks/client.go @@ -0,0 +1,101 @@ +package socks + +import ( + "fmt" + "io" + "io/ioutil" + "net" + "time" +) + +var tcpTimeout = 30 * time.Second + +func Dial(proxy, target string) (net.Conn, error) { + c, err := net.DialTimeout("tcp", proxy, tcpTimeout) + if err != nil { + return nil, fmt.Errorf("%s connect error", proxy) + } + + if _, err := ClientHandshake(c, ParseAddr(target), CmdConnect); err != nil { + return nil, err + } + return c, nil +} + +func DialUDP(proxy, target string) (_ net.PacketConn, _ net.Addr, err error) { + c, err := net.DialTimeout("tcp", proxy, tcpTimeout) + if err != nil { + err = fmt.Errorf("%s connect error", proxy) + return + } + + // tcp set keepalive + c.(*net.TCPConn).SetKeepAlive(true) + c.(*net.TCPConn).SetKeepAlivePeriod(tcpTimeout) + + defer func() { + if err != nil { + c.Close() + } + }() + + bindAddr, err := ClientHandshake(c, ParseAddr(target), CmdUDPAssociate) + if err != nil { + err = fmt.Errorf("%v client hanshake error", err) + return + } + + addr, err := net.ResolveUDPAddr("udp", bindAddr.String()) + if err != nil { + return + } + + targetAddr, err := net.ResolveUDPAddr("udp", target) + if err != nil { + return + } + + pc, err := net.ListenPacket("udp", "") + if err != nil { + return + } + + go func() { + io.Copy(ioutil.Discard, c) + c.Close() + // A UDP association terminates when the TCP connection that the UDP + // ASSOCIATE request arrived on terminates. RFC1928 + pc.Close() + }() + + return &socksUDPConn{PacketConn: pc, tcpConn: c, targetAddr: targetAddr}, addr, nil +} + +type socksUDPConn struct { + net.PacketConn + tcpConn net.Conn + targetAddr net.Addr +} + +func (c *socksUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { + packet, err := EncodeUDPPacket(c.targetAddr.String(), b) + if err != nil { + return + } + return c.PacketConn.WriteTo(packet, addr) +} + +func (c *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { + n, a, e := c.PacketConn.ReadFrom(b) + addr, payload, err := DecodeUDPPacket(b) + if err != nil { + return 0, nil, err + } + copy(b, payload) + return n - len(addr) - 3, a, e +} + +func (c *socksUDPConn) Close() error { + c.tcpConn.Close() + return c.PacketConn.Close() +} diff --git a/proxy/socks/socks.go b/proxy/socks/socks.go new file mode 100755 index 0000000..5584b14 --- /dev/null +++ b/proxy/socks/socks.go @@ -0,0 +1,246 @@ +package socks + +import ( + "bytes" + "errors" + "io" + "net" + "strconv" +) + +const Version = 5 + +// Error represents a SOCKS error +type Error byte + +func (err Error) Error() string { + return "SOCKS5 error: " + strconv.Itoa(int(err)) +} + +// Command is request commands as defined in RFC 1928 section 4. +type Command = uint8 + +// SOCKS request commands as defined in RFC 1928 section 4. +const ( + CmdConnect Command = 1 + CmdBind Command = 2 + CmdUDPAssociate Command = 3 +) + +// SOCKS address types as defined in RFC 1928 section 5. +const ( + AtypIPv4 = 1 + AtypDomainName = 3 + AtypIPv6 = 4 +) + +// MaxAddrLen is the maximum size of SOCKS address in bytes. +const MaxAddrLen = 1 + 1 + 255 + 2 + +// MaxAuthLen is the maximum size of user/password field in SOCKS5 Auth +const MaxAuthLen = 255 + +// Addr represents a SOCKS address as defined in RFC 1928 section 5. +type Addr []byte + +func (a Addr) String() string { + var host, port string + + switch a[0] { + case AtypDomainName: + host = string(a[2 : 2+int(a[1])]) + port = strconv.Itoa((int(a[2+int(a[1])]) << 8) | int(a[2+int(a[1])+1])) + case AtypIPv4: + host = net.IP(a[1 : 1+net.IPv4len]).String() + port = strconv.Itoa((int(a[1+net.IPv4len]) << 8) | int(a[1+net.IPv4len+1])) + case AtypIPv6: + host = net.IP(a[1 : 1+net.IPv6len]).String() + port = strconv.Itoa((int(a[1+net.IPv6len]) << 8) | int(a[1+net.IPv6len+1])) + } + + return net.JoinHostPort(host, port) +} + +// SOCKS errors as defined in RFC 1928 section 6. +const ( + ErrGeneralFailure = Error(1) + ErrConnectionNotAllowed = Error(2) + ErrNetworkUnreachable = Error(3) + ErrHostUnreachable = Error(4) + ErrConnectionRefused = Error(5) + ErrTTLExpired = Error(6) + ErrCommandNotSupported = Error(7) + ErrAddressNotSupported = Error(8) +) + +// Auth errors used to return a specific "Auth failed" error +var ErrAuth = errors.New("auth failed") + +// ClientHandshake fast-tracks SOCKS initialization to get target address to connect on client side. +func ClientHandshake(rw io.ReadWriter, addr Addr, command Command) (Addr, error) { + buf := make([]byte, MaxAddrLen) + var err error + + // VER, NMETHODS, METHODS + _, err = rw.Write([]byte{Version, 1, 0}) + + if err != nil { + return nil, err + } + + // VER, METHOD + if _, err := io.ReadFull(rw, buf[:2]); err != nil { + return nil, err + } + + if buf[0] != 5 { + return nil, errors.New("SOCKS version error") + } + + if buf[1] != 0 { + return nil, errors.New("SOCKS need auth") + } + + // VER, CMD, RSV, ADDR + if _, err := rw.Write(bytes.Join([][]byte{{5, command, 0}, addr}, []byte{})); err != nil { + return nil, err + } + + // VER, REP, RSV + if _, err := io.ReadFull(rw, buf[:3]); err != nil { + return nil, err + } + + return readAddr(rw, buf) +} + +func readAddr(r io.Reader, b []byte) (Addr, error) { + if len(b) < MaxAddrLen { + return nil, io.ErrShortBuffer + } + _, err := io.ReadFull(r, b[:1]) // read 1st byte for address type + if err != nil { + return nil, err + } + + switch b[0] { + case AtypDomainName: + _, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length + if err != nil { + return nil, err + } + domainLength := uint16(b[1]) + _, err = io.ReadFull(r, b[2:2+domainLength+2]) + return b[:1+1+domainLength+2], err + case AtypIPv4: + _, err = io.ReadFull(r, b[1:1+net.IPv4len+2]) + return b[:1+net.IPv4len+2], err + case AtypIPv6: + _, err = io.ReadFull(r, b[1:1+net.IPv6len+2]) + return b[:1+net.IPv6len+2], err + } + + return nil, ErrAddressNotSupported +} + +// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed. +func SplitAddr(b []byte) Addr { + addrLen := 1 + if len(b) < addrLen { + return nil + } + + switch b[0] { + case AtypDomainName: + if len(b) < 2 { + return nil + } + addrLen = 1 + 1 + int(b[1]) + 2 + case AtypIPv4: + addrLen = 1 + net.IPv4len + 2 + case AtypIPv6: + addrLen = 1 + net.IPv6len + 2 + default: + return nil + + } + + if len(b) < addrLen { + return nil + } + + return b[:addrLen] +} + +// ParseAddr parses the address in string s. Returns nil if failed. +func ParseAddr(s string) Addr { + var addr Addr + host, port, err := net.SplitHostPort(s) + if err != nil { + return nil + } + if ip := net.ParseIP(host); ip != nil { + if ip4 := ip.To4(); ip4 != nil { + addr = make([]byte, 1+net.IPv4len+2) + addr[0] = AtypIPv4 + copy(addr[1:], ip4) + } else { + addr = make([]byte, 1+net.IPv6len+2) + addr[0] = AtypIPv6 + copy(addr[1:], ip) + } + } else { + if len(host) > 255 { + return nil + } + addr = make([]byte, 1+1+len(host)+2) + addr[0] = AtypDomainName + addr[1] = byte(len(host)) + copy(addr[2:], host) + } + + portUint, err := strconv.ParseUint(port, 10, 16) + if err != nil { + return nil + } + + addr[len(addr)-2], addr[len(addr)-1] = byte(portUint>>8), byte(portUint) + + return addr +} + +func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) { + if len(packet) < 5 { + err = errors.New("insufficient length of packet") + return + } + + // packet[0] and packet[1] are reserved + if !bytes.Equal(packet[:2], []byte{0, 0}) { + err = errors.New("reserved fields should be zero") + return + } + + if packet[2] != 0 /* fragments */ { + err = errors.New("discarding fragmented payload") + return + } + + addr = SplitAddr(packet[3:]) + if addr == nil { + err = errors.New("failed to read UDP header") + } + + payload = bytes.Join([][]byte{packet[3+len(addr):]}, []byte{}) + return +} + +func EncodeUDPPacket(addr string, payload []byte) (packet []byte, err error) { + rAddr := ParseAddr(addr) + if rAddr == nil { + err = errors.New("cannot parse addr") + return + } + packet = bytes.Join([][]byte{{0, 0, 0}, rAddr, payload}, []byte{}) + return +} diff --git a/proxy/tcp.go b/proxy/tcp.go index 1bbce52..4e0bce1 100644 --- a/proxy/tcp.go +++ b/proxy/tcp.go @@ -5,24 +5,23 @@ import ( "strconv" "time" - "golang.org/x/net/proxy" - "github.com/xjasonlyu/tun2socks/common/dns" "github.com/xjasonlyu/tun2socks/common/log" "github.com/xjasonlyu/tun2socks/common/lsof" "github.com/xjasonlyu/tun2socks/common/stats" "github.com/xjasonlyu/tun2socks/core" + "github.com/xjasonlyu/tun2socks/proxy/socks" ) type tcpHandler struct { proxyHost string - proxyPort uint16 + proxyPort int fakeDns dns.FakeDns sessionStater stats.SessionStater } -func NewTCPHandler(proxyHost string, proxyPort uint16, fakeDns dns.FakeDns, sessionStater stats.SessionStater) core.TCPConnHandler { +func NewTCPHandler(proxyHost string, proxyPort int, fakeDns dns.FakeDns, sessionStater stats.SessionStater) core.TCPConnHandler { return &tcpHandler{ proxyHost: proxyHost, proxyPort: proxyPort, @@ -32,11 +31,6 @@ func NewTCPHandler(proxyHost string, proxyPort uint16, fakeDns dns.FakeDns, sess } func (h *tcpHandler) Handle(localConn net.Conn, target *net.TCPAddr) error { - dialer, err := proxy.SOCKS5("tcp", core.ParseTCPAddr(h.proxyHost, h.proxyPort).String(), nil, nil) - if err != nil { - return err - } - // Replace with a domain name if target address IP is a fake IP. var targetHost = target.IP.String() if h.fakeDns != nil { @@ -45,9 +39,12 @@ func (h *tcpHandler) Handle(localConn net.Conn, target *net.TCPAddr) error { } } + proxyAddr := net.JoinHostPort(h.proxyHost, strconv.Itoa(h.proxyPort)) targetAddr := net.JoinHostPort(targetHost, strconv.Itoa(target.Port)) - remoteConn, err := dialer.Dial("tcp", targetAddr) + // Dial + remoteConn, err := socks.Dial(proxyAddr, targetAddr) if err != nil { + log.Warnf("Dial %v error: %v", proxyAddr, err) return err } diff --git a/proxy/udp.go b/proxy/udp.go index bd6debd..0abd9b2 100644 --- a/proxy/udp.go +++ b/proxy/udp.go @@ -1,10 +1,8 @@ package proxy import ( - "bytes" "errors" "fmt" - "io" "net" "strconv" "sync" @@ -16,22 +14,22 @@ import ( "github.com/xjasonlyu/tun2socks/common/pool" "github.com/xjasonlyu/tun2socks/common/stats" "github.com/xjasonlyu/tun2socks/core" + "github.com/xjasonlyu/tun2socks/proxy/socks" ) type udpHandler struct { proxyHost string - proxyPort uint16 + proxyPort int timeout time.Duration - remoteAddrMap sync.Map - remoteConnMap sync.Map - remotePacketConnMap sync.Map + remoteAddrMap sync.Map + remoteConnMap sync.Map fakeDns dns.FakeDns sessionStater stats.SessionStater } -func NewUDPHandler(proxyHost string, proxyPort uint16, timeout time.Duration, fakeDns dns.FakeDns, sessionStater stats.SessionStater) core.UDPConnHandler { +func NewUDPHandler(proxyHost string, proxyPort int, timeout time.Duration, fakeDns dns.FakeDns, sessionStater stats.SessionStater) core.UDPConnHandler { return &udpHandler{ proxyHost: proxyHost, proxyPort: proxyPort, @@ -41,25 +39,7 @@ func NewUDPHandler(proxyHost string, proxyPort uint16, timeout time.Duration, fa } } -func (h *udpHandler) handleTCP(conn core.UDPConn, c net.Conn) { - // keep tcp connection alive - tcpKeepAlive(c) - - for { - // clear timeout settings - c.SetDeadline(time.Time{}) - _, err := c.Read(make([]byte, 1)) - if err != nil { - if err == io.EOF { - log.Debugf("UDP associate to %v closed by remote", c.RemoteAddr()) - } - h.Close(conn) - return - } - } -} - -func (h *udpHandler) fetchUDPInput(conn core.UDPConn, input net.PacketConn) { +func (h *udpHandler) fetchUDPInput(conn core.UDPConn, input net.PacketConn, addr *net.UDPAddr) { buf := pool.BufPool.Get().([]byte) defer func() { @@ -77,18 +57,7 @@ func (h *udpHandler) fetchUDPInput(conn core.UDPConn, input net.PacketConn) { return } - if n < 5 { - log.Warnf("short udp packet length: %d", n) - return - } - - addr := SplitAddr(buf[3:]) - resolvedAddr, err := net.ResolveUDPAddr("udp", addr.String()) - if err != nil { - return - } - - if _, err := conn.WriteFrom(buf[3+len(addr):n], resolvedAddr); err != nil { + if _, err := conn.WriteFrom(buf[:n], addr); err != nil { log.Warnf("failed to write UDP data: %v", err) return } @@ -113,57 +82,11 @@ func (h *udpHandler) Connect(conn core.UDPConn, target *net.UDPAddr) error { return errors.New("target address is empty") } - return h.connectInternal(conn, targetAddr) -} - -func (h *udpHandler) connectInternal(conn core.UDPConn, targetAddr string) error { - remoteConn, err := net.DialTimeout("tcp", core.ParseTCPAddr(h.proxyHost, h.proxyPort).String(), 4*time.Second) - if err != nil { - return err - } - remoteConn.SetDeadline(time.Now().Add(5 * time.Second)) - - // send VER, NMETHODS, METHODS - if _, err := remoteConn.Write([]byte{socks5Version, 1, 0}); err != nil { - return err - } - - buf := make([]byte, MaxAddrLen) - // read VER METHOD - if _, err := io.ReadFull(remoteConn, buf[:2]); err != nil { - return err - } - - destination := ParseAddr(targetAddr) - // write VER CMD RSV ATYP DST.ADDR DST.PORT - if _, err := remoteConn.Write(append([]byte{socks5Version, socks5UDPAssociate, 0}, destination...)); err != nil { - return err - } - - // read VER REP RSV ATYP BND.ADDR BND.PORT - if _, err := io.ReadFull(remoteConn, buf[:3]); err != nil { - return err - } - - rep := buf[1] - if rep != 0 { - return errors.New("SOCKS handshake failed") - } - - remoteAddr, err := readAddr(remoteConn, buf) - if err != nil { - return err - } - - resolvedRemoteAddr, err := net.ResolveUDPAddr("udp", remoteAddr.String()) - if err != nil { - return errors.New("failed to resolve remote address") - } - - go h.handleTCP(conn, remoteConn) - - remotePacketConn, err := net.ListenPacket("udp", "") + proxyAddr := net.JoinHostPort(h.proxyHost, strconv.Itoa(h.proxyPort)) + // Dial + remoteConn, remoteAddr, err := socks.DialUDP(proxyAddr, targetAddr) if err != nil { + log.Warnf("DialUDP %v error: %v", proxyAddr, err) return err } @@ -182,14 +105,13 @@ func (h *udpHandler) connectInternal(conn core.UDPConn, targetAddr string) error } h.sessionStater.AddSession(conn, sess) - remotePacketConn = stats.NewSessionPacketConn(remotePacketConn, sess) + remoteConn = stats.NewSessionPacketConn(remoteConn, sess) } - h.remoteAddrMap.Store(conn, resolvedRemoteAddr) + h.remoteAddrMap.Store(conn, remoteAddr) h.remoteConnMap.Store(conn, remoteConn) - h.remotePacketConnMap.Store(conn, remotePacketConn) - go h.fetchUDPInput(conn, remotePacketConn) + go h.fetchUDPInput(conn, remoteConn, target) log.Access(process, "proxy", "udp", conn.LocalAddr().String(), targetAddr) return nil @@ -197,31 +119,22 @@ func (h *udpHandler) connectInternal(conn core.UDPConn, targetAddr string) error func (h *udpHandler) ReceiveTo(conn core.UDPConn, data []byte, addr *net.UDPAddr) error { var remoteAddr net.Addr - var remotePacketConn net.PacketConn + var remoteConn net.PacketConn - if value, ok := h.remotePacketConnMap.Load(conn); ok { - remotePacketConn = value.(net.PacketConn) + if value, ok := h.remoteConnMap.Load(conn); ok { + remoteConn = value.(net.PacketConn) } if value, ok := h.remoteAddrMap.Load(conn); ok { remoteAddr = value.(net.Addr) } - if remoteAddr == nil || remotePacketConn == nil { + if remoteAddr == nil || remoteConn == nil { h.Close(conn) return errors.New(fmt.Sprintf("proxy connection %v->%v does not exists", conn.LocalAddr(), addr)) } - var targetHost = addr.IP.String() - if h.fakeDns != nil { - if host, exist := h.fakeDns.IPToHost(addr.IP); exist { - targetHost = host - } - } - - targetAddr := net.JoinHostPort(targetHost, strconv.Itoa(addr.Port)) - buf := bytes.Join([][]byte{{0, 0, 0}, ParseAddr(targetAddr), data[:]}, []byte{}) - if _, err := remotePacketConn.WriteTo(buf, remoteAddr); err != nil { + if _, err := remoteConn.WriteTo(data, remoteAddr); err != nil { h.Close(conn) return errors.New(fmt.Sprintf("write remote failed: %v", err)) } @@ -233,15 +146,10 @@ func (h *udpHandler) Close(conn core.UDPConn) { conn.Close() if remoteConn, ok := h.remoteConnMap.Load(conn); ok { - remoteConn.(net.Conn).Close() + remoteConn.(net.PacketConn).Close() h.remoteConnMap.Delete(conn) } - if remotePacketConn, ok := h.remotePacketConnMap.Load(conn); ok { - remotePacketConn.(net.PacketConn).Close() - h.remotePacketConnMap.Delete(conn) - } - h.remoteAddrMap.Delete(conn) if h.sessionStater != nil {