tproxy: optimize codes and now works properly

This commit is contained in:
nadoo 2021-07-19 23:06:28 +08:00
parent 650f1eed4e
commit 41861ff48e
7 changed files with 60 additions and 109 deletions

View File

@ -83,7 +83,7 @@ func parseConfig() *Config {
// setup a log func // setup a log func
if conf.Verbose { if conf.Verbose {
log.SetFlag(conf.LogFlags) log.SetFlags(conf.LogFlags)
log.F = log.Debugf log.F = log.Debugf
} }

View File

@ -120,7 +120,7 @@ func (c *Client) handleAnswer(respBytes []byte, clientAddr, dnsServer, network,
} }
c.cache.Set(qKey(resp.Question), valCopy(respBytes), ttl) 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) clientAddr, dnsServer, network, dialerAddr, resp.Question.QNAME, resp.Question.QTYPE, strings.Join(ips, ","), ttl)
return nil return nil

2
go.mod
View File

@ -18,7 +18,7 @@ require (
github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 // indirect github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 // indirect
github.com/xtaci/kcp-go/v5 v5.6.1 github.com/xtaci/kcp-go/v5 v5.6.1
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 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 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
) )

View File

@ -8,8 +8,8 @@ import (
// F is the main log function. // F is the main log function.
var F = func(string, ...interface{}) {} var F = func(string, ...interface{}) {}
// SetFlag sets the output flags for the logger. // SetFlags sets the output flags for the logger.
func SetFlag(flag int) { func SetFlags(flag int) {
stdlog.SetFlags(flag) stdlog.SetFlags(flag)
} }

View File

@ -28,11 +28,9 @@ func NewTProxy(s string, p proxy.Proxy) (*TProxy, error) {
return nil, err return nil, err
} }
addr := u.Host
tp := &TProxy{ tp := &TProxy{
proxy: p, proxy: p,
addr: addr, addr: u.Host,
} }
return tp, nil return tp, nil
@ -82,8 +80,9 @@ func (s *TProxy) ListenAndServeUDP() {
} }
var session *natEntry var session *natEntry
v, ok := nm.Load(lraddr.String()) sessionKey := lraddr.String() + dstAddr.String()
v, ok := nm.Load(sessionKey)
if !ok && v == nil { if !ok && v == nil {
pc, dialer, writeTo, err := s.proxy.DialUDP("udp", dstAddr.String()) pc, dialer, writeTo, err := s.proxy.DialUDP("udp", dstAddr.String())
if err != nil { if err != nil {
@ -91,20 +90,21 @@ func (s *TProxy) ListenAndServeUDP() {
continue continue
} }
lpc, err := DialUDP("udp", dstAddr, lraddr) lpc, err := ListenPacket(dstAddr)
if err != nil { 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 continue
} }
session = newNatEntry(pc, writeTo) 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) proxy.RelayUDP(lc, lraddr, pc, 2*time.Minute)
pc.Close() pc.Close()
nm.Delete(lraddr.String()) nm.Delete(key)
}(lpc, pc, lraddr) }(lpc, pc, lraddr, sessionKey)
log.F("[tproxyu] %s <-> %s via %s", lraddr, dstAddr, dialer.Addr()) log.F("[tproxyu] %s <-> %s via %s", lraddr, dstAddr, dialer.Addr())

View File

@ -1,5 +1,3 @@
// MIT License @LiamHaworth
// https://github.com/LiamHaworth/go-tproxy/blob/master/tproxy_udp.go
package tproxy package tproxy
import ( import (
@ -11,27 +9,24 @@ import (
"strconv" "strconv"
"syscall" "syscall"
"unsafe" "unsafe"
"golang.org/x/sys/unix"
) )
var nativeEndian binary.ByteOrder var nativeEndian binary.ByteOrder = binary.LittleEndian
func init() { func init() {
buf := [2]byte{} var x uint16 = 0x0102
*(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
switch buf {
case [2]byte{0xCD, 0xAB}:
nativeEndian = binary.LittleEndian
case [2]byte{0xAB, 0xCD}:
nativeEndian = binary.BigEndian nativeEndian = binary.BigEndian
default:
panic("Could not determine native endianness.")
} }
} }
// ListenUDP will construct a new UDP listener // The following code copies from:
// socket with the Linux IP_TRANSPARENT option // https://github.com/LiamHaworth/go-tproxy/blob/master/tproxy_udp.go
// set on the underlying socket // 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) { func ListenUDP(network string, laddr *net.UDPAddr) (*net.UDPConn, error) {
listener, err := net.ListenUDP(network, laddr) listener, err := net.ListenUDP(network, laddr)
if err != nil { if err != nil {
@ -45,6 +40,7 @@ func ListenUDP(network string, laddr *net.UDPAddr) (*net.UDPConn, error) {
defer fileDescriptorSource.Close() defer fileDescriptorSource.Close()
fileDescriptor := int(fileDescriptorSource.Fd()) fileDescriptor := int(fileDescriptorSource.Fd())
if err = syscall.SetsockoptInt(fileDescriptor, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil { 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)} 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 return n, addr, originalDst, nil
} }
// DialUDP connects to the remote address raddr on the network net, // ListenPacket acts like net.ListenPacket but the addr could be non-local.
// which must be "udp", "udp4", or "udp6". If laddr is not nil, it is func ListenPacket(addr *net.UDPAddr) (net.PacketConn, error) {
// used as the local address for the connection. var af int
func DialUDP(network string, laddr *net.UDPAddr, raddr *net.UDPAddr) (*net.UDPConn, error) { var sockaddr syscall.Sockaddr
remoteSocketAddress, err := udpAddrToSocketAddr(raddr)
if err != nil { if len(addr.IP) == 4 {
return nil, &net.OpError{Op: "dial", Err: fmt.Errorf("build destination socket address: %s", err)} 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) var fd int
if err != nil { var err error
return nil, &net.OpError{Op: "dial", Err: fmt.Errorf("build local socket address: %s", err)}
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 = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
if err != nil { syscall.Close(fd)
return nil, &net.OpError{Op: "dial", Err: fmt.Errorf("socket open: %s", err)} 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.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
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, 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 { fdFile := os.NewFile(uintptr(fd), fmt.Sprintf("net-udp-listen-%s", addr.String()))
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()))
defer fdFile.Close() defer fdFile.Close()
remoteConn, err := net.FileConn(fdFile) packetConn, err := net.FilePacketConn(fdFile)
if err != nil { if err != nil {
syscall.Close(fileDescriptor) syscall.Close(fd)
return nil, &net.OpError{Op: "dial", Err: fmt.Errorf("convert file descriptor to connection: %s", err)} return nil, &net.OpError{Op: "fake", Err: fmt.Errorf("convert file descriptor to connection: %s", err)}
} }
return remoteConn.(*net.UDPConn), nil return packetConn, 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
} }

View File

@ -13,7 +13,7 @@ ExecStart=/usr/bin/glider -config /etc/glider/%i.conf
# NOTE: # NOTE:
# work with systemd v229 or later, so glider can listen on port below 1024 with none-root user # 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_BIND_SERVICE: bind ports under 1024
# CAP_NET_RAW: bind raw socket and broadcasting (used by dhcpd) # CAP_NET_RAW: bind raw socket and broadcasting (used by dhcpd)
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW