ttor-dam.go - tordam - A library for peer discovery inside the Tor network
HTML git clone https://git.parazyd.org/tordam
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
ttor-dam.go (6110B)
---
1 // Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
2 //
3 // This file is part of tordam
4 //
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU Affero General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU Affero General Public License for more details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18 package main
19
20 import (
21 "crypto/ed25519"
22 "crypto/rand"
23 "encoding/base64"
24 "encoding/json"
25 "flag"
26 "fmt"
27 "io/ioutil"
28 "log"
29 "net"
30 "os"
31 "path/filepath"
32 "strings"
33 "sync"
34 "time"
35
36 "github.com/creachadair/jrpc2"
37 "github.com/creachadair/jrpc2/handler"
38 "github.com/creachadair/jrpc2/server"
39 "github.com/parazyd/tordam"
40 )
41
42 var (
43 generate = flag.Bool("g", false, "(Re)generate keys and exit")
44 portmap = flag.String("m", "13010:13010,13011:13011",
45 "Map of ports forwarded to/from Tor")
46 listen = flag.String("l", "127.0.0.1:49371", "Local listen address")
47 datadir = flag.String("d", os.Getenv("HOME")+"/.dam", "Data directory")
48 seeds = flag.String("s",
49 "p7qaewjgnvnaeihhyybmoofd5avh665kr3awoxlh5rt6ox743kjdr6qd.onion:49371",
50 "List of initial peers (comma-separated)")
51 noannounce = flag.Bool("n", false, "Do not announce to peers")
52 )
53
54 // generateED25519Keypair is a helper function to generate it, and save the
55 // seed to a file for later reuse.
56 func generateED25519Keypair(dir string) error {
57 _, sk, err := ed25519.GenerateKey(rand.Reader)
58 if err != nil {
59 return err
60 }
61 if err := os.MkdirAll(dir, 0700); err != nil {
62 return err
63 }
64
65 seedpath := filepath.Join(dir, "ed25519.seed")
66 log.Println("Writing ed25519 key seed to", seedpath)
67 return ioutil.WriteFile(seedpath,
68 []byte(base64.StdEncoding.EncodeToString(sk.Seed())), 0600)
69 }
70
71 // loadED25519Seed is a helper function to read an existing key seed and
72 // return an ed25519.PrivateKey.
73 func loadED25519Seed(file string) (ed25519.PrivateKey, error) {
74 log.Println("Reading ed25519 seed from", file)
75
76 data, err := ioutil.ReadFile(file)
77 if err != nil {
78 return nil, err
79 }
80 dec, err := base64.StdEncoding.DecodeString(string(data))
81 if err != nil {
82 return nil, err
83 }
84 return ed25519.NewKeyFromSeed(dec), nil
85 }
86
87 // main here is the reference workflow of tor-dam's peer discovery. Its steps
88 // are commented and implement a generic way of using the tordam library.
89 func main() {
90 flag.Parse()
91 var wg sync.WaitGroup
92 var err error
93
94 // Initialize tordam logger
95 tordam.LogInit(os.Stdout)
96
97 // Assign the global tordam data directory
98 tordam.Cfg.Datadir = *datadir
99
100 // Generate the ed25519 keypair used for signing and validating
101 if *generate {
102 if err := generateED25519Keypair(tordam.Cfg.Datadir); err != nil {
103 log.Fatal(err)
104 }
105 os.Exit(0)
106 }
107
108 // Assign portmap to tordam Cfg global and validate it
109 tordam.Cfg.Portmap = strings.Split(*portmap, ",")
110 if err := tordam.ValidatePortmap(tordam.Cfg.Portmap); err != nil {
111 log.Fatal(err)
112 }
113
114 // Validate and assign the local listening address
115 tordam.Cfg.Listen, err = net.ResolveTCPAddr("tcp", *listen)
116 if err != nil {
117 log.Fatalf("invalid listen address: %s (%v)", *listen, err)
118 }
119
120 // Load the ed25519 signing key into the tordam global
121 tordam.SignKey, err = loadED25519Seed(
122 filepath.Join(tordam.Cfg.Datadir, "ed25519.seed"))
123 if err != nil {
124 log.Fatal(err)
125 }
126
127 // Spawn Tor daemon and let it settle
128 tor, err := tordam.SpawnTor(tordam.Cfg.Listen, tordam.Cfg.Portmap,
129 tordam.Cfg.Datadir)
130 defer func() {
131 if err := tor.Process.Kill(); err != nil {
132 log.Println(err)
133 }
134 }()
135 if err != nil {
136 log.Fatal(err)
137 }
138 time.Sleep(2 * time.Second)
139 log.Println("Started Tor daemon on", tordam.Cfg.TorAddr.String())
140
141 // Read the onion hostname from the datadir and map it into the
142 // global tordam.Onion variable
143 onionaddr, err := ioutil.ReadFile(
144 filepath.Join(tordam.Cfg.Datadir, "hs", "hostname"))
145 if err != nil {
146 log.Fatal(err)
147 }
148 onionaddr = []byte(strings.TrimSuffix(string(onionaddr), "\n"))
149 tordam.Onion = strings.Join([]string{
150 string(onionaddr), fmt.Sprint(tordam.Cfg.Listen.Port)}, ":")
151 log.Println("Our onion address is:", tordam.Onion)
152
153 // Start the JSON-RPC server with announce endpoints.
154 // This is done in the program rather than internally in the library
155 // because it is more useful and easier to add additional JSON-RPC
156 // endpoints to the same server if necessary.
157 l, err := net.Listen(jrpc2.Network(tordam.Cfg.Listen.String()),
158 tordam.Cfg.Listen.String())
159 if err != nil {
160 log.Fatal(err)
161 }
162 defer l.Close()
163 // JSON-RPC endpoints are assigned here
164 assigner := handler.ServiceMap{
165 // "ann" is the JSON-RPC endpoint for peer discovery/announcement
166 "ann": handler.NewService(tordam.Ann{}),
167 }
168 go func() {
169 if err := server.Loop(l, server.NewStatic(assigner), nil); err != nil {
170 log.Println(err)
171 }
172 }()
173 log.Println("Started JSON-RPC server on", tordam.Cfg.Listen.String())
174
175 // If decided to not announce to anyone
176 if *noannounce {
177 // We shall sit here and wait
178 wg.Add(1)
179 wg.Wait()
180 }
181
182 // Validate given seeds
183 for _, i := range strings.Split(*seeds, ",") {
184 if err := tordam.ValidateOnionInternal(i); err != nil {
185 log.Fatalf("invalid seed %s (%v)", i, err)
186 }
187 }
188
189 // Announce to initial seeds
190 var succ int = 0 // Track of successful announces
191 for _, i := range strings.Split(*seeds, ",") {
192 wg.Add(1)
193 go func(x string) {
194 if err := tordam.Announce(x); err != nil {
195 log.Println("error in announce:", err)
196 } else {
197 succ++
198 }
199 wg.Done()
200 }(i)
201 }
202 wg.Wait()
203
204 if succ < 1 {
205 log.Println("No successful announces.")
206 } else {
207 log.Printf("Successfully announced to %d peers.", succ)
208 }
209
210 // Marshal the global Peers map to JSON and print it out.
211 j, _ := json.Marshal(tordam.Peers)
212 fmt.Println(string(j))
213 }