6c05e9d475
Use it to add multiple peer support. We try and keep 8 outbound peers active at all times. This address manager is not as complete as the one in bitcoind yet, but additional functionality is being worked on. We currently handle (in a similar manner to bitcoind): - biasing between new and already tried addresses based on number of connected peers. - rejection of non-default ports until desparate - address selection probabilities based on last successful connection and number of failures. - routability checks based on known unroutable subnets. - only connecting to each network `group' once at any one time. We currently lack support for: - tor ``addresses'' (an .onion address encoded in 64 bytes of ip address) - full state save and restore (we just save a json with the list of known addresses in it) - multiple buckets for new and tried addresses selected by a hash of address and source. The current algorithm functions the same as bitcoind would with only one bucket for new and tried (making the address cache rather smaller than it otherwise would be).
167 lines
4 KiB
Go
167 lines
4 KiB
Go
// Copyright (c) 2013 Conformal Systems LLC.
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
)
|
|
|
|
const (
|
|
torSucceeded = 0x00
|
|
torGeneralError = 0x01
|
|
torNotAllowed = 0x02
|
|
torNetUnreachable = 0x03
|
|
torHostUnreachable = 0x04
|
|
torConnectionRefused = 0x05
|
|
torTtlExpired = 0x06
|
|
torCmdNotSupported = 0x07
|
|
torAddrNotSupported = 0x08
|
|
)
|
|
|
|
var (
|
|
ErrTorInvalidAddressResponse = errors.New("Invalid address response")
|
|
ErrTorInvalidProxyResponse = errors.New("Invalid proxy response")
|
|
ErrTorUnrecognizedAuthMethod = errors.New("Invalid proxy authentication method")
|
|
|
|
torStatusErrors = map[byte]error{
|
|
torSucceeded: errors.New("Tor succeeded"),
|
|
torGeneralError: errors.New("Tor general error"),
|
|
torNotAllowed: errors.New("Tor not allowed"),
|
|
torNetUnreachable: errors.New("Tor network is unreachable"),
|
|
torHostUnreachable: errors.New("Tor host is unreachable"),
|
|
torConnectionRefused: errors.New("Tor connection refused"),
|
|
torTtlExpired: errors.New("Tor ttl expired"),
|
|
torCmdNotSupported: errors.New("Tor command not supported"),
|
|
torAddrNotSupported: errors.New("Tor address type not supported"),
|
|
}
|
|
)
|
|
|
|
// try individual DNS server return list of strings for responses.
|
|
func doDNSLookup(host, proxy string) ([]net.IP, error) {
|
|
var err error
|
|
var addrs []net.IP
|
|
|
|
if proxy != "" {
|
|
addrs, err = torLookupIP(host, proxy)
|
|
} else {
|
|
addrs, err = net.LookupIP(host)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return addrs, nil
|
|
}
|
|
|
|
// Use Tor to resolve DNS.
|
|
/*
|
|
TODO:
|
|
* this function must be documented internally
|
|
* this function does not handle IPv6
|
|
*/
|
|
func torLookupIP(host, proxy string) ([]net.IP, error) {
|
|
conn, err := net.Dial("tcp", proxy)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer conn.Close()
|
|
|
|
buf := []byte{'\x05', '\x01', '\x00'}
|
|
_, err = conn.Write(buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
buf = make([]byte, 2)
|
|
_, err = conn.Read(buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if buf[0] != '\x05' {
|
|
return nil, ErrTorInvalidProxyResponse
|
|
}
|
|
if buf[1] != '\x00' {
|
|
return nil, ErrTorUnrecognizedAuthMethod
|
|
}
|
|
|
|
buf = make([]byte, 7+len(host))
|
|
buf[0] = 5 // protocol version
|
|
buf[1] = '\xF0' // Tor Resolve
|
|
buf[2] = 0 // reserved
|
|
buf[3] = 3 // Tor Resolve
|
|
buf[4] = byte(len(host))
|
|
copy(buf[5:], host)
|
|
buf[5+len(host)] = 0 // Port 0
|
|
|
|
_, err = conn.Write(buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
buf = make([]byte, 4)
|
|
_, err = conn.Read(buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if buf[0] != 5 {
|
|
return nil, ErrTorInvalidProxyResponse
|
|
}
|
|
if buf[1] != 0 {
|
|
if int(buf[1]) > len(torStatusErrors) {
|
|
err = ErrTorInvalidProxyResponse
|
|
} else {
|
|
err := torStatusErrors[buf[1]]
|
|
if err == nil {
|
|
err = ErrTorInvalidProxyResponse
|
|
}
|
|
}
|
|
return nil, err
|
|
}
|
|
if buf[3] != 1 {
|
|
err := torStatusErrors[torGeneralError]
|
|
return nil, err
|
|
}
|
|
|
|
buf = make([]byte, 4)
|
|
bytes, err := conn.Read(buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if bytes != 4 {
|
|
return nil, ErrTorInvalidAddressResponse
|
|
}
|
|
|
|
r := binary.BigEndian.Uint32(buf)
|
|
|
|
addr := make([]net.IP, 1)
|
|
addr[0] = net.IPv4(byte(r>>24), byte(r>>16), byte(r>>8), byte(r))
|
|
|
|
return addr, nil
|
|
}
|
|
|
|
// dnsDiscover looks up the list of peers resolved by DNS for all hosts in
|
|
// seeders. If proxy is not "" then it is used as a tor proxy for the
|
|
// resolution. If any errors occur then the seeder that errored will not have
|
|
// any hosts in the list. Therefore if all hosts failed an empty slice of
|
|
// strings will be returned.
|
|
func dnsDiscover(seeder string, proxy string) []net.IP {
|
|
log.Debugf("[DISC] Fetching list of seeds from %v", seeder)
|
|
peers, err := doDNSLookup(seeder, proxy)
|
|
if err != nil {
|
|
seederPlusProxy := seeder
|
|
if proxy != "" {
|
|
seederPlusProxy = fmt.Sprintf("%s (proxy %s)",
|
|
seeder, proxy)
|
|
}
|
|
log.Warnf("[DISC] Failed to fetch dns seeds "+
|
|
"from %s: %v", seederPlusProxy, err)
|
|
return []net.IP{}
|
|
}
|
|
|
|
return peers
|
|
}
|