Add the shared packages for the sharks stack

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>
This commit is contained in:
VeillaxandClaude Sonnet 5.5 committed 2026-10-26 02:41:44 -06:00
1 parent c7c2ae8f8d
commit 2259580adf
16 files changed
+1359 -1

No files matched your search

+105
View File
@@ -0,0 +1,105 @@
// 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
}