From 0dc3ea651f998b01ce44801ac2b11f31e21848e9 Mon Sep 17 00:00:00 2001 From: nadoo <287492+nadoo@users.noreply.github.com> Date: Wed, 26 Jul 2017 18:56:24 +0800 Subject: [PATCH] parse domain in dns request playload. (prepare for the forward rule and ipset management features.) --- README.md | 5 +- dnstun.go | 48 ++++++++++- glider.conf.example | 2 +- main.go | 1 + redir.go | 112 ------------------------- redir_linux.go | 146 +++++++++++++++++++++++++++++++++ redir_linux_386.go | 17 ++++ redir_linux_other.go | 14 ++++ redir_win.go => redir_other.go | 2 +- 9 files changed, 229 insertions(+), 118 deletions(-) delete mode 100644 redir.go create mode 100644 redir_linux.go create mode 100644 redir_linux_386.go create mode 100644 redir_linux_other.go rename redir_win.go => redir_other.go (94%) diff --git a/README.md b/README.md index df2bfaa..fe93d1d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ we can set up local listeners as proxy, and forward requests to internet via for ``` |Forwarder ----------------->| Listener --> | | Internet - |Forwarder,Forwarder...----->| + |Forwarder --> Forwarder->...| ``` ## Install @@ -65,6 +65,7 @@ Available schemas for different modes: Available methods for ss: AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 + NOTE: chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305 Available forward strategies: rr: Round Robin mode @@ -147,7 +148,7 @@ checkduration=30 See [glider.conf.example](https://github.com/nadoo/glider/blob/master/glider.conf.example) ## Service -- Systemd: [https://github.com/nadoo/glider/blob/master/systemd/](https://github.com/nadoo/glider/blob/master/systemd/) +- systemd: [https://github.com/nadoo/glider/blob/master/systemd/](https://github.com/nadoo/glider/blob/master/systemd/) ## Links - [go-ss2](https://github.com/shadowsocks/go-shadowsocks2): the core ss protocol support diff --git a/dnstun.go b/dnstun.go index 8fa4594..625bf82 100644 --- a/dnstun.go +++ b/dnstun.go @@ -1,3 +1,5 @@ +// https://tools.ietf.org/html/rfc1035 + package main import ( @@ -6,6 +8,21 @@ import ( "net" ) +// UDPDNSHeaderLen is the length of UDP dns msg header +const UDPDNSHeaderLen = 12 + +// TCPDNSHEADERLen is the length of TCP dns msg header +const TCPDNSHEADERLen = 2 + UDPDNSHeaderLen + +// MaxUDPDNSLen is the max size of udp dns request. +// https://tools.ietf.org/html/rfc1035#section-4.2.1 +// Messages carried by UDP are restricted to 512 bytes (not counting the IP +// or UDP headers). Longer messages are truncated and the TC bit is set in +// the header. +// TODO: If the request length > 512 then the client will send TCP packets instead, +// so we should also serve tcp requests. +const MaxUDPDNSLen = 512 + type dnstun struct { Proxy addr string @@ -35,7 +52,8 @@ func (s *dnstun) ListenAndServe() { logf("listening UDP on %s", s.addr) for { - data := make([]byte, 512) + data := make([]byte, MaxUDPDNSLen) + n, clientAddr, err := l.ReadFrom(data) if err != nil { logf("DNS local read error: %v", err) @@ -43,7 +61,11 @@ func (s *dnstun) ListenAndServe() { } data = data[:n] + go func() { + // TODO: check domain rules and get a proper proxy. + domain := getDomain(data) + rc, err := s.GetProxy().Dial("tcp", s.raddr) if err != nil { logf("failed to connect to server %v: %v", s.raddr, err) @@ -51,7 +73,7 @@ func (s *dnstun) ListenAndServe() { } defer rc.Close() - logf("proxy-dnstun %s[dns.udp] <-> %s[dns.tcp]", clientAddr.String(), s.raddr) + logf("proxy-dnstun %s, %s <-> %s", domain, clientAddr.String(), s.raddr) // 2 bytes length after tcp header, before dns message length := make([]byte, 2) @@ -78,3 +100,25 @@ func (s *dnstun) ListenAndServe() { }() } } + +// getDomain from dns request playload, return []byte like: +// []byte{'w', 'w', 'w', '.', 'm', 's', 'n', '.', 'c', 'o', 'm', '.'} +// []byte("www.msn.com.") +func getDomain(p []byte) []byte { + var ret []byte + + for i := UDPDNSHeaderLen; i < len(p); { + l := int(p[i]) + + if l == 0 { + break + } + + ret = append(ret, p[i+1:i+l+1]...) + ret = append(ret, '.') + + i = i + l + 1 + } + + return ret +} diff --git a/glider.conf.example b/glider.conf.example index 600b6b8..04063bf 100644 --- a/glider.conf.example +++ b/glider.conf.example @@ -9,7 +9,7 @@ # # |Forwarder ----------------->| # Listener --> | | Internet -# |Forwarder,Forwarder...----->| +# |Forwarder --> Forwarder->...| # # ----------------------------------------------------------- # diff --git a/main.go b/main.go index 9d8ebf8..2d91cf9 100644 --- a/main.go +++ b/main.go @@ -56,6 +56,7 @@ func usage() { fmt.Fprintf(os.Stderr, "Available methods for ss:\n") fmt.Fprintf(os.Stderr, " "+ListCipher()) fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " NOTE: chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305\n") fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "Available forward strategies:\n") diff --git a/redir.go b/redir.go deleted file mode 100644 index 2193ef0..0000000 --- a/redir.go +++ /dev/null @@ -1,112 +0,0 @@ -// +build !windows - -package main - -import ( - "errors" - "fmt" - "net" - "syscall" -) - -const SO_ORIGINAL_DST = 80 - -type redir struct { - Proxy - addr string -} - -// RedirProxy returns a redirect proxy. -func RedirProxy(addr string, upProxy Proxy) (Proxy, error) { - s := &redir{ - Proxy: upProxy, - addr: addr, - } - - return s, nil -} - -// ListenAndServe redirected requests as a server. -func (s *redir) ListenAndServe() { - l, err := net.Listen("tcp", s.addr) - if err != nil { - logf("failed to listen on %s: %v", s.addr, err) - return - } - - logf("listening TCP on %s", s.addr) - - for { - c, err := l.Accept() - if err != nil { - logf("failed to accept: %v", err) - continue - } - - go func() { - defer c.Close() - - if c, ok := c.(*net.TCPConn); ok { - c.SetKeepAlive(true) - } - - tgt, c, err := getOriginalDstAddr(c) - if err != nil { - logf("failed to get target address: %v", err) - return - } - - rc, err := s.GetProxy().Dial("tcp", tgt.String()) - if err != nil { - logf("failed to connect to target: %v", err) - return - } - defer rc.Close() - - logf("proxy-redir %s <-> %s", c.RemoteAddr(), tgt) - - _, _, err = relay(c, rc) - if err != nil { - if err, ok := err.(net.Error); ok && err.Timeout() { - return // ignore i/o timeout - } - logf("relay error: %v", err) - } - - }() - } -} - -func getOriginalDstAddr(conn net.Conn) (addr net.Addr, c *net.TCPConn, err error) { - defer conn.Close() - - fc, err := conn.(*net.TCPConn).File() - if err != nil { - return - } - defer fc.Close() - - mreq, err := syscall.GetsockoptIPv6Mreq(int(fc.Fd()), syscall.IPPROTO_IP, SO_ORIGINAL_DST) - if err != nil { - return - } - - // only ipv4 support - ip := net.IPv4(mreq.Multiaddr[4], mreq.Multiaddr[5], mreq.Multiaddr[6], mreq.Multiaddr[7]) - port := uint16(mreq.Multiaddr[2])<<8 + uint16(mreq.Multiaddr[3]) - addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", ip.String(), port)) - if err != nil { - return - } - - cc, err := net.FileConn(fc) - if err != nil { - return - } - - c, ok := cc.(*net.TCPConn) - if !ok { - err = errors.New("not a TCP connection") - } - return -} diff --git a/redir_linux.go b/redir_linux.go new file mode 100644 index 0000000..cf64e8b --- /dev/null +++ b/redir_linux.go @@ -0,0 +1,146 @@ +// getOrigDst: +// https://github.com/shadowsocks/go-shadowsocks2/blob/master/tcp_linux.go#L30 + +package main + +import ( + "errors" + "net" + "syscall" + "unsafe" + + "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 +) + +type redir struct { + Proxy + addr string +} + +// RedirProxy returns a redirect proxy. +func RedirProxy(addr string, upProxy Proxy) (Proxy, error) { + s := &redir{ + Proxy: upProxy, + addr: addr, + } + + return s, nil +} + +// ListenAndServe redirected requests as a server. +func (s *redir) ListenAndServe() { + l, err := net.Listen("tcp", s.addr) + if err != nil { + logf("failed to listen on %s: %v", s.addr, err) + return + } + + logf("listening TCP on %s", s.addr) + + for { + c, err := l.Accept() + if err != nil { + logf("failed to accept: %v", err) + continue + } + + go func() { + defer c.Close() + + if c, ok := c.(*net.TCPConn); ok { + c.SetKeepAlive(true) + } + + // tgt, c, err := getOriginalDstAddr(c) + tgt, err := getOrigDst(c, false) + if err != nil { + logf("failed to get target address: %v", err) + return + } + + rc, err := s.GetProxy().Dial("tcp", tgt.String()) + if err != nil { + logf("failed to connect to target: %v", err) + return + } + defer rc.Close() + + logf("proxy-redir %s <-> %s", c.RemoteAddr(), tgt) + + _, _, err = relay(c, rc) + if err != nil { + if err, ok := err.(net.Error); ok && err.Timeout() { + return // ignore i/o timeout + } + logf("relay error: %v", err) + } + + }() + } +} + +// 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/redir_linux_386.go b/redir_linux_386.go new file mode 100644 index 0000000..8b8db6f --- /dev/null +++ b/redir_linux_386.go @@ -0,0 +1,17 @@ +package main + +import ( + "syscall" + "unsafe" +) + +const GETSOCKOPT = 15 // https://golang.org/src/syscall/syscall_linux_386.go#L183 + +func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error { + var a [6]uintptr + a[0], a[1], a[2], a[3], a[4], a[5] = a0, a1, a2, a3, a4, a5 + if _, _, errno := syscall.Syscall6(syscall.SYS_SOCKETCALL, call, uintptr(unsafe.Pointer(&a)), 0, 0, 0, 0); errno != 0 { + return errno + } + return nil +} diff --git a/redir_linux_other.go b/redir_linux_other.go new file mode 100644 index 0000000..96f9561 --- /dev/null +++ b/redir_linux_other.go @@ -0,0 +1,14 @@ +// +build linux,!386 + +package main + +import "syscall" + +const GETSOCKOPT = syscall.SYS_GETSOCKOPT + +func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error { + if _, _, errno := syscall.Syscall6(call, a0, a1, a2, a3, a4, a5); errno != 0 { + return errno + } + return nil +} diff --git a/redir_win.go b/redir_other.go similarity index 94% rename from redir_win.go rename to redir_other.go index c6227b1..7b68eea 100644 --- a/redir_win.go +++ b/redir_other.go @@ -1,4 +1,4 @@ -// +build windows +// +build !linux package main