From 750862abdbada88eb4fcbbd97ca857a2a8b37346 Mon Sep 17 00:00:00 2001 From: nadoo <287492+nadoo@users.noreply.github.com> Date: Wed, 12 Dec 2018 21:40:31 +0800 Subject: [PATCH] obfs: added simple-obfs support. #79 --- README.md | 23 +++- conf.go | 22 +++- config/glider.conf.example | 9 ++ main.go | 1 + proxy/forwarder.go | 2 +- proxy/http/http.go | 1 + proxy/kcp/kcp.go | 2 +- proxy/obfs/http.go | 78 +++++++++++ proxy/obfs/obfs.go | 109 ++++++++++++++++ proxy/obfs/tls.go | 257 +++++++++++++++++++++++++++++++++++++ proxy/tls/tls.go | 2 - 11 files changed, 497 insertions(+), 9 deletions(-) create mode 100644 proxy/obfs/http.go create mode 100644 proxy/obfs/obfs.go create mode 100644 proxy/obfs/tls.go diff --git a/README.md b/README.md index 83c0e1c..bf46682 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Listen (local proxy server): - UDP over TCP tunnel - TLS, use it together with above proxy protocols(tcp) - Unix domain socket, use it together with above proxy protocols(tcp) +- KCP protocol, use it together with above proxy protocols(tcp) Forward (local proxy client/upstream proxy server): @@ -39,6 +40,8 @@ Forward (local proxy client/upstream proxy server): - TLS, use it together with above proxy protocols(tcp) - Websocket, use it together with above proxy protocols(tcp) - Unix domain socket, use it together with above proxy protocols(tcp) +- KCP protocol, use it together with above proxy protocols(tcp) +- Simple-Obfs, use it together with above proxy protocols(tcp) DNS Forwarding Server (udp2tcp): @@ -113,7 +116,7 @@ glider -config CONFIGPATH -listen :8080 -verbose ## Usage ```bash -glider v0.6.9 usage: +glider v0.6.10 usage: -checkduration int proxy check interval(seconds) (default 30) -checkwebsite string @@ -166,10 +169,12 @@ Available Schemes: udptun: udp tunnel uottun: udp over tcp tunnel unix: unix domain socket + kcp: kcp protocol + simple-obfs: simple-obfs protocol Available schemes for different modes: - listen: mixed ss socks5 http redir redir6 tcptun udptun uottun tls unix - forward: ss socks5 http ssr vmess tls ws unix + listen: mixed ss socks5 http redir redir6 tcptun udptun uottun tls unix kcp + forward: ss socks5 http ssr vmess tls ws unix kcp simple-bfs SS scheme: ss://method:pass@host:port @@ -227,6 +232,18 @@ TLS and Websocket with a specified proxy protocol: Unix domain socket scheme: unix://path +KCP scheme: + kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM] + +Available crypt types for KCP: + none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20 + +Simple-Obfs scheme: + simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA] + +Available types for simple-obfs: + http, tls + DNS forwarding server: dns=:53 dnsserver=8.8.8.8:53 diff --git a/conf.go b/conf.go index aa59f96..d78da2b 100644 --- a/conf.go +++ b/conf.go @@ -122,11 +122,13 @@ func usage() { fmt.Fprintf(os.Stderr, " udptun: udp tunnel\n") fmt.Fprintf(os.Stderr, " uottun: udp over tcp tunnel\n") fmt.Fprintf(os.Stderr, " unix: unix domain socket\n") + fmt.Fprintf(os.Stderr, " kcp: kcp protocol\n") + fmt.Fprintf(os.Stderr, " simple-obfs: simple-obfs protocol\n") fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "Available schemes for different modes:\n") - fmt.Fprintf(os.Stderr, " listen: mixed ss socks5 http redir redir6 tcptun udptun uottun tls unix\n") - fmt.Fprintf(os.Stderr, " forward: ss socks5 http ssr vmess tls ws unix\n") + fmt.Fprintf(os.Stderr, " listen: mixed ss socks5 http redir redir6 tcptun udptun uottun tls unix kcp\n") + fmt.Fprintf(os.Stderr, " forward: ss socks5 http ssr vmess tls ws unix kcp simple-bfs\n") fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "SS scheme:\n") @@ -198,6 +200,22 @@ func usage() { fmt.Fprintf(os.Stderr, " unix://path\n") fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, "KCP scheme:\n") + fmt.Fprintf(os.Stderr, " kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM]\n") + fmt.Fprintf(os.Stderr, "\n") + + fmt.Fprintf(os.Stderr, "Available crypt types for KCP:\n") + fmt.Fprintf(os.Stderr, " none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20\n") + fmt.Fprintf(os.Stderr, "\n") + + fmt.Fprintf(os.Stderr, "Simple-Obfs scheme:\n") + fmt.Fprintf(os.Stderr, " simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]\n") + fmt.Fprintf(os.Stderr, "\n") + + fmt.Fprintf(os.Stderr, "Available types for simple-obfs:\n") + fmt.Fprintf(os.Stderr, " http, tls\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, "DNS forwarding server:\n") fmt.Fprintf(os.Stderr, " dns=:53\n") fmt.Fprintf(os.Stderr, " dnsserver=8.8.8.8:53\n") diff --git a/config/glider.conf.example b/config/glider.conf.example index da7fe9b..796b0ad 100644 --- a/config/glider.conf.example +++ b/config/glider.conf.example @@ -64,6 +64,9 @@ listen=socks5://:1080 # socks5 over unix domain socket # listen=unix:///tmp/glider.socket,socks5:// +# socks5 over kcp +# listen=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3,socks5:// + # FORWARDERS # ---------- # Forwarders, we can setup multiple forwarders. @@ -113,6 +116,12 @@ listen=socks5://:1080 # ss over tls # forward=tls://1.1.1.1:443,ss://AEAD_CHACHA20_POLY1305:pass@ +# ss over kcp +# forward=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3,ss://AEAD_CHACHA20_POLY1305:pass@ + +# ss with simple-obfs +# forward=simple-obfs://1.1.1.1:443?type=tls&host=apple.com,ss://AEAD_CHACHA20_POLY1305:pass@ + # socks5 over unix domain socket # forward=unix:///tmp/glider.socket,socks5:// diff --git a/main.go b/main.go index 42ff570..56b1715 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( _ "github.com/nadoo/glider/proxy/http" _ "github.com/nadoo/glider/proxy/kcp" _ "github.com/nadoo/glider/proxy/mixed" + _ "github.com/nadoo/glider/proxy/obfs" _ "github.com/nadoo/glider/proxy/socks5" _ "github.com/nadoo/glider/proxy/ss" _ "github.com/nadoo/glider/proxy/ssr" diff --git a/proxy/forwarder.go b/proxy/forwarder.go index ecc8dd1..eb3bb32 100644 --- a/proxy/forwarder.go +++ b/proxy/forwarder.go @@ -89,7 +89,7 @@ func (f *Forwarder) Dial(network, addr string) (c net.Conn, err error) { f.IncFailures() if f.Failures() >= f.MaxFailures() { f.Disable() - log.F("[forwarder] %s reaches maxfailures, set to DISABLED", f.addr) + log.F("[forwarder] %s reaches maxfailures.", f.addr) } } diff --git a/proxy/http/http.go b/proxy/http/http.go index b66a1dd..21582d1 100644 --- a/proxy/http/http.go +++ b/proxy/http/http.go @@ -258,6 +258,7 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) { _, code, _, ok := parseFirstLine(respTP) if ok && code == "200" { + // TODO: check here respTP.ReadMIMEHeader() return rc, err } else if code == "407" { diff --git a/proxy/kcp/kcp.go b/proxy/kcp/kcp.go index d766856..209230f 100644 --- a/proxy/kcp/kcp.go +++ b/proxy/kcp/kcp.go @@ -69,7 +69,7 @@ func NewKCP(s string, dialer proxy.Dialer) (*KCP, error) { parityShards, err := strconv.ParseUint(pShards, 10, 32) if err != nil { - log.F("[kcp] parse dataShards err: %s", err) + log.F("[kcp] parse parityShards err: %s", err) return nil, err } diff --git a/proxy/obfs/http.go b/proxy/obfs/http.go new file mode 100644 index 0000000..50941b8 --- /dev/null +++ b/proxy/obfs/http.go @@ -0,0 +1,78 @@ +package obfs + +import ( + "bufio" + "bytes" + "crypto/rand" + "encoding/base64" + "io" + "net" +) + +// HTTPObfs struct +type HTTPObfs struct { + obfsHost string + obfsURI string + obfsUA string +} + +// NewHTTPObfs returns a HTTPObfs object +func NewHTTPObfs(obfsHost, obfsURI, obfsUA string) *HTTPObfs { + return &HTTPObfs{obfsHost, obfsURI, obfsUA} +} + +// HTTPObfsConn struct +type HTTPObfsConn struct { + *HTTPObfs + + net.Conn + reader io.Reader +} + +// NewConn returns a new obfs connection +func (p *HTTPObfs) NewConn(c net.Conn) (net.Conn, error) { + cc := &HTTPObfsConn{ + Conn: c, + HTTPObfs: p, + } + + // send http header to remote server + _, err := cc.writeHeader() + return cc, err +} + +func (c *HTTPObfsConn) writeHeader() (int, error) { + buf := new(bytes.Buffer) + buf.Write([]byte("GET " + c.obfsURI + " HTTP/1.1\r\n")) + buf.Write([]byte("Host: " + c.obfsHost + "\r\n")) + buf.Write([]byte("User-Agent: " + c.obfsUA + "\r\n")) + buf.Write([]byte("Upgrade: websocket\r\n")) + buf.Write([]byte("Connection: Upgrade\r\n")) + + p := make([]byte, 16) + rand.Read(p) + buf.Write([]byte("Sec-WebSocket-Key: " + base64.StdEncoding.EncodeToString(p) + "\r\n")) + + buf.Write([]byte("\r\n")) + + return c.Conn.Write(buf.Bytes()) +} + +func (c *HTTPObfsConn) Read(b []byte) (n int, err error) { + if c.reader == nil { + r := bufio.NewReader(c.Conn) + c.reader = r + for { + l, _, err := r.ReadLine() + if err != nil { + return 0, err + } + + if len(l) == 0 { + break + } + } + } + + return c.reader.Read(b) +} diff --git a/proxy/obfs/obfs.go b/proxy/obfs/obfs.go new file mode 100644 index 0000000..73a9cfa --- /dev/null +++ b/proxy/obfs/obfs.go @@ -0,0 +1,109 @@ +// Package obfs implements simple-obfs of ss +package obfs + +import ( + "errors" + "net" + "net/url" + + "github.com/nadoo/glider/common/log" + "github.com/nadoo/glider/proxy" +) + +// Obfs struct +type Obfs struct { + dialer proxy.Dialer + addr string + + obfsType string + obfsHost string + obfsURI string + obfsUA string + + obfsConn func(c net.Conn) (net.Conn, error) +} + +func init() { + proxy.RegisterDialer("simple-obfs", NewObfsDialer) +} + +// NewObfs returns a proxy struct +func NewObfs(s string, dialer proxy.Dialer) (*Obfs, error) { + u, err := url.Parse(s) + if err != nil { + log.F("parse url err: %s", err) + return nil, err + } + + addr := u.Host + + query := u.Query() + obfsType := query.Get("type") + if obfsType == "" { + obfsType = "http" + } + + obfsHost := query.Get("host") + if obfsHost == "" { + return nil, errors.New("[obfs] host cannot be null") + } + + obfsURI := query.Get("uri") + if obfsURI == "" { + obfsURI = "/" + } + + obfsUA := query.Get("ua") + if obfsUA == "" { + obfsUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" + } + + p := &Obfs{ + dialer: dialer, + addr: addr, + obfsType: obfsType, + obfsHost: obfsHost, + obfsURI: obfsURI, + obfsUA: obfsUA, + } + + switch obfsType { + case "http": + httpObfs := NewHTTPObfs(obfsHost, obfsURI, obfsUA) + p.obfsConn = httpObfs.NewConn + case "tls": + tlsObfs := NewTLSObfs(obfsHost) + p.obfsConn = tlsObfs.NewConn + default: + return nil, errors.New("[obfs] unknown obfs type: " + obfsType) + } + + return p, nil +} + +// NewObfsDialer returns a proxy dialer +func NewObfsDialer(s string, dialer proxy.Dialer) (proxy.Dialer, error) { + return NewObfs(s, dialer) +} + +// Addr returns forwarder's address +func (s *Obfs) Addr() string { return s.addr } + +// NextDialer returns the next dialer +func (s *Obfs) NextDialer(dstAddr string) proxy.Dialer { return s.dialer.NextDialer(dstAddr) } + +// Dial connects to the address addr on the network net via the proxy +func (s *Obfs) Dial(network, addr string) (net.Conn, error) { + c, err := s.dialer.Dial("tcp", s.addr) + if err != nil { + log.F("[obfs] dial to %s error: %s", s.addr, err) + return nil, err + } + + return s.obfsConn(c) +} + +// DialUDP connects to the given address via the proxy +func (s *Obfs) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) { + return nil, nil, errors.New("obfs client does not support udp now") +} diff --git a/proxy/obfs/tls.go b/proxy/obfs/tls.go new file mode 100644 index 0000000..34dbda5 --- /dev/null +++ b/proxy/obfs/tls.go @@ -0,0 +1,257 @@ +// https://www.ietf.org/rfc/rfc5246.txt +// https://golang.org/src/crypto/tls/handshake_messages.go + +// NOTE: +// https://github.com/shadowsocks/simple-obfs/blob/master/src/obfs_tls.c +// The official obfs-server only checks 6 static bytes of client hello packet, +// so if we send a malformed packet, e.g: set a wrong length number of extensions, +// obfs-server will treat it as a correct packet, but in wireshak, it's malformed. + +package obfs + +import ( + "bufio" + "bytes" + "crypto/rand" + "encoding/binary" + "io" + "net" +) + +const ( + lenSize = 2 + chunkSize = 1 << 13 // 8192 +) + +// TLSObfs struct +type TLSObfs struct { + obfsHost string +} + +// NewTLSObfs returns a TLSObfs object +func NewTLSObfs(obfsHost string) *TLSObfs { + return &TLSObfs{obfsHost: obfsHost} +} + +// TLSObfsConn struct +type TLSObfsConn struct { + *TLSObfs + + net.Conn + reqSent bool + reader *bufio.Reader + buf []byte + leftBytes int +} + +// NewConn returns a new obfs connection +func (p *TLSObfs) NewConn(c net.Conn) (net.Conn, error) { + cc := &TLSObfsConn{ + Conn: c, + TLSObfs: p, + buf: make([]byte, lenSize), + } + + return cc, nil +} + +func (c *TLSObfsConn) Write(b []byte) (int, error) { + if !c.reqSent { + c.reqSent = true + return c.handshake(b) + } + + n := len(b) + for i := 0; i < n; i += chunkSize { + end := i + chunkSize + if end > n { + end = n + } + + buf := new(bytes.Buffer) + buf.Write([]byte{0x17, 0x03, 0x03}) + binary.Write(buf, binary.BigEndian, uint16(len(b[i:end]))) + buf.Write(b[i:end]) + + _, err := c.Conn.Write(buf.Bytes()) + if err != nil { + return 0, err + } + } + + return n, nil +} + +func (c *TLSObfsConn) Read(b []byte) (int, error) { + if c.reader == nil { + c.reader = bufio.NewReader(c.Conn) + // Server Hello + // TLSv1.2 Record Layer: Handshake Protocol: Server Hello (96 bytes) + // TLSv1.2 Record Layer: Change Cipher Spec Protocol: Change Cipher Spec (6 bytes) + c.reader.Discard(102) + } + + if c.leftBytes == 0 { + // TLSv1.2 Record Layer: + // 1st packet: handshake encrypted message / following packets: application data + // 1 byte: Content Type: Handshake (22) / Application Data (23) + // 2 bytes: Version: TLS 1.2 (0x0303) + c.reader.Discard(3) + + // get length + _, err := io.ReadFull(c.reader, c.buf[:lenSize]) + if err != nil { + return 0, err + } + + c.leftBytes = int(binary.BigEndian.Uint16(c.buf[:lenSize])) + } + + readLen := len(b) + if readLen > c.leftBytes { + readLen = c.leftBytes + } + + m, err := c.reader.Read(b[:readLen]) + if err != nil { + return 0, err + } + + c.leftBytes -= m + + return m, nil +} + +func (c *TLSObfsConn) handshake(b []byte) (int, error) { + buf := new(bytes.Buffer) + + // prepare extension & clientHello content + bufExt, bufHello := extention(b, c.obfsHost), clientHello() + + // prepare lengths + extLen := bufExt.Len() + helloLen := bufHello.Len() + 2 + extLen // 2: len(extContentLength) + handshakeLen := 4 + helloLen // 1: len(0x01) + 3: len(clientHelloContentLength) + + // TLS Record Layer Begin + // Content Type: Handshake (22) + buf.WriteByte(0x16) + + // Version: TLS 1.0 (0x0301) + buf.Write([]byte{0x03, 0x01}) + + // length + binary.Write(buf, binary.BigEndian, uint16(handshakeLen)) + + // Handshake Begin + // Handshake Type: Client Hello (1) + buf.WriteByte(0x01) + + // length: uint24(3 bytes), but golang doesn't have this type + buf.Write([]byte{uint8(helloLen >> 16), uint8(helloLen >> 8), uint8(helloLen)}) + + // clientHello content + buf.Write(bufHello.Bytes()) + + // Extension Begin + // ext content length + binary.Write(buf, binary.BigEndian, uint16(extLen)) + + // ext content + buf.Write(bufExt.Bytes()) + + _, err := c.Conn.Write(buf.Bytes()) + if err != nil { + return 0, err + } + + return len(b), nil +} + +func clientHello() *bytes.Buffer { + buf := new(bytes.Buffer) + + // Version: TLS 1.2 (0x0303) + buf.Write([]byte{0x03, 0x03}) + + // Random + // https://tools.ietf.org/id/draft-mathewson-no-gmtunixtime-00.txt + random := make([]byte, 32) + rand.Read(random) + buf.Write(random) + + // Session ID Length: 32 + buf.WriteByte(32) + // Session ID + sessionID := make([]byte, 32) + rand.Read(sessionID) + buf.Write(sessionID) + + // https://github.com/shadowsocks/simple-obfs/blob/7659eeccf473aa41eb294e92c32f8f60a8747325/src/obfs_tls.c#L57 + // Cipher Suites Length: 56 + buf.Write([]byte{0x00, 0x38}) + // Cipher Suites (28 suites) + buf.Write([]byte{ + 0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f, + 0x00, 0x9e, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a, + 0xc0, 0x14, 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, 0x00, 0x9d, 0x00, 0x9c, 0x00, 0x3d, + 0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0xff, + }) + + // Compression Methods Length: 1 + buf.WriteByte(0x01) + // Compression Methods (1 method) + buf.WriteByte(0x00) + + return buf +} + +func extention(b []byte, server string) *bytes.Buffer { + buf := new(bytes.Buffer) + + // Extension: SessionTicket TLS + buf.Write([]byte{0x00, 0x23}) // type + // NOTE: send some data in sessionticket, the server will treat it as data too + binary.Write(buf, binary.BigEndian, uint16(len(b))) // length + buf.Write(b) + + // Extension: server_name + buf.Write([]byte{0x00, 0x00}) // type + binary.Write(buf, binary.BigEndian, uint16(len(server)+5)) // length + binary.Write(buf, binary.BigEndian, uint16(len(server)+3)) // Server Name list length + buf.WriteByte(0x00) // Server Name Type: host_name (0) + binary.Write(buf, binary.BigEndian, uint16(len(server))) // Server Name length + buf.Write([]byte(server)) + + // https://github.com/shadowsocks/simple-obfs/blob/7659eeccf473aa41eb294e92c32f8f60a8747325/src/obfs_tls.c#L88 + // Extension: ec_point_formats (len=4) + buf.Write([]byte{0x00, 0x0b}) // type + binary.Write(buf, binary.BigEndian, uint16(4)) // length + buf.WriteByte(0x03) // format length + buf.Write([]byte{0x01, 0x00, 0x02}) + + // Extension: supported_groups (len=10) + buf.Write([]byte{0x00, 0x0a}) // type + binary.Write(buf, binary.BigEndian, uint16(10)) // length + binary.Write(buf, binary.BigEndian, uint16(8)) // Supported Groups List Length: 8 + buf.Write([]byte{0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18}) + + // Extension: signature_algorithms (len=32) + buf.Write([]byte{0x00, 0x0d}) // type + binary.Write(buf, binary.BigEndian, uint16(32)) // length + binary.Write(buf, binary.BigEndian, uint16(30)) // Signature Hash Algorithms Length: 30 + buf.Write([]byte{ + 0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05, 0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 0x04, 0x02, + 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03, + }) + + // Extension: encrypt_then_mac (len=0) + buf.Write([]byte{0x00, 0x16}) // type + binary.Write(buf, binary.BigEndian, uint16(0)) // length + + // Extension: extended_master_secret (len=0) + buf.Write([]byte{0x00, 0x17}) // type + binary.Write(buf, binary.BigEndian, uint16(0)) // length + + return buf +} diff --git a/proxy/tls/tls.go b/proxy/tls/tls.go index 3e1bffd..e795e27 100644 --- a/proxy/tls/tls.go +++ b/proxy/tls/tls.go @@ -81,7 +81,6 @@ func NewTLSDialer(s string, dialer proxy.Dialer) (proxy.Dialer, error) { InsecureSkipVerify: p.skipVerify, ClientSessionCache: stdtls.NewLRUClientSessionCache(64), MinVersion: stdtls.VersionTLS10, - MaxVersion: stdtls.VersionTLS12, } return p, err @@ -111,7 +110,6 @@ func NewTLSServer(s string, dialer proxy.Dialer) (proxy.Server, error) { p.tlsConfig = &stdtls.Config{ Certificates: []stdtls.Certificate{cert}, MinVersion: stdtls.VersionTLS10, - MaxVersion: stdtls.VersionTLS12, } p.server, err = proxy.ServerFromURL(transport[1], dialer)