Asynchronous networking @ GopherCon 2018

Asynchronous networking @ GopherCon 2018

An introduction to the net package, livecoding a TLS-aware TCP proxy.

Read the code! → https://github.com/FiloSottile/mostly-harmless/tree/master/talks/asyncnet

9fdab9d005b82612cadbfe699b541f83?s=128

Filippo Valsorda

August 28, 2018
Tweet

Transcript

  1. 3.
  2. 4.

    The event loop Request 1 Thread Request 1 Request 2

    Request 3 Request 3 Pop from event queue Monitor for events Request 2
  3. 5.

    Goroutines go Request 2 Request 1 go Request 1 go

    Request 3 Block Block Request 3 Request 2 Block
  4. 8.
  5. 9.

    ACCEPTING CONNECTIONS — RECAP 01. ACCEPTING CONNECTIONS l, err :=

    net.Listen("tcp", "localhost:4242") if err != nil { log.Fatal(err) } Open a net.Listener. Never block in the Accept loop. for { conn, err := l.Accept() if err != nil { log.Fatal(err) } go serviceConn(conn) } Call Accept in a loop, and start new goroutines for each Conn.
  6. 12.
  7. 13.

    A SIMPLE PROXY — RECAP 02. A SIMPLE PROXY upstream,

    err := net.Dial("tcp", addr) if err != nil { log.Println(err) return } defer upstream.Close() Connect to upstream. go io.Copy(upstream, conn) _, err = io.Copy(conn, upstream) Copy both directions. Close cancels both io.Copy.
  8. 15.
  9. 16.
  10. 17.
  11. 18.
  12. 19.

    PARSING A TLS CLIENT HELLO — RECAP 03. PARSING TLS

    conn.SetDeadline(time.Now().Add(30 * time.Second)) var buf bytes.Buffer if _, err := io.CopyN(&buf, conn, 1+2+2); err != nil { return } length := binary.BigEndian.Uint16(buf.Bytes()[3:5]) if _, err := io.CopyN(&buf, conn, int64(length)); err != nil { return } Set a timeout and
 read the message. ch, ok := ParseClientHello(buf.Bytes()) if ok { log.Printf("Received connection for %q!", ch.SNI) } Parse TLS.
  13. 20.

    Pre-read connection 03. PARSING TLS Client Hello Already Read() up

    to here rest of the connection Would only proxy this part In bytes.Buffer
  14. 21.
  15. 22.

    PROXYING A PRE-READ CONNECTION — RECAP 03. PARSING TLS type

    prefixConn struct { io.Reader net.Conn } func (c prefixConn) Read(b []byte) (int, error) { return c.Reader.Read(b) } Make a net.Conn wrapper. proxyConn(prefixConn{ Reader: io.MultiReader(&buf, conn), Conn: conn, }, "gophercon.com:https") Use MultiReader.
  16. 25.
  17. 26.

    SERVING A TLS CONNECTION — RECAP 04. SERVING TLS c

    := tls.Server(prefixConn{ Reader: io.MultiReader(&buf, conn), Conn: conn, }, config) Wrap the net.Conn with tls.Server. proxyConn(c, "gophercon.com:http") Service the wrapped plaintext connection. tls.Conn is a net.Conn wrapper. It takes care of handshake and de/encryption.