mirror of
https://github.com/nadoo/glider.git
synced 2025-04-22 04:02:07 +08:00
dnstun: add the ability to support both udp and tcp dns request
This commit is contained in:
parent
1a14762d84
commit
b29d972adc
124
dns.go
Normal file
124
dns.go
Normal file
@ -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]
|
||||
}
|
113
dnstun.go
113
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]
|
||||
}
|
||||
|
16
proxy.go
16
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 + "'")
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user