tor-dam

tor distributed announce mechanism (not a dht)
git clone https://git.parazyd.org/tor-dam
Log | Files | Refs | README | LICENSE

tor-dam.go (5347B)


      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 	"flag"
     25 	"fmt"
     26 	"io/ioutil"
     27 	"log"
     28 	"net"
     29 	"os"
     30 	"strings"
     31 	"sync"
     32 	"time"
     33 
     34 	"github.com/creachadair/jrpc2"
     35 	"github.com/creachadair/jrpc2/handler"
     36 	"github.com/creachadair/jrpc2/server"
     37 	"github.com/parazyd/tordam"
     38 )
     39 
     40 var (
     41 	generate = flag.Bool("g", false, "(Re)generate keys and exit")
     42 	portmap  = flag.String("m", "13010:13010,13011:13011", "Map of ports forwarded to/from Tor")
     43 	listen   = flag.String("l", "127.0.0.1:49371", "Local listen address")
     44 	datadir  = flag.String("datadir", os.Getenv("HOME")+"/.dam", "Data directory")
     45 	seeds    = flag.String("s",
     46 		"p7qaewjgnvnaeihhyybmoofd5avh665kr3awoxlh5rt6ox743kjdr6qd.onion:49371",
     47 		"List of initial peers (comma-separated)")
     48 	noannounce = flag.Bool("n", false, "Do not announce to peers")
     49 )
     50 
     51 func generateED25519Keypair(dir string) error {
     52 	_, sk, err := ed25519.GenerateKey(rand.Reader)
     53 	if err != nil {
     54 		return err
     55 	}
     56 	if err := os.MkdirAll(dir, 0700); err != nil {
     57 		return err
     58 	}
     59 
     60 	seedpath := strings.Join([]string{dir, "ed25519.seed"}, "/")
     61 	log.Println("Writing ed25519 key seed to", seedpath)
     62 	return ioutil.WriteFile(seedpath,
     63 		[]byte(base64.StdEncoding.EncodeToString(sk.Seed())), 0600)
     64 }
     65 
     66 func loadED25519Seed(file string) (ed25519.PrivateKey, error) {
     67 	log.Println("Reading ed25519 seed from", file)
     68 
     69 	data, err := ioutil.ReadFile(file)
     70 	if err != nil {
     71 		return nil, err
     72 	}
     73 	dec, err := base64.StdEncoding.DecodeString(string(data))
     74 	if err != nil {
     75 		return nil, err
     76 	}
     77 	return ed25519.NewKeyFromSeed(dec), nil
     78 }
     79 
     80 func main() {
     81 	flag.Parse()
     82 	var wg sync.WaitGroup
     83 	var err error
     84 
     85 	if *generate {
     86 		if err := generateED25519Keypair(*datadir); err != nil {
     87 			log.Fatal(err)
     88 		}
     89 		os.Exit(0)
     90 	}
     91 
     92 	// Validate given seeds
     93 	for _, i := range strings.Split(*seeds, ",") {
     94 		if err := tordam.ValidateOnionInternal(i); err != nil {
     95 			log.Fatalf("invalid seed %s (%v)", i, err)
     96 		}
     97 	}
     98 
     99 	// Assign portmap to tordam Cfg global and validate it
    100 	tordam.Cfg.Portmap = strings.Split(*portmap, ",")
    101 	if err := tordam.ValidatePortmap(tordam.Cfg.Portmap); err != nil {
    102 		log.Fatal(err)
    103 	}
    104 
    105 	// Validate and assign the local listening address
    106 	tordam.Cfg.Listen, err = net.ResolveTCPAddr("tcp", *listen)
    107 	if err != nil {
    108 		log.Fatalf("invalid listen address: %s (%v)", *listen, err)
    109 	}
    110 
    111 	// Assign the global tordam data directory
    112 	tordam.Cfg.Datadir = *datadir
    113 
    114 	// Load the ed25519 signing key into the tordam global
    115 	tordam.SignKey, err = loadED25519Seed(strings.Join(
    116 		[]string{*datadir, "ed25519.seed"}, "/"))
    117 	if err != nil {
    118 		log.Fatal(err)
    119 	}
    120 
    121 	// Spawn Tor daemon and let it settle
    122 	tor, err := tordam.SpawnTor(tordam.Cfg.Listen, tordam.Cfg.Portmap,
    123 		tordam.Cfg.Datadir)
    124 	defer tor.Process.Kill()
    125 	if err != nil {
    126 		log.Fatal(err)
    127 	}
    128 	time.Sleep(2 * time.Second)
    129 	log.Println("Started Tor daemon on", tordam.Cfg.TorAddr.String())
    130 
    131 	// Read the onion hostname from the datadir and map it into the
    132 	// global tordam.Onion variable
    133 	onionaddr, err := ioutil.ReadFile(strings.Join([]string{
    134 		tordam.Cfg.Datadir, "hs", "hostname"}, "/"))
    135 	if err != nil {
    136 		log.Fatal(err)
    137 	}
    138 	onionaddr = []byte(strings.TrimSuffix(string(onionaddr), "\n"))
    139 	tordam.Onion = strings.Join([]string{
    140 		string(onionaddr), fmt.Sprint(tordam.Cfg.Listen.Port)}, ":")
    141 	log.Println("Our onion address is:", tordam.Onion)
    142 
    143 	// Start the JSON-RPC server with announce endpoints.
    144 	// This is done in the library user rather than internally in the library
    145 	// because it is more useful and easier to add additional JSON-RPC
    146 	// endpoints to the same server.
    147 	l, err := net.Listen(jrpc2.Network(tordam.Cfg.Listen.String()),
    148 		tordam.Cfg.Listen.String())
    149 	if err != nil {
    150 		log.Fatal(err)
    151 	}
    152 	defer l.Close()
    153 	// Endpoints are assigned here
    154 	assigner := handler.ServiceMap{
    155 		// "ann" is the JSON-RPC endpoint for peer discovery/announcement
    156 		"ann": handler.NewService(tordam.Ann{}),
    157 	}
    158 	go server.Loop(l, server.NewStatic(assigner), nil)
    159 	log.Println("Started JSON-RPC server on", tordam.Cfg.Listen.String())
    160 
    161 	// If decided to not announce to anyone
    162 	if *noannounce {
    163 		// We shall sit here and wait
    164 		wg.Add(1)
    165 		wg.Wait()
    166 	}
    167 
    168 	// Announce to initial seeds
    169 	var succ int = 0 // Track of successful announces
    170 	for _, i := range strings.Split(*seeds, ",") {
    171 		wg.Add(1)
    172 		go func(x string) {
    173 			if err := tordam.Announce(x); err != nil {
    174 				log.Println("error in announce:", err)
    175 			} else {
    176 				succ++
    177 			}
    178 			wg.Done()
    179 		}(i)
    180 	}
    181 	wg.Wait()
    182 
    183 	if succ < 1 {
    184 		log.Fatal("No successful announces.")
    185 	} else {
    186 		log.Printf("Successfully announced to %d peers.", succ)
    187 	}
    188 }