diff --git a/Connection.go b/Connection.go index 851cbe5..62019c2 100644 --- a/Connection.go +++ b/Connection.go @@ -7,6 +7,7 @@ Author: Peter Kleissner package core import ( + "encoding/binary" "errors" "net" "sync/atomic" @@ -243,6 +244,28 @@ func (peer *PeerInfo) GetRTT() (rtt time.Duration) { // ---- sending code ---- +// setAnnouncementPorts sets the fields Internal Port and External Port according to the connection details. +// This is important for the remote peer to make smart decisions whether this peer is behind a NAT/firewall and supports port forwarding/UPnP. +func setAnnouncementPorts(packet *PacketRaw, n *Network) { + if packet.Command != CommandAnnouncement && packet.Command != CommandResponse { // only for Announcement and Response messages + return + } + + // The internal port is set to where the network listens on. + // Datacenter: This should usually be the same as the outgoing port. + // NAT: The internal port will be different than the outgoing one. + portI := uint16(n.address.Port) + + // External port: This is usually unknown, except in these 2 cases: + // UPnP: The port is forwarded automatically. + // Manual override in config: The user can specify a (global) incoming port that must be open on all listening IPs. + // This external port will be then passed onto other peers who will use it to connect. + portE := n.externalPort + + binary.LittleEndian.PutUint16(packet.Payload[15:17], portI) + binary.LittleEndian.PutUint16(packet.Payload[17:19], portE) +} + // send sends a raw packet to the peer. Only uses active connections. func (peer *PeerInfo) send(packet *PacketRaw) (err error) { if len(peer.connectionActive) == 0 { @@ -251,11 +274,7 @@ func (peer *PeerInfo) send(packet *PacketRaw) (err error) { packet.Protocol = ProtocolVersion - raw, err := PacketEncrypt(peerPrivateKey, peer.PublicKey, packet) - if err != nil { - return err - } - + // always count as one sent packet even if sent via broadcast atomic.AddUint64(&peer.StatsPacketSent, 1) // Send out the wire. Use connectionLatest if available. @@ -263,6 +282,13 @@ func (peer *PeerInfo) send(packet *PacketRaw) (err error) { // Windows: This works great in case the adapter gets disabled, however, does not detect if the network cable is unplugged. c := peer.connectionLatest if c != nil { + setAnnouncementPorts(packet, c.Network) + + raw, err := PacketEncrypt(peerPrivateKey, peer.PublicKey, packet) + if err != nil { + return err + } + c.LastPacketOut = time.Now() if err = c.Network.send(c.Address.IP, c.Address.Port, raw); err == nil { @@ -281,6 +307,13 @@ func (peer *PeerInfo) send(packet *PacketRaw) (err error) { // The receiver is responsible for incoming deduplication of packets. activeConnections := peer.GetConnections(true) for _, c := range activeConnections { + setAnnouncementPorts(packet, c.Network) + + raw, err := PacketEncrypt(peerPrivateKey, peer.PublicKey, packet) + if err != nil { + return err + } + c.LastPacketOut = time.Now() c.Network.send(c.Address.IP, c.Address.Port, raw) } @@ -312,39 +345,27 @@ func sendAllNetworks(receiverPublicKey *btcec.PublicKey, packet *PacketRaw, remo networksMutex.RLock() defer networksMutex.RUnlock() + networksTarget := networks4 if IsIPv6(remote.IP.To16()) { - for _, network := range networks6 { - // Do not mix link-local unicast targets with non link-local networks (only when iface is known, i.e. not catch all local) - if network.iface != nil && remote.IP.IsLinkLocalUnicast() != network.address.IP.IsLinkLocalUnicast() { - continue - } + networksTarget = networks6 + } - packet.Sequence = msgArbitrarySequence(receiverPublicKey, sequenceData).sequence - if raw, err = PacketEncrypt(peerPrivateKey, receiverPublicKey, packet); err != nil { - return err - } - - err = network.send(remote.IP, remote.Port, raw) - if err == nil { - successCount++ - } + for _, network := range networksTarget { + // Do not mix link-local unicast targets with non link-local networks (only when iface is known, i.e. not catch all local) + if network.iface != nil && remote.IP.IsLinkLocalUnicast() != network.address.IP.IsLinkLocalUnicast() { + continue } - } else { - for _, network := range networks4 { - // Do not mix link-local unicast targets with non link-local networks (only when iface is known, i.e. not catch all local) - if network.iface != nil && remote.IP.IsLinkLocalUnicast() != network.address.IP.IsLinkLocalUnicast() { - continue - } - packet.Sequence = msgArbitrarySequence(receiverPublicKey, sequenceData).sequence - if raw, err = PacketEncrypt(peerPrivateKey, receiverPublicKey, packet); err != nil { - return err - } + setAnnouncementPorts(packet, network) - err = network.send(remote.IP, remote.Port, raw) - if err == nil { - successCount++ - } + packet.Sequence = msgArbitrarySequence(receiverPublicKey, sequenceData).sequence + if raw, err = PacketEncrypt(peerPrivateKey, receiverPublicKey, packet); err != nil { + return err + } + + err = network.send(remote.IP, remote.Port, raw) + if err == nil { + successCount++ } } diff --git a/Message Encoding.go b/Message Encoding.go index 568c7ac..9177512 100644 --- a/Message Encoding.go +++ b/Message Encoding.go @@ -33,6 +33,7 @@ const ( CommandPing = 2 // Keep-alive message (no payload). CommandPong = 3 // Response to ping (no payload). CommandLocalDiscovery = 4 // Local discovery + CommandTraverse = 5 // Help establish a connection between 2 remote peers // Blockchain CommandGet = 6 // Request blocks for specified peer. @@ -78,6 +79,8 @@ type MessageAnnouncement struct { Actions uint8 // Action bit array. See ActionX BlockchainHeight uint32 // Blockchain height BlockchainVersion uint64 // Blockchain version + PortInternal uint16 // Internal port. Can be used to detect NATs. + PortExternal uint16 // External port if known. 0 if not. Can be used for UPnP support. UserAgent string // User Agent. Format "Software/Version". Required in the initial announcement/bootstrap. UTF-8 encoded. Max length is 255 bytes. FindPeerKeys []KeyHash // FIND_PEER data FindDataKeys []KeyHash // FIND_VALUE data @@ -130,6 +133,8 @@ type MessageResponse struct { Actions uint8 // Action bit array. See ActionX BlockchainHeight uint32 // Blockchain height BlockchainVersion uint64 // Blockchain version + PortInternal uint16 // Internal port. Can be used to detect NATs. + PortExternal uint16 // External port if known. 0 if not. Can be used for UPnP support. UserAgent string // User Agent. Format "Software/Version". Required in the initial announcement/bootstrap. UTF-8 encoded. Max length is 255 bytes. Hash2Peers []Hash2Peer // List of peers that know the requested hashes or at least are close to it FilesEmbed []EmbeddedFileData // Files that were embedded in the response @@ -139,7 +144,7 @@ type MessageResponse struct { // ---- message decoding ---- // Minimum length of Announcement payload header without User Agent -const announcementPayloadHeaderSize = 16 +const announcementPayloadHeaderSize = 20 // msgDecodeAnnouncement decodes the incoming announcement message. Returns nil if invalid. func msgDecodeAnnouncement(msg *MessageRaw) (result *MessageAnnouncement, err error) { @@ -156,8 +161,10 @@ func msgDecodeAnnouncement(msg *MessageRaw) (result *MessageAnnouncement, err er result.Actions = msg.Payload[2] result.BlockchainHeight = binary.LittleEndian.Uint32(msg.Payload[3:7]) result.BlockchainVersion = binary.LittleEndian.Uint64(msg.Payload[7:15]) + result.PortInternal = binary.LittleEndian.Uint16(msg.Payload[15:17]) + result.PortExternal = binary.LittleEndian.Uint16(msg.Payload[17:19]) - userAgentLength := int(msg.Payload[15]) + userAgentLength := int(msg.Payload[19]) if userAgentLength > 0 { if userAgentLength > len(msg.Payload)-announcementPayloadHeaderSize { return nil, errors.New("announcement: user agent overflow") @@ -274,8 +281,10 @@ func msgDecodeResponse(msg *MessageRaw) (result *MessageResponse, err error) { result.Actions = msg.Payload[2] result.BlockchainHeight = binary.LittleEndian.Uint32(msg.Payload[3:7]) result.BlockchainVersion = binary.LittleEndian.Uint64(msg.Payload[7:15]) + result.PortInternal = binary.LittleEndian.Uint16(msg.Payload[15:17]) + result.PortExternal = binary.LittleEndian.Uint16(msg.Payload[17:19]) - userAgentLength := int(msg.Payload[15]) + userAgentLength := int(msg.Payload[19]) read := announcementPayloadHeaderSize if userAgentLength > 0 { diff --git a/Network.go b/Network.go index 6494b9d..42116f9 100644 --- a/Network.go +++ b/Network.go @@ -26,6 +26,7 @@ type Network struct { multicastSocket net.PacketConn // Multicast socket, IPv6 only. broadcastSocket net.PacketConn // Broadcast socket, IPv4 only. broadcastIPv4 []net.IP // Broadcast IPs, IPv4 only. + externalPort uint16 // External port. 0 if not known. isTerminated bool // If true, the network was signaled for termination terminateSignal chan interface{} // gets closed on termination signal, can be used in select via "case _ = <- network.terminateSignal:" sync.RWMutex // for sychronized closing @@ -218,8 +219,8 @@ func GetNetworks(networkType int) (networks []*Network) { } // GetListen returns connectivity information -func (network *Network) GetListen() (listen *net.UDPAddr, multicastIPv6 net.IP, broadcastIPv4 []net.IP) { - return network.address, network.multicastIP, network.broadcastIPv4 +func (network *Network) GetListen() (listen *net.UDPAddr, multicastIPv6 net.IP, broadcastIPv4 []net.IP, externalPort uint16) { + return network.address, network.multicastIP, network.broadcastIPv4, network.externalPort } // GetAdapterName returns the adapter name, if available