/* File Name: Network UPnP.go Copyright: 2021 Peernet s.r.o. Author: Peter Kleissner Currently only supports IPv4 networks. */ package core import ( "math/rand" "net" "sync" "time" "github.com/PeernetOfficial/core/upnp" ) var upnpListInterfaces map[string]struct{} var upnpMutex sync.RWMutex func (nets *Networks) startUPnP() { upnpListInterfaces = make(map[string]struct{}) for _, cidr := range []string{ "10.0.0.0/8", // RFC1918 "172.16.0.0/12", // RFC1918 "192.168.0.0/16", // RFC1918 } { if _, block, err := net.ParseCIDR(cidr); err == nil { privateIPv4Blocks = append(privateIPv4Blocks, block) } } if config.PortForward > 0 { config.EnableUPnP = false } if !config.EnableUPnP { return } for _, network := range nets.networks4 { go network.upnpAuto() } } // upnpIsEligible checks if the network is eligible for UPnP func (network *Network) upnpIsEligible() bool { // IPv4 only for now. if !IsIPv4(network.address.IP) { return false } // The network interface must be known which indicates that the IP address is NOT a wildcard. // Port forwarding requires to specify a local IP. In case of listening on a wildcard that would not be known and guessing (looking at you btcd) is not a solution. if network.iface == nil || network.address.IP.IsUnspecified() { return false } // IPv4/IPv6: No link-local addresses, no loopback. Multicast would be invalid anyway. if network.address.IP.IsLinkLocalUnicast() || network.address.IP.IsLoopback() || network.address.IP.IsMulticast() { return false } // IPv4: Must be private IP. if IsIPv4(network.address.IP) && !isPrivateIP(network.address.IP) { return false } return true } var privateIPv4Blocks []*net.IPNet func isPrivateIP(ip net.IP) bool { for _, block := range privateIPv4Blocks { if block.Contains(ip) { return true } } return false } // upnpAuto runs a UPnP daemon to forward the port, refresh the forwarding and continuously monitor if the forwarding remains valid. func (network *Network) upnpAuto() { if !config.EnableUPnP || !network.upnpIsEligible() { return } nat, err := upnp.Discover(network.address.IP) if err != nil { return } network.nat = nat externalIP, err := nat.GetExternalAddress() if err != nil { return } network.ipExternal = externalIP // Only allow 1 UPnP worker at a time for registering the adapter. upnpMutex.Lock() defer upnpMutex.Unlock() // If there is already a running UPnP on the adapter, skip. if _, ok := upnpListInterfaces[network.GetAdapterName()]; ok { return } if err := network.upnpTryPortForward(); err != nil { return } upnpListInterfaces[network.GetAdapterName()] = struct{}{} go network.upnpMonitorPortForward() } // upnpMonitorPortForward monitors the port forwarding status func (network *Network) upnpMonitorPortForward() { ticker := time.NewTicker(time.Second * 10) monitorLoop: for { select { case <-ticker.C: case <-network.terminateSignal: // Remove port mapping. Note that in case the network is unavailable this is likely to fail. network.nat.DeletePortMapping("UDP", network.portExternal) network.portExternal = 0 network.ipExternal = net.IP{} break monitorLoop } // 3 tries var err error for n := 0; n < 3; n++ { if err = network.upnpValidate(); err == nil { continue monitorLoop } } // invalid :( Filters.LogError("upnpMonitorPortForward", "port forwarding invalidated for local IP %s (adapter %s) external IP %s port %d\n", network.address.String(), network.iface.Name, network.ipExternal.String(), network.portExternal) network.portExternal = 0 network.ipExternal = net.IP{} break } ticker.Stop() upnpMutex.Lock() delete(upnpListInterfaces, network.GetAdapterName()) upnpMutex.Unlock() } func (network *Network) upnpTryPortForward() (err error) { // Try forwarding the port. First to the same one listening, otherwise random. mappedExternalPort, err := network.nat.AddPortMapping("UDP", network.address.IP, uint16(network.address.Port), uint16(network.address.Port), "Peernet", 0) if err != nil { mappedExternalPort, err = network.nat.AddPortMapping("UDP", network.address.IP, uint16(network.address.Port), uint16(randInt(1024, 65535)), "Peernet", 0) } if err != nil { return err } // validate if err := network.upnpValidate(); err != nil { return err } // valid! network.portExternal = mappedExternalPort return nil } func randInt(min int, max int) int { return min + rand.Intn(max-min) } func (network *Network) upnpValidate() (err error) { // TODO: Send special message which validates the UPnP mapping return nil } // TODO: Function to check if there is an existing port forward