mirror of
https://github.com/PeernetOfficial/core.git
synced 2026-07-18 19:17:51 +01:00
Working UPnP code. Close #16
This commit is contained in:
@@ -171,7 +171,11 @@ func networkChangeMonitor() {
|
||||
func networkChangeInterfaceNew(iface net.Interface, addresses []net.Addr) {
|
||||
log.Printf("networkChangeInterfaceNew new interface '%s' (%d IPs)\n", iface.Name, len(addresses))
|
||||
|
||||
networkStart(iface, addresses)
|
||||
networks := networkStart(iface, addresses)
|
||||
|
||||
for _, network := range networks {
|
||||
go network.upnpAuto()
|
||||
}
|
||||
|
||||
go nodesDHT.RefreshBuckets(0)
|
||||
}
|
||||
@@ -214,7 +218,11 @@ func networkChangeInterfaceRemove(iface string, addresses []net.Addr) {
|
||||
func networkChangeIPNew(iface net.Interface, address net.Addr) {
|
||||
log.Printf("networkChangeIPNew new interface '%s' IP %s\n", iface.Name, address.String())
|
||||
|
||||
networkStart(iface, []net.Addr{address})
|
||||
networks := networkStart(iface, []net.Addr{address})
|
||||
|
||||
for _, network := range networks {
|
||||
go network.upnpAuto()
|
||||
}
|
||||
|
||||
go nodesDHT.RefreshBuckets(0)
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ func initNetwork() {
|
||||
}
|
||||
|
||||
// networkStart will start the listeners on all the IP addresses for the network
|
||||
func networkStart(iface net.Interface, addresses []net.Addr) {
|
||||
func networkStart(iface net.Interface, addresses []net.Addr) (networks []*Network) {
|
||||
for _, address := range addresses {
|
||||
net1 := address.(*net.IPNet)
|
||||
|
||||
@@ -133,7 +133,11 @@ func networkStart(iface net.Interface, addresses []net.Addr) {
|
||||
addListenAddress(netw.address)
|
||||
|
||||
log.Printf("Listen on network '%s' UDP %s\n", iface.Name, netw.address.String())
|
||||
|
||||
networks = append(networks, netw)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// networkPrepareListen prepares to listen on the given IP address. If port is 0, one is chosen automatically.
|
||||
@@ -147,7 +151,7 @@ func networkPrepareListen(ipA string, port int) (network *Network, err error) {
|
||||
network.terminateSignal = make(chan interface{})
|
||||
|
||||
// get the network interface that belongs to the IP
|
||||
if ipA != "0.0.0.0" && ipA != "::" {
|
||||
if !ip.IsUnspecified() { // checks for IPv4 "0.0.0.0" and IPv6 "::"
|
||||
network.iface, network.ipnet = FindInterfaceByIP(ip)
|
||||
if network.iface == nil {
|
||||
return nil, errors.New("error finding the network interface belonging to IP")
|
||||
|
||||
165
Network UPnP.go
Normal file
165
Network UPnP.go
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
File Name: Network UPnP.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Currently only supports IPv4 networks.
|
||||
TODO: Limit mapping to X hours. Auto remap upon expiration.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/upnp"
|
||||
)
|
||||
|
||||
var upnpListInterfaces map[string]struct{}
|
||||
var upnpMutex sync.RWMutex
|
||||
|
||||
func startUPnP() {
|
||||
upnpListInterfaces = make(map[string]struct{})
|
||||
|
||||
if config.PortForward > 0 {
|
||||
config.EnableUPnP = false
|
||||
}
|
||||
if !config.EnableUPnP {
|
||||
return
|
||||
}
|
||||
|
||||
for _, network := range networks4 {
|
||||
go network.upnpAuto()
|
||||
}
|
||||
}
|
||||
|
||||
// upnpIsEligible checks if the network is eligible for UPnP
|
||||
func (network *Network) upnpIsEligible() bool {
|
||||
// No link-local addresses. 169.254.*.*
|
||||
// No loopback.
|
||||
if network.address.IP.IsLinkLocalUnicast() || network.address.IP.IsLoopback() {
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Only allow 1 UPnP worker at a time.
|
||||
upnpMutex.Lock()
|
||||
defer upnpMutex.Unlock()
|
||||
|
||||
// If there is already a running UPnP on the adapter, skip.
|
||||
if _, ok := upnpListInterfaces[network.GetAdapterName()]; ok {
|
||||
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
|
||||
|
||||
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 :(
|
||||
log.Printf("upnpAuto port forwarding invalidated for local IP %s (adapter %s) external IP %s port %d", 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
|
||||
}
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/upnp"
|
||||
)
|
||||
|
||||
// Network is a connection adapter through one network interface (adapter).
|
||||
@@ -27,6 +29,8 @@ type Network struct {
|
||||
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
|
||||
|
||||
@@ -26,4 +26,5 @@ func Connect() {
|
||||
go autoPingAll()
|
||||
go networkChangeMonitor()
|
||||
go autoBucketRefresh()
|
||||
go startUPnP()
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -3,7 +3,7 @@ module github.com/PeernetOfficial/core
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/PeernetOfficial/core/dht v0.0.0-20210425014452-611dfa3b1ce9
|
||||
github.com/PeernetOfficial/core/dht v0.0.0-20210506124834-e929d6ba2f22
|
||||
github.com/btcsuite/btcd v0.21.0-beta.0.20210401013323-36a96f6a0025
|
||||
github.com/pkg/errors v0.9.1
|
||||
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc
|
||||
|
||||
4
go.sum
4
go.sum
@@ -1,5 +1,5 @@
|
||||
github.com/PeernetOfficial/core/dht v0.0.0-20210425014452-611dfa3b1ce9 h1:kcjoWZl9GZ9uSajS6t0tiKDpaatUQ64tl3OqXPc+YsY=
|
||||
github.com/PeernetOfficial/core/dht v0.0.0-20210425014452-611dfa3b1ce9/go.mod h1:sb2H21VIVTohCXVrDFo/Skl2tN8Yzba+MXSS6NgCYXw=
|
||||
github.com/PeernetOfficial/core/dht v0.0.0-20210506124834-e929d6ba2f22 h1:FHBHtQR3ShxuxkG8tgE4ER86UmbNdXam/I1irdiusqg=
|
||||
github.com/PeernetOfficial/core/dht v0.0.0-20210506124834-e929d6ba2f22/go.mod h1:sb2H21VIVTohCXVrDFo/Skl2tN8Yzba+MXSS6NgCYXw=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.21.0-beta.0.20210401013323-36a96f6a0025 h1:aoVqvZk4mLyF3WZbqEVPq+vXnwL2wekZg4P4mjYJNLs=
|
||||
|
||||
@@ -3,3 +3,18 @@
|
||||
There are 2 reference implementations which both are based on 'Taipei Torrent'. This UPnP code is a fork mostly from the btcd one with some changes forked from tendermint.
|
||||
* https://github.com/btcsuite/btcd/blob/master/upnp.go
|
||||
* https://github.com/tendermint/tendermint/tree/master/p2p/upnp
|
||||
|
||||
This library supports only IPv4 UPnP currently. The IPv6 UPnP protocol is specified here: http://upnp.org/specs/arch/UPnP-arch-AnnexAIPv6-v1.pdf
|
||||
|
||||
## Special Cases
|
||||
|
||||
FritzBox:
|
||||
* Users must manually enable port sharing for the host. Otherwise the router returns XML error code 606.
|
||||
* If the internal port is already forwarded under a different external port, error code 718 is returned.
|
||||
* If the internal port is already forwarded under the same external port, it does not return an error.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Using Wireshark to intercept the UPnP request and response can help.
|
||||
|
||||
For Windows there is a UPnP tool here http://miniupnp.free.fr/files/. The tool supports listing existing mappings which is an interesting functionality. It would make sense to implement the list function to delete or reuse any existing mappings.
|
||||
|
||||
91
upnp/UPnP.go
91
upnp/UPnP.go
@@ -17,31 +17,31 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// NAT is an interface representing a NAT traversal options for example UPNP or
|
||||
// NAT-PMP. It provides methods to query and manipulate this traversal to allow
|
||||
// access to services.
|
||||
// NAT is an interface representing a NAT traversal options for example UPNP or NAT-PMP.
|
||||
// It provides methods to query and manipulate this traversal to allow access to services.
|
||||
type NAT interface {
|
||||
// Get the external address from outside the NAT.
|
||||
GetExternalAddress() (addr net.IP, err error)
|
||||
// Add a port mapping for protocol ("udp" or "tcp") from external port to internal port with description lasting for timeout.
|
||||
AddPortMapping(protocol string, internalIP net.IP, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error)
|
||||
AddPortMapping(protocol string, internalIP net.IP, internalPort, externalPort uint16, description string, timeout int) (mappedExternalPort uint16, err error)
|
||||
// Remove a previously added port mapping from external port to internal port.
|
||||
DeletePortMapping(protocol string, externalPort, internalPort int) (err error)
|
||||
DeletePortMapping(protocol string, externalPort uint16) (err error)
|
||||
}
|
||||
|
||||
type upnpNAT struct {
|
||||
serviceURL string
|
||||
urnDomain string
|
||||
localIP net.IP
|
||||
}
|
||||
|
||||
// Discover searches the local network for a UPnP router returning a NAT
|
||||
// for the network if so, nil if not.
|
||||
func Discover() (nat NAT, err error) {
|
||||
// Discover searches the local network for a UPnP router returning a NAT for the network if so, nil if not.
|
||||
// Socket must be an active local socket.
|
||||
func Discover(localIP net.IP) (nat NAT, err error) {
|
||||
ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn, err := net.ListenPacket("udp4", ":0")
|
||||
conn, err := net.ListenPacket("udp4", net.JoinHostPort(localIP.String(), "0")) // use a random port
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -97,11 +97,11 @@ func Discover() (nat NAT, err error) {
|
||||
}
|
||||
locURL := strings.TrimSpace(loc[0:endIndex])
|
||||
var serviceURL, urnDomain string
|
||||
serviceURL, urnDomain, err = getServiceURL(locURL)
|
||||
serviceURL, urnDomain, err = getServiceURL(localIP, locURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nat = &upnpNAT{serviceURL: serviceURL, urnDomain: urnDomain}
|
||||
nat = &upnpNAT{serviceURL: serviceURL, urnDomain: urnDomain, localIP: localIP}
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -185,8 +185,25 @@ func getChildService(d *device, serviceType string) *service {
|
||||
|
||||
// getServiceURL parses the xml description at the given root url to find the
|
||||
// url for the WANIPConnection service to be used for port forwarding.
|
||||
func getServiceURL(rootURL string) (url, urnDomain string, err error) {
|
||||
r, err := http.Get(rootURL)
|
||||
func getServiceURL(localIP net.IP, rootURL string) (url, urnDomain string, err error) {
|
||||
|
||||
webclient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: nil,
|
||||
DialContext: (&net.Dialer{
|
||||
LocalAddr: &net.TCPAddr{
|
||||
IP: localIP,
|
||||
},
|
||||
Timeout: 3 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
TLSHandshakeTimeout: 3 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
},
|
||||
Timeout: 3 * time.Second,
|
||||
}
|
||||
|
||||
r, err := webclient.Get(rootURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -259,7 +276,7 @@ type soapEnvelope struct {
|
||||
// soapRequests performs a soap request with the given parameters and returns
|
||||
// the xml replied stripped of the soap headers. in the case that the request is
|
||||
// unsuccessful the an error is returned.
|
||||
func soapRequest(url, function, message, domain string) (replyXML []byte, err error) {
|
||||
func (n *upnpNAT) soapRequest(url, function, message, domain string) (replyXML []byte, err error) {
|
||||
fullMessage := "<?xml version=\"1.0\" ?>" +
|
||||
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
|
||||
"<s:Body>" + message + "</s:Body></s:Envelope>"
|
||||
@@ -275,7 +292,23 @@ func soapRequest(url, function, message, domain string) (replyXML []byte, err er
|
||||
req.Header.Set("Cache-Control", "no-cache")
|
||||
req.Header.Set("Pragma", "no-cache")
|
||||
|
||||
r, err := http.DefaultClient.Do(req)
|
||||
webclient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: nil,
|
||||
DialContext: (&net.Dialer{
|
||||
LocalAddr: &net.TCPAddr{
|
||||
IP: n.localIP,
|
||||
},
|
||||
Timeout: 3 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
TLSHandshakeTimeout: 3 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
},
|
||||
Timeout: 3 * time.Second,
|
||||
}
|
||||
|
||||
r, err := webclient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -307,7 +340,7 @@ type getExternalIPAddressResponse struct {
|
||||
// from the UPnP router.
|
||||
func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {
|
||||
message := "<u:GetExternalIPAddress xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n</u:GetExternalIPAddress>"
|
||||
response, err := soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain)
|
||||
response, err := n.soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -325,22 +358,25 @@ func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// AddPortMapping implements the NAT interface by setting up a port forwarding
|
||||
// from the UPnP router to the local machine with the given ports and protocol.
|
||||
func (n *upnpNAT) AddPortMapping(protocol string, internalIP net.IP, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) {
|
||||
// AddPortMapping forwards a port at the UPnP router to the specified IP address and port. Lease duration is in seconds.
|
||||
// FritzBox routers: Forwarding an already forwarded port results in no error. If the internal port is already forwarded under a different external port, error code 718 is returned in XML.
|
||||
func (n *upnpNAT) AddPortMapping(protocol string, internalIP net.IP, internalPort, externalPort uint16, description string, leaseDuration int) (mappedExternalPort uint16, err error) {
|
||||
// A single concatenation would break ARM compilation.
|
||||
message := "<u:AddPortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
|
||||
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort)
|
||||
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(int(externalPort))
|
||||
message += "</NewExternalPort><NewProtocol>" + strings.ToUpper(protocol) + "</NewProtocol>"
|
||||
message += "<NewInternalPort>" + strconv.Itoa(internalPort) + "</NewInternalPort>" +
|
||||
message += "<NewInternalPort>" + strconv.Itoa(int(internalPort)) + "</NewInternalPort>" +
|
||||
"<NewInternalClient>" + internalIP.String() + "</NewInternalClient>" +
|
||||
"<NewEnabled>1</NewEnabled><NewPortMappingDescription>"
|
||||
message += description +
|
||||
"</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) +
|
||||
"</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(leaseDuration) +
|
||||
"</NewLeaseDuration></u:AddPortMapping>"
|
||||
|
||||
response, err := soapRequest(n.serviceURL, "AddPortMapping", message, n.urnDomain)
|
||||
response, err := n.soapRequest(n.serviceURL, "AddPortMapping", message, n.urnDomain)
|
||||
if err != nil {
|
||||
// If UPnP is not allowed for the host (with FritzBox routers the user must manually enable it), the router returns "<errorCode>606</errorCode>"
|
||||
// in the XML response with HTTP code 500 Internal Server Error.
|
||||
// If the internal port is already forwarded under a different external port, error code 718 is returned in XML.
|
||||
return
|
||||
}
|
||||
|
||||
@@ -352,16 +388,15 @@ func (n *upnpNAT) AddPortMapping(protocol string, internalIP net.IP, externalPor
|
||||
return mappedExternalPort, err
|
||||
}
|
||||
|
||||
// DeletePortMapping implements the NAT interface by removing up a port forwarding
|
||||
// from the UPnP router to the local machine with the given ports and.
|
||||
func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) {
|
||||
// DeletePortMapping deletes a port mapping.
|
||||
func (n *upnpNAT) DeletePortMapping(protocol string, externalPort uint16) (err error) {
|
||||
|
||||
message := "<u:DeletePortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
|
||||
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) +
|
||||
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(int(externalPort)) +
|
||||
"</NewExternalPort><NewProtocol>" + strings.ToUpper(protocol) + "</NewProtocol>" +
|
||||
"</u:DeletePortMapping>"
|
||||
|
||||
response, err := soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain)
|
||||
response, err := n.soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
27
upnp/UPnP_test.go
Normal file
27
upnp/UPnP_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Small code for manual tests/development.
|
||||
|
||||
package upnp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUPnP(t *testing.T) {
|
||||
localIP := net.ParseIP("0.0.0.0")
|
||||
|
||||
nat, err := Discover(localIP)
|
||||
if err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
addr, err := nat.GetExternalAddress()
|
||||
if err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", addr.String())
|
||||
}
|
||||
Reference in New Issue
Block a user