/* File Name: Network.go Copyright: 2021 Peernet s.r.o. Author: Peter Kleissner */ package core import ( "errors" "net" "sync" "sync/atomic" "time" "github.com/PeernetOfficial/core/protocol" "github.com/PeernetOfficial/core/upnp" ) // Network is a connection adapter through one network interface (adapter). // Note that for each IP on the same adapter separate network entries are created. type Network struct { iface *net.Interface // Network interface belonging to the IP. May not be set. ipnet *net.IPNet // IP network the listening address belongs to. May not be set. address *net.UDPAddr // IP:Port where the server listens socket *net.UDPConn // active socket for send/receive multicastIP net.IP // Multicast IP, IPv6 only. multicastSocket net.PacketConn // Multicast socket, IPv6 only. broadcastSocket net.PacketConn // Broadcast socket, IPv4 only. broadcastIPv4 []net.IP // Broadcast IPs, IPv4 only. portExternal uint16 // External port. 0 if not known. ipExternal net.IP // External IP of the network. Usually not known. nat upnp.NAT // UPnP: NAT information 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 networkGroup *Networks // Pointer to the pool of networks that this is part of } // Default ports to use. This may be randomized in the future to prevent fingerprinting (and subsequent blocking) by corporate and ISP firewalls. const defaultPort = 'p' // 112 // AutoAssignPort assigns a port for the given IP. Use port 0 for zero configuration. func (network *Network) AutoAssignPort(ip net.IP, port int) (err error) { networkA := "udp6" if IsIPv4(ip) { networkA = "udp4" } // A common error return is "bind: The requested address is not valid in its context.". // This error was observed when the network interface might not be ready after boot but also when listening on a link-local IPv4 (169.254.) for an inactive adapter. // Previously the algorithm retried up to n times, but this would unnecessarily delay startup in case the IP is actual unlistenable. connectPortTry := func(port int) (address *net.UDPAddr, socket *net.UDPConn, err error) { address = &net.UDPAddr{IP: ip, Port: port} if socket, err = net.ListenUDP(networkA, address); err != nil { return nil, nil, err } if port == 0 { localAddr := socket.LocalAddr() if localAddr == nil { return nil, nil, errors.New("invalid port assignment") } address.Port = localAddr.(*net.UDPAddr).Port } return address, socket, nil } if port != 0 { network.address, network.socket, err = connectPortTry(port) return err } // try default main port, then random if network.address, network.socket, err = connectPortTry(defaultPort); err == nil { return nil } if network.address, network.socket, err = connectPortTry(0); err == nil { return nil } return err } // send sends a message func (network *Network) send(IP net.IP, port int, raw []byte) (err error) { _, err = network.socket.WriteTo(raw, &net.UDPAddr{IP: IP, Port: port}) return err } // Max packet size is 64 KB. const maxPacketSize = 65536 // Listen starts listening for incoming packets on the given UDP connection func (network *Network) Listen() { if !network.address.IP.IsLinkLocalUnicast() { if IsIPv4(network.address.IP) { atomic.AddInt64(&network.networkGroup.countListen4, 1) } else { atomic.AddInt64(&network.networkGroup.countListen6, 1) } } for !network.isTerminated { // Buffer: Must be created for each packet as it is passed as pointer. // If the buffer is too small, ReadFromUDP only reads until its length and returns this error: "wsarecvfrom: A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself." buffer := make([]byte, maxPacketSize) length, sender, err := network.socket.ReadFromUDP(buffer) if err != nil { // Exit on closed socket. Error will be "use of closed network connection". if network.isTerminated { return } Filters.LogError("Listen", "receiving UDP message: %v\n", err) // Only log for debug purposes. time.Sleep(time.Millisecond * 50) // In case of endless errors, prevent ddos of CPU. continue } if length < protocol.PacketLengthMin { // Discard packets that do not meet the minimum length. continue } // send the packet to a channel which is processed by multiple workers. network.networkGroup.rawPacketsIncoming <- networkWire{network: network, sender: sender, raw: buffer[:length], receiverPublicKey: peerPublicKey, unicast: true} } } // packetWorker handles incoming packets. func (nets *Networks) packetWorker() { for packet := range nets.rawPacketsIncoming { decoded, senderPublicKey, err := protocol.PacketDecrypt(packet.raw, packet.receiverPublicKey) if err != nil { //Filters.LogError("packetWorker", "decrypting packet from '%s': %s\n", packet.sender.String(), err.Error()) // Only log for debug purposes. continue } // immediately discard message if sender = self if senderPublicKey.IsEqual(peerPublicKey) { continue } // supported protocol version if decoded.Protocol != 0 { continue } connection := &Connection{Network: packet.network, Address: packet.sender, Status: ConnectionActive} Filters.PacketIn(decoded, senderPublicKey, connection) // 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 raw := &protocol.MessageRaw{SenderPublicKey: senderPublicKey, PacketRaw: *decoded} switch decoded.Command { case protocol.CommandAnnouncement: // Announce if announce, _ := protocol.DecodeAnnouncement(raw); announce != nil { // 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 } peer.Features = announce.Features isBlockchainUpdate := peer.BlockchainHeight != announce.BlockchainHeight || peer.BlockchainVersion != announce.BlockchainVersion peer.BlockchainHeight = announce.BlockchainHeight peer.BlockchainVersion = announce.BlockchainVersion peer.blockchainLastRefresh = time.Now() Filters.MessageIn(peer, raw, announce) peer.cmdAnouncement(announce, connection) if isBlockchainUpdate { peer.remoteBlockchainUpdate() } } case protocol.CommandResponse: // Response if response, _ := protocol.DecodeResponse(raw); response != nil { // Validate sequence number which prevents unsolicited responses. isLast := response.IsLast() sequenceInfo, valid, rtt := nets.Sequences.ValidateSequence(raw.SenderPublicKey, raw.Sequence, isLast, !isLast) if !valid { //Filters.LogError("packetWorker", "message with invalid sequence %d command %d from %s\n", raw.Sequence, raw.Command, raw.connection.Address.String()) // Only log for debug purposes. continue } else if rtt > 0 { connection.RoundTripTime = rtt } raw.SequenceInfo = sequenceInfo // 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 } peer.Features = response.Features isBlockchainUpdate := peer.BlockchainHeight != response.BlockchainHeight || peer.BlockchainVersion != response.BlockchainVersion peer.BlockchainHeight = response.BlockchainHeight peer.BlockchainVersion = response.BlockchainVersion peer.blockchainLastRefresh = time.Now() Filters.MessageIn(peer, raw, response) peer.cmdResponse(response, connection) if isBlockchainUpdate { peer.remoteBlockchainUpdate() } } case protocol.CommandLocalDiscovery: // Local discovery, sent via IPv4 broadcast and IPv6 multicast if announce, _ := protocol.DecodeAnnouncement(raw); announce != nil { if len(announce.UserAgent) > 0 { peer.UserAgent = announce.UserAgent } peer.Features = announce.Features isBlockchainUpdate := peer.BlockchainHeight != announce.BlockchainHeight || peer.BlockchainVersion != announce.BlockchainVersion peer.BlockchainHeight = announce.BlockchainHeight peer.BlockchainVersion = announce.BlockchainVersion peer.blockchainLastRefresh = time.Now() Filters.MessageIn(peer, raw, announce) peer.cmdLocalDiscovery(announce, connection) if isBlockchainUpdate { peer.remoteBlockchainUpdate() } } case protocol.CommandPing: // Ping Filters.MessageIn(peer, raw, nil) peer.cmdPing(raw, connection) case protocol.CommandPong: // Ping // Validate sequence number which prevents unsolicited responses. sequenceInfo, valid, rtt := nets.Sequences.ValidateSequence(raw.SenderPublicKey, raw.Sequence, true, false) if !valid { //Filters.LogError("packetWorker", "message with invalid sequence %d command %d from %s\n", raw.Sequence, raw.Command, raw.connection.Address.String()) // Only log for debug purposes. continue } else if rtt > 0 { connection.RoundTripTime = rtt } raw.SequenceInfo = sequenceInfo Filters.MessageIn(peer, raw, nil) peer.cmdPong(raw, connection) case protocol.CommandChat: // Chat [debug] Filters.MessageIn(peer, raw, nil) peer.cmdChat(raw, connection) case protocol.CommandTraverse: if traverse, _ := protocol.DecodeTraverse(raw); traverse != nil { Filters.MessageIn(peer, raw, traverse) if traverse.TargetPeer.IsEqual(peerPublicKey) && traverse.AuthorizedRelayPeer.IsEqual(peer.PublicKey) { peer.cmdTraverseReceive(traverse) } else if traverse.AuthorizedRelayPeer.IsEqual(peerPublicKey) { peer.cmdTraverseForward(traverse) } } case protocol.CommandTransfer: if msg, _ := protocol.DecodeTransfer(raw); msg != nil { // Validate sequence number which prevents unsolicited responses. isLast := msg.IsLast() sequenceInfo, valid, rtt := nets.Sequences.ValidateSequenceBi(raw.SenderPublicKey, raw.Sequence, isLast) if msg.Control != protocol.TransferControlRequestStart && !valid { //Filters.LogError("packetWorker", "message with invalid sequence %d command %d from %s\n", raw.Sequence, raw.Command, raw.connection.Address.String()) // Only log for debug purposes. continue } else if rtt > 0 { connection.RoundTripTime = rtt } raw.SequenceInfo = sequenceInfo peer.cmdTransfer(msg, connection) } case protocol.CommandGetBlock: if msg, _ := protocol.DecodeGetBlock(raw); msg != nil { // Validate sequence number which prevents unsolicited responses. isLast := msg.IsLast() sequenceInfo, valid, rtt := nets.Sequences.ValidateSequenceBi(raw.SenderPublicKey, raw.Sequence, isLast) if msg.Control != protocol.GetBlockControlRequestStart && !valid { //Filters.LogError("packetWorker", "message with invalid sequence %d command %d from %s\n", raw.Sequence, raw.Command, raw.connection.Address.String()) // Only log for debug purposes. continue } else if rtt > 0 { connection.RoundTripTime = rtt } raw.SequenceInfo = sequenceInfo peer.cmdGetBlock(msg, connection) } default: // Unknown command Filters.MessageIn(peer, raw, nil) } } } // GetNetworks returns the list of connected networks func GetNetworks(networkType int) (networksConnected []*Network) { switch networkType { case 4: return networks.networks4 case 6: return networks.networks6 } return nil } // GetListen returns connectivity information func (network *Network) GetListen() (listen *net.UDPAddr, multicastIPv6 net.IP, broadcastIPv4 []net.IP, ipExternal net.IP, portExternal uint16) { return network.address, network.multicastIP, network.broadcastIPv4, network.ipExternal, network.portExternal } // GetAdapterName returns the adapter name, if available func (network *Network) GetAdapterName() string { if network.iface != nil { return network.iface.Name } return "[unknown adapter]" } // Terminate sends the termination signal to all workers. It is safe to call Terminate multiple times. func (network *Network) Terminate() { network.Lock() defer network.Unlock() if network.isTerminated { return } if !network.address.IP.IsLinkLocalUnicast() { if IsIPv4(network.address.IP) { atomic.AddInt64(&network.networkGroup.countListen4, -1) } else { atomic.AddInt64(&network.networkGroup.countListen6, -1) } } // set the termination signal network.isTerminated = true close(network.terminateSignal) // safety guaranteed via lock network.socket.Close() // Will stop the listener from blocking on network.socket.ReadFromUDP network.networkGroup.ipListen.Remove(network.address) } // SelfReportedPorts returns the internal and external ports as self-reported by the peer to others. func (network *Network) SelfReportedPorts() (portI, portE uint16) { // 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(network.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 = network.portExternal if config.PortForward > 0 { portE = config.PortForward } return portI, portE } // FeatureSupport returns supported features by this peer func FeatureSupport() (feature byte) { if networks.countListen4 > 0 { feature |= 1 << protocol.FeatureIPv4Listen } if networks.countListen6 > 0 { feature |= 1 << protocol.FeatureIPv6Listen } if networks.localFirewall { feature |= 1 << protocol.FeatureFirewall } return feature }