diff --git a/go.mod b/go.mod index 453b627d..7aa4b934 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.12 require ( github.com/riobard/go-bloom v0.0.0-20200213042214-218e1707c495 - golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 + golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d ) replace ( diff --git a/go.sum b/go.sum index 0e6b7bf8..685f40ff 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,4 @@ github.com/golang/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -github.com/golang/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:qNA2pFZuLItM7tUdO3b9JA2ibgtrUMUznl87BaPSgIg= -github.com/golang/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= github.com/golang/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= github.com/golang/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:5JyrLPvD/ZdaYkT7IqKhsP5xt7aLjA99KXRtk4EIYDk= github.com/golang/sys v0.0.0-20190412213103-97732733099d h1:blRtD+FQOxZ6P7jigy+HS0R8zyGOMOv8TET4wCpzVwM= @@ -8,3 +6,5 @@ github.com/golang/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQ github.com/golang/text v0.3.0/go.mod h1:GUiq9pdJKRKKAZXiVgWFEvocYuREvC14NhI4OPgEjeE= github.com/riobard/go-bloom v0.0.0-20200213042214-218e1707c495 h1:p7xbxYTzzfXghR1kpsJDeoVVRRWAotKc8u7FP/N48rU= github.com/riobard/go-bloom v0.0.0-20200213042214-218e1707c495/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= +golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U= +golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/nfutil/nf_linux.go b/nfutil/nf_linux.go new file mode 100644 index 00000000..dc165363 --- /dev/null +++ b/nfutil/nf_linux.go @@ -0,0 +1,55 @@ +package nfutil + +import ( + "net" + "syscall" + "unsafe" +) + +// Get the original destination of a TCP connection redirected by Netfilter. +func GetOrigDst(c *net.TCPConn, ipv6 bool) (*net.TCPAddr, error) { + rc, err := c.SyscallConn() + if err != nil { + return nil, err + } + var addr *net.TCPAddr + rc.Control(func(fd uintptr) { + if ipv6 { + addr, err = ipv6_getorigdst(fd) + } else { + addr, err = getorigdst(fd) + } + }) + return addr, err +} + +// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c +func getorigdst(fd uintptr) (*net.TCPAddr, error) { + const _SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h + var raw syscall.RawSockaddrInet4 + siz := unsafe.Sizeof(raw) + if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, _SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { + return nil, err + } + var addr net.TCPAddr + addr.IP = raw.Addr[:] + port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // raw.Port is big-endian + addr.Port = int(port[0])<<8 | int(port[1]) + return &addr, nil +} + +// Call ipv6_getorigdst() from linux/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c +// NOTE: I haven't tried yet but it should work since Linux 3.8. +func ipv6_getorigdst(fd uintptr) (*net.TCPAddr, error) { + const _IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h + var raw syscall.RawSockaddrInet6 + siz := unsafe.Sizeof(raw) + if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IPV6, _IP6T_SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { + return nil, err + } + var addr net.TCPAddr + addr.IP = raw.Addr[:] + port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // raw.Port is big-endian + addr.Port = int(port[0])<<8 | int(port[1]) + return &addr, nil +} diff --git a/tcp_linux_386.go b/nfutil/socketcall_linux_386.go similarity index 96% rename from tcp_linux_386.go rename to nfutil/socketcall_linux_386.go index 8b8db6fb..e4c91e48 100644 --- a/tcp_linux_386.go +++ b/nfutil/socketcall_linux_386.go @@ -1,4 +1,4 @@ -package main +package nfutil import ( "syscall" diff --git a/tcp_linux_other.go b/nfutil/socketcall_linux_other.go similarity index 94% rename from tcp_linux_other.go rename to nfutil/socketcall_linux_other.go index 96f9561a..6f7ca324 100644 --- a/tcp_linux_other.go +++ b/nfutil/socketcall_linux_other.go @@ -1,6 +1,6 @@ // +build linux,!386 -package main +package nfutil import "syscall" diff --git a/pfutil/pf_darwin.go b/pfutil/pf_darwin.go new file mode 100644 index 00000000..ad2127fb --- /dev/null +++ b/pfutil/pf_darwin.go @@ -0,0 +1,51 @@ +package pfutil + +import ( + "net" + "syscall" + "unsafe" +) + +func NatLookup(c *net.TCPConn) (*net.TCPAddr, error) { + const ( + PF_INOUT = 0 + PF_IN = 1 + PF_OUT = 2 + IOC_OUT = 0x40000000 + IOC_IN = 0x80000000 + IOC_INOUT = IOC_IN | IOC_OUT + IOCPARM_MASK = 0x1FFF + LEN = 4*16 + 4*4 + 4*1 + // #define _IOC(inout,group,num,len) (inout | ((len & IOCPARM_MASK) << 16) | ((group) << 8) | (num)) + // #define _IOWR(g,n,t) _IOC(IOC_INOUT, (g), (n), sizeof(t)) + // #define DIOCNATLOOK _IOWR('D', 23, struct pfioc_natlook) + DIOCNATLOOK = IOC_INOUT | ((LEN & IOCPARM_MASK) << 16) | ('D' << 8) | 23 + ) + fd, err := syscall.Open("/dev/pf", 0, syscall.O_RDONLY) + if err != nil { + return nil, err + } + defer syscall.Close(fd) + nl := struct { // struct pfioc_natlook + saddr, daddr, rsaddr, rdaddr [16]byte + sxport, dxport, rsxport, rdxport [4]byte + af, proto, protoVariant, direction uint8 + }{ + af: syscall.AF_INET, + proto: syscall.IPPROTO_TCP, + direction: PF_OUT, + } + saddr := c.RemoteAddr().(*net.TCPAddr) + daddr := c.LocalAddr().(*net.TCPAddr) + copy(nl.saddr[:], saddr.IP) + copy(nl.daddr[:], daddr.IP) + nl.sxport[0], nl.sxport[1] = byte(saddr.Port>>8), byte(saddr.Port) + nl.dxport[0], nl.dxport[1] = byte(daddr.Port>>8), byte(daddr.Port) + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), DIOCNATLOOK, uintptr(unsafe.Pointer(&nl))); errno != 0 { + return nil, errno + } + var addr net.TCPAddr + addr.IP = nl.rdaddr[:4] + addr.Port = int(nl.rdxport[0])<<8 | int(nl.rdxport[1]) + return &addr, nil +} diff --git a/tcp_darwin.go b/tcp_darwin.go new file mode 100644 index 00000000..1497d5d8 --- /dev/null +++ b/tcp_darwin.go @@ -0,0 +1,24 @@ +package main + +import ( + "net" + + "github.com/shadowsocks/go-shadowsocks2/pfutil" + "github.com/shadowsocks/go-shadowsocks2/socks" +) + +func redirLocal(addr, server string, shadow func(net.Conn) net.Conn) { + tcpLocal(addr, server, shadow, natLookup) +} + +func redir6Local(addr, server string, shadow func(net.Conn) net.Conn) { + panic("TCP6 redirect not supported") +} + +func natLookup(c net.Conn) (socks.Addr, error) { + if tc, ok := c.(*net.TCPConn); ok { + addr, err := pfutil.NatLookup(tc) + return socks.ParseAddr(addr.String()), err + } + panic("not TCP connection") +} diff --git a/tcp_linux.go b/tcp_linux.go index 84d10e78..6f4d11e7 100644 --- a/tcp_linux.go +++ b/tcp_linux.go @@ -1,18 +1,19 @@ package main import ( - "errors" "net" - "syscall" - "unsafe" + "github.com/shadowsocks/go-shadowsocks2/nfutil" "github.com/shadowsocks/go-shadowsocks2/socks" ) -const ( - SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h - IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h -) +func getOrigDst(c net.Conn, ipv6 bool) (socks.Addr, error) { + if tc, ok := c.(*net.TCPConn); ok { + addr, err := nfutil.GetOrigDst(tc, ipv6) + return socks.ParseAddr(addr.String()), err + } + panic("not a TCP connection") +} // Listen on addr for netfilter redirected TCP connections func redirLocal(addr, server string, shadow func(net.Conn) net.Conn) { @@ -25,64 +26,3 @@ func redir6Local(addr, server string, shadow func(net.Conn) net.Conn) { logf("TCP6 redirect %s <-> %s", addr, server) tcpLocal(addr, server, shadow, func(c net.Conn) (socks.Addr, error) { return getOrigDst(c, true) }) } - -// Get the original destination of a TCP connection. -func getOrigDst(conn net.Conn, ipv6 bool) (socks.Addr, error) { - c, ok := conn.(*net.TCPConn) - if !ok { - return nil, errors.New("only work with TCP connection") - } - f, err := c.File() - if err != nil { - return nil, err - } - defer f.Close() - - fd := f.Fd() - - // The File() call above puts both the original socket fd and the file fd in blocking mode. - // Set the file fd back to non-blocking mode and the original socket fd will become non-blocking as well. - // Otherwise blocking I/O will waste OS threads. - if err := syscall.SetNonblock(int(fd), true); err != nil { - return nil, err - } - - if ipv6 { - return ipv6_getorigdst(fd) - } - - return getorigdst(fd) -} - -// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c -func getorigdst(fd uintptr) (socks.Addr, error) { - raw := syscall.RawSockaddrInet4{} - siz := unsafe.Sizeof(raw) - if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { - return nil, err - } - - addr := make([]byte, 1+net.IPv4len+2) - addr[0] = socks.AtypIPv4 - copy(addr[1:1+net.IPv4len], raw.Addr[:]) - port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian - addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1] - return addr, nil -} - -// Call ipv6_getorigdst() from linux/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c -// NOTE: I haven't tried yet but it should work since Linux 3.8. -func ipv6_getorigdst(fd uintptr) (socks.Addr, error) { - raw := syscall.RawSockaddrInet6{} - siz := unsafe.Sizeof(raw) - if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { - return nil, err - } - - addr := make([]byte, 1+net.IPv6len+2) - addr[0] = socks.AtypIPv6 - copy(addr[1:1+net.IPv6len], raw.Addr[:]) - port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian - addr[1+net.IPv6len], addr[1+net.IPv6len+1] = port[0], port[1] - return addr, nil -} diff --git a/tcp_other.go b/tcp_other.go index a7c41d63..aa4549a7 100644 --- a/tcp_other.go +++ b/tcp_other.go @@ -1,4 +1,4 @@ -// +build !linux +// +build !linux,!darwin package main