diff --git a/cmd/spoof-dpi/main.go b/cmd/spoof-dpi/main.go index f49cc19..57fc3b2 100644 --- a/cmd/spoof-dpi/main.go +++ b/cmd/spoof-dpi/main.go @@ -11,9 +11,16 @@ import ( "github.com/xvzc/SpoofDPI/util" ) +var VERSION = "v0.0.0(dev)" func main() { util.ParseArgs() - config := util.GetConfig() + config := util.GetConfig() + if *config.Version { + println("spoog-dpi", VERSION) + println("\nA simple and fast anti-censorship tool written in Go.") + println("https://github.com/xvzc/SpoofDPI") + os.Exit(0) + } pxy := proxy.New(config) if *config.Debug { diff --git a/net/dial.go b/net/dial.go deleted file mode 100644 index dc3aebe..0000000 --- a/net/dial.go +++ /dev/null @@ -1,31 +0,0 @@ -package net - -import ( - "net" - "strconv" -) - -func ListenTCP(network string, addr *TCPAddr) (Listener, error) { - l, err := net.ListenTCP(network, addr.Addr) - if err != nil { - return Listener{}, err - } - - return Listener{listener: l}, nil -} - -func DialTCP(network string, ip string, port string) (*Conn, error) { - p, _ := strconv.Atoi(port) - - addr := &net.TCPAddr{ - IP: net.ParseIP(ip), - Port: p, - } - - conn, err := net.DialTCP(network, nil, addr) - if err != nil { - return &Conn{}, err - } - - return &Conn{*conn}, nil -} diff --git a/net/listener.go b/net/listener.go deleted file mode 100644 index 522150c..0000000 --- a/net/listener.go +++ /dev/null @@ -1,18 +0,0 @@ -package net - -import ( - "net" -) - -type Listener struct { - listener *net.TCPListener -} - -func (l *Listener) Accept() (*Conn, error) { - conn, err := l.listener.AcceptTCP() - if err != nil { - return &Conn{}, err - } - - return &Conn{*conn}, nil -} diff --git a/net/tcp.go b/net/tcp.go deleted file mode 100644 index cdd6e80..0000000 --- a/net/tcp.go +++ /dev/null @@ -1,20 +0,0 @@ -package net - -import ( - "net" -) - -type TCPAddr struct { - Addr *net.TCPAddr -} - -func TcpAddr(ip string, port int) *TCPAddr { - addr := &net.TCPAddr{ - IP: net.ParseIP(ip), - Port: port, - } - - return &TCPAddr{ - Addr: addr, - } -} diff --git a/proxy/http.go b/proxy/http.go index 922cd9e..b219cf7 100644 --- a/proxy/http.go +++ b/proxy/http.go @@ -1,23 +1,30 @@ package proxy import ( + "net" + "strconv" + log "github.com/sirupsen/logrus" - "github.com/xvzc/SpoofDPI/net" + "github.com/xvzc/SpoofDPI/packet" ) -func (pxy *Proxy) HandleHttp(lConn *net.Conn, pkt *packet.HttpPacket, ip string) { +func (pxy *Proxy) HandleHttp(lConn *net.TCPConn, pkt *packet.HttpPacket, ip string) { pkt.Tidy() - // Create connection to server - var port = "80" + // Create a connection to the requested server + var port int = 80 + var err error if pkt.Port() != "" { - port = pkt.Port() + port, err = strconv.Atoi(pkt.Port()) + if err != nil { + log.Debug("[HTTPS] Error while parsing port for ", pkt.Domain(), " aborting..") + } } - rConn, err := net.DialTCP("tcp", ip, port) + rConn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: net.ParseIP(ip), Port: port}) if err != nil { - lConn.Close() + lConn.Close() log.Debug("[HTTP] ", err) return } @@ -32,7 +39,7 @@ func (pxy *Proxy) HandleHttp(lConn *net.Conn, pkt *packet.HttpPacket, ip string) log.Debug("[HTTP] New connection to the server ", pkt.Domain(), " ", rConn.LocalAddr()) - go rConn.Serve(lConn, "[HTTP]", lConn.RemoteAddr().String(), pkt.Domain(), pxy.timeout) + go Serve(rConn, lConn, "[HTTP]", lConn.RemoteAddr().String(), pkt.Domain(), pxy.timeout) _, err = rConn.Write(pkt.Raw()) if err != nil { @@ -42,5 +49,5 @@ func (pxy *Proxy) HandleHttp(lConn *net.Conn, pkt *packet.HttpPacket, ip string) log.Debug("[HTTP] Sent a request to ", pkt.Domain()) - lConn.Serve(rConn, "[HTTP]", lConn.RemoteAddr().String(), pkt.Domain(), pxy.timeout) + Serve(lConn, rConn, "[HTTP]", lConn.RemoteAddr().String(), pkt.Domain(), pxy.timeout) } diff --git a/proxy/https.go b/proxy/https.go index fb5fa94..91c4bdc 100644 --- a/proxy/https.go +++ b/proxy/https.go @@ -1,21 +1,27 @@ package proxy import ( + "net" + "strconv" + log "github.com/sirupsen/logrus" - "github.com/xvzc/SpoofDPI/net" "github.com/xvzc/SpoofDPI/packet" ) -func (pxy *Proxy) HandleHttps(lConn *net.Conn, initPkt *packet.HttpPacket, ip string) { +func (pxy *Proxy) HandleHttps(lConn *net.TCPConn, initPkt *packet.HttpPacket, ip string) { // Create a connection to the requested server - var port = "443" + var port int = 443 + var err error if initPkt.Port() != "" { - port = initPkt.Port() + port, err = strconv.Atoi(initPkt.Port()) + if err != nil { + log.Debug("[HTTPS] Error while parsing port for ", initPkt.Domain(), " aborting..") + } } - rConn, err := net.DialTCP("tcp4", ip, port) + rConn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: net.ParseIP(ip), Port: port}) if err != nil { - lConn.Close() + lConn.Close() log.Debug("[HTTPS] ", err) return } @@ -39,7 +45,7 @@ func (pxy *Proxy) HandleHttps(lConn *net.Conn, initPkt *packet.HttpPacket, ip st log.Debug("[HTTPS] Sent 200 Connection Estabalished to ", lConn.RemoteAddr()) // Read client hello - clientHello, err := lConn.ReadBytes() + clientHello, err := ReadBytes(lConn) if err != nil { log.Debug("[HTTPS] Error reading client hello from the client", err) return @@ -51,22 +57,27 @@ func (pxy *Proxy) HandleHttps(lConn *net.Conn, initPkt *packet.HttpPacket, ip st chPkt := packet.NewHttpsPacket(clientHello) - go rConn.Serve(lConn, "[HTTPS]", rConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout) + lConn.SetLinger(3) + rConn.SetLinger(3) + + go Serve(rConn, lConn, "[HTTPS]", rConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout) if pxy.patternExists() && !pxy.patternMatches([]byte(initPkt.Domain())) { - if _, err := rConn.Write(chPkt.Raw()); err != nil { - log.Debug("[HTTPS] Error writing client hello to ", initPkt.Domain(), err) - return - } + log.Debug("[HTTPS] Writing plain client hello to ", initPkt.Domain()) + if _, err := rConn.Write(chPkt.Raw()); err != nil { + log.Debug("[HTTPS] Error writing plain client hello to ", initPkt.Domain(), err) + return + } } else { - chunks := pxy.splitInChunks(chPkt.Raw(), pxy.windowSize) - if _, err := rConn.WriteChunks(chunks); err != nil { - log.Debug("[HTTPS] Error writing client hello to ", initPkt.Domain(), err) - return - } - } + log.Debug("[HTTPS] Writing chunked client hello to ", initPkt.Domain()) + chunks := pxy.splitInChunks(chPkt.Raw(), pxy.windowSize) + if _, err := WriteChunks(rConn, chunks); err != nil { + log.Debug("[HTTPS] Error writing chunked client hello to ", initPkt.Domain(), err) + return + } + } - lConn.Serve(rConn, "[HTTPS]", lConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout) + Serve(lConn, rConn, "[HTTPS]", lConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout) } func (pxy *Proxy) splitInChunks(bytes []byte, size int) [][]byte { diff --git a/net/conn.go b/proxy/io.go similarity index 71% rename from net/conn.go rename to proxy/io.go index d258bdd..3f691a1 100644 --- a/net/conn.go +++ b/proxy/io.go @@ -1,4 +1,4 @@ -package net +package proxy import ( "errors" @@ -11,11 +11,7 @@ import ( const BUF_SIZE = 1024 -type Conn struct { - net.TCPConn -} - -func (conn *Conn) WriteChunks(c [][]byte) (n int, err error) { +func WriteChunks(conn *net.TCPConn, c [][]byte) (n int, err error) { total := 0 for i := 0; i < len(c); i++ { b, err := conn.Write(c[i]) @@ -29,7 +25,7 @@ func (conn *Conn) WriteChunks(c [][]byte) (n int, err error) { return total, nil } -func (conn *Conn) ReadBytes() ([]byte, error) { +func ReadBytes(conn *net.TCPConn) ([]byte, error) { ret := make([]byte, 0) buf := make([]byte, BUF_SIZE) @@ -57,15 +53,15 @@ func (conn *Conn) ReadBytes() ([]byte, error) { return ret, nil } -func (from *Conn) Serve(to *Conn, proto string, fd string, td string, timeout int) { +func Serve(from *net.TCPConn, to *net.TCPConn, proto string, fd string, td string, timeout int) { proto += " " for { - from.SetReadDeadline( - time.Now().Add(time.Millisecond * time.Duration(timeout)), - ) + from.SetReadDeadline( + time.Now().Add(time.Millisecond * time.Duration(timeout)), + ) - buf, err := from.ReadBytes() + buf, err := ReadBytes(from) if err != nil { if err == io.EOF { log.Debug(proto, "Finished ", fd) diff --git a/proxy/proxy.go b/proxy/proxy.go index f1c5562..de5a9fd 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -2,12 +2,13 @@ package proxy import ( "fmt" + "net" "os" "regexp" + "strconv" log "github.com/sirupsen/logrus" "github.com/xvzc/SpoofDPI/dns" - "github.com/xvzc/SpoofDPI/net" "github.com/xvzc/SpoofDPI/packet" "github.com/xvzc/SpoofDPI/util" ) @@ -34,34 +35,26 @@ func New(config *util.Config) *Proxy { } } -func (pxy *Proxy) TcpAddr() *net.TCPAddr { - return net.TcpAddr(pxy.addr, pxy.port) -} - -func (pxy *Proxy) Port() int { - return pxy.port -} - func (pxy *Proxy) Start() { - l, err := net.ListenTCP("tcp4", pxy.TcpAddr()) + l, err := net.ListenTCP("tcp4", &net.TCPAddr{IP: net.ParseIP(pxy.addr), Port: pxy.port}) if err != nil { - log.Fatal("Error creating listener: ", err) + log.Fatal("[PROXY] Error creating listener: ", err) os.Exit(1) } - log.Println(fmt.Sprintf("Connection timeout is set to %dms", pxy.timeout)) + log.Println(fmt.Sprintf("[PROXY] Connection timeout is set to %dms", pxy.timeout)) - log.Println("Created a listener on port", pxy.Port()) + log.Println("[PROXY] Created a listener on port", pxy.port) for { conn, err := l.Accept() if err != nil { - log.Fatal("Error accepting connection: ", err) + log.Fatal("[PROXY] Error accepting connection: ", err) continue } go func() { - b, err := conn.ReadBytes() + b, err := ReadBytes(conn.(*net.TCPConn)) if err != nil { return } @@ -70,32 +63,68 @@ func (pxy *Proxy) Start() { pkt, err := packet.NewHttpPacket(b) if err != nil { - log.Debug("Error while parsing request: ", string(b)) - conn.Close() + log.Debug("[PROXY] Error while parsing request: ", string(b)) + conn.Close() return } if !pkt.IsValidMethod() { - log.Debug("Unsupported method: ", pkt.Method()) - conn.Close() + log.Debug("[PROXY] Unsupported method: ", pkt.Method()) + conn.Close() return } ip, err := pxy.resolver.Lookup(pkt.Domain()) if err != nil { - log.Error("[HTTP] Error looking up for domain with ", pkt.Domain(), " ", err) + log.Error("[PROXY] Error looking up for domain with ", pkt.Domain(), " ", err) conn.Write([]byte(pkt.Version() + " 502 Bad Gateway\r\n\r\n")) - conn.Close() + conn.Close() + return + } + + // Avoid recursively querying self + if pkt.Port() == strconv.Itoa(pxy.port) && isLoopedRequest(net.ParseIP(ip)) { + log.Error("[HTTP] Invalid request: final target has the same IP and port as our proxy") + conn.Close() return } if pkt.IsConnectMethod() { - log.Debug("[HTTPS] Start") - pxy.HandleHttps(conn, pkt, ip) + log.Debug("[PROXY] Start HTTPS") + pxy.HandleHttps(conn.(*net.TCPConn), pkt, ip) } else { - log.Debug("[HTTP] Start") - pxy.HandleHttp(conn, pkt, ip) + log.Debug("[PROXY] Start HTTP") + pxy.HandleHttp(conn.(*net.TCPConn), pkt, ip) } }() } } + +func isLoopedRequest(ip net.IP) bool { + // we don't handle IPv6 at all it seems + if ip.To4() == nil { + return false + } + + if ip.IsLoopback() { + return true + } + + // Get list of available addresses + // See `ip -4 addr show` + addr, err := net.InterfaceAddrs() // needs AF_NETLINK on linux + if err != nil { + log.Error("[PROXY] Error while getting addresses of our network interfaces: ", err) + return false + } + + for _, addr := range addr { + if ipnet, ok := addr.(*net.IPNet); ok { + if ipnet.IP.To4() != nil && ipnet.IP.To4().Equal(ip) { + return true + } + } + } + + return false +} diff --git a/util/config.go b/util/config.go index 37b6c55..e472d4b 100644 --- a/util/config.go +++ b/util/config.go @@ -23,6 +23,7 @@ type Config struct { AllowedPattern *regexp.Regexp AllowedUrls *regexp.Regexp WindowSize *int + Version *bool } type ArrayFlags []string @@ -55,6 +56,7 @@ func ParseArgs() { config.NoBanner = flag.Bool("no-banner", false, "Disable banner") config.Timeout = flag.Int("timeout", 2000, "timeout in milliseconds") config.WindowSize = flag.Int("window-size", 50, "window-size for fragmented client hello") + config.Version = flag.Bool("v", false, "print version") flag.Var(&allowedHosts, "url", "Bypass DPI only on this url, can be passed multiple times") allowedPattern = flag.String(