Include both IPv4/IPv6 addresses in peer records in Response message. This makes peer list sharing more efficient and speeds up discovery. Close #24

This commit is contained in:
Kleissner
2021-05-21 02:33:32 +02:00
parent 82033e124a
commit 1a44b9c5e7
6 changed files with 130 additions and 67 deletions

View File

@@ -231,16 +231,12 @@ func (peer *PeerInfo) cmdResponseBootstrapFindSelf(msg *MessageResponse, closest
continue
}
// Use the self-reported external port if available.
port := closePeer.Port
if closePeer.PortReportedExternal > 0 {
port = closePeer.PortReportedExternal
}
// Initiate contact. Once a response comes back, the peer will be actually added to the peer list.
if contactArbitraryPeer(closePeer.PublicKey, &net.UDPAddr{IP: closePeer.IP, Port: int(port)}, closePeer.PortReportedInternal) {
// Blacklist the target Peer ID, IP:Port for contact in the next 10 minutes.
// TODO
for _, address := range closePeer.ToAddresses() {
// Initiate contact. Once a response comes back, the peer will be actually added to the peer list.
if contactArbitraryPeer(closePeer.PublicKey, &net.UDPAddr{IP: address.IP, Port: int(address.Port)}, address.PortInternal) {
// Blacklist the target Peer ID, IP:Port for contact in the next 10 minutes.
// TODO
}
}
}
}
@@ -253,8 +249,16 @@ func ShouldSendFindSelf() bool {
// IsBadQuality checks if the returned peer record is bad quality and should be discarded
func (record *PeerRecord) IsBadQuality() bool {
isIPv4 := record.IPv4 != nil && !record.IPv4.IsUnspecified()
isIPv6 := record.IPv6 != nil && !record.IPv6.IsUnspecified()
// At least one IP must be provided.
if !isIPv4 && !isIPv6 {
return true
}
// Internal port must be provided. Otherwise the external port is likely not provided either, and checking the NAT and port forwarded status is not possible.
if record.PortReportedInternal == 0 {
if isIPv4 && record.IPv4PortReportedInternal == 0 || isIPv6 && record.IPv6PortReportedInternal == 0 {
//fmt.Printf("IsBadQuality port internal not available for target %s port %d, peer %s\n", record.IP.String(), record.Port, hex.EncodeToString(record.PublicKey.SerializeCompressed()))
return true
}
@@ -267,3 +271,26 @@ func (record *PeerRecord) IsBadQuality() bool {
return false
}
// ToAddresses returns the addresses in a usable way
func (record *PeerRecord) ToAddresses() (addresses []*peerAddress) {
// IPv4
ipv4Port := record.IPv4Port
if record.IPv4PortReportedExternal > 0 { // Use the external port if available
ipv4Port = record.IPv4PortReportedExternal
}
if record.IPv4 != nil && !record.IPv4.IsUnspecified() {
addresses = append(addresses, &peerAddress{IP: record.IPv4, Port: ipv4Port, PortInternal: record.IPv4PortReportedInternal})
}
// IPv6
ipv6Port := record.IPv6Port
if record.IPv6PortReportedExternal > 0 { // Use the external port if available
ipv6Port = record.IPv6PortReportedExternal
}
if record.IPv6 != nil && !record.IPv6.IsUnspecified() {
addresses = append(addresses, &peerAddress{IP: record.IPv6, Port: ipv6Port, PortInternal: record.IPv6PortReportedInternal})
}
return addresses
}

View File

@@ -15,7 +15,7 @@ import (
)
// respondClosesContactsCount is the number of closest contact to respond.
// Each peer record will take 60 bytes. Overhead is 77 + 20 payload header + UA length + 6 + 34 = 137 bytes without UA.
// Each peer record will take 70 bytes. Overhead is 77 + 20 payload header + UA length + 6 + 34 = 137 bytes without UA.
// It makes sense to stay below 508 bytes (no fragmentation). Reporting back 5 contacts for FIND_SELF requests should do the magic.
const respondClosesContactsCount = 5
@@ -98,18 +98,32 @@ func (peer *PeerInfo) cmdAnouncement(msg *MessageAnnouncement) {
}
func (peer *PeerInfo) peer2Record(allowLocal, allowIPv4, allowIPv6 bool) (result *PeerRecord) {
if connection := peer.GetConnection2Share(allowLocal, allowIPv4, allowIPv6); connection != nil {
return &PeerRecord{
PublicKey: peer.PublicKey,
NodeID: peer.NodeID,
IP: connection.Address.IP,
Port: uint16(connection.Address.Port),
PortReportedInternal: connection.PortInternal,
PortReportedExternal: connection.PortExternal,
}
connectionIPv4 := peer.GetConnection2Share(allowLocal, allowIPv4, false)
connectionIPv6 := peer.GetConnection2Share(allowLocal, false, allowIPv6)
if connectionIPv4 == nil && connectionIPv6 == nil {
return nil
}
return nil
result = &PeerRecord{
PublicKey: peer.PublicKey,
NodeID: peer.NodeID,
}
if connectionIPv4 != nil {
result.IPv4 = connectionIPv4.Address.IP
result.IPv4Port = uint16(connectionIPv4.Address.Port)
result.IPv4PortReportedInternal = connectionIPv4.PortInternal
result.IPv4PortReportedExternal = connectionIPv4.PortExternal
}
if connectionIPv6 != nil {
result.IPv6 = connectionIPv6.Address.IP
result.IPv6Port = uint16(connectionIPv6.Address.Port)
result.IPv6PortReportedInternal = connectionIPv6.PortInternal
result.IPv6PortReportedExternal = connectionIPv6.PortExternal
}
return result
}
// cmdResponse handles the response to the announcement

View File

@@ -16,7 +16,7 @@ import (
)
// Version is the current core library version
const Version = "Alpha 2"
const Version = "Alpha 2/20.05.2021"
var config struct {
LogFile string `yaml:"LogFile"` // Log file

View File

@@ -102,6 +102,10 @@ func (peer *PeerInfo) IsConnectable(allowLocal, allowIPv4, allowIPv6 bool) bool
// GetConnection2Share returns a connection to share. Nil if none.
// allowLocal specifies whether it is OK to return local IPs.
func (peer *PeerInfo) GetConnection2Share(allowLocal, allowIPv4, allowIPv6 bool) (connection *Connection) {
if !allowLocal && !allowIPv4 && !allowIPv6 {
return nil
}
peer.RLock()
defer peer.RUnlock()

View File

@@ -104,14 +104,18 @@ type InfoStore struct {
// PeerRecord informs about a peer
type PeerRecord struct {
PublicKey *btcec.PublicKey // Public Key
NodeID []byte // Kademlia Node ID
IP net.IP // IP address
Port uint16 // Port (actual one used for connection)
PortReportedInternal uint16 // Internal port as reported by that peer. This can be used to identify whether the peer is potentially behind a NAT.
PortReportedExternal uint16 // External port as reported by that peer. This is used in case of port forwarding (manual or automated).
LastContact uint32 // Last contact in seconds
LastContactT time.Time // Last contact time translated from seconds
PublicKey *btcec.PublicKey // Public Key
NodeID []byte // Kademlia Node ID
IPv4 net.IP // IPv4 address. 0 if not set.
IPv4Port uint16 // Port (actual one used for connection)
IPv4PortReportedInternal uint16 // Internal port as reported by that peer. This can be used to identify whether the peer is potentially behind a NAT.
IPv4PortReportedExternal uint16 // External port as reported by that peer. This is used in case of port forwarding (manual or automated).
IPv6 net.IP // IPv6 address. 0 if not set.
IPv6Port uint16 // Port (actual one used for connection)
IPv6PortReportedInternal uint16 // Internal port as reported by that peer. This can be used to identify whether the peer is potentially behind a NAT.
IPv6PortReportedExternal uint16 // External port as reported by that peer. This is used in case of port forwarding (manual or automated).
LastContact uint32 // Last contact in seconds
LastContactT time.Time // Last contact time translated from seconds
}
// Hash2Peer links a hash to peers who are known to store the data and to peers who are considered close to the hash
@@ -372,7 +376,7 @@ func msgDecodeResponse(msg *MessageRaw) (result *MessageResponse, err error) {
}
// Length of peer record in bytes
const peerRecordSize = 60
const peerRecordSize = 70
// decodePeerRecord decodes the response data for FIND_SELF, FIND_PEER and FIND_VALUE messages
func decodePeerRecord(data []byte, count int) (hash2Peers []Hash2Peer, read int, valid bool) {
@@ -402,19 +406,31 @@ func decodePeerRecord(data []byte, count int) (hash2Peers []Hash2Peer, read int,
peerIDcompressed := make([]byte, 33)
copy(peerIDcompressed[:], data[index:index+33])
ipB := make([]byte, 16)
copy(ipB[:], data[index+33:index+33+16])
peer.IP = ipB
if peer.IP.To4() != nil { // If IPv4, convert to native 4-byte representation
peer.IP = peer.IP.To4()
// IPv4
ipv4B := make([]byte, 4)
copy(ipv4B[:], data[index+33:index+33+4])
peer.IPv4 = ipv4B
peer.IPv4Port = binary.LittleEndian.Uint16(data[index+37 : index+37+2])
peer.IPv4PortReportedInternal = binary.LittleEndian.Uint16(data[index+39 : index+39+2])
peer.IPv4PortReportedExternal = binary.LittleEndian.Uint16(data[index+41 : index+41+2])
// IPv6
ipv6B := make([]byte, 16)
copy(ipv6B[:], data[index+43:index+43+16])
peer.IPv6 = ipv6B
peer.IPv6Port = binary.LittleEndian.Uint16(data[index+59 : index+59+2])
peer.IPv6PortReportedInternal = binary.LittleEndian.Uint16(data[index+61 : index+61+2])
peer.IPv6PortReportedExternal = binary.LittleEndian.Uint16(data[index+63 : index+63+2])
if peer.IPv6.To4() != nil { // IPv6 address mismatch
return nil, 0, false
}
peer.Port = binary.LittleEndian.Uint16(data[index+49 : index+49+2])
peer.PortReportedInternal = binary.LittleEndian.Uint16(data[index+51 : index+51+2])
peer.PortReportedExternal = binary.LittleEndian.Uint16(data[index+53 : index+53+2])
peer.LastContact = binary.LittleEndian.Uint32(data[index+55 : index+55+4])
peer.LastContact = binary.LittleEndian.Uint32(data[index+65 : index+65+4])
peer.LastContactT = time.Now().Add(-time.Second * time.Duration(peer.LastContact))
reason := data[index+59]
reason := data[index+69]
var err error
if peer.PublicKey, err = btcec.ParsePubKey(peerIDcompressed, btcec.S256()); err != nil {
@@ -692,7 +708,7 @@ createPacketLoop:
packetSize += 34
count2 := uint16(0)
for m, peer := range hash2Peer.Storing {
for m := range hash2Peer.Storing {
if isPacketSizeExceed(packetSize, peerRecordSize) { // check if minimum length is available in packet
packetsRaw = append(packetsRaw, raw[:packetSize])
hash2Peers = hash2Peers[n:]
@@ -701,13 +717,7 @@ createPacketLoop:
}
index := packetSize
copy(raw[index:index+33], peer.PublicKey.SerializeCompressed())
copy(raw[index+33:index+33+16], peer.IP.To16())
binary.LittleEndian.PutUint16(raw[index+49:index+51], peer.Port)
binary.LittleEndian.PutUint16(raw[index+51:index+53], peer.PortReportedInternal)
binary.LittleEndian.PutUint16(raw[index+53:index+55], peer.PortReportedExternal)
binary.LittleEndian.PutUint32(raw[index+55:index+59], peer.LastContact)
raw[index+59] = 1
encodePeerRecord(raw[index:index+peerRecordSize], &hash2Peer.Storing[m], 1)
packetSize += peerRecordSize
binary.LittleEndian.PutUint16(raw[count2Index+0:count2Index+2], uint16(m+1))
@@ -716,7 +726,7 @@ createPacketLoop:
hash2Peer.Storing = nil
for m, peer := range hash2Peer.Closest {
for m := range hash2Peer.Closest {
if isPacketSizeExceed(packetSize, peerRecordSize) { // check if minimum length is available in packet
packetsRaw = append(packetsRaw, raw[:packetSize])
hash2Peers = hash2Peers[n:]
@@ -725,13 +735,7 @@ createPacketLoop:
}
index := packetSize
copy(raw[index:index+33], peer.PublicKey.SerializeCompressed())
copy(raw[index+33:index+33+16], peer.IP.To16())
binary.LittleEndian.PutUint16(raw[index+49:index+51], peer.Port)
binary.LittleEndian.PutUint16(raw[index+51:index+53], peer.PortReportedInternal)
binary.LittleEndian.PutUint16(raw[index+53:index+55], peer.PortReportedExternal)
binary.LittleEndian.PutUint32(raw[index+55:index+59], peer.LastContact)
raw[index+59] = 0
encodePeerRecord(raw[index:index+peerRecordSize], &hash2Peer.Closest[m], 0)
packetSize += peerRecordSize
count2++
@@ -799,6 +803,25 @@ createPacketLoop:
}
}
// encodePeerRecord encodes a single peer record and stores it into raw
func encodePeerRecord(raw []byte, peer *PeerRecord, reason uint8) {
copy(raw[0:0+33], peer.PublicKey.SerializeCompressed())
binary.LittleEndian.PutUint32(raw[65:65+4], peer.LastContact)
raw[69] = reason
// IPv4
copy(raw[33:33+4], peer.IPv4.To4())
binary.LittleEndian.PutUint16(raw[37:37+2], peer.IPv4Port)
binary.LittleEndian.PutUint16(raw[39:39+2], peer.IPv4PortReportedInternal)
binary.LittleEndian.PutUint16(raw[41:41+2], peer.IPv4PortReportedExternal)
// IPv6
copy(raw[43:43+16], peer.IPv6.To16())
binary.LittleEndian.PutUint16(raw[59:59+2], peer.IPv6Port)
binary.LittleEndian.PutUint16(raw[61:61+2], peer.IPv6PortReportedInternal)
binary.LittleEndian.PutUint16(raw[63:63+2], peer.IPv6PortReportedExternal)
}
// setSelfReportedPorts sets the fields Internal Port and External Port according to the connection details.
// This is important for the remote peer to make smart decisions whether this peer is behind a NAT/firewall and supports port forwarding/UPnP.
func (packet *PacketRaw) setSelfReportedPorts(n *Network) {

View File

@@ -212,16 +212,11 @@ func records2Nodes(records []PeerRecord, peerSource *PeerInfo) (nodes []*dht.Nod
peer = peerSource
} else if peer = PeerlistLookup(record.PublicKey); peer == nil {
// Create temporary peer which is not added to the global list and not added to Kademlia.
var addresses []*peerAddress
port := record.Port
if record.PortReportedExternal > 0 { // Use the external port if available
port = record.PortReportedExternal
}
addresses = append(addresses, &peerAddress{IP: record.IP, Port: port, PortInternal: record.PortReportedInternal})
// traversePeer is set to the peer who provided the node information.
addresses := record.ToAddresses()
if len(addresses) == 0 {
continue
}
peer = &PeerInfo{PublicKey: record.PublicKey, connectionActive: nil, connectionLatest: nil, NodeID: publicKey2NodeID(record.PublicKey), messageSequence: rand.Uint32(), isVirtual: true, targetAddresses: addresses, traversePeer: peerSource}
}