From 2fa1a5c4b98f24388a99a8e4279c79c82e52027f Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sun, 12 Jun 2022 19:37:27 +0800 Subject: [PATCH] Chore: update tproxy udp packet read logic --- listener/tproxy/packet.go | 7 ++- listener/tproxy/{tproxy.go => tcp.go} | 0 listener/tproxy/udp.go | 11 ++-- listener/tproxy/udp_linux.go | 84 +++++++++++++-------------- listener/tproxy/udp_other.go | 7 ++- transport/socks5/socks5.go | 15 +++++ 6 files changed, 71 insertions(+), 53 deletions(-) rename listener/tproxy/{tproxy.go => tcp.go} (100%) diff --git a/listener/tproxy/packet.go b/listener/tproxy/packet.go index 8aa3e9bf..9299df9d 100644 --- a/listener/tproxy/packet.go +++ b/listener/tproxy/packet.go @@ -2,12 +2,13 @@ package tproxy import ( "net" + "net/netip" "github.com/Dreamacro/clash/common/pool" ) type packet struct { - lAddr *net.UDPAddr + lAddr netip.AddrPort buf []byte } @@ -17,7 +18,7 @@ func (c *packet) Data() []byte { // WriteBack opens a new socket binding `addr` to write UDP packet back func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { - tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr) + tc, err := dialUDP("udp", addr.(*net.UDPAddr).AddrPort(), c.lAddr) if err != nil { n = 0 return @@ -29,7 +30,7 @@ func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { // LocalAddr returns the source IP/Port of UDP Packet func (c *packet) LocalAddr() net.Addr { - return c.lAddr + return &net.UDPAddr{IP: c.lAddr.Addr().AsSlice(), Port: int(c.lAddr.Port()), Zone: c.lAddr.Addr().Zone()} } func (c *packet) Drop() { diff --git a/listener/tproxy/tproxy.go b/listener/tproxy/tcp.go similarity index 100% rename from listener/tproxy/tproxy.go rename to listener/tproxy/tcp.go diff --git a/listener/tproxy/udp.go b/listener/tproxy/udp.go index c7e6d99e..60783563 100644 --- a/listener/tproxy/udp.go +++ b/listener/tproxy/udp.go @@ -2,6 +2,7 @@ package tproxy import ( "net" + "net/netip" "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/common/pool" @@ -58,7 +59,7 @@ func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) oob := make([]byte, 1024) for { buf := pool.Get(pool.UDPBufferSize) - n, oobn, _, lAddr, err := c.ReadMsgUDP(buf, oob) + n, oobn, _, lAddr, err := c.ReadMsgUDPAddrPort(buf, oob) if err != nil { pool.Put(buf) if rl.closed { @@ -67,19 +68,19 @@ func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) continue } - rAddr, err := getOrigDst(oob, oobn) + rAddr, err := getOrigDst(oob[:oobn]) if err != nil { continue } - handlePacketConn(l, in, buf[:n], lAddr, rAddr) + handlePacketConn(in, buf[:n], lAddr, rAddr) } }() return rl, nil } -func handlePacketConn(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) { - target := socks5.ParseAddrToSocksAddr(rAddr) +func handlePacketConn(in chan<- *inbound.PacketAdapter, buf []byte, lAddr, rAddr netip.AddrPort) { + target := socks5.AddrFromStdAddrPort(rAddr) pkt := &packet{ lAddr: lAddr, buf: buf, diff --git a/listener/tproxy/udp_linux.go b/listener/tproxy/udp_linux.go index 8cda96fd..472a23d3 100644 --- a/listener/tproxy/udp_linux.go +++ b/listener/tproxy/udp_linux.go @@ -3,13 +3,14 @@ package tproxy import ( - "encoding/binary" - "errors" "fmt" "net" + "net/netip" "os" "strconv" "syscall" + + "golang.org/x/sys/unix" ) const ( @@ -19,7 +20,7 @@ const ( // dialUDP acts like net.DialUDP for transparent proxy. // It binds to a non-local address(`lAddr`). -func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { +func dialUDP(network string, lAddr, rAddr netip.AddrPort) (uc *net.UDPConn, err error) { rSockAddr, err := udpAddrToSockAddr(rAddr) if err != nil { return nil, err @@ -35,23 +36,25 @@ func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPCo return nil, err } + defer func() { + if err != nil { + syscall.Close(fd) + } + }() + if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { - syscall.Close(fd) return nil, err } if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil { - syscall.Close(fd) return nil, err } if err = syscall.Bind(fd, lSockAddr); err != nil { - syscall.Close(fd) return nil, err } if err = syscall.Connect(fd, rSockAddr); err != nil { - syscall.Close(fd) return nil, err } @@ -60,35 +63,26 @@ func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPCo c, err := net.FileConn(fdFile) if err != nil { - syscall.Close(fd) return nil, err } return c.(*net.UDPConn), nil } -func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) { - switch { - case addr.IP.To4() != nil: - ip := [4]byte{} - copy(ip[:], addr.IP.To4()) - - return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil - - default: - ip := [16]byte{} - copy(ip[:], addr.IP.To16()) - - zoneID, err := strconv.ParseUint(addr.Zone, 10, 32) - if err != nil { - zoneID = 0 - } - - return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil +func udpAddrToSockAddr(addr netip.AddrPort) (syscall.Sockaddr, error) { + if addr.Addr().Is4() { + return &syscall.SockaddrInet4{Addr: addr.Addr().As4(), Port: int(addr.Port())}, nil } + + zoneID, err := strconv.ParseUint(addr.Addr().Zone(), 10, 32) + if err != nil { + zoneID = 0 + } + + return &syscall.SockaddrInet6{Addr: addr.Addr().As16(), Port: int(addr.Port()), ZoneId: uint32(zoneID)}, nil } -func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int { +func udpAddrFamily(net string, lAddr, rAddr netip.AddrPort) int { switch net[len(net)-1] { case '4': return syscall.AF_INET @@ -96,29 +90,35 @@ func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int { return syscall.AF_INET6 } - if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) { + if lAddr.Addr().Is4() && rAddr.Addr().Is4() { return syscall.AF_INET } return syscall.AF_INET6 } -func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { - msgs, err := syscall.ParseSocketControlMessage(oob[:oobn]) +func getOrigDst(oob []byte) (netip.AddrPort, error) { + // oob contains socket control messages which we need to parse. + scms, err := unix.ParseSocketControlMessage(oob) if err != nil { - return nil, err + return netip.AddrPort{}, fmt.Errorf("parse control message: %w", err) } - for _, msg := range msgs { - if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR { - ip := net.IP(msg.Data[4:8]) - port := binary.BigEndian.Uint16(msg.Data[2:4]) - return &net.UDPAddr{IP: ip, Port: int(port)}, nil - } else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR { - ip := net.IP(msg.Data[8:24]) - port := binary.BigEndian.Uint16(msg.Data[2:4]) - return &net.UDPAddr{IP: ip, Port: int(port)}, nil - } + // retrieve the destination address from the SCM. + sa, err := unix.ParseOrigDstAddr(&scms[0]) + if err != nil { + return netip.AddrPort{}, fmt.Errorf("retrieve destination: %w", err) } - return nil, errors.New("cannot find origDst") + // encode the destination address into a cmsg. + var rAddr netip.AddrPort + switch v := sa.(type) { + case *unix.SockaddrInet4: + rAddr = netip.AddrPortFrom(netip.AddrFrom4(v.Addr), uint16(v.Port)) + case *unix.SockaddrInet6: + rAddr = netip.AddrPortFrom(netip.AddrFrom16(v.Addr), uint16(v.Port)) + default: + return netip.AddrPort{}, fmt.Errorf("unsupported address type: %T", v) + } + + return rAddr, nil } diff --git a/listener/tproxy/udp_other.go b/listener/tproxy/udp_other.go index db4a1409..b35b07dd 100644 --- a/listener/tproxy/udp_other.go +++ b/listener/tproxy/udp_other.go @@ -5,12 +5,13 @@ package tproxy import ( "errors" "net" + "net/netip" ) -func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { - return nil, errors.New("UDP redir not supported on current platform") +func getOrigDst(oob []byte) (netip.AddrPort, error) { + return netip.AddrPort{}, errors.New("UDP redir not supported on current platform") } -func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { +func dialUDP(network string, lAddr, rAddr netip.AddrPort) (*net.UDPConn, error) { return nil, errors.New("UDP redir not supported on current platform") } diff --git a/transport/socks5/socks5.go b/transport/socks5/socks5.go index 2950819a..b051827b 100644 --- a/transport/socks5/socks5.go +++ b/transport/socks5/socks5.go @@ -398,6 +398,21 @@ func ParseAddrToSocksAddr(addr net.Addr) Addr { return parsed } +func AddrFromStdAddrPort(addrPort netip.AddrPort) Addr { + addr := addrPort.Addr() + if addr.Is4() { + ip4 := addr.As4() + return []byte{AtypIPv4, ip4[0], ip4[1], ip4[2], ip4[3], byte(addrPort.Port() >> 8), byte(addrPort.Port())} + } + + buf := make([]byte, 1+net.IPv6len+2) + buf[0] = AtypIPv6 + copy(buf[1:], addr.AsSlice()) + buf[1+net.IPv6len] = byte(addrPort.Port() >> 8) + buf[1+net.IPv6len+1] = byte(addrPort.Port()) + return buf +} + // DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet` func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) { if len(packet) < 5 {