mirror of
https://github.com/nadoo/glider.git
synced 2025-04-21 19:52: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
|
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 {
|
type dnstun struct {
|
||||||
*proxy
|
*proxy
|
||||||
raddr string
|
raddr string
|
||||||
|
|
||||||
|
udp Proxy
|
||||||
|
tcp Proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNSTunProxy returns a dns forwarder. client -> dns.udp -> glider -> forwarder -> remote dns addr
|
// DNSTun returns a dns forwarder. client -> dns.udp -> glider -> forwarder -> remote dns addr
|
||||||
func DNSTunProxy(addr, raddr string, upProxy Proxy) (Proxy, error) {
|
func DNSTun(addr, raddr string, upProxy Proxy) (Proxy, error) {
|
||||||
s := &dnstun{
|
s := &dnstun{
|
||||||
proxy: newProxy(addr, upProxy),
|
proxy: newProxy(addr, upProxy),
|
||||||
raddr: raddr,
|
raddr: raddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.udp, _ = DNSForwarder(addr, raddr, upProxy)
|
||||||
|
s.tcp, _ = TCPTun(addr, raddr, upProxy)
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenAndServe .
|
// ListenAndServe .
|
||||||
func (s *dnstun) ListenAndServe() {
|
func (s *dnstun) ListenAndServe() {
|
||||||
l, err := net.ListenPacket("udp", s.addr)
|
if s.udp != nil {
|
||||||
if err != nil {
|
go s.udp.ListenAndServe()
|
||||||
logf("failed to listen on %s: %v", s.addr, err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
defer l.Close()
|
|
||||||
|
|
||||||
logf("listening UDP on %s", s.addr)
|
if s.tcp != nil {
|
||||||
|
s.tcp.ListenAndServe()
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
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":
|
case "ss":
|
||||||
p, err := SSProxy(addr, user, pass, forwarder)
|
p, err := SSProxy(addr, user, pass, forwarder)
|
||||||
return p, err
|
return p, err
|
||||||
case "socks5":
|
|
||||||
return SOCKS5Proxy("tcp", addr, user, pass, forwarder)
|
|
||||||
case "redir":
|
case "redir":
|
||||||
return RedirProxy(addr, forwarder)
|
return RedirProxy(addr, forwarder)
|
||||||
case "tcptun":
|
case "tcptun":
|
||||||
d := strings.Split(addr, "=")
|
d := strings.Split(addr, "=")
|
||||||
return TCPTunProxy(d[0], d[1], forwarder)
|
return TCPTun(d[0], d[1], forwarder)
|
||||||
case "dnstun":
|
case "dnstun":
|
||||||
d := strings.Split(addr, "=")
|
d := strings.Split(addr, "=")
|
||||||
return DNSTunProxy(d[0], d[1], forwarder)
|
return DNSTun(d[0], d[1], forwarder)
|
||||||
case "http":
|
|
||||||
return HTTPProxy(addr, forwarder)
|
|
||||||
case "mixed":
|
|
||||||
return MixedProxy("tcp", addr, user, pass, forwarder)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("unknown schema '" + u.Scheme + "'")
|
return nil, errors.New("unknown schema '" + u.Scheme + "'")
|
||||||
|
@ -7,8 +7,8 @@ type tcptun struct {
|
|||||||
raddr string
|
raddr string
|
||||||
}
|
}
|
||||||
|
|
||||||
// TCPTunProxy returns a redirect proxy.
|
// TCPTun returns a redirect proxy.
|
||||||
func TCPTunProxy(addr, raddr string, upProxy Proxy) (Proxy, error) {
|
func TCPTun(addr, raddr string, upProxy Proxy) (Proxy, error) {
|
||||||
s := &tcptun{
|
s := &tcptun{
|
||||||
proxy: newProxy(addr, upProxy),
|
proxy: newProxy(addr, upProxy),
|
||||||
raddr: raddr,
|
raddr: raddr,
|
||||||
|
Loading…
Reference in New Issue
Block a user