From 0fe2ce60ec2117a99e99f7eaa0c51e72734bb4f7 Mon Sep 17 00:00:00 2001 From: Kleissner Date: Tue, 4 May 2021 01:13:11 +0200 Subject: [PATCH] Add reported internal and external port to Connection. New function peer.IsBehindNAT. PeerlistAdd is now called on every incoming message. User Agent fixes. --- Commands.go | 36 +++++++++--------------------------- Connection.go | 32 +++++++++++++++++++++++++++++--- Message Encoding.go | 16 ++++++++-------- Network.go | 26 ++++++++++++++++++++++---- Peer ID.go | 4 ++++ 5 files changed, 72 insertions(+), 42 deletions(-) diff --git a/Commands.go b/Commands.go index 6fe3a63..2d2cd37 100644 --- a/Commands.go +++ b/Commands.go @@ -16,18 +16,12 @@ import ( ) // respondClosesContactsCount is the number of closest contact to respond. -// Each peer record will take 55 bytes. Overhead is 77 + 15 payload header + UA length + 6 + 34 = 132 bytes without UA. +// Each peer record will take 55 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 // cmdAnouncement handles an incoming announcement func (peer *PeerInfo) cmdAnouncement(msg *MessageAnnouncement) { - var added bool - if peer == nil { - peer, added = PeerlistAdd(msg.SenderPublicKey, msg.connection) - fmt.Printf("Incoming initial announcement from %s\n", msg.connection.Address.String()) - } - // Filter function to only share peers that are "connectable" to the remote one. It checks IPv4, IPv6, and local connection. filterFunc := func(allowLocal, allowIPv4, allowIPv6 bool) dht.NodeFilterFunc { return func(node *dht.Node) (accept bool) { @@ -99,7 +93,8 @@ func (peer *PeerInfo) cmdAnouncement(msg *MessageAnnouncement) { peer.announcementStore(msg.InfoStoreFiles) } - peer.sendResponse(msg.Sequence, added, hash2Peers, filesEmbed, hashesNotFound) + sendUA := msg.UserAgent != "" // Send user agent if one was provided. Per protocol the first announcement message must have the User Agent set. + peer.sendResponse(msg.Sequence, sendUA, hash2Peers, filesEmbed, hashesNotFound) } func (peer *PeerInfo) peer2Record(allowLocal, allowIPv4, allowIPv6 bool) (result *PeerRecord) { @@ -117,11 +112,6 @@ func (peer *PeerInfo) peer2Record(allowLocal, allowIPv4, allowIPv6 bool) (result // cmdResponse handles the response to the announcement func (peer *PeerInfo) cmdResponse(msg *MessageResponse) { - if peer == nil { - peer, _ = PeerlistAdd(msg.SenderPublicKey, msg.connection) - fmt.Printf("Incoming initial response from %s\n", msg.connection.Address.String()) - } - // The sequence data is used to correlate this response with the announcement. if msg.sequence == nil || msg.sequence.data == nil { // If there is no sequence data but there were results returned, it means we received unsolicited response data. It will be rejected. @@ -176,18 +166,18 @@ func (peer *PeerInfo) cmdResponse(msg *MessageResponse) { // cmdPing handles an incoming ping message func (peer *PeerInfo) cmdPing(msg *MessageRaw) { - if peer == nil { - // Unexpected incoming ping, reply with announcement message. For security reasons the remote peer is not asked for FIND_SELF. - peer, _ = PeerlistAdd(msg.SenderPublicKey, msg.connection) + // If PortInternal is 0, it means no incoming announcement or response message was received on that connection. + // This means the ping is unexpected. In that case for security reasons the remote peer is not asked for FIND_SELF. + if msg.connection.PortInternal == 0 { peer.sendAnnouncement(true, false, nil, nil, nil, nil) + return } + peer.send(&PacketRaw{Command: CommandPong, Sequence: msg.Sequence}) - //fmt.Printf("Incoming ping from %s on %s\n", msg.connection.Address.String(), msg.connection.Address.String()) } // cmdPong handles an incoming pong message func (peer *PeerInfo) cmdPong(msg *MessageRaw) { - //fmt.Printf("Incoming pong from %s on %s\n", msg.connection.Address.String(), msg.connection.Address.String()) } // cmdChat handles a chat message [debug] @@ -205,15 +195,7 @@ func (peer *PeerInfo) cmdLocalDiscovery(msg *MessageAnnouncement) { // return //} - if peer == nil { - peer, _ = PeerlistAdd(msg.SenderPublicKey, msg.connection) - - fmt.Printf("Incoming initial local discovery from %s\n", msg.connection.Address.String()) - //} else { - // fmt.Printf("Incoming secondary local discovery from %s\n", msg.connection.Address.String()) - } - - peer.sendAnnouncement(true, true, nil, nil, nil, &bootstrapFindSelf{}) + peer.sendAnnouncement(true, ShouldSendFindSelf(), nil, nil, nil, &bootstrapFindSelf{}) } // pingTime is the time in seconds to send out ping messages diff --git a/Connection.go b/Connection.go index 62019c2..c6b4ca7 100644 --- a/Connection.go +++ b/Connection.go @@ -19,8 +19,10 @@ import ( // Connection is an established connection between a remote IP address and a local network adapter. // New connections may only be created in case of successful INCOMING packets. type Connection struct { - Network *Network // network which received the packet - Address *net.UDPAddr // address of the sender or receiver + Network *Network // Network which received the packet. + Address *net.UDPAddr // Address of the remote peer. + PortInternal uint16 // Internal listening port reported by remote peer. 0 if no Announcement/Response message was yet received. + PortExternal uint16 // External listening port reported by remote peer. 0 if not known by the peer. LastPacketIn time.Time // Last time an incoming packet was received. LastPacketOut time.Time // Last time an outgoing packet was attempted to send. LastPingOut time.Time // Last ping out. @@ -110,7 +112,7 @@ func (peer *PeerInfo) registerConnection(incoming *Connection) (result *Connecti peer.Lock() defer peer.Unlock() - // first check if already an active connection + // first check if already an active connection to the same IP for _, connection := range peer.connectionActive { if connection.Equal(incoming) { // Connection already established. Verify port and update if necessary. @@ -242,6 +244,30 @@ func (peer *PeerInfo) GetRTT() (rtt time.Duration) { return 0 } +// IsBehindNAT checks if the peer is behind NAT +func (peer *PeerInfo) IsBehindNAT() (result bool) { + peer.Lock() + defer peer.Unlock() + + // Default is no. Only if a public network reports different connected port vs internal one, NAT is assumed. + // This also assumes that all 3rd party clients bind their connection to the outgoing port. + // PortInternal is 0 if no Announcement or Response message was received. + + for _, connection := range peer.connectionActive { + if connection.PortInternal > 0 && connection.Address.Port != int(connection.PortInternal) { + return true + } + } + + for _, connection := range peer.connectionInactive { + if connection.PortInternal > 0 && connection.Address.Port != int(connection.PortInternal) { + return true + } + } + + return false +} + // ---- sending code ---- // setAnnouncementPorts sets the fields Internal Port and External Port according to the connection details. diff --git a/Message Encoding.go b/Message Encoding.go index 9177512..c6ac931 100644 --- a/Message Encoding.go +++ b/Message Encoding.go @@ -495,12 +495,12 @@ createPacketLoop: // only on initial announcement the User Agent must be provided according to the protocol spec if sendUA { - if len(UserAgent) > 255 { - UserAgent = UserAgent[:255] - } userAgentB := []byte(UserAgent) + if len(userAgentB) > 255 { + userAgentB = userAgentB[:255] + } - raw[15] = byte(len(userAgentB)) + raw[19] = byte(len(userAgentB)) copy(raw[announcementPayloadHeaderSize:announcementPayloadHeaderSize+len(userAgentB)], userAgentB) packetSize += len(userAgentB) } @@ -637,12 +637,12 @@ createPacketLoop: // only on initial response the User Agent must be provided according to the protocol spec if sendUA { - if len(UserAgent) > 255 { - UserAgent = UserAgent[:255] - } userAgentB := []byte(UserAgent) + if len(userAgentB) > 255 { + userAgentB = userAgentB[:255] + } - raw[15] = byte(len(userAgentB)) + raw[19] = byte(len(userAgentB)) copy(raw[announcementPayloadHeaderSize:announcementPayloadHeaderSize+len(userAgentB)], userAgentB) packetSize += len(userAgentB) } diff --git a/Network.go b/Network.go index 42116f9..8f248fa 100644 --- a/Network.go +++ b/Network.go @@ -147,13 +147,13 @@ func packetWorker(packets <-chan networkWire) { connection := &Connection{Network: packet.network, Address: packet.sender, Status: ConnectionActive} - peer := PeerlistLookup(senderPublicKey) - if peer != nil { - // Existing peers: Update statistics and network address if new - atomic.AddUint64(&peer.StatsPacketReceived, 1) + // A peer structure will always be returned, even if the peer won't be added to the peer list. + peer, added := PeerlistAdd(senderPublicKey, connection) + if !added { connection = peer.registerConnection(connection) } + atomic.AddUint64(&peer.StatsPacketReceived, 1) connection.LastPacketIn = time.Now() // process the packet @@ -162,6 +162,13 @@ func packetWorker(packets <-chan networkWire) { switch decoded.Command { case CommandAnnouncement: // Announce if announce, _ := msgDecodeAnnouncement(raw); announce != nil { + // Update known internal/external port and User Agent + connection.PortInternal = announce.PortInternal + connection.PortExternal = announce.PortExternal + if len(announce.UserAgent) > 0 { + peer.UserAgent = announce.UserAgent + } + peer.cmdAnouncement(announce) } @@ -175,11 +182,22 @@ func packetWorker(packets <-chan networkWire) { connection.RoundTripTime = rtt } + // Update known internal/external port and User Agent + connection.PortInternal = response.PortInternal + connection.PortExternal = response.PortExternal + if len(response.UserAgent) > 0 { + peer.UserAgent = response.UserAgent + } + peer.cmdResponse(response) } case CommandLocalDiscovery: // Local discovery, sent via IPv4 broadcast and IPv6 multicast if announce, _ := msgDecodeAnnouncement(raw); announce != nil { + if len(announce.UserAgent) > 0 { + peer.UserAgent = announce.UserAgent + } + peer.cmdLocalDiscovery(announce) } diff --git a/Peer ID.go b/Peer ID.go index 3530983..d5cdcb8 100644 --- a/Peer ID.go +++ b/Peer ID.go @@ -89,12 +89,14 @@ type PeerInfo struct { sync.RWMutex // Mutex for access to list of connections. messageSequence uint32 // Sequence number. Increased with every message. IsRootPeer bool // Whether the peer is a trusted root peer. + UserAgent string // User Agent reported by remote peer. Empty if no Announcement/Response message was yet received. // statistics StatsPacketSent uint64 // Count of packets sent StatsPacketReceived uint64 // Count of packets received } +// peerList keeps track of all peers var peerList map[[btcec.PubKeyBytesLenCompressed]byte]*PeerInfo var peerlistMutex sync.RWMutex @@ -121,6 +123,8 @@ func PeerlistAdd(PublicKey *btcec.PublicKey, connections ...*Connection) (peer * // add to Kademlia nodesDHT.AddNode(&dht.Node{ID: peer.NodeID, Info: peer}) + // TODO: If the node isn't added to Kademlia, it should be either added temporarily to the peerList with an expiration, or to a temp list, or not at all. + // send to all channels non-blocking for _, monitor := range peerMonitor { select {