Add reported internal and external port to Connection. New function peer.IsBehindNAT.

PeerlistAdd is now called on every incoming message. User Agent fixes.
This commit is contained in:
Kleissner
2021-05-04 01:13:11 +02:00
parent f3c9d5e17f
commit 0fe2ce60ec
5 changed files with 72 additions and 42 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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 {