Skip to content

Commit

Permalink
Split Netfilter and pf code into subpackages
Browse files Browse the repository at this point in the history
  • Loading branch information
riobard committed Feb 20, 2020
1 parent c03a025 commit 8b8484a
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 74 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
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=
github.com/golang/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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=
55 changes: 55 additions & 0 deletions nfutil/nf_linux.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion tcp_linux_386.go → nfutil/socketcall_linux_386.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package nfutil

import (
"syscall"
Expand Down
2 changes: 1 addition & 1 deletion tcp_linux_other.go → nfutil/socketcall_linux_other.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// +build linux,!386

package main
package nfutil

import "syscall"

Expand Down
51 changes: 51 additions & 0 deletions pfutil/pf_darwin.go
Original file line number Diff line number Diff line change
@@ -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
}
24 changes: 24 additions & 0 deletions tcp_darwin.go
Original file line number Diff line number Diff line change
@@ -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")
}
76 changes: 8 additions & 68 deletions tcp_linux.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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
}
2 changes: 1 addition & 1 deletion tcp_other.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// +build !linux
// +build !linux,!darwin

package main

Expand Down

0 comments on commit 8b8484a

Please sign in to comment.