From bfe2f28cbe2ec8dd477ef99ad175452505db9f3a Mon Sep 17 00:00:00 2001 From: Sudhi Herle Date: Fri, 3 Jul 2020 18:03:53 -0700 Subject: [PATCH] Major feature update: * Added socks server capability * Fully tested Quic client/server * E2E test cases * Updated diagram for using the socks feature * updated readme --- README.md | 168 ++++++++++++++++------- docs/README.md | 79 +++++++++++ docs/socks-example.dia | Bin 0 -> 1854 bytes docs/socks-example.png | Bin 0 -> 29526 bytes etc/gotun.conf | 38 ++++-- etc/socks_client.conf | 58 ++++++++ etc/socks_server.conf | 58 ++++++++ gotun/conf.go | 31 +++-- gotun/main.go | 6 +- gotun/mocked_test.go | 183 +++++++++++++++++++------ gotun/quic_test.go | 2 +- gotun/server.go | 299 ++++++++++++++++++++++++++++++----------- gotun/socks_test.go | 208 ++++++++++++++++++++++++++++ gotun/tcp_test.go | 6 +- gotun/tcpdial.go | 8 +- gotun/utils.go | 36 +++++ 16 files changed, 976 insertions(+), 204 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/socks-example.dia create mode 100644 docs/socks-example.png create mode 100644 etc/socks_client.conf create mode 100644 etc/socks_server.conf create mode 100644 gotun/socks_test.go diff --git a/README.md b/README.md index 94816ac..3482ce4 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,15 @@ # go-tunnel - Robust Quic/TLS Tunnel (Stunnel replacement) ## What is it? -A supercharged [Stunnel](https://www.stunnel.org) replacement written in golang. It is +A supercharged [Stunnel](https://www.stunnel.org) replacement written in golang. is in a sense a proxy enabling addition of network-encryption to existing clients without any source code changes. -go-tunnel uses golang's TLS stack and built-in certification verification. - ## Features - TLS 1.3 for client and server mode (TLS Connect or TLS Listen) - Quic client and server mode (Quic listen or Quic connect) +- Optional SOCKS for connecting endpoint (SOCKS server) - Optional TLS client certificate (for Quic/TLS Connect) - SNI on the listening Quic/TLS server - Ratelimits - global and per-IP @@ -19,50 +18,66 @@ go-tunnel uses golang's TLS stack and built-in certification verification. - YAML Configuration file - Access Control on per IP or subnet basis (allow/deny combination) - Strong ciphers and curves preferred on both client & server +- Comes with end-to-end tests covering variety of scenarios Note that TLS private keys need to be *unencrypted*; we don't support password protected private keys yet. The main reason for this is that when `gotun` is daemonized, it may not be possible to obtain the password in an interactive manner. Additionally, for SNI support, it may be impossible to ask for interactive password in the middle of a client connection setup. -### Motivating Example -Let us suppose that you have a SOCKS5 server on host `192.168.55.3` and this -is accessible via a "gateway" node `172.16.55.3`. Furthermore, let us say that -clients/browsers wishing to use the SOCKS5 proxy are in the `10.0.0.0/24` subnet. -And to keep things simple, let us assume that one host in the `10.0.0.0` network -can access the gateway node: `10.0.0.5`. +## Motivating Example +Lets assume you have a public server on `proxy.example.com` +listening on Quic/UDP supporting SOCKS protocol for connecting to +outbound destinations. For security reasons, you want to limit +access to only clients that are TLS authenticated (TLS client +certs). + +Lets also assume that you have a laptop that wants to connect to the +SOCKS server efficiently. + +Using two instances of `gotun`, you can accomplish this: + +1. Local gotun instance on your laptop configured to accept TCP and + connect using Quic to the external server `proxy.example.com` -Ordinarily, we'd create a IP routing rule on `10.0.0.5` to make the hosts on its network -access the `192.168.55.0/24` via `172.16.55.3`. But, we desire the communication -between `10.0.0.0/24` and `172.16.55.0/24` to be encrypted. +2. Server gotun instance on the external host configured to accept + authenticated Quic connections and proxy via SOCKS. -Thus, with go-tunnel, one can setup a "bridge" between the two networks - and the bridge -is encrypted with TLS. The picture below explains the connectivity: +3. Configure your laptop browser to use the "local" SOCKS server. -![example diagram](/docs/example-diagram.png) +Using Quic to connect the two `gotun` instances reduces the TCP/TLS +overhead of every socks connection. And, TLS client certs enables +strong authentication on the external server. -In the setup above, hosts will treat `10.0.0.5:1080` as their "real" SOCKS server. Behind the -scenes, go-tunnel is relaying the packets from `10.0.0.5` to `172.16.55.3` via TLS. And, in turn -`172.16.55.3` relays the decrypted packets to the actual SOCKS server on `192.168.55.3`. +The picture below explains the connectivity: -The config file shown above actually demonstrates a really secure tunnel - where the server and -client both use certificates to authenticate each other. +![example diagram](/docs/socks-example.png) -Assuming the config on "Gotunnel-A" is in file `a.conf`, and the config on "Gotunnel-B" is in -`b.conf`, to run the above example, on host "Gotunnel-A": +In the setup above, the laptop browser clients will treat +`127.0.0.1:1080` as their "real" SOCKS server. Behind the scenes, +`gotun` will tunnel the packets via Quic to a remote endpoint where +a second `gotun` instance will unbundle the SOCKS protocol and +connect to the final destination. - gotun -d a.conf +The config file shown above actually demonstrates a really secure tunnel +- where the server and client both use certificates to authenticate each other. -And, on host "Gotunnel-B": +Assuming the config on "Gotunnel Laptop" is in file `client.conf`, and the +config on "Gotunnel Server" is in `server.conf`, to run the above example, +on host "Gotunnel-A": - gotun -d b.conf + gotun client.conf +And, on the public server: -The `-d` flag runs `gotun` in debug mode - where the logs are sent -to STDOUT. + gotun server.conf -### Building go-tunnel -You need a reasonably new Golang toolchain (1.8+). And the `go` +The `-d` flag for `gotun` runs it in debug mode - where the logs are sent +to STDOUT. It's not recommended to run a production server in debug +mode (too many log messages). + +## Building go-tunnel +You need a reasonably new Golang toolchain (1.14+). And the `go` executable needs to be in your path. Then run: make @@ -105,8 +120,8 @@ In debug mode, the logs are sent to STDOUT and the debug level is set to DEBUG In the absence of the `-d` flag, the default log level is INFO or whatever is set in the config file. -### Config File -The config file is a YAML v2 document. A self-explanatory example is below: +## Config File +The config file is a YAML v2 document. A complete, self-explanatory example is below: ```yaml @@ -121,6 +136,10 @@ log: STDOUT # Logging level - "DEBUG", "INFO", "WARN", "ERROR" loglevel: DEBUG +# config dir - where all non-absolute file references below will +# apply. +config-dir: /etc/gotun + # Listeners listen: # Listen plain text @@ -129,25 +148,26 @@ listen: deny: [] timeout: - connect: 10 - read: 10 - write: 30 + connect: 5 + read: 2 + write: 2 # limit to N reqs/sec globally ratelimit: global: 2000 - perhost: 30 + per-host: 30 + cache-size: 10000 # Connect via TLS connect: address: host.name:443 bind: my.ip.address tls: - cert: /path/to/crt - key: /path/to/key - # path to CA bundle that can verify the server certificate. - # This can be a file or a directory. - ca: /path/to/ca.crt + cert: /path/to/crt + key: /path/to/key + # path to CA bundle that can verify the server certificate. + # This can be a file or a directory. + ca: /path/to/ca.crt # if address is a name, then servername is populated from it. # else, if it is an IP address, it must be set below. @@ -159,19 +179,18 @@ listen: allow: [127.0.0.1/8, 11.0.1.0/24, 11.0.2.0/24] deny: [] timeout: - connect: 8 - read: 9 - write: 27 + connect: 5 + read: 2 + write: 2 tls: - sni: true - certdir: /path/to/cert/dir + sni: /path/to/cert/dir # clientcert can be "required" or "optional" or "blank" or absent. # if it is required/optional, then clientca must be set to the list of # CAs that can verify a presented client cert. - clientcert: required - clientca: /path/to/clientca.crt + client-cert: required + client-ca: /path/to/clientca.crt # plain connect but use proxy-protocol v1 when speaking # downstream @@ -179,9 +198,29 @@ listen: address: 55.66.77.88:80 proxyprotocol: v1 + + # Listen on Quic + client auth and connect to SOCKS + - address: 127.0.0.1:8443 + tls: + quic: true + cert: /path/to/crt + key: /path/to/key + # path to CA bundle that can verify the server certificate. + # This can be a file or a directory. + ca: /path/to/ca.crt + + client-cert: required + client-ca: /path/to/clientca.crt + + connect: + address: SOCKS + ``` -### Using SNI +The `etc/` directory has example configurations for running +Quic+SOCKS on a public server and a local laptop. + +## Using SNI SNI is exposed via domain specific certs & keys in the `tls.certdir` config block. SNI is enabled by setting `tls.sni` config element to `true`; and each hostname that is requested via SNI needs a cert and key file with the file prefix of hostname. e.g., if the client is looking @@ -190,7 +229,15 @@ for hostname "blog.mydomain.com" via SNI, then `gotun` will look for `blog.mydom an example for SNI configured on listen address `127.0.0.1:9443`. -### Performance Test +## Security +`gotun` tries to be safe by default: + +- Opinionated TLS 1.3 configuration +- All config file references are checked for safety: e.g., any TLS + certs/keys are verified to have sane permissions (NOT group/world + writable) + +## Performance Test Using iperf3 on two debian-linux (amd64) hosts connected via Gigabit Ethernet and `gotun` running on either end, the performance looks like so: @@ -224,7 +271,7 @@ CPU Utilization: local/sender 1.8% (0.0%u/1.7%s), remote/receiver 9.0% (0.6%u/8. ``` -### Access Control Rules +## Access Control Rules Go-tunnel implements a flexible ACL by combination of allow/deny rules. The rules are evaluated in the following order: @@ -234,7 +281,7 @@ allow/deny rules. The rules are evaluated in the following order: - Explicit denial takes precedence over explicit allow - Default (fall through) policy is to deny -#### Example of allow/deny combinations +### Example of allow/deny combinations 1. Allow all: @@ -273,7 +320,24 @@ If you are a developer, the notes here will be useful for you: - The code uses go modules; so, you'll need a reasonably new go toolchain (1.10+) -- The go-tunnel code is in `./gotun`. +- The go-tunnel code is in `./gotun`: + + * main.go: `main()` for `gotun` + * server.go: Implements TCP/TLS and Quic servers; also + implements the SOCKS server protocol + * conf.go: YAML configuration file parser + * quicdial.go: Dial outbound connections via Quic + streams + * tcpdial.go: Dial outbound connections via TCP + * safety.go: Safely open files/dirs referenced in config file + +- Tests: running tests: `go test -v ./gotun` + Some of the tests/helpers: + * mocked_test.go: Mock servers and clients + * tcp_test.go: Tests for TCP/TLS to TCP/TLS + * quic_test.go: Tests for TCP/TLS to Quic and vice versa + * socks_test.go: Tests for socks (includes a test for the + example configuration above) + * utils_test.go: test helpers (e.g., `assert()`) - We build `build` - a a master shell script to build the daemons; it does two very important things: diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..b44ff05 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,79 @@ +# Example Configurations for various scenarios + +## TLS to TCP proxy with client authentication +If you want to have strong authentication of clients to access a +service behind the proxy, then the following configuration is a good +starting point: + +```yaml + +log: SYSLOG +loglevel: INFO + +listen: + - address: ip.address:lport + ratelimit: + global: 20000 + per-host: 50 + # LRU cache size for per-host rate limit + cache-size: 50000 + + tls: + cert: /path/to/server.crt + key: /path/to/server.key + # ca can be a file containing multiple certs or a + # directory containing ca certs + ca: /path/to/ca.bundle + + client-cert: required + client-ca: /path/to/clientca.crt + + + connect: + address: host.name:port + proxy-protocol: v1 + +``` + +Now, clients that have a valid cert/key pair can connect to to +`ip.address:lport` above. And if the cert/key pair is valid and +accepted by the proxy, the client will be connected to the backend +on `host.name:port`. + +## Quic to TCP with client authentication +If you have a modern quic client (e.g., most chrome browsers) but +your service is still serving legacy TCP/TLS, `gotunnel` can help +bridge this protocol gap: configure it to listen on a quic port and +relay connections from client to the backend TCP/TLS service: + +```yaml + +log: SYSLOG +loglevel: INFO + +listen: + - address: ip.address:lport + ratelimit: + global: 20000 + per-host: 50 + + tls: + quic: true + cert: /path/to/server.crt + key: /path/to/server.key + # ca can be a file containing multiple certs or a + # directory containing ca certs + ca: /path/to/ca.bundle + + clientcert: required + clientca: /path/to/clientca.crt + + + connect: + address: host.name:port + proxy-protocol: v1 +``` + +The configuration is essentially the same as the previous one - with +the addition of the **`quic: true`** setting. This setting forces +`gotunnel` to listen on a UDP port. diff --git a/docs/socks-example.dia b/docs/socks-example.dia new file mode 100644 index 0000000000000000000000000000000000000000..2fce2600ed745085d0fa4d4c3105fea3b0fc729e GIT binary patch literal 1854 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 no verification (InsecureSkipVerify = true) + # servername: a.example.com + + +# vim: expandtab:sw=4:ts=4:tw=88: diff --git a/etc/socks_server.conf b/etc/socks_server.conf new file mode 100644 index 0000000..e1f6c51 --- /dev/null +++ b/etc/socks_server.conf @@ -0,0 +1,58 @@ +# Example config file: +# - listen on Quic and connect to dynamic endpoints based on socks messages +# in the quic stream +# - outgoing connection is dynamic - based on socks message +# stream +# +# Log file; can be one of: +# - Absolute path +# - SYSLOG +# - STDOUT +# - STDERR +log: STDOUT +#log: STDOUT + +# Logging level - "DEBUG", "INFO", "WARN", "ERROR" +loglevel: DEBUG + +# Drop privileges after reading certs and binding to listeners +uid: nobody +gid: nobody + +# Listeners +listen: + - address: :4430 + + timeout: + connect: 2 + read: 2 + write: 3 + + # Quic server needs a TLS config + tls: + quic: true + cert: /path/to/server.crt + key: /path/to/server.key + ca: /path/to/ca.crt # server cert verification chain + + # We will use client certs to authenticate clients + clientcert: required + clientca: /path/to/clientca.crt + + # if address is a name, then servername is populated from it. + # else, if it is an IP address, it must be set below. + # Not setting it => no verification (InsecureSkipVerify = true) + # servername: a.example.com + + # limit to N reqs/sec globally + ratelimit: + global: 20000 + perhost: 100 + + # special connect address of SOCKS indicates dynamic end points derived + # from the underlying socks protocol in each stream + connect: + address: SOCKS + + +# vim: expandtab:sw=4:ts=4:tw=88: diff --git a/gotun/conf.go b/gotun/conf.go index 3364501..f3c4b41 100644 --- a/gotun/conf.go +++ b/gotun/conf.go @@ -52,8 +52,9 @@ type ListenConf struct { } type RateLimit struct { - Global int `yaml:"global"` - PerHost int `yaml:"perhost"` + Global int `yaml:"global"` + PerHost int `yaml:"per-host"` + CacheSize int `yaml:"cache-size"` } // An IP/Subnet @@ -72,22 +73,25 @@ type Timeouts struct { type ConnectConf struct { Addr string `yaml:"address"` Bind string - ProxyProtocol string + ProxyProtocol string `yaml:"proxy-protocol"` Quic bool `yaml:"quic"` Tls *TlsClientConf `yaml:"tls"` } // Tls Conf type TlsServerConf struct { - Quic bool + Quic bool + + // this is the name of a directory where we look for $SERVER.crt + // where $SERVER is in the handshake message Sni string Cert string Key string - ClientCert string + ClientCert string `yaml:"client-cert"` // this can be a file or dir. It is needed to verify the client provided // certificate. - ClientCA string + ClientCA string `yaml:"client-ca"` } // Tls client conf @@ -121,11 +125,11 @@ func ReadYAML(fn string) (*Conf, error) { if err = validate(&cfg); err != nil { return nil, err } - return defaults(&cfg), nil + return ConfDefaults(&cfg), nil } // Setup sane defaults if needed -func defaults(c *Conf) *Conf { +func ConfDefaults(c *Conf) *Conf { for _, l := range c.Listen { if l.Ratelimit == nil { l.Ratelimit = &RateLimit{} @@ -136,6 +140,11 @@ func defaults(c *Conf) *Conf { if l.Ratelimit.PerHost <= 0 { l.Ratelimit.PerHost = 10 } + + if l.Ratelimit.CacheSize <= 0 { + l.Ratelimit.CacheSize = 5000 + } + t := &l.Timeout if t.Connect == 0 { t.Connect = 5 @@ -246,6 +255,10 @@ func (tc *ConnectConf) IsQuic() bool { return tc.Tls != nil && tc.Quic } +func (tc *ConnectConf) IsSocks() bool { + return strings.ToUpper(tc.Addr) == "SOCKS" +} + // parse TLS server config func (lc *ListenConf) ParseTlsServerConf(c *Conf) *tls.Config { t := lc.Tls @@ -381,7 +394,7 @@ func (c *Conf) ReadCA(nm string) *x509.CertPool { for i := range fdv { fd := fdv[i] fn := fd.Name() - if !strings.HasSuffix(fn, ".pem") { + if !strings.HasSuffix(fn, ".pem") || !strings.HasSuffix(fn, ".crt") { fd.Close() continue } diff --git a/gotun/main.go b/gotun/main.go index 5415cd2..c19198f 100644 --- a/gotun/main.go +++ b/gotun/main.go @@ -28,7 +28,7 @@ var Buildtime string = "UNDEFINED" var ProductVersion string = "UNDEFINED" // Network I/O buffer size -var BufSize uint = 65536 +var IOSize uint64 = 65536 // Number of minutes of profile data to capture // XXX Where should this be set? Config file?? @@ -49,7 +49,7 @@ func main() { debugFlag := flag.BoolP("debug", "d", false, "Run in debug mode") verFlag := flag.BoolP("version", "v", false, "Show version info and quit") - flag.UintVarP(&BufSize, "io-bufsize", "B", BufSize, "Set network I/O buffer size to `b` bytes") + flag.SizeVarP(&IOSize, "iobuf-size", "B", IOSize, "Set network I/O buffer size to `b` bytes") usage := fmt.Sprintf("%s [options] config-file", os.Args[0]) @@ -104,8 +104,6 @@ func main() { log.Info("gotun - %s [%s - built on %s] starting up (logging at %s)...", ProductVersion, RepoVersion, Buildtime, log.Prio()) - cfg.Dump(log) - if len(cfg.Listen) == 0 { die("%s: no listeners in config file", cfgfile) } diff --git a/gotun/mocked_test.go b/gotun/mocked_test.go index 5227875..f533b19 100644 --- a/gotun/mocked_test.go +++ b/gotun/mocked_test.go @@ -17,6 +17,7 @@ import ( "io" "math/big" "net" + "strconv" "strings" "sync" "testing" @@ -89,10 +90,10 @@ func (s *tcpserver) accept() { } func (s *tcpserver) relay(fd net.Conn) { - there := fd.LocalAddr().String() + there := fd.RemoteAddr().String() assert := newAsserter(s.t) done := s.ctx.Done() - from := fmt.Sprintf("%s--%s", there, fd.RemoteAddr().String()) + from := fmt.Sprintf("%s--%s", there, fd.LocalAddr().String()) defer func() { s.wg.Done() @@ -120,7 +121,7 @@ func (s *tcpserver) relay(fd net.Conn) { h := sha256.New() for i := 0; ; i++ { fd.SetReadDeadline(time.Now().Add(rto)) - nr, err := readfull(fd, buf) + nr, err := ReadAll(fd, buf) select { case <-done: return @@ -140,7 +141,7 @@ func (s *tcpserver) relay(fd net.Conn) { //s.t.Logf("%s: %d: RX %d [%x]\n", from, i, nr, sum[:]) fd.SetWriteDeadline(time.Now().Add(rto)) - nw, err := writefull(fd, sum[:]) + nw, err := WriteAll(fd, sum[:]) select { case <-done: return @@ -236,7 +237,7 @@ func (c *tcpclient) loop(n int) error { h := sha256.New() for i := 0; i < n; i++ { - nw, err := writefull(fd, buf) + nw, err := WriteAll(fd, buf) select { case <-done: return nil @@ -253,7 +254,7 @@ func (c *tcpclient) loop(n int) error { //c.t.Logf("%s: %d: TX %d [%x]\n", from, i, nw, suma[:]) - nr, err := readfull(fd, sumr[:]) + nr, err := ReadAll(fd, sumr[:]) select { case <-done: return nil @@ -275,39 +276,6 @@ func (c *tcpclient) loop(n int) error { return nil } -func writefull(fd io.Writer, b []byte) (int, error) { - var z int - n := len(b) - for n > 0 { - nw, err := fd.Write(b) - if err != nil { - return z, err - } - - n -= nw - z += nw - b = b[nw:] - } - - return z, nil -} - -func readfull(fd io.Reader, b []byte) (int, error) { - var z int - n := len(b) - for n > 0 { - nr, err := fd.Read(b) - if err != nil { - return z, err - } - - n -= nr - z += nr - b = b[nr:] - } - return z, nil -} - type quicserver struct { quic.Listener @@ -419,7 +387,7 @@ func (q *quicserver) relay(fd *qconn) { h := sha256.New() for i := 0; ; i++ { fd.SetReadDeadline(time.Now().Add(rto)) - nr, err := readfull(fd, buf) + nr, err := ReadAll(fd, buf) select { case <-done: return @@ -439,7 +407,7 @@ func (q *quicserver) relay(fd *qconn) { //q.t.Logf("%s: %d: RX %d [%x]\n", from, i, nr, sum[:]) fd.SetWriteDeadline(time.Now().Add(rto)) - nw, err := writefull(fd, sum[:]) + nw, err := WriteAll(fd, sum[:]) select { case <-done: return @@ -539,7 +507,7 @@ func (q *quicclient) start(n int) { h := sha256.New() for i := 0; i < n; i++ { - nw, err := writefull(fd, buf) + nw, err := WriteAll(fd, buf) select { case <-done: return @@ -556,7 +524,7 @@ func (q *quicclient) start(n int) { //c.t.Logf("%s: %d: TX %d [%x]\n", from, i, nw, suma[:]) - nr, err := readfull(fd, sumr[:]) + nr, err := ReadAll(fd, sumr[:]) select { case <-done: return @@ -583,6 +551,135 @@ func (q *quicclient) stop() { q.Close() } +// socks client +type socksclient struct { + *tcpclient + + dstnet string + dstaddr string +} + +func newSocksClient(addr string, dstnet, dstaddr string, t *testing.T) *socksclient { + tcp := newTcpClient("tcp", addr, nil, t) + s := &socksclient{ + tcpclient: tcp, + dstnet: dstnet, + dstaddr: dstaddr, + } + return s +} + +func (s *socksclient) start(n int) error { + var err error + dial := &net.Dialer{ + Timeout: 1 * time.Second, + } + + s.Conn, err = dial.DialContext(s.ctx, s.network, s.addr) + if err != nil { + return err + } + + s.t.Logf("mock socks client: %s connected to %s\n", s.LocalAddr().String(), s.addr) + err = s.dosocks() + if err != nil { + return err + } + + return s.tcpclient.loop(n) +} + +func (s *socksclient) stop() { + s.tcpclient.stop() +} + +func (s *socksclient) dosocks() error { + var buf [512]byte + var err error + + assert := newAsserter(s.t) + buf[0] = 0x5 + buf[1] = 0x0 + + _, err = WriteAll(s, buf[:2]) + if err != nil { + return err + } + + // auth response is exactly two bytes + _, err = ReadAll(s, buf[:2]) + if err != nil { + return err + } + + // now write the connecting info + buf[0] = 0x5 + buf[1] = 0x1 // connect + buf[2] = 0x0 // resv + + host, sport, err := net.SplitHostPort(s.dstaddr) + if err != nil { + return err + } + + port, err := strconv.ParseUint(sport, 10, 16) + if err != nil { + return err + } + + n := 4 + addr := buf[4:] + ip := net.ParseIP(host) + if ip == nil { + assert(len(host) < 255, "socks dest addr too long (%d)", len(host)) + + buf[3] = 0x3 // domain name + buf[4] = byte(len(host)) + addr := buf[5:] + copy(addr, []byte(host)) + n += 1 + int(buf[4]) + } else { + if ip4 := ip.To4(); ip4 != nil { + n += 4 + buf[3] = 0x1 + copy(addr, ip4) + } else { + n += 16 + buf[3] = 0x4 + copy(addr, ip) + } + } + switch s.dstnet { + case "udp", "udp4", "udp6": + buf[1] = 0x3 + default: + } + + buf[n] = byte(port >> 8) + buf[n+1] = byte(port & 0xff) + n += 2 + + _, err = WriteAll(s, buf[:n]) + if err != nil { + return err + } + + // finally read response (n bytes) + nr, err := s.Read(buf[:]) + if err != nil { + return err + } + + if nr < 2 { + return fmt.Errorf("socks: reply too small (%d bytes)", nr) + } + + if buf[1] != 0x0 { + return fmt.Errorf("socks: Server error: %#x", buf[1]) + } + return nil +} + type pki struct { ca *x509.Certificate cakey *ecdsa.PrivateKey diff --git a/gotun/quic_test.go b/gotun/quic_test.go index 83a1243..dc5df70 100644 --- a/gotun/quic_test.go +++ b/gotun/quic_test.go @@ -32,7 +32,7 @@ func quicSetup(lport, cport int) *Conf { Listen: []*ListenConf{lc}, } - return defaults(c) + return ConfDefaults(c) } // Client -> gotun Quic diff --git a/gotun/server.go b/gotun/server.go index a3857ff..ffe7135 100644 --- a/gotun/server.go +++ b/gotun/server.go @@ -11,11 +11,13 @@ package main import ( "context" "crypto/tls" + //"encoding/hex" "errors" "fmt" "io" "net" "path" + "strings" "sync" "time" @@ -101,7 +103,7 @@ func NewServer(lc *ListenConf, c *Conf, log *L.Logger) Proxy { log = log.New(addr, 0) // Conf file specifies ratelimit as N conns/sec - rl, err := ratelimit.New(lc.Ratelimit.Global, lc.Ratelimit.PerHost, 10000) + rl, err := ratelimit.New(lc.Ratelimit.Global, lc.Ratelimit.PerHost, lc.Ratelimit.CacheSize) if err != nil { die("%s: Can't create ratelimiter: %s", addr, err) } @@ -117,8 +119,10 @@ func NewServer(lc *ListenConf, c *Conf, log *L.Logger) Proxy { ctx: ctx, cancel: cancel, activeConn: make(map[string]*relay), + + // IOSize is a global in main; it can be changed via command line flag pool: &sync.Pool{ - New: func() interface{} { return make([]byte, BufSize) }, + New: func() interface{} { return make([]byte, IOSize) }, }, rl: rl, } @@ -393,29 +397,67 @@ func (p *Server) handleConn(conn Conn, ctx context.Context, log *L.Logger) { conn.Close() }() - peer, err := p.dial.Dial(p.dialnet, p.Connect.Addr, conn, ctx) + var err error + var network, addr string + var nr int + + b0 := p.getBuf() + addr = p.Connect.Addr + network = p.dialnet + socks := p.Connect.IsSocks() + + if socks { + network, addr, nr, err = p.socks(conn, b0, log) + if err != nil { + return + } + } + + peer, err := p.dial.Dial(network, addr, conn, ctx) if err != nil { - log.Warn("can't connect to %s: %s", p.Connect.Addr, err) + log.Warn("can't connect to %s: %s", addr, err) + + // send error message or success to client + // buffer b0 is still intact + if socks { + b0[1] = 0x4 // XXX generic error + WriteAll(conn, b0[:nr]) + } return } + if socks { + b0[1] = 0x0 + _, err = WriteAll(conn, b0[:nr]) + select { + case <-p.ctx.Done(): + return + default: + } + if err != nil { + log.Warn("can't write socks response: %s", err) + return + } + } + defer peer.Close() // we grab the printable info before the socket is closed - lhs_theirs := conn.RemoteAddr().String() - rhs_theirs := peer.RemoteAddr().String() - inbound := fmt.Sprintf("%s-%s", conn.LocalAddr().String(), lhs_theirs) - outbound := fmt.Sprintf("%s-%s", peer.LocalAddr().String(), rhs_theirs) + lhs_there := conn.LocalAddr().String() + lhs_here := conn.RemoteAddr().String() + rhs_here := peer.LocalAddr().String() + rhs_there := peer.RemoteAddr().String() + inbound := fmt.Sprintf("%s-%s", lhs_here, lhs_there) + outbound := fmt.Sprintf("%s-%s", rhs_here, rhs_there) // we really need to log this in the parent logger p.log.Debug("LHS %s, RHS %s", inbound, outbound) // create a child logger anchored to the remote-addr - log = log.New(rhs_theirs, 0) + log = log.New(outbound, 0) var wg sync.WaitGroup - b0 := p.getBuf() b1 := p.getBuf() wg.Add(2) @@ -446,7 +488,155 @@ func (p *Server) handleConn(conn Conn, ctx context.Context, log *L.Logger) { p.putBuf(b0) p.putBuf(b1) - log.Info("%s: rd %d, wr %d; %s: rd %d, wr %d", lhs_theirs, r1, w0, rhs_theirs, r0, w1) + log.Info("%s: rd %d, wr %d; %s: rd %d, wr %d", inbound, r1, w0, outbound, r0, w1) +} + +// Negotiate socksv5 with peer 'fd' and return endpoints to dial +func (p *Server) socks(fd Conn, buf []byte, log *L.Logger) (network, addr string, nr int, err error) { + + done := p.ctx.Done() + // Socksv5 state machine: + // 1. Read Methods + // 2. Write Method Response + // 3. Read ConnInfo + // 4. Write Conn Response + + n, err := fd.Read(buf) + select { + case <-done: + err = errShutdown + return + default: + } + if err != nil { + log.Warn("unable to read Socks version info: %s", err) + return + } + + if n < 2 { + log.Warn("insufficient socks method info (exp at least 2, saw %d)", n) + err = errMsgTooSmall + return + } + + if buf[0] != 0x5 { + log.Warn("unsupported socks version %d", buf[0]) + err = errUnsupportedSocksVer + return + } + + // so, we write a hard-coded response saying "no auth needed" + buf[1] = 0 + _, err = WriteAll(fd, buf[:2]) + select { + case <-done: + err = errShutdown + return + default: + } + if err != nil { + log.Warn("unable to write socks greeting: %s", err) + return + } + + // next read conn setup info + n, err = fd.Read(buf) + select { + case <-done: + err = errShutdown + return + default: + } + if err != nil { + log.Warn("unable to read socks connect info: %s", err) + return + } + + // minimum size is 10 bytes: + // 0: ver + // 1: cmd + // 2: resv + // 3: atype + // 4-7: IPv4 addr + // 8-9: port + if n < 10 { + log.Warn("insufficient socks method info (exp at least 10, saw %d)", n) + err = errMsgTooSmall + return + } + + //log.Debug("socks-connect: %d bytes\n%s", n, hex.Dump(buf[:n])) + + nr = n + switch buf[1] { + case 0x1: + network = "tcp" + case 0x2: + log.Warn("unsupported 'bind' type for socks") + err = errUnsupportedMethod + return + case 0x3: + network = "udp" + } + + // buf[2] is Reserved + + // we've consumed 4 bytes so far + n -= 4 + + want := 0 + daddr := buf[4:] + + // Now connecting dest addr & port + switch buf[3] { + case 0x1: // ipv4 address + want = 4 + case 0x3: // fqdn; first octet is length + want = int(buf[4]) + daddr = buf[5:] + + case 0x4: // ipv6 addr + want = 16 + + default: + log.Warn("unknown socks addr type %#x", buf[3]) + err = errUnsupportedAddr + return + } + + // we must have enough bytes for addr:port + if n < (want + 2) { + log.Warn("insufficient socks method info (exp at least %d, saw %d)", want+2, n) + err = errMsgTooSmall + return + } + + port := daddr[want:] + switch buf[3] { + case 0x1: + addr = fmt.Sprintf("%d.%d.%d.%d", daddr[0], daddr[1], daddr[2], daddr[3]) + case 0x3: + var s strings.Builder + for i := 0; i < want; i++ { + s.WriteByte(daddr[i]) + } + addr = s.String() + case 0x4: + var s strings.Builder + s.WriteString(fmt.Sprintf("[%02x", daddr[0])) + for i := 1; i < want; i++ { + s.WriteString(fmt.Sprintf(":%02x", daddr[i])) + } + s.WriteRune(']') + addr = s.String() + } + + iport := (uint16(port[0]) << 8) + uint16(port[1]) + addr = fmt.Sprintf("%s:%d", addr, iport) + err = nil + + log.Debug("socks: connecting to %s/%s", network, addr) + return } func (p *Server) getBuf() []byte { @@ -455,6 +645,8 @@ func (p *Server) getBuf() []byte { } func (p *Server) putBuf(b []byte) { + // resize before putting it back + b = b[:cap(b)] p.pool.Put(b) } @@ -486,7 +678,7 @@ func (p *Server) cancellableCopy(d, s Conn, buf []byte, ctx context.Context, log case nr > 0: d.SetWriteDeadline(time.Now().Add(wto)) x += nr - nw, err := d.Write(buf[:nr]) + nw, err := WriteAll(d, buf[:nr]) select { case <-done: return @@ -509,68 +701,6 @@ func (p *Server) cancellableCopy(d, s Conn, buf []byte, ctx context.Context, log } } -/* -// interruptible copy -func (p *Server) xcancellableCopy(d, s Conn, buf []byte, ctx context.Context, log *L.Logger) (r, w int) { - - ch := make(chan bool) - go func() { - r, w = p.copyBuf(d, s, buf, log) - close(ch) - }() - - select { - case <-ch: - - case <-ctx.Done(): - // This forces both copy go-routines to end the for{} loops. - log.Debug("SHUTDOWN: Force closing %s and %s", - d.RemoteAddr().String(), s.LocalAddr().String()) - d.Close() - s.Close() - } - - return -} - -// copy from 's' to 'd' using 'buf' -func (p *Server) xcopyBuf(d, s Conn, buf []byte, log *L.Logger) (x, y int) { - rto := time.Duration(p.Timeout.Read) * time.Second - wto := time.Duration(p.Timeout.Write) * time.Second - for { - s.SetReadDeadline(time.Now().Add(rto)) - nr, err := s.Read(buf) - if err != nil { - if err != io.EOF && err != context.Canceled && !isReset(err) { - log.Debug("%s: nr %d, read err %s", s.LocalAddr().String(), nr, err) - return - } - } - - if nr > 0 { - d.SetWriteDeadline(time.Now().Add(wto)) - x += nr - nw, err := d.Write(buf[:nr]) - if err != nil { - log.Debug("%s: Write Err %s", d.RemoteAddr().String(), err) - return - } - if nw != nr { - return - } - y += nw - } - if err != nil { - log.Debug("%s: read error: %s", s.RemoteAddr().String(), err) - return - } - if nr == 0 { - log.Debug("%s: EOF", s.RemoteAddr().String()) - return - } - } -} - // Accept() new socket connections from the listener func (p *TCPServer) Accept() (net.Conn, error) { ln := p.TCPListener @@ -598,37 +728,38 @@ func (p *TCPServer) Accept() (net.Conn, error) { return nil, err } + there := nc.RemoteAddr() + // First enforce a global ratelimit if !p.rl.Allow() { - p.log.Debug("global ratelimit reached: %s", nc.RemoteAddr().String()) + p.log.Debug("global ratelimit reached: %s", there) nc.Close() continue } // Then a per-host ratelimit - if !p.rl.AllowHost(nc.RemoteAddr()) { - p.log.Debug("per-host ratelimit reached: %s", nc.RemoteAddr().String()) + if !p.rl.AllowHost(there) { + p.log.Debug("per-host ratelimit reached: %s", there) nc.Close() continue } if !AclOK(p.ListenConf, nc) { - p.log.Debug("ACL failure: %s", nc.RemoteAddr().String()) + p.log.Debug("ACL failure: %s", there) nc.Close() continue } - p.log.Debug("Accepted new connection from %s", nc.RemoteAddr().String()) + p.log.Debug("Accepted new connection from %s", there) return nc, nil } } -*/ func (s *Server) getSNIHandler(dir string, log *L.Logger) func(h *tls.ClientHelloInfo) (*tls.Certificate, error) { conf := s.conf dir = conf.Path(dir) if !isdir(dir) { - die("%s: certdir %s is not a directory?", s.Addr, dir) + die("%s: SNI %s is not a directory?", s.Addr, dir) } fp := func(h *tls.ClientHelloInfo) (*tls.Certificate, error) { @@ -676,6 +807,12 @@ var ( errNoCert = errors.New("SNI: no cert/key for name") errNoCACerts = errors.New("TLS: no CA certs found") errShutdown = errors.New("server shutdown") + + // socks errors + errMsgTooSmall = errors.New("socks: message too small") + errUnsupportedMethod = errors.New("socks: unsupported method") + errUnsupportedAddr = errors.New("socks: unsupported address") + errUnsupportedSocksVer = errors.New("socks: unsupported version") ) // vim: noexpandtab:ts=8:sw=8:tw=88: diff --git a/gotun/socks_test.go b/gotun/socks_test.go new file mode 100644 index 0000000..e09d5b7 --- /dev/null +++ b/gotun/socks_test.go @@ -0,0 +1,208 @@ +// socks_test.go - test socks+quic to endpoints + +package main + +import ( + "crypto/tls" + "crypto/x509" + "testing" +) + +// tcp -> socks +func TestSocksToTcpIP4(t *testing.T) { + assert := newAsserter(t) + + log := newLogger(t) + // first tunnel instance: TCP->Quic + cfga := testSetup(8030, 8031) + lca := cfga.Listen[0] + lca.Connect.Addr = "SOCKS" + + cfga.Dump(log) + + // start simple server on the other end of socks + s := newTcpServer("tcp4", "127.0.0.1:9010", nil, t) + assert(s != nil, "tcp server-a creation failed") + + // now, create a gotunnel instance + gt := NewServer(lca, cfga, log) + + gt.Start() + + // Create socks client + c := newSocksClient(lca.Addr, "tcp4", "127.0.0.1:9010", t) + assert(c != nil, "socks client failed") + + err := c.start(10) + assert(err == nil, "can't dial socks: %s", err) + + assert(c.nw == s.nr, "i/o mismatch: client TX %d, server RX %d", c.nw, s.nr) + assert(c.nr == s.nw, "i/o mismatch: server TX %d, client RX %d", s.nw, c.nr) + + c.stop() + s.stop() + gt.Stop() + log.Close() +} + +// socks hostname +func TestSocksToTcpHost(t *testing.T) { + assert := newAsserter(t) + + log := newLogger(t) + + // first tunnel instance: TCP->Quic + cfga := testSetup(8030, 8031) + lca := cfga.Listen[0] + lca.Connect.Addr = "SOCKS" + + cfga.Dump(log) + + // start simple server on the other end of socks + s := newTcpServer("tcp4", "localhost:9010", nil, t) + assert(s != nil, "tcp server-a creation failed") + + // now, create a gotunnel instance + gt := NewServer(lca, cfga, log) + + gt.Start() + + // Create socks client + c := newSocksClient(lca.Addr, "tcp4", "localhost:9010", t) + assert(c != nil, "socks client failed") + + err := c.start(10) + assert(err == nil, "can't dial socks: %s", err) + + assert(c.nw == s.nr, "i/o mismatch: client TX %d, server RX %d", c.nw, s.nr) + assert(c.nr == s.nw, "i/o mismatch: server TX %d, client RX %d", s.nw, c.nr) + + c.stop() + s.stop() + gt.Stop() + log.Close() +} + +func TestSocksToQuicIP4(t *testing.T) { + assert := newAsserter(t) + + log := newLogger(t) + + pki, err := newPKI() + assert(err == nil, "can't create PKI: %s", err) + + pkic, err := newPKI() + assert(err == nil, "can't create client PKI: %s", err) + + clientCert, err := pkic.ClientCert("client.name") + assert(err == nil, "can't create client cert: %s", err) + + spool := x509.NewCertPool() + spool.AddCert(pki.ca) + + cpool := x509.NewCertPool() + cpool.AddCert(pkic.ca) + + // first tunnel instance: TCP->Quic + cfga := testSetup(8030, 8031) + lca := cfga.Listen[0] + + // we want outgoing connect to be quic + auth + lca.Connect.Quic = true + + // This is second tunnel instance + cfgb := quicSetup(8031, 8032) + lcb := cfgb.Listen[0] + lcb.Connect.Addr = "SOCKS" + + cfga.Dump(log) + cfgb.Dump(log) + + cert, err := pki.ServerCert("server.name", lcb.Addr) + assert(err == nil, "can't create server cert: %s", err) + + // Quic TLS cnfig for second instance + tlsbCfg := &tls.Config{ + MinVersion: tls.VersionTLS13, + ServerName: "server.name", + SessionTicketsDisabled: true, + CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, // Go 1.8 only + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, // Go 1.8 only + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + + // Best disabled, as they don't provide Forward Secrecy, + // but might be necessary for some clients + // tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + // tls.TLS_RSA_WITH_AES_128_GCM_SHA256, + }, + NextProtos: []string{"relay"}, + RootCAs: spool, + Certificates: []tls.Certificate{cert}, + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: cpool, + CurvePreferences: []tls.CurveID{ + tls.CurveP256, + tls.X25519, + }, + } + + // client TLS config; we need the proper root + tlsaCfg := *tlsbCfg + tlsaCfg.Certificates = []tls.Certificate{clientCert} + + // outbound connection is a Quic client + lca.clientCfg = &tlsaCfg + lcb.serverCfg = tlsbCfg + + // to simulate a real server on the other side, we will + // run TCP on port 9010, 9011 + + // create a TCP server on the other end of second tunnel + s0 := newTcpServer("tcp", "127.0.0.1:9010", nil, t) + assert(s0 != nil, "tcp server-a creation failed") + + //s1 := newTcpServer("tcp", "127.0.0.1:9011", nil, t) + //assert(s1 != nil, "tcp server-b creation failed") + + // tunnel #1: TCP -> Quic + gta := NewServer(lca, cfga, log) + gtb := NewServer(lcb, cfgb, log) + + gta.Start() + gtb.Start() + + // Now create a mock client to send data to mock server + c0 := newSocksClient(lca.Addr, "tcp", "127.0.0.1:9010", t) + assert(c0 != nil, "client creation failed") + + c1 := newSocksClient(lca.Addr, "tcp", "127.0.0.1:9010", t) + assert(c1 != nil, "client-2 creation failed") + + err = c0.start(10) + assert(err == nil, "tcp client error: %s", err) + + err = c1.start(10) + assert(err == nil, "tcp client-2 error: %s", err) + + assert(c0.nw+c1.nw == s0.nr, "i/o mismatch: client TX %d; %d, server RX %d", c0.nw, c1.nw, s0.nr) + assert(c0.nr+c1.nr == s0.nw, "i/o mismatch: server TX %d; %d, client RX %d; %d", s0.nw, c0.nr, c1.nr) + + c0.stop() + c1.stop() + s0.stop() + gta.Stop() + gtb.Stop() + log.Close() +} + +/* +func TestSocksToQuicHost(t *testing.T) { +} + +func TestSocksToQuicIP6(t *testing.T) { +} +*/ diff --git a/gotun/tcp_test.go b/gotun/tcp_test.go index 00b6ca5..57828dd 100644 --- a/gotun/tcp_test.go +++ b/gotun/tcp_test.go @@ -31,12 +31,12 @@ func testSetup(lport, cport int) *Conf { Listen: []*ListenConf{lc}, } - return defaults(c) + return ConfDefaults(c) } // Client -> gotun TCP // gotun -> backend TCP -func TestTcpToTcp(t *testing.T) { +func TestTcpToTls(t *testing.T) { assert := newAsserter(t) // create a logger @@ -108,7 +108,7 @@ func TestTcpToTcp(t *testing.T) { log.Close() } -func TestTcpToTls(t *testing.T) { +func TestTcpToTcp(t *testing.T) { assert := newAsserter(t) // create a logger diff --git a/gotun/tcpdial.go b/gotun/tcpdial.go index 21cd5cb..6240a02 100644 --- a/gotun/tcpdial.go +++ b/gotun/tcpdial.go @@ -45,7 +45,7 @@ func (t *tcpDialer) Dial(network string, addr string, lhs Conn, ctx context.Cont return nil, fmt.Errorf("can't dial %s: %w", addr, err) } - t.log.Debug("%s connected to %s", peer.LocalAddr().String(), addr) + t.log.Debug("%s connected to %s", peer.LocalAddr().String(), addr) if t.r.clientTls != nil { econn := tls.Client(peer, t.r.clientTls) err := econn.Handshake() @@ -55,7 +55,7 @@ func (t *tcpDialer) Dial(network string, addr string, lhs Conn, ctx context.Cont } st := econn.ConnectionState() - t.log.Debug("tls client handshake with %s complete; Version %#x, Cipher %#x", addr, + t.log.Debug("connection %s updgraded to TLS; Version %#x, Cipher %#x", addr, st.Version, st.CipherSuite) peer = econn } @@ -69,7 +69,9 @@ func (t *tcpDialer) Dial(network string, addr string, lhs Conn, ctx context.Cont a2.Network(), a1.IP.String(), a2.IP.String(), a1.Port, a2.Port) peer.Write([]byte(s)) default: - t.r.log.Debug("%s: no support for PROXY Protocol %s", addr, t.r.Connect.ProxyProtocol) + if len(t.r.Connect.ProxyProtocol) > 0 { + t.r.log.Debug("%s: no support for PROXY Protocol %s", addr, t.r.Connect.ProxyProtocol) + } } return peer, nil diff --git a/gotun/utils.go b/gotun/utils.go index bdb5cee..4524cfd 100644 --- a/gotun/utils.go +++ b/gotun/utils.go @@ -10,6 +10,7 @@ package main import ( "fmt" + "io" "net" "os" "syscall" @@ -28,6 +29,41 @@ func isReset(err error) bool { return false } +// Write all bytes in 'b' and return err +func WriteAll(fd io.Writer, b []byte) (int, error) { + var z int + n := len(b) + for n > 0 { + nw, err := fd.Write(b) + if err != nil { + return z, err + } + + n -= nw + z += nw + b = b[nw:] + } + + return z, nil +} + +// Read upto len(b) bytes from fd +func ReadAll(fd io.Reader, b []byte) (int, error) { + var z int + n := len(b) + for n > 0 { + nr, err := fd.Read(b) + if err != nil { + return z, err + } + + n -= nr + z += nr + b = b[nr:] + } + return z, nil +} + // Format a time duration func format(t time.Duration) string { u0 := t.Nanoseconds() / 1000