model, tunnel, wg, authtoken, netguard, docker and dotenv, split out of the original single repository so Hammerhead, Blacktip and Bamboo can live in their own repos and import them. README extended; LICENSE unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
106 lines
2.8 KiB
Go
106 lines
2.8 KiB
Go
// 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
|
|
}
|