From 530cd541c136d478a1f36f23301ec7266a4d7a75 Mon Sep 17 00:00:00 2001 From: Kleissner Date: Sat, 8 May 2021 13:03:45 +0200 Subject: [PATCH] Add new fields Port Internal and Port External to the exchanged peer record according to the latest Whitepaper 0.7.3. This allows peers to detect each other's NAT and port forwarding status. --- Commands.go | 12 +++++----- Message Encoding.go | 54 ++++++++++++++++++++++++++------------------- Peer ID.go | 2 +- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/Commands.go b/Commands.go index b8ac25c..c0b577d 100644 --- a/Commands.go +++ b/Commands.go @@ -16,7 +16,7 @@ import ( ) // respondClosesContactsCount is the number of closest contact to respond. -// Each peer record will take 55 bytes. Overhead is 77 + 20 payload header + UA length + 6 + 34 = 137 bytes without UA. +// Each peer record will take 60 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 @@ -100,10 +100,12 @@ 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), + PublicKey: peer.PublicKey, + NodeID: peer.NodeID, + IP: connection.Address.IP, + Port: uint16(connection.Address.Port), + PortReportedInternal: connection.PortInternal, + PortReportedExternal: connection.PortExternal, } } diff --git a/Message Encoding.go b/Message Encoding.go index c6ac931..6f2620d 100644 --- a/Message Encoding.go +++ b/Message Encoding.go @@ -104,11 +104,14 @@ 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 - Port uint16 // Port - LastContact uint32 // Last contact in seconds + 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 } // Hash2Peer links a hash to peers who are known to store the data and to peers who are considered close to the hash @@ -351,6 +354,9 @@ func msgDecodeResponse(msg *MessageRaw) (result *MessageResponse, err error) { return } +// Length of peer record in bytes +const peerRecordSize = 60 + // 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) { index := 0 @@ -370,7 +376,7 @@ func decodePeerRecord(data []byte, count int) (hash2Peers []Hash2Peer, read int, // Response contains peer records for m := 0; m < int(countField); m++ { - if read += 56; len(data) < read { + if read += peerRecordSize; len(data) < read { return nil, 0, false } @@ -387,8 +393,11 @@ func decodePeerRecord(data []byte, count int) (hash2Peers []Hash2Peer, read int, } peer.Port = binary.LittleEndian.Uint16(data[index+49 : index+49+2]) - peer.LastContact = binary.LittleEndian.Uint32(data[index+51 : index+51+4]) - reason := data[index+55] + 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.LastContactT = time.Now().Add(-time.Second * time.Duration(peer.LastContact)) + reason := data[index+59] var err error if peer.PublicKey, err = btcec.ParsePubKey(peerIDcompressed, btcec.S256()); err != nil { @@ -403,7 +412,7 @@ func decodePeerRecord(data []byte, count int) (hash2Peers []Hash2Peer, read int, hash2Peer.Storing = append(hash2Peer.Storing, peer) } - index += 56 + index += peerRecordSize } hash2Peers = append(hash2Peers, hash2Peer) @@ -654,7 +663,7 @@ createPacketLoop: // Encode the peer response data for FIND_SELF, FIND_PEER and FIND_VALUE requests. if len(hash2Peers) > 0 { for n, hash2Peer := range hash2Peers { - if isPacketSizeExceed(packetSize, 34+56) { // check if minimum length is available in packet + if isPacketSizeExceed(packetSize, 34+peerRecordSize) { // check if minimum length is available in packet packetsRaw = append(packetsRaw, raw[:packetSize]) hash2Peers = hash2Peers[n:] continue createPacketLoop @@ -668,7 +677,7 @@ createPacketLoop: count2 := uint16(0) for m, peer := range hash2Peer.Storing { - if isPacketSizeExceed(packetSize, 56) { // check if minimum length is available in packet + if isPacketSizeExceed(packetSize, peerRecordSize) { // check if minimum length is available in packet packetsRaw = append(packetsRaw, raw[:packetSize]) hash2Peers = hash2Peers[n:] hash2Peer.Storing = hash2Peer.Storing[m:] @@ -679,10 +688,12 @@ createPacketLoop: 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.PutUint32(raw[index+51:index+55], peer.LastContact) - raw[index+55] = 1 + 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 - packetSize += 56 + packetSize += peerRecordSize binary.LittleEndian.PutUint16(raw[count2Index+0:count2Index+2], uint16(m+1)) count2++ } @@ -690,7 +701,7 @@ createPacketLoop: hash2Peer.Storing = nil for m, peer := range hash2Peer.Closest { - if isPacketSizeExceed(packetSize, 56) { // check if minimum length is available in packet + if isPacketSizeExceed(packetSize, peerRecordSize) { // check if minimum length is available in packet packetsRaw = append(packetsRaw, raw[:packetSize]) hash2Peers = hash2Peers[n:] hash2Peer.Closest = hash2Peer.Closest[m:] @@ -701,10 +712,12 @@ createPacketLoop: 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.PutUint32(raw[index+51:index+55], peer.LastContact) - raw[index+55] = 0 + 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 - packetSize += 56 + packetSize += peerRecordSize count2++ binary.LittleEndian.PutUint16(raw[count2Index+0:count2Index+2], count2) } @@ -809,8 +822,3 @@ func (peer *PeerInfo) sendResponse(sequence uint32, sendUA bool, hash2Peers []Ha return err } - -// lastContact2Time translates a last contact time in seconds to a timestamp -func lastContact2Time(LastContact uint32) time.Time { - return time.Now().Add(-time.Second * time.Duration(LastContact)) -} diff --git a/Peer ID.go b/Peer ID.go index d5cdcb8..6e99aed 100644 --- a/Peer ID.go +++ b/Peer ID.go @@ -205,7 +205,7 @@ func record2Peer(record PeerRecord, network *Network) (peerN *PeerInfo) { func records2Nodes(records []PeerRecord, network *Network) (nodes []*dht.Node) { for _, record := range records { peer := record2Peer(record, network) - nodes = append(nodes, &dht.Node{ID: peer.NodeID, LastSeen: lastContact2Time(record.LastContact), Info: peer}) + nodes = append(nodes, &dht.Node{ID: peer.NodeID, LastSeen: record.LastContactT, Info: peer}) } return