From d0a2e655c9599f462bb20bd49bc69b8e1e330a21 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 11 Feb 2015 17:19:31 +0100 Subject: [PATCH] cmd/ethereum, cmd/mist, eth, p2p: use package p2p/nat This deletes the old NAT implementation. --- cmd/ethereum/flags.go | 11 +- cmd/ethereum/main.go | 27 ++-- cmd/mist/flags.go | 10 +- cmd/mist/main.go | 25 ++-- eth/backend.go | 14 +- p2p/nat.go | 23 --- p2p/natpmp.go | 55 ------- p2p/natupnp.go | 341 ------------------------------------------ p2p/server.go | 70 ++------- 9 files changed, 54 insertions(+), 522 deletions(-) delete mode 100644 p2p/nat.go delete mode 100644 p2p/natpmp.go delete mode 100644 p2p/natupnp.go diff --git a/cmd/ethereum/flags.go b/cmd/ethereum/flags.go index 594acdf7f2..99e094b476 100644 --- a/cmd/ethereum/flags.go +++ b/cmd/ethereum/flags.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/vm" ) @@ -44,8 +45,6 @@ var ( StartWebSockets bool RpcPort int WsPort int - NatType string - PMPGateway string OutboundPort string ShowGenesis bool AddPeer string @@ -53,6 +52,7 @@ var ( GenAddr bool BootNodes string NodeKey *ecdsa.PrivateKey + NAT nat.Interface SecretFile string ExportDir string NonInteractive bool @@ -127,18 +127,21 @@ func Init() { var ( nodeKeyFile = flag.String("nodekey", "", "network private key file") nodeKeyHex = flag.String("nodekeyhex", "", "network private key (for testing)") + natstr = flag.String("nat", "any", "port mapping mechanism (any|none|upnp|pmp|extip:)") ) flag.BoolVar(&Dial, "dial", true, "dial out connections (default on)") flag.BoolVar(&SHH, "shh", true, "run whisper protocol (default on)") flag.StringVar(&OutboundPort, "port", "30303", "listening port") - flag.StringVar(&NatType, "nat", "", "NAT support (UPNP|PMP) (none)") - flag.StringVar(&PMPGateway, "pmp", "", "Gateway IP for NAT-PMP") + flag.StringVar(&BootNodes, "bootnodes", "", "space-separated node URLs for discovery bootstrap") flag.IntVar(&MaxPeer, "maxpeer", 30, "maximum desired peers") flag.Parse() var err error + if NAT, err = nat.Parse(*natstr); err != nil { + log.Fatalf("-nat: %v", err) + } switch { case *nodeKeyFile != "" && *nodeKeyHex != "": log.Fatal("Options -nodekey and -nodekeyhex are mutually exclusive") diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 3f616c0948..d2b3418cdf 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -62,20 +62,19 @@ func main() { utils.InitConfig(VmType, ConfigFile, Datadir, "ETH") ethereum, err := eth.New(ð.Config{ - Name: p2p.MakeName(ClientIdentifier, Version), - KeyStore: KeyStore, - DataDir: Datadir, - LogFile: LogFile, - LogLevel: LogLevel, - MaxPeers: MaxPeer, - Port: OutboundPort, - NATType: PMPGateway, - PMPGateway: PMPGateway, - KeyRing: KeyRing, - Shh: SHH, - Dial: Dial, - BootNodes: BootNodes, - NodeKey: NodeKey, + Name: p2p.MakeName(ClientIdentifier, Version), + KeyStore: KeyStore, + DataDir: Datadir, + LogFile: LogFile, + LogLevel: LogLevel, + MaxPeers: MaxPeer, + Port: OutboundPort, + NAT: NAT, + KeyRing: KeyRing, + Shh: SHH, + Dial: Dial, + BootNodes: BootNodes, + NodeKey: NodeKey, }) if err != nil { diff --git a/cmd/mist/flags.go b/cmd/mist/flags.go index dd72b5043f..eb280f71bd 100644 --- a/cmd/mist/flags.go +++ b/cmd/mist/flags.go @@ -34,6 +34,7 @@ import ( "bitbucket.org/kardianos/osext" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/vm" ) @@ -41,12 +42,10 @@ var ( Identifier string KeyRing string KeyStore string - PMPGateway string StartRpc bool StartWebSockets bool RpcPort int WsPort int - NatType string OutboundPort string ShowGenesis bool AddPeer string @@ -54,6 +53,7 @@ var ( GenAddr bool BootNodes string NodeKey *ecdsa.PrivateKey + NAT nat.Interface SecretFile string ExportDir string NonInteractive bool @@ -117,7 +117,6 @@ func Init() { flag.BoolVar(&StartWebSockets, "ws", false, "start websocket server") flag.BoolVar(&NonInteractive, "y", false, "non-interactive mode (say yes to confirmations)") flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") - flag.StringVar(&NatType, "nat", "", "NAT support (UPNP|PMP) (none)") flag.StringVar(&SecretFile, "import", "", "imports the file given (hex or mnemonic formats)") flag.StringVar(&ExportDir, "export", "", "exports the session keyring to files in the directory given") flag.StringVar(&LogFile, "logfile", "", "log file (defaults to standard output)") @@ -132,15 +131,18 @@ func Init() { var ( nodeKeyFile = flag.String("nodekey", "", "network private key file") nodeKeyHex = flag.String("nodekeyhex", "", "network private key (for testing)") + natstr = flag.String("nat", "any", "port mapping mechanism (any|none|upnp|pmp|extip:)") ) flag.StringVar(&OutboundPort, "port", "30303", "listening port") - flag.StringVar(&PMPGateway, "pmp", "", "Gateway IP for NAT-PMP") flag.StringVar(&BootNodes, "bootnodes", "", "space-separated node URLs for discovery bootstrap") flag.IntVar(&MaxPeer, "maxpeer", 30, "maximum desired peers") flag.Parse() var err error + if NAT, err = nat.Parse(*natstr); err != nil { + log.Fatalf("-nat: %v", err) + } switch { case *nodeKeyFile != "" && *nodeKeyHex != "": log.Fatal("Options -nodekey and -nodekeyhex are mutually exclusive") diff --git a/cmd/mist/main.go b/cmd/mist/main.go index 4d6bac9b7d..52b5e4e944 100644 --- a/cmd/mist/main.go +++ b/cmd/mist/main.go @@ -52,19 +52,18 @@ func run() error { config := utils.InitConfig(VmType, ConfigFile, Datadir, "ETH") ethereum, err := eth.New(ð.Config{ - Name: p2p.MakeName(ClientIdentifier, Version), - KeyStore: KeyStore, - DataDir: Datadir, - LogFile: LogFile, - LogLevel: LogLevel, - MaxPeers: MaxPeer, - Port: OutboundPort, - NATType: NatType, - PMPGateway: PMPGateway, - BootNodes: BootNodes, - NodeKey: NodeKey, - KeyRing: KeyRing, - Dial: true, + Name: p2p.MakeName(ClientIdentifier, Version), + KeyStore: KeyStore, + DataDir: Datadir, + LogFile: LogFile, + LogLevel: LogLevel, + MaxPeers: MaxPeer, + Port: OutboundPort, + NAT: NAT, + BootNodes: BootNodes, + NodeKey: NodeKey, + KeyRing: KeyRing, + Dial: true, }) if err != nil { mainlogger.Fatalln(err) diff --git a/eth/backend.go b/eth/backend.go index f3e4842a77..29cf7d8363 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -13,6 +13,7 @@ import ( ethlogger "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/pow/ezp" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/whisper" @@ -28,10 +29,8 @@ type Config struct { LogLevel int KeyRing string - MaxPeers int - Port string - NATType string - PMPGateway string + MaxPeers int + Port string // This should be a space-separated list of // discovery node URLs. @@ -41,6 +40,7 @@ type Config struct { // If nil, an ephemeral key is used. NodeKey *ecdsa.PrivateKey + NAT nat.Interface Shh bool Dial bool @@ -147,10 +147,6 @@ func New(config *Config) (*Ethereum, error) { ethProto := EthProtocol(eth.txPool, eth.chainManager, eth.blockPool) protocols := []p2p.Protocol{ethProto, eth.whisper.Protocol()} - nat, err := p2p.ParseNAT(config.NATType, config.PMPGateway) - if err != nil { - return nil, err - } netprv := config.NodeKey if netprv == nil { if netprv, err = crypto.GenerateKey(); err != nil { @@ -163,7 +159,7 @@ func New(config *Config) (*Ethereum, error) { MaxPeers: config.MaxPeers, Protocols: protocols, Blacklist: eth.blacklist, - NAT: nat, + NAT: config.NAT, NoDial: !config.Dial, BootstrapNodes: config.parseBootNodes(), } diff --git a/p2p/nat.go b/p2p/nat.go deleted file mode 100644 index 9b771c3e8c..0000000000 --- a/p2p/nat.go +++ /dev/null @@ -1,23 +0,0 @@ -package p2p - -import ( - "fmt" - "net" -) - -func ParseNAT(natType string, gateway string) (nat NAT, err error) { - switch natType { - case "UPNP": - nat = UPNP() - case "PMP": - ip := net.ParseIP(gateway) - if ip == nil { - return nil, fmt.Errorf("cannot resolve PMP gateway IP %s", gateway) - } - nat = PMP(ip) - case "": - default: - return nil, fmt.Errorf("unrecognised NAT type '%s'", natType) - } - return -} diff --git a/p2p/natpmp.go b/p2p/natpmp.go deleted file mode 100644 index 6714678c4c..0000000000 --- a/p2p/natpmp.go +++ /dev/null @@ -1,55 +0,0 @@ -package p2p - -import ( - "fmt" - "net" - "time" - - natpmp "github.com/jackpal/go-nat-pmp" -) - -// Adapt the NAT-PMP protocol to the NAT interface - -// TODO: -// + Register for changes to the external address. -// + Re-register port mapping when router reboots. -// + A mechanism for keeping a port mapping registered. -// + Discover gateway address automatically. - -type natPMPClient struct { - client *natpmp.Client -} - -// PMP returns a NAT traverser that uses NAT-PMP. The provided gateway -// address should be the IP of your router. -func PMP(gateway net.IP) (nat NAT) { - return &natPMPClient{natpmp.NewClient(gateway)} -} - -func (*natPMPClient) String() string { - return "NAT-PMP" -} - -func (n *natPMPClient) GetExternalAddress() (net.IP, error) { - response, err := n.client.GetExternalAddress() - if err != nil { - return nil, err - } - return response.ExternalIPAddress[:], nil -} - -func (n *natPMPClient) AddPortMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error { - if lifetime <= 0 { - return fmt.Errorf("lifetime must not be <= 0") - } - // Note order of port arguments is switched between our AddPortMapping and the client's AddPortMapping. - _, err := n.client.AddPortMapping(protocol, intport, extport, int(lifetime/time.Second)) - return err -} - -func (n *natPMPClient) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) { - // To destroy a mapping, send an add-port with - // an internalPort of the internal port to destroy, an external port of zero and a time of zero. - _, err = n.client.AddPortMapping(protocol, internalPort, 0, 0) - return -} diff --git a/p2p/natupnp.go b/p2p/natupnp.go deleted file mode 100644 index 2e0d8ce8d5..0000000000 --- a/p2p/natupnp.go +++ /dev/null @@ -1,341 +0,0 @@ -package p2p - -// Just enough UPnP to be able to forward ports -// - -import ( - "bytes" - "encoding/xml" - "errors" - "fmt" - "net" - "net/http" - "os" - "strconv" - "strings" - "time" -) - -const ( - upnpDiscoverAttempts = 3 - upnpDiscoverTimeout = 5 * time.Second -) - -// UPNP returns a NAT port mapper that uses UPnP. It will attempt to -// discover the address of your router using UDP broadcasts. -func UPNP() NAT { - return &upnpNAT{} -} - -type upnpNAT struct { - serviceURL string - ourIP string -} - -func (n *upnpNAT) String() string { - return "UPNP" -} - -func (n *upnpNAT) discover() error { - if n.serviceURL != "" { - // already discovered - return nil - } - - ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900") - if err != nil { - return err - } - // TODO: try on all network interfaces simultaneously. - // Broadcasting on 0.0.0.0 could select a random interface - // to send on (platform specific). - conn, err := net.ListenPacket("udp4", ":0") - if err != nil { - return err - } - defer conn.Close() - - conn.SetDeadline(time.Now().Add(10 * time.Second)) - st := "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n" - buf := bytes.NewBufferString( - "M-SEARCH * HTTP/1.1\r\n" + - "HOST: 239.255.255.250:1900\r\n" + - st + - "MAN: \"ssdp:discover\"\r\n" + - "MX: 2\r\n\r\n") - message := buf.Bytes() - answerBytes := make([]byte, 1024) - for i := 0; i < upnpDiscoverAttempts; i++ { - _, err = conn.WriteTo(message, ssdp) - if err != nil { - return err - } - nn, _, err := conn.ReadFrom(answerBytes) - if err != nil { - continue - } - answer := string(answerBytes[0:nn]) - if strings.Index(answer, "\r\n"+st) < 0 { - continue - } - // HTTP header field names are case-insensitive. - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 - locString := "\r\nlocation: " - answer = strings.ToLower(answer) - locIndex := strings.Index(answer, locString) - if locIndex < 0 { - continue - } - loc := answer[locIndex+len(locString):] - endIndex := strings.Index(loc, "\r\n") - if endIndex < 0 { - continue - } - locURL := loc[0:endIndex] - var serviceURL string - serviceURL, err = getServiceURL(locURL) - if err != nil { - return err - } - var ourIP string - ourIP, err = getOurIP() - if err != nil { - return err - } - n.serviceURL = serviceURL - n.ourIP = ourIP - return nil - } - return errors.New("UPnP port discovery failed.") -} - -func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) { - if err := n.discover(); err != nil { - return nil, err - } - info, err := n.getStatusInfo() - return net.ParseIP(info.externalIpAddress), err -} - -func (n *upnpNAT) AddPortMapping(protocol string, extport, intport int, description string, lifetime time.Duration) error { - if err := n.discover(); err != nil { - return err - } - - // A single concatenation would break ARM compilation. - message := "\r\n" + - "" + strconv.Itoa(extport) - message += "" + protocol + "" - message += "" + strconv.Itoa(extport) + "" + - "" + n.ourIP + "" + - "1" - message += description + - "" + fmt.Sprint(lifetime/time.Second) + - "" - - // TODO: check response to see if the port was forwarded - _, err := soapRequest(n.serviceURL, "AddPortMapping", message) - return err -} - -func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) error { - if err := n.discover(); err != nil { - return err - } - - message := "\r\n" + - "" + strconv.Itoa(externalPort) + - "" + protocol + "" + - "" - - // TODO: check response to see if the port was deleted - _, err := soapRequest(n.serviceURL, "DeletePortMapping", message) - return err -} - -type statusInfo struct { - externalIpAddress string -} - -func (n *upnpNAT) getStatusInfo() (info statusInfo, err error) { - message := "\r\n" + - "" - - var response *http.Response - response, err = soapRequest(n.serviceURL, "GetStatusInfo", message) - if err != nil { - return - } - - // TODO: Write a soap reply parser. It has to eat the Body and envelope tags... - - response.Body.Close() - return -} - -// service represents the Service type in an UPnP xml description. -// Only the parts we care about are present and thus the xml may have more -// fields than present in the structure. -type service struct { - ServiceType string `xml:"serviceType"` - ControlURL string `xml:"controlURL"` -} - -// deviceList represents the deviceList type in an UPnP xml description. -// Only the parts we care about are present and thus the xml may have more -// fields than present in the structure. -type deviceList struct { - XMLName xml.Name `xml:"deviceList"` - Device []device `xml:"device"` -} - -// serviceList represents the serviceList type in an UPnP xml description. -// Only the parts we care about are present and thus the xml may have more -// fields than present in the structure. -type serviceList struct { - XMLName xml.Name `xml:"serviceList"` - Service []service `xml:"service"` -} - -// device represents the device type in an UPnP xml description. -// Only the parts we care about are present and thus the xml may have more -// fields than present in the structure. -type device struct { - XMLName xml.Name `xml:"device"` - DeviceType string `xml:"deviceType"` - DeviceList deviceList `xml:"deviceList"` - ServiceList serviceList `xml:"serviceList"` -} - -// specVersion represents the specVersion in a UPnP xml description. -// Only the parts we care about are present and thus the xml may have more -// fields than present in the structure. -type specVersion struct { - XMLName xml.Name `xml:"specVersion"` - Major int `xml:"major"` - Minor int `xml:"minor"` -} - -// root represents the Root document for a UPnP xml description. -// Only the parts we care about are present and thus the xml may have more -// fields than present in the structure. -type root struct { - XMLName xml.Name `xml:"root"` - SpecVersion specVersion - Device device -} - -func getChildDevice(d *device, deviceType string) *device { - dl := d.DeviceList.Device - for i := 0; i < len(dl); i++ { - if dl[i].DeviceType == deviceType { - return &dl[i] - } - } - return nil -} - -func getChildService(d *device, serviceType string) *service { - sl := d.ServiceList.Service - for i := 0; i < len(sl); i++ { - if sl[i].ServiceType == serviceType { - return &sl[i] - } - } - return nil -} - -func getOurIP() (ip string, err error) { - hostname, err := os.Hostname() - if err != nil { - return - } - p, err := net.LookupIP(hostname) - if err != nil && len(p) > 0 { - return - } - return p[0].String(), nil -} - -func getServiceURL(rootURL string) (url string, err error) { - r, err := http.Get(rootURL) - if err != nil { - return - } - defer r.Body.Close() - if r.StatusCode >= 400 { - err = errors.New(string(r.StatusCode)) - return - } - var root root - err = xml.NewDecoder(r.Body).Decode(&root) - - if err != nil { - return - } - a := &root.Device - if a.DeviceType != "urn:schemas-upnp-org:device:InternetGatewayDevice:1" { - err = errors.New("No InternetGatewayDevice") - return - } - b := getChildDevice(a, "urn:schemas-upnp-org:device:WANDevice:1") - if b == nil { - err = errors.New("No WANDevice") - return - } - c := getChildDevice(b, "urn:schemas-upnp-org:device:WANConnectionDevice:1") - if c == nil { - err = errors.New("No WANConnectionDevice") - return - } - d := getChildService(c, "urn:schemas-upnp-org:service:WANIPConnection:1") - if d == nil { - err = errors.New("No WANIPConnection") - return - } - url = combineURL(rootURL, d.ControlURL) - return -} - -func combineURL(rootURL, subURL string) string { - protocolEnd := "://" - protoEndIndex := strings.Index(rootURL, protocolEnd) - a := rootURL[protoEndIndex+len(protocolEnd):] - rootIndex := strings.Index(a, "/") - return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL -} - -func soapRequest(url, function, message string) (r *http.Response, err error) { - fullMessage := "" + - "\r\n" + - "" + message + "" - - req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage)) - if err != nil { - return - } - req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"") - req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3") - //req.Header.Set("Transfer-Encoding", "chunked") - req.Header.Set("SOAPAction", "\"urn:schemas-upnp-org:service:WANIPConnection:1#"+function+"\"") - req.Header.Set("Connection", "Close") - req.Header.Set("Cache-Control", "no-cache") - req.Header.Set("Pragma", "no-cache") - - r, err = http.DefaultClient.Do(req) - if err != nil { - return - } - - if r.Body != nil { - defer r.Body.Close() - } - - if r.StatusCode >= 400 { - // log.Stderr(function, r.StatusCode) - err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function) - r = nil - return - } - return -} diff --git a/p2p/server.go b/p2p/server.go index 3cab611021..a0f2dee23d 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -13,13 +13,12 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/nat" ) const ( - defaultDialTimeout = 10 * time.Second - refreshPeersInterval = 30 * time.Second - portMappingUpdateInterval = 15 * time.Minute - portMappingTimeout = 20 * time.Minute + defaultDialTimeout = 10 * time.Second + refreshPeersInterval = 30 * time.Second ) var srvlog = logger.NewLogger("P2P Server") @@ -72,7 +71,7 @@ type Server struct { // If set to a non-nil value, the given NAT port mapper // is used to make the listening port available to the // Internet. - NAT NAT + NAT nat.Interface // If Dialer is set to a non-nil value, the given Dialer // is used to dial outbound peer connections. @@ -89,7 +88,6 @@ type Server struct { lock sync.RWMutex running bool listener net.Listener - laddr *net.TCPAddr // real listen addr peers map[discover.NodeID]*Peer ntab *discover.Table @@ -100,16 +98,6 @@ type Server struct { peerConnect chan *discover.Node } -// NAT is implemented by NAT traversal methods. -type NAT interface { - GetExternalAddress() (net.IP, error) - AddPortMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error - DeletePortMapping(protocol string, extport, intport int) error - - // Should return name of the method. - String() string -} - type handshakeFunc func(io.ReadWriter, *ecdsa.PrivateKey, *discover.Node) (discover.NodeID, []byte, error) type newPeerHook func(*Peer) @@ -220,14 +208,17 @@ func (srv *Server) startListening() error { if err != nil { return err } - srv.ListenAddr = listener.Addr().String() - srv.laddr = listener.Addr().(*net.TCPAddr) + laddr := listener.Addr().(*net.TCPAddr) + srv.ListenAddr = laddr.String() srv.listener = listener srv.loopWG.Add(1) go srv.listenLoop() - if !srv.laddr.IP.IsLoopback() && srv.NAT != nil { + if !laddr.IP.IsLoopback() && srv.NAT != nil { srv.loopWG.Add(1) - go srv.natLoop(srv.laddr.Port) + go func() { + nat.Map(srv.NAT, srv.quit, "tcp", laddr.Port, laddr.Port, "ethereum p2p") + srv.loopWG.Done() + }() } return nil } @@ -276,45 +267,6 @@ func (srv *Server) listenLoop() { } } -func (srv *Server) natLoop(port int) { - defer srv.loopWG.Done() - for { - srv.updatePortMapping(port) - select { - case <-time.After(portMappingUpdateInterval): - // one more round - case <-srv.quit: - srv.removePortMapping(port) - return - } - } -} - -func (srv *Server) updatePortMapping(port int) { - srvlog.Infoln("Attempting to map port", port, "with", srv.NAT) - err := srv.NAT.AddPortMapping("tcp", port, port, "ethereum p2p", portMappingTimeout) - if err != nil { - srvlog.Errorln("Port mapping error:", err) - return - } - extip, err := srv.NAT.GetExternalAddress() - if err != nil { - srvlog.Errorln("Error getting external IP:", err) - return - } - srv.lock.Lock() - extaddr := *(srv.listener.Addr().(*net.TCPAddr)) - extaddr.IP = extip - srvlog.Infoln("Mapped port, external addr is", &extaddr) - srv.laddr = &extaddr - srv.lock.Unlock() -} - -func (srv *Server) removePortMapping(port int) { - srvlog.Infoln("Removing port mapping for", port, "with", srv.NAT) - srv.NAT.DeletePortMapping("tcp", port, port) -} - func (srv *Server) dialLoop() { defer srv.loopWG.Done() refresh := time.NewTicker(refreshPeersInterval)