// Package tunnel holds what blacktip and bamboo agree on inside the WireGuard // tunnel: the target header that starts each proxied connection, and a helper // that pipes two connections together. package tunnel import ( "bufio" "encoding/json" "io" "sync" ) // Ports inside the WireGuard network where a site agent accepts proxied // connections. TCP streams start with a Header line; UDP datagrams are // prefixed as described in blacktip's udpTunnel. const ( ProxyTCPPort = 9111 ProxyUDPPort = 9112 MailTCPPort = 9113 // on the edge: a site's SMTP relay hands it mail to deliver ) // MailMsg is one request from a site's SMTP relay to the edge's mail port. The // stream carries one mail transaction: "rcpt" (the first one also carries the // sender and makes the edge connect to the recipient's server), more "rcpt", // then "data" and "body". The edge answers each with a MailReply line. type MailMsg struct { Op string `json:"op"` // rcpt, data, body, rset, quit Egress string `json:"egress,omitempty"` From string `json:"from,omitempty"` To string `json:"to,omitempty"` Len int `json:"len,omitempty"` // body: that many bytes follow this line (the message, unstuffed) } type MailReply struct { Code int `json:"code"` Text string `json:"text"` } // MaxMessage is the largest message the relay accepts. const MaxMessage = 50 << 20 // ReadLine reads up to '\n' one byte at a time, so nothing after it is consumed. func ReadLine(r io.Reader) (string, error) { var out []byte one := make([]byte, 1) for len(out) < 4096 { if _, err := io.ReadFull(r, one); err != nil { return "", err } if one[0] == '\n' { return string(out), nil } out = append(out, one[0]) } return "", io.ErrShortBuffer } type Header struct { Proto string `json:"proto"` // "tcp" or "udp" Host string `json:"host"` Port int `json:"port"` } func WriteHeader(w io.Writer, h Header) error { b, err := json.Marshal(h) if err != nil { return err } _, err = w.Write(append(b, '\n')) return err } func ReadHeader(r *bufio.Reader) (Header, error) { var h Header line, err := r.ReadBytes('\n') if err != nil { return h, err } return h, json.Unmarshal(line, &h) } type closeWriter interface{ CloseWrite() error } // Proxy copies both directions between a and b and closes both when done. // aR and bR are the readers for each side (they may be buffered wrappers). // It returns the bytes moved a->b and b->a. func Proxy(a io.ReadWriteCloser, aR io.Reader, b io.ReadWriteCloser, bR io.Reader) (aToB, bToA int64) { var wg sync.WaitGroup cp := func(dst io.ReadWriteCloser, src io.Reader, n *int64) { defer wg.Done() *n, _ = io.Copy(dst, src) if cw, ok := dst.(closeWriter); ok { cw.CloseWrite() } else { dst.Close() } } wg.Add(2) go cp(b, aR, &aToB) go cp(a, bR, &bToA) wg.Wait() a.Close() b.Close() return }