New flag Firewall in the Features field to inform uncontacted peers that a Traverse message might be required to establish a connection. Close #61

This flag can be activated via the config setting LocalFirewall.
The Features field exists in Announcement/Response messages and was now added to peer records.
This commit is contained in:
Kleissner
2021-11-20 04:28:03 +01:00
parent 446c62bcd9
commit f7e8f1821b
11 changed files with 54 additions and 20 deletions

View File

@@ -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<<protocol.FeatureFirewall) > 0)
}
}
}

View File

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

View File

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

View File

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

View File

@@ -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<<protocol.FeatureFirewall) > 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<<protocol.FeatureFirewall) > 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 {

View File

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

View File

@@ -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<<protocol.FeatureFirewall) > 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<<protocol.FeatureFirewall) > 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
}

View File

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

View File

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

View File

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

View File

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