Initial basic support for selection of external ip address.

This implements only the bare bones of external ip address selection
using very similar algorithms and selection methods to bitcoind. Every
address we bind to, and if we bind to the wildcard, every listening
address is recorded, and one for the appropriate address type of the
peer is selected.

Support for fetching addresses via upnp, external services, or via the
command line are not yet implemented.

Closes #35
This commit is contained in:
Owain G. Ainsworth 2013-11-26 00:40:16 +00:00
parent 1e836d26f4
commit f93203b91e
4 changed files with 206 additions and 32 deletions

View file

@ -334,6 +334,8 @@ type AddrManager struct {
quit chan bool
nTried int
nNew int
lamtx sync.Mutex
localAddresses map[string]*localAddress
}
func (a *AddrManager) getNewBucket(netAddr, srcAddr *btcwire.NetAddress) int {
@ -750,6 +752,7 @@ func NewAddrManager() *AddrManager {
am := AddrManager{
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
quit: make(chan bool),
localAddresses: make(map[string]*localAddress),
}
am.reset()
return &am
@ -1152,6 +1155,7 @@ func Valid(na *btcwire.NetAddress) bool {
// not. This is true as long as the address is valid and is not in any reserved
// ranges.
func Routable(na *btcwire.NetAddress) bool {
// TODO(oga) bitcoind doesn't include RFC3849 here, but should we?
return Valid(na) && !(RFC1918(na) || RFC3927(na) || RFC4862(na) ||
(RFC4193(na) && !Tor(na)) || RFC4843(na) || Local(na))
}
@ -1211,3 +1215,142 @@ func GroupKey(na *btcwire.NetAddress) string {
return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(bits, 128)}).String()
}
// addressPrio is an enum type used to describe the heirarchy of local address
// discovery methods.
type addressPrio int
const (
InterfacePrio addressPrio = iota // address of local interface.
BoundPrio // Address explicitly bound to.
UpnpPrio // External IP discovered from UPnP
HttpPrio // Obtained from internet service.
ManualPrio // provided by --externalip.
)
type localAddress struct {
na *btcwire.NetAddress
score addressPrio
}
// addLocalAddress adds na to the list of known local addresses to advertise
// with the given priority.
func (a *AddrManager) addLocalAddress(na *btcwire.NetAddress,
priority addressPrio) {
// sanity check.
if !Routable(na) {
amgrLog.Debugf("rejecting address %s:%d due to routability",
na.IP, na.Port)
return
}
amgrLog.Debugf("adding address %s:%d",
na.IP, na.Port)
a.lamtx.Lock()
defer a.lamtx.Unlock()
key := NetAddressKey(na)
la, ok := a.localAddresses[key]
if !ok || la.score < priority {
if ok {
la.score = priority + 1
} else {
a.localAddresses[key] = &localAddress{
na: na,
score: priority,
}
}
}
}
// getReachabilityFrom returns the relative reachability of na from fromna.
func getReachabilityFrom(na, fromna *btcwire.NetAddress) int {
const (
Unreachable = 0
Default = iota
Teredo
Ipv6_weak
Ipv4
Ipv6_strong
Private
)
if !Routable(fromna) {
return Unreachable
} else if Tor(fromna) {
if Tor(na) {
return Private
} else if Routable(na) && na.IP.To4() != nil {
return Ipv4
} else {
return Default
}
} else if RFC4380(fromna) {
if !Routable(na) {
return Default
} else if RFC4380(na) {
return Teredo
} else if na.IP.To4() != nil {
return Ipv4
} else { // ipv6
return Ipv6_weak
}
} else if fromna.IP.To4() != nil {
if Routable(na) && na.IP.To4() != nil {
return Ipv4
}
return Default
} else /* ipv6 */ {
var tunnelled bool
// Is our v6 is tunnelled?
if RFC3964(na) || RFC6052(na) || RFC6145(na) {
tunnelled = true
}
if !Routable(na) {
return Default
} else if RFC4380(na) {
return Teredo
} else if na.IP.To4() != nil {
return Ipv4
} else if tunnelled {
// only prioritise ipv6 if we aren't tunnelling it.
return Ipv6_weak
}
return Ipv6_strong
}
}
// getBestLocalAddress returns the most appropriate local address that we know
// of to be contacted by rna
func (a *AddrManager) getBestLocalAddress(rna *btcwire.NetAddress) *btcwire.NetAddress {
a.lamtx.Lock()
defer a.lamtx.Unlock()
bestreach := 0
var bestscore addressPrio
var bestna *btcwire.NetAddress
for _, la := range a.localAddresses {
reach := getReachabilityFrom(la.na, rna)
if reach > bestreach ||
(reach == bestreach && la.score > bestscore) {
bestreach = reach
bestscore = la.score
bestna = la.na
}
}
if bestna != nil {
amgrLog.Debugf("Suggesting address %s:%d for %s:%d",
bestna.IP, bestna.Port, rna.IP, rna.Port)
} else {
amgrLog.Debugf("No worthy address for %s:%d",
rna.IP, rna.Port)
// Send something unroutable if nothing suitable.
bestna = &btcwire.NetAddress{
Timestamp: time.Now(),
Services: 0,
IP: net.IP([]byte{0, 0, 0, 0}),
Port: 0,
}
}
return bestna
}

16
peer.go
View file

@ -281,18 +281,14 @@ func (p *peer) handleVersionMsg(msg *btcwire.MsgVersion) {
if !p.inbound {
// TODO(davec): Only do this if not doing the initial block
// download and the local address is routable.
if !cfg.DisableListen {
// Advertise the local address.
na, err := newNetAddress(p.conn.LocalAddr(), p.services)
if err != nil {
p.logError("Can't advertise local "+
"address: %v", err)
p.Disconnect()
return
}
addresses := []*btcwire.NetAddress{na}
if !cfg.DisableListen /* && isCurrent? */ {
// get address that best matches. p.na
lna := p.server.addrManager.getBestLocalAddress(p.na)
if Routable(lna) {
addresses := []*btcwire.NetAddress{lna}
p.pushAddrMsg(addresses)
}
}
// Request known addresses if the server address manager needs
// more and the peer has a protocol version new enough to

View file

@ -472,7 +472,7 @@ func newRPCServer(listenAddrs []string, s *server) (*rpcServer, error) {
// TODO(oga) this code is similar to that in server, should be
// factored into something shared.
ipv4ListenAddrs, ipv6ListenAddrs, err := parseListeners(listenAddrs)
ipv4ListenAddrs, ipv6ListenAddrs, _, err := parseListeners(listenAddrs)
if err != nil {
return nil, err
}

View file

@ -771,28 +771,31 @@ func (s *server) ScheduleShutdown(duration time.Duration) {
// listeners on the correct interface "tcp4" and "tcp6". It also properly
// detects addresses which apply to "all interfaces" and adds the address to
// both slices.
func parseListeners(addrs []string) ([]string, []string, error) {
func parseListeners(addrs []string) ([]string, []string, bool, error) {
ipv4ListenAddrs := make([]string, 0, len(addrs)*2)
ipv6ListenAddrs := make([]string, 0, len(addrs)*2)
haveWildcard := false
for _, addr := range addrs {
host, _, err := net.SplitHostPort(addr)
if err != nil {
// Shouldn't happen due to already being normalized.
return nil, nil, err
return nil, nil, false, err
}
// Empty host or host of * on plan9 is both IPv4 and IPv6.
if host == "" || (host == "*" && runtime.GOOS == "plan9") {
ipv4ListenAddrs = append(ipv4ListenAddrs, addr)
ipv6ListenAddrs = append(ipv6ListenAddrs, addr)
haveWildcard = true
continue
}
// Parse the IP.
ip := net.ParseIP(host)
if ip == nil {
return nil, nil, fmt.Errorf("'%s' is not a valid IP "+
"address", host)
return nil, nil, false, fmt.Errorf("'%s' is not a "+
"valid IP address", host)
}
// To4 returns nil when the IP is not an IPv4 address, so use
@ -803,7 +806,7 @@ func parseListeners(addrs []string) ([]string, []string, error) {
ipv4ListenAddrs = append(ipv4ListenAddrs, addr)
}
}
return ipv4ListenAddrs, ipv6ListenAddrs, nil
return ipv4ListenAddrs, ipv6ListenAddrs, haveWildcard, nil
}
// newServer returns a new btcd server configured to listen on addr for the
@ -815,14 +818,39 @@ func newServer(listenAddrs []string, db btcdb.Db, btcnet btcwire.BitcoinNet) (*s
return nil, err
}
amgr := NewAddrManager()
var listeners []net.Listener
if !cfg.DisableListen {
ipv4Addrs, ipv6Addrs, err := parseListeners(listenAddrs)
ipv4Addrs, ipv6Addrs, wildcard, err :=
parseListeners(listenAddrs)
if err != nil {
return nil, err
}
listeners = make([]net.Listener, 0, len(ipv4Addrs)+len(ipv6Addrs))
// TODO(oga) nonstandard port...
if wildcard {
port, err :=
strconv.ParseUint(activeNetParams.listenPort,
10, 16)
if err != nil {
// I can't think of a cleaner way to do this...
goto nowc
}
addrs, err := net.InterfaceAddrs()
for _, a := range addrs {
ip, _, err := net.ParseCIDR(a.String())
if err != nil {
continue
}
na := btcwire.NewNetAddressIPPort(ip,
uint16(port), btcwire.SFNodeNetwork)
amgr.addLocalAddress(na, InterfacePrio)
}
}
nowc:
for _, addr := range ipv4Addrs {
listener, err := net.Listen("tcp4", addr)
if err != nil {
@ -831,6 +859,10 @@ func newServer(listenAddrs []string, db btcdb.Db, btcnet btcwire.BitcoinNet) (*s
continue
}
listeners = append(listeners, listener)
if na, err := deserialiseNetAddress(addr); err == nil {
amgr.addLocalAddress(na, BoundPrio)
}
}
for _, addr := range ipv6Addrs {
@ -841,6 +873,9 @@ func newServer(listenAddrs []string, db btcdb.Db, btcnet btcwire.BitcoinNet) (*s
continue
}
listeners = append(listeners, listener)
if na, err := deserialiseNetAddress(addr); err == nil {
amgr.addLocalAddress(na, BoundPrio)
}
}
if len(listeners) == 0 {
@ -852,7 +887,7 @@ func newServer(listenAddrs []string, db btcdb.Db, btcnet btcwire.BitcoinNet) (*s
nonce: nonce,
listeners: listeners,
btcnet: btcnet,
addrManager: NewAddrManager(),
addrManager: amgr,
newPeers: make(chan *peer, cfg.MaxPeers),
donePeers: make(chan *peer, cfg.MaxPeers),
banPeers: make(chan *peer, cfg.MaxPeers),