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 Jan 31, 2020
1 parent 0136fbd commit 580a199
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 115 deletions.
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
}
56 changes: 7 additions & 49 deletions tcp_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,19 @@ package main

import (
"net"
"syscall"
"unsafe"

"github.com/riobard/go-shadowsocks2/pfutil"
"github.com/riobard/go-shadowsocks2/socks"
)

func redirLocal(addr string, d Dialer) { tcpLocal(addr, d, pfNatLookup) }
func redirLocal(addr string, d Dialer) { tcpLocal(addr, d, natLookup) }
func redir6Local(addr string, d Dialer) { panic("TCP6 redirect not supported") }
func tproxyTCP(addr string, d Dialer) { panic("TPROXY TCP not supported") }

func pfNatLookup(c net.Conn) (socks.Addr, 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,
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
}
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
}

addr := make([]byte, 1+net.IPv4len+2)
addr[0] = socks.AtypIPv4
copy(addr[1:1+net.IPv4len], nl.rdaddr[:4])
copy(addr[1+net.IPv4len:], nl.rdxport[:2])
return addr, nil
panic("not TCP connection")
}
72 changes: 8 additions & 64 deletions tcp_linux.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package main

import (
"errors"
"net"
"syscall"
"unsafe"

"github.com/riobard/go-shadowsocks2/nfutil"
"github.com/riobard/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 string, d Dialer) {
Expand All @@ -26,64 +28,6 @@ func redir6Local(addr string, d Dialer) {
tcpLocal(addr, d, 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")
}

rc, err := c.SyscallConn()
if err != nil {
return nil, err
}

var addr socks.Addr

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) (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
}

func tproxyTCP(addr string, d Dialer) error {
l, err := net.Listen("tcp", addr)
if err != nil {
Expand Down

0 comments on commit 580a199

Please sign in to comment.