From 41861ff48e9a386f5f3ec8c65a15d13bbde40635 Mon Sep 17 00:00:00 2001 From: nadoo <287492+nadoo@users.noreply.github.com> Date: Mon, 19 Jul 2021 23:06:28 +0800 Subject: [PATCH] tproxy: optimize codes and now works properly --- config.go | 2 +- dns/client.go | 2 +- go.mod | 2 +- log/log.go | 4 +- proxy/tproxy/tproxy_linux.go | 20 ++--- proxy/tproxy/udp_linux.go | 137 +++++++++++------------------------ systemd/glider@.service | 2 +- 7 files changed, 60 insertions(+), 109 deletions(-) diff --git a/config.go b/config.go index f9af07f..6a27f4f 100644 --- a/config.go +++ b/config.go @@ -83,7 +83,7 @@ func parseConfig() *Config { // setup a log func if conf.Verbose { - log.SetFlag(conf.LogFlags) + log.SetFlags(conf.LogFlags) log.F = log.Debugf } diff --git a/dns/client.go b/dns/client.go index 728c098..94fff0b 100644 --- a/dns/client.go +++ b/dns/client.go @@ -120,7 +120,7 @@ func (c *Client) handleAnswer(respBytes []byte, clientAddr, dnsServer, network, } c.cache.Set(qKey(resp.Question), valCopy(respBytes), ttl) - log.F("[dns] %s <-> %s(%s) via %s, %s/%d: %s, %ds", + log.F("[dns] %s <-> %s(%s) via %s, %s/%d: %s, ttl: %ds", clientAddr, dnsServer, network, dialerAddr, resp.Question.QNAME, resp.Question.QTYPE, strings.Join(ips, ","), ttl) return nil diff --git a/go.mod b/go.mod index bb1f6de..df27c59 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 // indirect github.com/xtaci/kcp-go/v5 v5.6.1 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 - golang.org/x/net v0.0.0-20210716203947-853a461950ff + golang.org/x/net v0.0.0-20210716203947-853a461950ff // indirect golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c ) diff --git a/log/log.go b/log/log.go index 8e7d3a6..b4f8fd1 100644 --- a/log/log.go +++ b/log/log.go @@ -8,8 +8,8 @@ import ( // F is the main log function. var F = func(string, ...interface{}) {} -// SetFlag sets the output flags for the logger. -func SetFlag(flag int) { +// SetFlags sets the output flags for the logger. +func SetFlags(flag int) { stdlog.SetFlags(flag) } diff --git a/proxy/tproxy/tproxy_linux.go b/proxy/tproxy/tproxy_linux.go index f7c9b8e..64bde56 100644 --- a/proxy/tproxy/tproxy_linux.go +++ b/proxy/tproxy/tproxy_linux.go @@ -28,11 +28,9 @@ func NewTProxy(s string, p proxy.Proxy) (*TProxy, error) { return nil, err } - addr := u.Host - tp := &TProxy{ proxy: p, - addr: addr, + addr: u.Host, } return tp, nil @@ -82,8 +80,9 @@ func (s *TProxy) ListenAndServeUDP() { } var session *natEntry - v, ok := nm.Load(lraddr.String()) + sessionKey := lraddr.String() + dstAddr.String() + v, ok := nm.Load(sessionKey) if !ok && v == nil { pc, dialer, writeTo, err := s.proxy.DialUDP("udp", dstAddr.String()) if err != nil { @@ -91,20 +90,21 @@ func (s *TProxy) ListenAndServeUDP() { continue } - lpc, err := DialUDP("udp", dstAddr, lraddr) + lpc, err := ListenPacket(dstAddr) if err != nil { - log.F("[tproxyu] dial to %s as %s error: %v", lraddr, dstAddr, err) + log.F("[tproxyu] ListenPacket as %s error: %v", dstAddr, err) + pc.Close() continue } session = newNatEntry(pc, writeTo) - nm.Store(lraddr.String(), session) + nm.Store(sessionKey, session) - go func(lc net.PacketConn, pc net.PacketConn, lraddr *net.UDPAddr) { + go func(lc net.PacketConn, pc net.PacketConn, lraddr *net.UDPAddr, key string) { proxy.RelayUDP(lc, lraddr, pc, 2*time.Minute) pc.Close() - nm.Delete(lraddr.String()) - }(lpc, pc, lraddr) + nm.Delete(key) + }(lpc, pc, lraddr, sessionKey) log.F("[tproxyu] %s <-> %s via %s", lraddr, dstAddr, dialer.Addr()) diff --git a/proxy/tproxy/udp_linux.go b/proxy/tproxy/udp_linux.go index 4877eb2..09c3d5b 100644 --- a/proxy/tproxy/udp_linux.go +++ b/proxy/tproxy/udp_linux.go @@ -1,5 +1,3 @@ -// MIT License @LiamHaworth -// https://github.com/LiamHaworth/go-tproxy/blob/master/tproxy_udp.go package tproxy import ( @@ -11,27 +9,24 @@ import ( "strconv" "syscall" "unsafe" + + "golang.org/x/sys/unix" ) -var nativeEndian binary.ByteOrder +var nativeEndian binary.ByteOrder = binary.LittleEndian func init() { - buf := [2]byte{} - *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) - - switch buf { - case [2]byte{0xCD, 0xAB}: - nativeEndian = binary.LittleEndian - case [2]byte{0xAB, 0xCD}: + var x uint16 = 0x0102 + if *(*byte)(unsafe.Pointer(&x)) == 0x01 { nativeEndian = binary.BigEndian - default: - panic("Could not determine native endianness.") } } -// ListenUDP will construct a new UDP listener -// socket with the Linux IP_TRANSPARENT option -// set on the underlying socket +// The following code copies from: +// https://github.com/LiamHaworth/go-tproxy/blob/master/tproxy_udp.go +// MIT License by @LiamHaworth + +// ListenUDP acts like net.ListenUDP but returns an conn with IP_TRANSPARENT option. func ListenUDP(network string, laddr *net.UDPAddr) (*net.UDPConn, error) { listener, err := net.ListenUDP(network, laddr) if err != nil { @@ -45,6 +40,7 @@ func ListenUDP(network string, laddr *net.UDPAddr) (*net.UDPConn, error) { defer fileDescriptorSource.Close() fileDescriptor := int(fileDescriptorSource.Fd()) + if err = syscall.SetsockoptInt(fileDescriptor, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil { return nil, &net.OpError{Op: "listen", Net: network, Source: nil, Addr: laddr, Err: fmt.Errorf("set socket option: IP_TRANSPARENT: %s", err)} } @@ -113,95 +109,50 @@ func ReadFromUDP(conn *net.UDPConn, b []byte) (int, *net.UDPAddr, *net.UDPAddr, return n, addr, originalDst, nil } -// DialUDP connects to the remote address raddr on the network net, -// which must be "udp", "udp4", or "udp6". If laddr is not nil, it is -// used as the local address for the connection. -func DialUDP(network string, laddr *net.UDPAddr, raddr *net.UDPAddr) (*net.UDPConn, error) { - remoteSocketAddress, err := udpAddrToSocketAddr(raddr) - if err != nil { - return nil, &net.OpError{Op: "dial", Err: fmt.Errorf("build destination socket address: %s", err)} +// ListenPacket acts like net.ListenPacket but the addr could be non-local. +func ListenPacket(addr *net.UDPAddr) (net.PacketConn, error) { + var af int + var sockaddr syscall.Sockaddr + + if len(addr.IP) == 4 { + af = syscall.AF_INET + sockaddr = &syscall.SockaddrInet4{Port: addr.Port} + copy(sockaddr.(*syscall.SockaddrInet4).Addr[:], addr.IP) + } else { + af = syscall.AF_INET6 + sockaddr = &syscall.SockaddrInet6{Port: addr.Port} + copy(sockaddr.(*syscall.SockaddrInet6).Addr[:], addr.IP) } - localSocketAddress, err := udpAddrToSocketAddr(laddr) - if err != nil { - return nil, &net.OpError{Op: "dial", Err: fmt.Errorf("build local socket address: %s", err)} + var fd int + var err error + + if fd, err = syscall.Socket(af, syscall.SOCK_DGRAM, 0); err != nil { + return nil, &net.OpError{Op: "fake", Err: fmt.Errorf("socket open: %s", err)} } - fileDescriptor, err := syscall.Socket(udpAddrFamily(network, laddr, raddr), syscall.SOCK_DGRAM, 0) - if err != nil { - return nil, &net.OpError{Op: "dial", Err: fmt.Errorf("socket open: %s", err)} + if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil { + syscall.Close(fd) + return nil, &net.OpError{Op: "fake", Err: fmt.Errorf("set socket option: IP_TRANSPARENT: %s", err)} } - if err = syscall.SetsockoptInt(fileDescriptor, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { - syscall.Close(fileDescriptor) - return nil, &net.OpError{Op: "dial", Err: fmt.Errorf("set socket option: SO_REUSEADDR: %s", err)} + syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + + syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1) + + if err = syscall.Bind(fd, sockaddr); err != nil { + syscall.Close(fd) + return nil, &net.OpError{Op: "fake", Err: fmt.Errorf("socket bind: %s", err)} } - if err = syscall.SetsockoptInt(fileDescriptor, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil { - syscall.Close(fileDescriptor) - return nil, &net.OpError{Op: "dial", Err: fmt.Errorf("set socket option: IP_TRANSPARENT: %s", err)} - } - - if err = syscall.Bind(fileDescriptor, localSocketAddress); err != nil { - syscall.Close(fileDescriptor) - return nil, &net.OpError{Op: "dial", Err: fmt.Errorf("socket bind: %s", err)} - } - - if err = syscall.Connect(fileDescriptor, remoteSocketAddress); err != nil { - syscall.Close(fileDescriptor) - return nil, &net.OpError{Op: "dial", Err: fmt.Errorf("socket connect: %s", err)} - } - - fdFile := os.NewFile(uintptr(fileDescriptor), fmt.Sprintf("net-udp-dial-%s", raddr.String())) + fdFile := os.NewFile(uintptr(fd), fmt.Sprintf("net-udp-listen-%s", addr.String())) defer fdFile.Close() - remoteConn, err := net.FileConn(fdFile) + packetConn, err := net.FilePacketConn(fdFile) if err != nil { - syscall.Close(fileDescriptor) - return nil, &net.OpError{Op: "dial", Err: fmt.Errorf("convert file descriptor to connection: %s", err)} + syscall.Close(fd) + return nil, &net.OpError{Op: "fake", Err: fmt.Errorf("convert file descriptor to connection: %s", err)} } - return remoteConn.(*net.UDPConn), nil -} - -// udpAddToSockerAddr will convert a UDPAddr -// into a Sockaddr that may be used when -// connecting and binding sockets -func udpAddrToSocketAddr(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 { - return nil, err - } - - return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil - } -} - -// udpAddrFamily will attempt to work -// out the address family based on the -// network and UDP addresses -func udpAddrFamily(net string, laddr, raddr *net.UDPAddr) int { - switch net[len(net)-1] { - case '4': - return syscall.AF_INET - case '6': - return syscall.AF_INET6 - } - - if (laddr == nil || laddr.IP.To4() != nil) && - (raddr == nil || laddr.IP.To4() != nil) { - return syscall.AF_INET - } - return syscall.AF_INET6 + return packetConn, nil } diff --git a/systemd/glider@.service b/systemd/glider@.service index 2d3972a..20840bd 100644 --- a/systemd/glider@.service +++ b/systemd/glider@.service @@ -13,7 +13,7 @@ ExecStart=/usr/bin/glider -config /etc/glider/%i.conf # NOTE: # work with systemd v229 or later, so glider can listen on port below 1024 with none-root user -# CAP_NET_ADMIN: ipset +# CAP_NET_ADMIN: ipset, setsockopt: IP_TRANSPARENT # CAP_NET_BIND_SERVICE: bind ports under 1024 # CAP_NET_RAW: bind raw socket and broadcasting (used by dhcpd) CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW