ztip

zero-trust i2p coordinator
git clone git@git.kloet.net/ztip.git
Download | Log | Files | Refs | README

commit cf1de3f894bf5a46a7bb70527b3f31ef351596b4
Author: Andrew Kloet <andrew@kloet.net>
Date:   Tue, 17 Mar 2026 13:03:36 -0400

initial commit

Diffstat:
A.gitignore | 2++
AREADME | 2++
Ago.mod | 20++++++++++++++++++++
Ago.sum | 45+++++++++++++++++++++++++++++++++++++++++++++
Aztip.go | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 172 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +keys +ztip diff --git a/README b/README @@ -0,0 +1,2 @@ +Zero-trust coordination server and nodes using the i2p network as a backend +ALPHA ALPHA ALPHA diff --git a/go.mod b/go.mod @@ -0,0 +1,20 @@ +module git.kloet.net/ztip + +go 1.25.5 + +require github.com/go-i2p/go-sam-go v0.33.0 + +require ( + github.com/go-i2p/common v0.0.1 // indirect + github.com/go-i2p/crypto v0.0.1 // indirect + github.com/go-i2p/i2pkeys v0.33.92 // indirect + github.com/go-i2p/logger v0.0.1 // indirect + github.com/oklog/ulid/v2 v2.1.1 // indirect + github.com/samber/lo v1.52.0 // indirect + github.com/samber/oops v1.19.3 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect +) diff --git a/go.sum b/go.sum @@ -0,0 +1,45 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-i2p/common v0.0.1 h1:gONnjtwrfo+2uWPai8SsTPDSa0++hhiD7Bvhn/dKI0Q= +github.com/go-i2p/common v0.0.1/go.mod h1:pmh6KuO6gZsj/8rQnaaOK3YHUdnXPydnYE0k7n7OT9Q= +github.com/go-i2p/crypto v0.0.1 h1:04yo8tX9+CRkT5zugEPhyJQ38syeXIW+1GykDfbB418= +github.com/go-i2p/crypto v0.0.1/go.mod h1:ni/LJcr7MrPhXmYbQ2b5enpe8ppT3qhfiwr3I1QJeZM= +github.com/go-i2p/go-sam-go v0.33.0 h1:4V9eSc0jBmWOEK6kWnujvoAcbdrYyZQRczZ3NRYSDFU= +github.com/go-i2p/go-sam-go v0.33.0/go.mod h1:yh0p4igH39DL3ZLni40lzCb9NxQcZF0jqDYCiPNqAv8= +github.com/go-i2p/i2pkeys v0.33.92 h1:e2vx3vf7tNesaJ8HmAlGPOcfiGM86jzeIGxh27I9J2Y= +github.com/go-i2p/i2pkeys v0.33.92/go.mod h1:BRURQ/twxV0WKjZlFSKki93ivBi+MirZPWudfwTzMpE= +github.com/go-i2p/logger v0.0.1 h1:OFDZMjqiNXbPIm+SDxiwYtI6ocC3mb9V/t5kvZ+6XQ0= +github.com/go-i2p/logger v0.0.1/go.mod h1:te7Zj3g3oMeIl8uBXAgO62UKmZ6m6kHRNg1Mm+X8Hzk= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= +github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/samber/oops v1.19.3 h1:GAfSUOCh/JbnKAj5Ia+r/3KNnBdK+VdVDq1F5I8nDfM= +github.com/samber/oops v1.19.3/go.mod h1:1lIO/SwpPltzw5cDO8/oiyVuLiQt3/8iid21Vb8QP+8= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ztip.go b/ztip.go @@ -0,0 +1,103 @@ +package main + +import ( + "io" + "log" + "net" + "time" + + sam3 "github.com/go-i2p/go-sam-go" +) + +type Node struct { + identity string + groups []string +} + +type Forward struct { + Name string + KeyFile string + LocalAddr net.Addr +} + +func main() { + samAddr := "127.0.0.1:7656" + log.Println("Connecting to SAM server:", samAddr) + sam, err := sam3.NewSAM(samAddr) + if err != nil { + log.Fatal(err) + } + + forwards := []Forward{ + { + Name: "navidrome", + KeyFile: "navidrome.dat", + LocalAddr: &net.TCPAddr{IP: net.ParseIP("192.168.0.3"), Port: 4533}, + }, + { + Name: "http8080", + KeyFile: "http8080.dat", + LocalAddr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8080}, + }, + } + + startServices(sam, forwards) + select {} +} + +func startServices(sam *sam3.SAM, forwards []Forward) { + for _, fwd := range forwards { + keys, err := sam.EnsureKeyfile("keys/" + fwd.KeyFile) + if err != nil { + log.Printf("Failed to setup keys for %s: %v", fwd.Name, err) + continue + } + + session, err := sam.NewStreamSession(fwd.Name+time.Now().Format("15:04:05"), keys, sam3.Options_Warning_ZeroHop) + if err != nil { + log.Printf("Failed to create session for %s: %v", fwd.Name, err) + continue + } + + go func(target net.Addr, s *sam3.StreamSession) { + listener, err := s.Listen() + if err != nil { + log.Printf("Failed to listen for [%s]:%s", fwd.Name, err) + } + log.Printf("Service [%s] listening at %s", fwd.Name, keys.Addr().Base32()) + + for { + conn, err := listener.Accept() + if err != nil { + log.Println("accept error:", err) + continue + } + log.Printf("%s wants: %s", conn.RemoteAddr().String(), target) + go proxy(conn, target) + } + }(fwd.LocalAddr, session) + } +} + +func proxy(i2pConn net.Conn, localAddr net.Addr) { + defer i2pConn.Close() + + localConn, err := net.DialTimeout(localAddr.Network(), localAddr.String(), 5*time.Second) + if err != nil { + log.Printf("ERROR: Local dial failed: %v", err) + return + } + defer localConn.Close() + + errChan := make(chan error, 2) + + cp := func(dst io.Writer, src io.Reader) { + _, err := io.Copy(dst, src) + errChan <- err + } + + go cp(localConn, i2pConn) + go cp(i2pConn, localConn) + + <-errChan +}