commit 9a3c22f8e25ae7dd29c5c5869191ad8d5ca8d233
parent 2f66ffd8201aa31aba279822a292125485bffe51
Author: parazyd <parazyd@dyne.org>
Date: Sun, 7 Mar 2021 21:31:59 +0100
Add integration example in cmd/tor-dam.
Diffstat:
7 files changed, 222 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
@@ -20,3 +20,6 @@ https://pkg.go.dev/github.com/parazyd/tordam
tor-dam is a small library that can be used to facilitate peer to peer
services in the Tor network with simple mechanisms.
+
+A basic integration example can be found and reviewed in the form of
+a single go file: [cmd/tor-dam/tor-dam.go](cmd/tor-dam/tor-dam.go).
diff --git a/announce_test.go b/announce_test.go
@@ -37,7 +37,7 @@ func TestAnnounce(t *testing.T) {
"12345:54321,666:3521",
}
- ret, err := ann.Init(ann{}, context.Background(), vals)
+ ret, err := Ann.Init(Ann{}, context.Background(), vals)
if err != nil {
t.Fatal(err)
}
@@ -52,7 +52,7 @@ func TestAnnounce(t *testing.T) {
base64.StdEncoding.EncodeToString(ed25519.Sign(sk, []byte(ret[0]))),
}
- ret, err = ann.Validate(ann{}, context.Background(), vals)
+ ret, err = Ann.Validate(Ann{}, context.Background(), vals)
if err != nil {
t.Fatal(err)
}
diff --git a/cmd/tor-dam/tor-dam.go b/cmd/tor-dam/tor-dam.go
@@ -0,0 +1,187 @@
+// Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
+//
+// This file is part of tordam
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+package main
+
+import (
+ "crypto/ed25519"
+ "crypto/rand"
+ "encoding/base64"
+ "flag"
+ "io/ioutil"
+ "log"
+ "net"
+ "os"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/creachadair/jrpc2"
+ "github.com/creachadair/jrpc2/handler"
+ "github.com/creachadair/jrpc2/server"
+ "github.com/parazyd/tordam"
+)
+
+var (
+ generate = flag.Bool("g", false, "(Re)generate keys and exit")
+ portmap = flag.String("m", "13010:13010,13011:13011", "Map of ports forwarded to/from Tor")
+ listen = flag.String("l", "127.0.0.1:49371", "Local listen address")
+ datadir = flag.String("datadir", os.Getenv("HOME")+"/.dam", "Data directory")
+ seeds = flag.String("s",
+ "p7qaewjgnvnaeihhyybmoofd5avh665kr3awoxlh5rt6ox743kjdr6qd.onion:49371",
+ "List of initial peers (comma-separated)")
+ noannounce = flag.Bool("n", false, "Do not announce to peers")
+)
+
+func generateED25519Keypair(dir string) error {
+ _, sk, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ return err
+ }
+ if err := os.MkdirAll(dir, 0700); err != nil {
+ return err
+ }
+
+ seedpath := strings.Join([]string{dir, "ed25519.seed"}, "/")
+ log.Println("Writing ed25519 key seed to", seedpath)
+ return ioutil.WriteFile(seedpath,
+ []byte(base64.StdEncoding.EncodeToString(sk.Seed())), 0600)
+}
+
+func loadED25519Seed(file string) (ed25519.PrivateKey, error) {
+ log.Println("Reading ed25519 seed from", file)
+
+ data, err := ioutil.ReadFile(file)
+ if err != nil {
+ return nil, err
+ }
+ dec, err := base64.StdEncoding.DecodeString(string(data))
+ if err != nil {
+ return nil, err
+ }
+ return ed25519.NewKeyFromSeed(dec), nil
+}
+
+func main() {
+ flag.Parse()
+ var wg sync.WaitGroup
+ var err error
+
+ if *generate {
+ if err := generateED25519Keypair(*datadir); err != nil {
+ log.Fatal(err)
+ }
+ os.Exit(0)
+ }
+
+ // Validate given seeds
+ for _, i := range strings.Split(*seeds, ",") {
+ if err := tordam.ValidateOnionInternal(i); err != nil {
+ log.Fatalf("invalid seed %s (%v)", i, err)
+ }
+ }
+
+ // Assign portmap to tordam Cfg global and validate it
+ tordam.Cfg.Portmap = strings.Split(*portmap, ",")
+ if err := tordam.ValidatePortmap(tordam.Cfg.Portmap); err != nil {
+ log.Fatal(err)
+ }
+
+ // Validate and assign the local listening address
+ tordam.Cfg.Listen, err = net.ResolveTCPAddr("tcp", *listen)
+ if err != nil {
+ log.Fatal("invalid listen address: %s (%v)", *listen, err)
+ }
+
+ // Assign the global tordam data directory
+ tordam.Cfg.Datadir = *datadir
+
+ // Load the ed25519 signing key into the tordam global
+ tordam.SignKey, err = loadED25519Seed(strings.Join(
+ []string{*datadir, "ed25519.seed"}, "/"))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Spawn Tor daemon and let it settle
+ tor, err := tordam.SpawnTor(tordam.Cfg.Listen, tordam.Cfg.Portmap,
+ tordam.Cfg.Datadir)
+ defer tor.Process.Kill()
+ if err != nil {
+ log.Fatal(err)
+ }
+ time.Sleep(2 * time.Second)
+ log.Println("Started Tor daemon on", tordam.Cfg.TorAddr.String())
+
+ // Read the onion hostname from the datadir and map it into the
+ // global tordam.Onion variable
+ onionaddr, err := ioutil.ReadFile(strings.Join([]string{
+ tordam.Cfg.Datadir, "hs", "hostname"}, "/"))
+ if err != nil {
+ log.Fatal(err)
+ }
+ onionaddr = []byte(strings.TrimSuffix(string(onionaddr), "\n"))
+ tordam.Onion = strings.Join([]string{
+ string(onionaddr), string(tordam.Cfg.Listen.Port)}, ":")
+ log.Println("Our onion address is:", tordam.Onion)
+
+ // Start the JSON-RPC server with announce endpoints.
+ // This is done in the library user rather than internally in the library
+ // because it is more useful and easier to add additional JSON-RPC
+ // endpoints to the same server.
+ l, err := net.Listen(jrpc2.Network(tordam.Cfg.Listen.String()),
+ tordam.Cfg.Listen.String())
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+ // Endpoints are assigned here
+ assigner := handler.ServiceMap{
+ // "ann" is the JSON-RPC endpoint for peer discovery/announcement
+ "ann": handler.NewService(tordam.Ann{}),
+ }
+ go server.Loop(l, server.NewStatic(assigner), nil)
+ log.Println("Started JSON-RPC server on", tordam.Cfg.Listen.String())
+
+ // If decided to not announce to anyone
+ if *noannounce {
+ // We shall sit here and wait
+ wg.Add(1)
+ wg.Wait()
+ }
+
+ // Announce to initial seeds
+ var succ int = 0 // Track of successful announces
+ for _, i := range strings.Split(*seeds, ",") {
+ wg.Add(1)
+ go func(x string) {
+ if err := tordam.Announce(i); err != nil {
+ log.Println("error in announce:", err)
+ } else {
+ succ++
+ }
+ wg.Done()
+ }(i)
+ }
+ wg.Wait()
+
+ if succ < 1 {
+ log.Fatal("No successful announces.")
+ } else {
+ log.Printf("Successfully announced to %d peers.", succ)
+ }
+}
diff --git a/config.go b/config.go
@@ -24,12 +24,10 @@ import (
// Config is the configuration structure, to be filled by library user.
type Config struct {
- Listen *net.TCPAddr // Local listen address for the JSON-RPC server
- TorAddr *net.TCPAddr // Tor SOCKS5 proxy address, filled by SpawnTor()
- Datadir string // Path to data directory
- Portmap []string // The peer's portmap, to be mapped in the Tor HS
- Seeds []string // Initial peer(s)
- Announce bool // Announce or not
+ Listen *net.TCPAddr // Local listen address for the JSON-RPC server
+ TorAddr *net.TCPAddr // Tor SOCKS5 proxy address, filled by SpawnTor()
+ Datadir string // Path to data directory
+ Portmap []string // The peer's portmap, to be mapped in the Tor HS
}
// SignKey is an ed25519 private key, to be assigned by library user.
diff --git a/go.mod b/go.mod
@@ -1,3 +1,8 @@
module github.com/parazyd/tordam
go 1.16
+
+require (
+ github.com/creachadair/jrpc2 v0.12.0 // indirect
+ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
+)
diff --git a/go.sum b/go.sum
@@ -0,0 +1,18 @@
+bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M=
+bitbucket.org/creachadair/stringset v0.0.9 h1:L4vld9nzPt90UZNrXjNelTshD74ps4P5NGs3Iq6yN3o=
+bitbucket.org/creachadair/stringset v0.0.9/go.mod h1:t+4WcQ4+PXTa8aQdNKe40ZP6iwesoMFWAxPGd3UGjyY=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/creachadair/jrpc2 v0.12.0 h1:cr7QMg8JYLubTneMy7UCQLWPD6LObJ7ZK8T5GeiwbLQ=
+github.com/creachadair/jrpc2 v0.12.0/go.mod h1:aACneXzxBPPoiu+nAo5duWP8L4y0//yuHkpkW9uDpo8=
+github.com/creachadair/staticfile v0.1.3/go.mod h1:a3qySzCIXEprDGxk6tSxSI+dBBdLzqeBOMhZ+o2d3pM=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/rpc_announce.go b/rpc_announce.go
@@ -26,7 +26,7 @@ import (
"time"
)
-type ann struct{}
+type Ann struct{}
// Init takes three parameters:
// - onion: onionaddress:port where the peer and tordam can be reached
@@ -46,7 +46,7 @@ type ann struct{}
// "result": ["somenonce", "somerevokekey"]
// }
// On any kind of failure returns an error and the reason.
-func (ann) Init(ctx context.Context, vals []string) ([]string, error) {
+func (Ann) Init(ctx context.Context, vals []string) ([]string, error) {
if len(vals) != 3 && len(vals) != 4 {
return nil, errors.New("invalid parameters")
}
@@ -137,7 +137,7 @@ func (ann) Init(ctx context.Context, vals []string) ([]string, error) {
// "result": ["unlikelynameforan.onion:69", "yetanother.onion:420"]
// }
// On any kind of failure returns an error and the reason.
-func (ann) Validate(ctx context.Context, vals []string) ([]string, error) {
+func (Ann) Validate(ctx context.Context, vals []string) ([]string, error) {
if len(vals) != 2 {
return nil, errors.New("invalid parameters")
}