tor-dam

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

announce.go (4416B)


      1 package main
      2 
      3 /*
      4  * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
      5  *
      6  * This file is part of tor-dam
      7  *
      8  * This program is free software: you can redistribute it and/or modify
      9  * it under the terms of the GNU Affero General Public License as published by
     10  * the Free Software Foundation, either version 3 of the License, or
     11  * (at your option) any later version.
     12  *
     13  * This program is distributed in the hope that it will be useful,
     14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  * GNU Affero General Public License for more details.
     17  *
     18  * You should have received a copy of the GNU Affero General Public License
     19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     20  */
     21 
     22 import (
     23 	"bytes"
     24 	"compress/gzip"
     25 	"crypto/ed25519"
     26 	"crypto/rand"
     27 	"encoding/base64"
     28 	"encoding/json"
     29 	"io/ioutil"
     30 	"log"
     31 	"math/big"
     32 	"os"
     33 	"strings"
     34 )
     35 
     36 func fetchNodeList(epLists []string, remote bool) ([]string, error) {
     37 	var ns, nl []string
     38 
     39 	log.Println("Building a list of nodes")
     40 
     41 	// Remote network entrypoints
     42 	if !remote {
     43 		for _, i := range epLists {
     44 			log.Println("Fetching", i)
     45 			n, err := httpGet(i)
     46 			if err != nil {
     47 				return nil, err
     48 			}
     49 			ns = parseDirs(ns, n)
     50 		}
     51 	}
     52 
     53 	// Local workdir/dirs.txt
     54 	ld := strings.Join([]string{*workdir, "dirs.txt"}, "/")
     55 	if _, err := os.Stat(ld); err == nil {
     56 		ln, err := ioutil.ReadFile(ld)
     57 		if err != nil {
     58 			return nil, err
     59 		}
     60 		ns = parseDirs(ns, ln)
     61 	}
     62 
     63 	// Local nodes from redis
     64 	nodes, _ := rcli.Keys(rctx, "*.onion").Result()
     65 	for _, i := range nodes {
     66 		valid, err := rcli.HGet(rctx, i, "valid").Result()
     67 		if err != nil {
     68 			// Possible RedisCli bug, possible Redis bug. To be investigated.
     69 			// Sometimes it returns err, but it's empty and does not say what's
     70 			// happening exactly.
     71 			continue
     72 		}
     73 		if valid == "1" {
     74 			ns = append(ns, i)
     75 		}
     76 	}
     77 
     78 	// Remove possible dupes. Duplicates can cause race conditions and are
     79 	// redundant to the entire logic.
     80 	// TODO: Work this in above automatically (by changing the var type)
     81 	encounter := map[string]bool{}
     82 	for i := range ns {
     83 		encounter[ns[i]] = true
     84 	}
     85 	ns = []string{}
     86 	for key := range encounter {
     87 		ns = append(ns, key)
     88 	}
     89 
     90 	if len(ns) < 1 {
     91 		log.Fatal("Couldn't find any nodes to announce to. Exiting...")
     92 	} else if len(ns) <= 6 {
     93 		log.Printf("Found %d nodes\n", len(ns))
     94 		nl = ns
     95 	} else {
     96 		log.Printf("Found %d nodes. Picking out 6 at random\n", len(ns))
     97 		for i := 0; i <= 5; i++ {
     98 			n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(ns))))
     99 			nl = append(nl, ns[n.Int64()])
    100 			ns[n.Int64()] = ns[len(ns)-1]
    101 			ns = ns[:len(ns)-1]
    102 		}
    103 	}
    104 
    105 	return nl, nil
    106 }
    107 
    108 func announce(addr string, vals map[string]string) (bool, error) {
    109 	msg, _ := json.Marshal(vals)
    110 
    111 	log.Println("Announcing keypair to", addr)
    112 	resp, err := httpPost("http://"+addr+":49371"+"/announce", msg)
    113 	if err != nil {
    114 		return false, err
    115 	}
    116 
    117 	// Parse server's reply
    118 	var m Message
    119 	dec := json.NewDecoder(resp.Body)
    120 	if err := dec.Decode(&m); err != nil {
    121 		return false, err
    122 	}
    123 
    124 	if resp.StatusCode != 200 {
    125 		log.Printf("%s returned error: %s\n", addr, m.Secret)
    126 		return false, nil
    127 	}
    128 
    129 	log.Println("Got nonce from", addr)
    130 
    131 	sig := ed25519.Sign(signingKey, []byte(m.Secret))
    132 
    133 	vals["secret"] = m.Secret
    134 	vals["message"] = m.Secret
    135 	vals["signature"] = base64.StdEncoding.EncodeToString(sig)
    136 	msg, _ = json.Marshal(vals)
    137 
    138 	log.Println("Sending back signed secret to", addr)
    139 	resp, err = httpPost("http://"+addr+":49371"+"/announce", msg)
    140 	if err != nil {
    141 		return false, err
    142 	}
    143 
    144 	dec = json.NewDecoder(resp.Body)
    145 	if err := dec.Decode(&m); err != nil {
    146 		return false, err
    147 	}
    148 
    149 	if resp.StatusCode != 200 {
    150 		log.Printf("%s returned error: %s\n", addr, m.Secret)
    151 		return false, nil
    152 	}
    153 
    154 	log.Printf("%s handshake valid\n", addr)
    155 	data, err := base64.StdEncoding.DecodeString(m.Secret)
    156 	if err != nil {
    157 		// Not a list of nodes
    158 		log.Printf("%s replied: %s\n", addr, m.Secret)
    159 		return true, nil
    160 	}
    161 
    162 	log.Println("Got node data, processing...")
    163 	b := bytes.NewReader(data)
    164 	r, _ := gzip.NewReader(b)
    165 	nodes := make(map[string]map[string]interface{})
    166 	dec = json.NewDecoder(r)
    167 	if err = dec.Decode(&nodes); err != nil {
    168 		return false, err
    169 	}
    170 
    171 	for k, v := range nodes {
    172 		log.Printf("Adding %s to redis\n", k)
    173 		if _, err := rcli.HSet(rctx, k, v).Result(); err != nil {
    174 			log.Fatal(err)
    175 		}
    176 	}
    177 
    178 	return true, nil
    179 }