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:
parent
1e836d26f4
commit
f93203b91e
4 changed files with 206 additions and 32 deletions
143
addrmanager.go
143
addrmanager.go
|
@ -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
16
peer.go
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
49
server.go
49
server.go
|
@ -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),
|
||||
|
|
Loading…
Add table
Reference in a new issue