diff --git a/dns.go b/dns.go new file mode 100644 index 0000000..a079de0 --- /dev/null +++ b/dns.go @@ -0,0 +1,124 @@ +// https://tools.ietf.org/html/rfc1035 + +package main + +import ( + "encoding/binary" + "io/ioutil" + "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 dns struct { + *proxy + raddr string +} + +// DNSForwarder returns a dns forwarder. client -> dns.udp -> glider -> forwarder -> remote dns addr +func DNSForwarder(addr, raddr string, upProxy Proxy) (Proxy, error) { + s := &dns{ + proxy: newProxy(addr, upProxy), + raddr: raddr, + } + + return s, nil +} + +// ListenAndServe . +func (s *dns) ListenAndServe() { + l, err := net.ListenPacket("udp", s.addr) + if err != nil { + logf("failed to listen on %s: %v", s.addr, err) + return + } + defer l.Close() + + logf("listening UDP on %s", s.addr) + + for { + data := make([]byte, MaxUDPDNSLen) + + n, clientAddr, err := l.ReadFrom(data) + if err != nil { + logf("DNS local read error: %v", err) + continue + } + + data = data[:n] + + go func() { + // TODO: check domain rules and get a proper upstream name server. + domain := getDomain(data) + + rc, err := s.GetProxy(s.raddr).Dial("tcp", s.raddr) + if err != nil { + logf("failed to connect to server %v: %v", s.raddr, err) + return + } + defer rc.Close() + + logf("proxy-dns %s, %s <-> %s", domain, clientAddr.String(), s.raddr) + + // 2 bytes length after tcp header, before dns message + length := make([]byte, 2) + binary.BigEndian.PutUint16(length, uint16(len(data))) + rc.Write(length) + rc.Write(data) + + resp, err := ioutil.ReadAll(rc) + if err != nil { + logf("error in ioutil.ReadAll: %s\n", err) + return + } + + // length is not needed in udp dns response. (2 bytes) + // SEE RFC1035, section 4.2.2 TCP: The message is prefixed with a two byte length field which gives the message length, excluding the two byte length field. + if len(resp) > 2 { + msg := resp[2:] + _, err = l.WriteTo(msg, clientAddr) + if err != nil { + logf("error in local write: %s\n", err) + } + } + + }() + } +} + +// 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 + } + + // TODO: check here + // domain name could not be null, so the length of ret always >= 1? + return ret[:len(ret)-1] +} diff --git a/dnstun.go b/dnstun.go index 4cfc1c8..559728b 100644 --- a/dnstun.go +++ b/dnstun.go @@ -2,123 +2,34 @@ package main -import ( - "encoding/binary" - "io/ioutil" - "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 raddr string + + udp Proxy + tcp Proxy } -// DNSTunProxy returns a dns forwarder. client -> dns.udp -> glider -> forwarder -> remote dns addr -func DNSTunProxy(addr, raddr string, upProxy Proxy) (Proxy, error) { +// DNSTun returns a dns forwarder. client -> dns.udp -> glider -> forwarder -> remote dns addr +func DNSTun(addr, raddr string, upProxy Proxy) (Proxy, error) { s := &dnstun{ proxy: newProxy(addr, upProxy), raddr: raddr, } + s.udp, _ = DNSForwarder(addr, raddr, upProxy) + s.tcp, _ = TCPTun(addr, raddr, upProxy) + return s, nil } // ListenAndServe . func (s *dnstun) ListenAndServe() { - l, err := net.ListenPacket("udp", s.addr) - if err != nil { - logf("failed to listen on %s: %v", s.addr, err) - return + if s.udp != nil { + go s.udp.ListenAndServe() } - defer l.Close() - logf("listening UDP on %s", s.addr) - - for { - data := make([]byte, MaxUDPDNSLen) - - n, clientAddr, err := l.ReadFrom(data) - if err != nil { - logf("DNS local read error: %v", err) - continue - } - - data = data[:n] - - go func() { - // TODO: check domain rules and get a proper upstream name server. - domain := getDomain(data) - - rc, err := s.GetProxy(s.raddr).Dial("tcp", s.raddr) - if err != nil { - logf("failed to connect to server %v: %v", s.raddr, err) - return - } - defer rc.Close() - - logf("proxy-dnstun %s, %s <-> %s", domain, clientAddr.String(), s.raddr) - - // 2 bytes length after tcp header, before dns message - length := make([]byte, 2) - binary.BigEndian.PutUint16(length, uint16(len(data))) - rc.Write(length) - rc.Write(data) - - resp, err := ioutil.ReadAll(rc) - if err != nil { - logf("error in ioutil.ReadAll: %s\n", err) - return - } - - // length is not needed in udp dns response. (2 bytes) - // SEE RFC1035, section 4.2.2 TCP: The message is prefixed with a two byte length field which gives the message length, excluding the two byte length field. - if len(resp) > 2 { - msg := resp[2:] - _, err = l.WriteTo(msg, clientAddr) - if err != nil { - logf("error in local write: %s\n", err) - } - } - - }() + if s.tcp != nil { + s.tcp.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 - } - - // TODO: check here - // domain name could not be null, so the length of ret always >= 1? - return ret[:len(ret)-1] -} diff --git a/proxy.go b/proxy.go index 86e7dd2..87520a8 100644 --- a/proxy.go +++ b/proxy.go @@ -90,23 +90,23 @@ func ProxyFromURL(s string, forwarder Proxy) (Proxy, error) { } switch u.Scheme { + case "mixed": + return MixedProxy("tcp", addr, user, pass, forwarder) + case "http": + return HTTPProxy(addr, forwarder) + case "socks5": + return SOCKS5Proxy("tcp", addr, user, pass, forwarder) case "ss": p, err := SSProxy(addr, user, pass, forwarder) return p, err - case "socks5": - return SOCKS5Proxy("tcp", addr, user, pass, forwarder) case "redir": return RedirProxy(addr, forwarder) case "tcptun": d := strings.Split(addr, "=") - return TCPTunProxy(d[0], d[1], forwarder) + return TCPTun(d[0], d[1], forwarder) case "dnstun": d := strings.Split(addr, "=") - return DNSTunProxy(d[0], d[1], forwarder) - case "http": - return HTTPProxy(addr, forwarder) - case "mixed": - return MixedProxy("tcp", addr, user, pass, forwarder) + return DNSTun(d[0], d[1], forwarder) } return nil, errors.New("unknown schema '" + u.Scheme + "'") diff --git a/tcptun.go b/tcptun.go index 0fbe677..8ccbbe5 100644 --- a/tcptun.go +++ b/tcptun.go @@ -7,8 +7,8 @@ type tcptun struct { raddr string } -// TCPTunProxy returns a redirect proxy. -func TCPTunProxy(addr, raddr string, upProxy Proxy) (Proxy, error) { +// TCPTun returns a redirect proxy. +func TCPTun(addr, raddr string, upProxy Proxy) (Proxy, error) { s := &tcptun{ proxy: newProxy(addr, upProxy), raddr: raddr,