diff --git a/Bootstrap.go b/Bootstrap.go index 9075f98..510bf12 100644 --- a/Bootstrap.go +++ b/Bootstrap.go @@ -105,7 +105,7 @@ func (peer *rootPeer) contact() { for _, address := range peer.addresses { // Port internal is always set to 0 for root peers. It disables NAT detection and will not send out a Traverse message. - contactArbitraryPeer(peer.publicKey, address, 0) + contactArbitraryPeer(peer.publicKey, address, 0, false) } } @@ -205,7 +205,7 @@ func (nets *Networks) autoMulticastBroadcast() { } // contactArbitraryPeer contacts a new arbitrary peer for the first time. -func contactArbitraryPeer(publicKey *btcec.PublicKey, address *net.UDPAddr, receiverPortInternal uint16) (contacted bool) { +func contactArbitraryPeer(publicKey *btcec.PublicKey, address *net.UDPAddr, receiverPortInternal uint16, receiverFirewall bool) (contacted bool) { findSelf := ShouldSendFindSelf() _, blockchainHeight, blockchainVersion := UserBlockchain.Header() packets := protocol.EncodeAnnouncement(true, findSelf, nil, nil, nil, FeatureSupport(), blockchainHeight, blockchainVersion, userAgent) @@ -216,7 +216,7 @@ func contactArbitraryPeer(publicKey *btcec.PublicKey, address *net.UDPAddr, rece Filters.MessageOutAnnouncement(publicKey, nil, raw, findSelf, nil, nil, nil) - networks.sendAllNetworks(publicKey, raw, address, receiverPortInternal, nil, &bootstrapFindSelf{}) + networks.sendAllNetworks(publicKey, raw, address, receiverPortInternal, receiverFirewall, nil, &bootstrapFindSelf{}) return true } @@ -258,7 +258,7 @@ func (peer *PeerInfo) cmdResponseBootstrapFindSelf(msg *protocol.MessageResponse } // Initiate contact. Once a response comes back, the peer will be actually added to the peer list. - contactArbitraryPeer(closePeer.PublicKey, &net.UDPAddr{IP: address.IP, Port: int(address.Port)}, address.PortInternal) + contactArbitraryPeer(closePeer.PublicKey, &net.UDPAddr{IP: address.IP, Port: int(address.Port)}, address.PortInternal, closePeer.Features&(1< 0) } } } diff --git a/Commands.go b/Commands.go index 05d7aeb..bec0161 100644 --- a/Commands.go +++ b/Commands.go @@ -118,6 +118,7 @@ func (peer *PeerInfo) peer2Record(allowLocal, allowIPv4, allowIPv6 bool) (result result = &protocol.PeerRecord{ PublicKey: peer.PublicKey, NodeID: peer.NodeID, + Features: peer.Features, } if connectionIPv4 != nil { diff --git a/Config Default.yaml b/Config Default.yaml index df10cc4..15f0283 100644 --- a/Config Default.yaml +++ b/Config Default.yaml @@ -29,8 +29,9 @@ SeedList: Address: ["194.233.66.99:112","[2407:3640:2057:5241::1]:112"] # Connection settings -EnableUPnP: true # Enables support for UPnP. +EnableUPnP: true # Enables support for UPnP. +LocalFirewall: false # Indicates that a local firewall may drop unsolicited incoming packets. # PortForward specifies an external port that was manually forwarded by the user. All listening IPs must have that same port number forwarded! # If this setting is invalid, it will prohibit other peers from connecting. If set, it automatically disables UPnP. -PortForward: 0 # Default not set. +PortForward: 0 # Default not set. diff --git a/Config.go b/Config.go index 001cc66..c6e8eb2 100644 --- a/Config.go +++ b/Config.go @@ -17,7 +17,7 @@ import ( ) // Version is the current core library version -const Version = "Alpha 4/15.11.2021" +const Version = "Alpha 4/20.11.2021" var config struct { // Locations of important files and folders @@ -39,7 +39,8 @@ var config struct { SeedListVersion int `yaml:"SeedListVersion"` // Connection settings - EnableUPnP bool `yaml:"EnableUPnP"` // Enables support for UPnP. + EnableUPnP bool `yaml:"EnableUPnP"` // Enables support for UPnP. + LocalFirewall bool `yaml:"LocalFirewall"` // Indicates that a local firewall may drop unsolicited incoming packets. // PortForward specifies an external port that was manually forwarded by the user. All listening IPs must have that same port number forwarded! // If this setting is invalid, it will prohibit other peers from connecting. If set, it automatically disables UPnP. diff --git a/Connection.go b/Connection.go index 922a967..f6a0592 100644 --- a/Connection.go +++ b/Connection.go @@ -29,7 +29,8 @@ type Connection struct { Expires time.Time // Inactive connections only: Expiry date. If it does not become active by that date, it will be considered expired and removed. Status int // 0 = Active established connection, 1 = Inactive, 2 = Removed, 3 = Redundant RoundTripTime time.Duration // Full round-trip time of last reply. - traversePeer *PeerInfo // Temporary peer that may act as proxy for a Traverse message used for the first packet. This is used to establish this Connection to a peer that is behind a NAT. + Firewall bool // Whether the remote peer indicates a potential firewall. This means a Traverse message shall be sent to establish a connection. + traversePeer *PeerInfo // Temporary peer that may act as proxy for a Traverse message used for the first packet. This is used to establish this Connection to a peer that is behind a NAT or firewall. } // Connection status @@ -325,9 +326,15 @@ func (peer *PeerInfo) IsPortForward() (result bool) { return false } +// IsFirewallReported checks if the peer reported to be behind a firewall +func (peer *PeerInfo) IsFirewallReported() (result bool) { + return peer.Features&(1< 0 +} + // ---- sending code ---- // send sends the packet to the peer on the connection +// isFirstPacket indicates whether this is the first packet to an uncontacted peer. func (c *Connection) send(packet *protocol.PacketRaw, receiverPublicKey *btcec.PublicKey, isFirstPacket bool) (err error) { if c == nil { return errors.New("invalid connection") @@ -347,8 +354,8 @@ func (c *Connection) send(packet *protocol.PacketRaw, receiverPublicKey *btcec.P err = c.Network.send(c.Address.IP, c.Address.Port, raw) - // Send Traverse message if the peer is behind a NAT and this is the first message. Only for Announcement. - if err == nil && isFirstPacket && c.IsBehindNAT() && c.traversePeer != nil && packet.Command == protocol.CommandAnnouncement { + // Send Traverse message if the peer is behind a NAT or firewall and this is the first message. Only for Announcement. + if err == nil && isFirstPacket && (c.IsBehindNAT() || c.Firewall) && c.traversePeer != nil && packet.Command == protocol.CommandAnnouncement { c.traversePeer.sendTraverse(packet, receiverPublicKey) } @@ -359,7 +366,7 @@ func (c *Connection) send(packet *protocol.PacketRaw, receiverPublicKey *btcec.P func (peer *PeerInfo) send(packet *protocol.PacketRaw) (err error) { if peer.isVirtual { // special case for peers that were not contacted before for _, address := range peer.targetAddresses { - networks.sendAllNetworks(peer.PublicKey, packet, &net.UDPAddr{IP: address.IP, Port: int(address.Port)}, address.PortInternal, peer.traversePeer, nil) + networks.sendAllNetworks(peer.PublicKey, packet, &net.UDPAddr{IP: address.IP, Port: int(address.Port)}, address.PortInternal, peer.Features&(1< 0, peer.traversePeer, nil) } return } @@ -414,8 +421,8 @@ func (peer *PeerInfo) sendConnection(packet *protocol.PacketRaw, connection *Con } // sendAllNetworks sends a raw packet via all networks. It assigns a new sequence for each sent packet. -// receiverPortInternal is important for NAT detection and sending the traverse message. -func (nets *Networks) sendAllNetworks(receiverPublicKey *btcec.PublicKey, packet *protocol.PacketRaw, remote *net.UDPAddr, receiverPortInternal uint16, traversePeer *PeerInfo, sequenceData interface{}) (err error) { +// receiverPortInternal is important for NAT detection and sending the traverse message. Firewall indicates whether the remote peer was reported to be behind a firewall. +func (nets *Networks) sendAllNetworks(receiverPublicKey *btcec.PublicKey, packet *protocol.PacketRaw, remote *net.UDPAddr, receiverPortInternal uint16, receiverFirewall bool, traversePeer *PeerInfo, sequenceData interface{}) (err error) { nets.RLock() defer nets.RUnlock() @@ -436,7 +443,7 @@ func (nets *Networks) sendAllNetworks(receiverPublicKey *btcec.PublicKey, packet if sequenceData != nil { packet.Sequence = nets.Sequences.ArbitrarySequence(receiverPublicKey, sequenceData).SequenceNumber } - err = (&Connection{Network: network, Address: remote, PortInternal: receiverPortInternal, traversePeer: traversePeer}).send(packet, receiverPublicKey, isFirstPacket) + err = (&Connection{Network: network, Address: remote, PortInternal: receiverPortInternal, traversePeer: traversePeer, Firewall: receiverFirewall}).send(packet, receiverPublicKey, isFirstPacket) isFirstPacket = false if err == nil { diff --git a/Message Send.go b/Message Send.go index fa81f85..8e0e3fc 100644 --- a/Message Send.go +++ b/Message Send.go @@ -26,6 +26,12 @@ func (peer *PeerInfo) pingConnection(connection *Connection) { } } +// Ping sends a ping. This function exists only for debugging purposes, it should not be used normally. +// This ping is not used for uptime detection and the LastPingOut time in connections is not set. +func (peer *PeerInfo) Ping() { + peer.send(&protocol.PacketRaw{Command: protocol.CommandPing, Sequence: networks.Sequences.NewSequence(peer.PublicKey, &peer.messageSequence, nil).SequenceNumber}) +} + // Chat sends a text message func (peer *PeerInfo) Chat(text string) { peer.send(&protocol.PacketRaw{Command: protocol.CommandChat, Payload: []byte(text)}) diff --git a/Network.go b/Network.go index 8b0d1d3..da1755c 100644 --- a/Network.go +++ b/Network.go @@ -171,6 +171,7 @@ func (nets *Networks) packetWorker() { // Update known internal/external port and User Agent connection.PortInternal = announce.PortInternal connection.PortExternal = announce.PortExternal + connection.Firewall = announce.Features&(1< 0 if len(announce.UserAgent) > 0 { peer.UserAgent = announce.UserAgent } @@ -199,6 +200,7 @@ func (nets *Networks) packetWorker() { // Update known internal/external port and User Agent connection.PortInternal = response.PortInternal connection.PortExternal = response.PortExternal + connection.Firewall = response.Features&(1< 0 if len(response.UserAgent) > 0 { peer.UserAgent = response.UserAgent } @@ -359,5 +361,8 @@ func FeatureSupport() (feature byte) { if networks.countListen6 > 0 { feature |= 1 << protocol.FeatureIPv6Listen } + if networks.localFirewall { + feature |= 1 << protocol.FeatureFirewall + } return feature } diff --git a/Networks.go b/Networks.go index aa15733..50ec4d6 100644 --- a/Networks.go +++ b/Networks.go @@ -32,6 +32,9 @@ type Networks struct { // ipListen keeps a simple list of IPs listened to. This allows quickly identifying if an IP matches with a listened one. ipListen *ipList + + // localFirewall indicates if a local firewall may drop unsolicited incoming packets + localFirewall bool } // ReplyTimeout is the round-trip timeout for message sequences. @@ -47,4 +50,10 @@ func initMessageSequence() { networks.Sequences = protocol.NewSequenceManager(ReplyTimeout) networks.ipListen = NewIPList() + + // There is currently no suitable live firewall detection code. Instead, there is the config flag. + // Windows: If the user runs as non-admin, it can be assumed that the Windows Firewall creates a rule to drop unsolicited incoming packets. + // Changing the Windows Firewall (via netsh or otherwise) requires elevated admin rights. + // This flag will be passed on to other peers to indicate that uncontacted peers shall use the Traverse message for establishing connections. + networks.localFirewall = config.LocalFirewall } diff --git a/Peer ID.go b/Peer ID.go index 3f0b099..cb03e60 100644 --- a/Peer ID.go +++ b/Peer ID.go @@ -96,7 +96,7 @@ type PeerInfo struct { 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. - Features uint8 // Feature bit array. 0 = IPv4_LISTEN, 1 = IPv6_LISTEN + Features uint8 // Feature bit array. 0 = IPv4_LISTEN, 1 = IPv6_LISTEN, 1 = FIREWALL isVirtual bool // Whether it is a virtual peer for establishing a connection. targetAddresses []*peerAddress // Virtual peer: Addresses to send any replies. traversePeer *PeerInfo // Virtual peer: Same field as in connection. @@ -222,7 +222,7 @@ func records2Nodes(records []protocol.PeerRecord, peerSource *PeerInfo) (nodes [ continue } - peer = &PeerInfo{PublicKey: record.PublicKey, connectionActive: nil, connectionLatest: nil, NodeID: protocol.PublicKey2NodeID(record.PublicKey), messageSequence: rand.Uint32(), isVirtual: true, targetAddresses: addresses, traversePeer: peerSource} + peer = &PeerInfo{PublicKey: record.PublicKey, connectionActive: nil, connectionLatest: nil, NodeID: protocol.PublicKey2NodeID(record.PublicKey), messageSequence: rand.Uint32(), isVirtual: true, targetAddresses: addresses, traversePeer: peerSource, Features: record.Features} } nodes = append(nodes, &dht.Node{ID: peer.NodeID, LastSeen: record.LastContactT, Info: peer}) @@ -239,6 +239,7 @@ func selfPeerRecord() (result protocol.PeerRecord) { //IP: network.address.IP, //Port: uint16(network.address.Port), LastContact: 0, + Features: FeatureSupport(), } } diff --git a/protocol/Message Encoding Announcement.go b/protocol/Message Encoding Announcement.go index 8b39009..2979d3e 100644 --- a/protocol/Message Encoding Announcement.go +++ b/protocol/Message Encoding Announcement.go @@ -16,7 +16,7 @@ import ( type MessageAnnouncement struct { *MessageRaw // Underlying raw message Protocol uint8 // Protocol version supported (low 4 bits). - Features uint8 // Feature support (high 4 bits). Future use. + Features uint8 // Feature support Actions uint8 // Action bit array. See ActionX BlockchainHeight uint32 // Blockchain height BlockchainVersion uint64 // Blockchain version @@ -44,6 +44,7 @@ type InfoStore struct { const ( FeatureIPv4Listen = 0 // Sender listens on IPv4 FeatureIPv6Listen = 1 // Sender listens on IPv6 + FeatureFirewall = 2 // Sender indicates a potential firewall. This informs uncontacted peers that a Traverse message might be required to establish a connection. ) // Actions between peers, sent via Announcement message. They correspond to the bit array index. diff --git a/protocol/Message Encoding Response.go b/protocol/Message Encoding Response.go index bb27620..badc180 100644 --- a/protocol/Message Encoding Response.go +++ b/protocol/Message Encoding Response.go @@ -47,6 +47,7 @@ type PeerRecord struct { 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 + Features uint8 // Feature support. Same as in Announcement/Response message. } // Hash2Peer links a hash to peers who are known to store the data and to peers who are considered close to the hash @@ -208,7 +209,8 @@ func decodePeerRecord(data []byte, count int) (hash2Peers []Hash2Peer, read int, 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+69] + peer.Features = data[index+69] & 0x7F + reason := data[index+69] >> 7 var err error if peer.PublicKey, err = btcec.ParsePubKey(peerIDcompressed, btcec.S256()); err != nil { @@ -421,7 +423,7 @@ createPacketLoop: 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 + raw[69] = peer.Features | reason<<7 // IPv4 copy(raw[33:33+4], peer.IPv4.To4())