lbcd/btcd/discovery.go
2013-08-06 19:17:05 -05:00

171 lines
4.1 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 this is used as a tor proxy for the
// resolution. If any errors occur then that 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(seeders []string, proxy string) []net.IP {
peers := []net.IP{}
for _, seeder := range seeders {
log.Debugf("[DISC] Fetching list of seeds from %v", seeder)
newPeers, 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)
continue
}
peers = append(peers, newPeers...)
}
return peers
}