diff --git a/Bootstrap.go b/Bootstrap.go index 3b3928a..9ef2830 100644 --- a/Bootstrap.go +++ b/Bootstrap.go @@ -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 +} diff --git a/Commands.go b/Commands.go index a1b3d61..4c6f20e 100644 --- a/Commands.go +++ b/Commands.go @@ -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 diff --git a/Config.go b/Config.go index 11ae87d..6599ebe 100644 --- a/Config.go +++ b/Config.go @@ -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 diff --git a/Connection.go b/Connection.go index 9ce1507..50778bf 100644 --- a/Connection.go +++ b/Connection.go @@ -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() diff --git a/Message Encoding.go b/Message Encoding.go index 1f8df30..89a46e7 100644 --- a/Message Encoding.go +++ b/Message Encoding.go @@ -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) { diff --git a/Peer ID.go b/Peer ID.go index 6ff7c11..ab818dd 100644 --- a/Peer ID.go +++ b/Peer ID.go @@ -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} }