tAdd integration example in cmd/tor-dam. - tordam - A library for peer discovery inside the Tor network
 (HTM) git clone https://git.parazyd.org/tordam
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 9a3c22f8e25ae7dd29c5c5869191ad8d5ca8d233
 (DIR) parent 2f66ffd8201aa31aba279822a292125485bffe51
 (HTM) Author: parazyd <parazyd@dyne.org>
       Date:   Sun,  7 Mar 2021 21:31:59 +0100
       
       Add integration example in cmd/tor-dam.
       
       Diffstat:
         M README.md                           |       3 +++
         M announce_test.go                    |       4 ++--
         A cmd/tor-dam/tor-dam.go              |     187 +++++++++++++++++++++++++++++++
         M config.go                           |      10 ++++------
         M go.mod                              |       5 +++++
         A go.sum                              |      18 ++++++++++++++++++
         M rpc_announce.go                     |       6 +++---
       
       7 files changed, 222 insertions(+), 11 deletions(-)
       ---
 (DIR) diff --git a/README.md b/README.md
       t@@ -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).
 (DIR) diff --git a/announce_test.go b/announce_test.go
       t@@ -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)
                }
       t@@ -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)
                }
 (DIR) diff --git a/cmd/tor-dam/tor-dam.go b/cmd/tor-dam/tor-dam.go
       t@@ -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)
       +        }
       +}
 (DIR) diff --git a/config.go b/config.go
       t@@ -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.
 (DIR) diff --git a/go.mod b/go.mod
       t@@ -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
       +)
 (DIR) diff --git a/go.sum b/go.sum
       t@@ -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=
 (DIR) diff --git a/rpc_announce.go b/rpc_announce.go
       t@@ -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
       t@@ -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")
                }
       t@@ -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")
                }