diff --git a/config.go b/config.go index fa7623225..8818b625e 100644 --- a/config.go +++ b/config.go @@ -172,7 +172,7 @@ type config struct { DisableDNSSeed bool `long:"nodnsseed" description:"DEPRECATED: use --noseeders"` ExternalIPs []string `long:"externalip" description:"Add a public-facing IP to the list of local external IPs that dcrd will advertise to other peers"` NoDiscoverIP bool `long:"nodiscoverip" description:"Disable automatic network address discovery of local external IPs"` - Upnp bool `long:"upnp" description:"Use UPnP to map our listening port outside of NAT"` + Upnp bool `long:"upnp" description:"REMOVED: This feature is no longer available and this flag will be removed in a future version"` // Banning options. DisableBanning bool `long:"nobanning" description:"Disable banning of misbehaving peers"` diff --git a/dcrd.go b/dcrd.go index 80274e56e..d81e6776b 100644 --- a/dcrd.go +++ b/dcrd.go @@ -71,6 +71,12 @@ func dcrdMain() error { dcrdLog.Info("File logging disabled") } + if cfg.Upnp { + dcrdLog.Warnf("The --upnp option is no longer supported. Make sure " + + "to manually map the listening port on your router if you are " + + "behind NAT and wish to receive inbound connections") + } + // Block and transaction processing can cause bursty allocations. This // limits the garbage collector from excessively overallocating during // bursts. It does this by tweaking the soft memory limit. diff --git a/doc.go b/doc.go index f17cc381e..e8dbc3b10 100644 --- a/doc.go +++ b/doc.go @@ -119,7 +119,6 @@ Application Options: peers --nodiscoverip Disable automatic network address discovery of local external IPs - --upnp Use UPnP to map our listening port outside of NAT --nobanning Disable banning of misbehaving peers --banduration= How long to ban misbehaving peers. Valid time units are {s, m, h}. Minimum 1 second (default: diff --git a/docs/default_ports.md b/docs/default_ports.md index e070d02dc..ced87d956 100644 --- a/docs/default_ports.md +++ b/docs/default_ports.md @@ -2,12 +2,9 @@ While dcrd is highly configurable when it comes to the network configuration, the following is intended to be a quick reference for the default ports used so port forwarding can be configured as required. -dcrd provides a `--upnp` flag which can be used to automatically map the Decred -peer-to-peer listening port if your router supports UPnP. If your router does -not support UPnP, or you don't wish to use it, please note that only the Decred -peer-to-peer port should be forwarded unless you specifically want to allow RPC -access to your dcrd from external sources such as in more advanced network -configurations. +Please note that only the Decred peer-to-peer port should be forwarded unless +you specifically want to allow RPC access to your dcrd from external sources +such as in more advanced network configurations. |Name|Port| |----|----| diff --git a/server.go b/server.go index e85438f60..be75c4296 100644 --- a/server.go +++ b/server.go @@ -588,7 +588,6 @@ type server struct { peerState peerState relayInv chan relayMsg broadcast chan broadcastMsg - nat *upnpNAT db database.DB timeSource blockchain.MedianTimeSource services wire.ServiceFlag @@ -2747,12 +2746,11 @@ func (s *server) handleAddPeer(sp *serverPeer) bool { // - There is an external IP explicitly set (--externalip) // - Listening has been disabled (--nolisten, listen disabled because of // --connect, etc) - // - Universal Plug and Play is enabled (--upnp) // - The active network is simnet or regnet if (cfg.Proxy != "" || cfg.OnionProxy != "") || cfg.NoDiscoverIP || len(cfg.ExternalIPs) > 0 || - (cfg.DisableListen || len(cfg.Listeners) == 0) || cfg.Upnp || + (cfg.DisableListen || len(cfg.Listeners) == 0) || s.chainParams.Name == simNetParams.Name || s.chainParams.Name == regNetParams.Name { @@ -3586,14 +3584,6 @@ func (s *server) Run(ctx context.Context) { wg.Done() }() - if s.nat != nil { - wg.Add(1) - go func() { - s.upnpUpdateThread(ctx) - wg.Done() - }() - } - if !cfg.DisableRPC { // Start the RPC server and rebroadcast handler which ensures // transactions submitted to the RPC server are rebroadcast until being @@ -3690,64 +3680,6 @@ func parseListeners(addrs []string) ([]net.Addr, error) { return netAddrs, nil } -func (s *server) upnpUpdateThread(ctx context.Context) { - // Go off immediately to prevent code duplication, thereafter we renew - // lease every 15 minutes. - timer := time.NewTimer(0 * time.Second) - lport, _ := strconv.ParseInt(s.chainParams.DefaultPort, 10, 16) - - first := true -out: - for { - select { - case <-timer.C: - // TODO: pick external port more cleverly - // TODO: know which ports we are listening to on an external net. - // TODO: if specific listen port doesn't work then ask for wildcard - // listen port? - // XXX this assumes timeout is in seconds. - listenPort, err := s.nat.AddPortMapping("tcp", int(lport), int(lport), - "dcrd listen port", 20*60) - if err != nil { - srvrLog.Warnf("can't add UPnP port mapping: %v", err) - } - if first && err == nil { - // TODO: look this up periodically to see if upnp domain changed - // and so did ip. - externalip, err := s.nat.GetExternalAddress() - if err != nil { - srvrLog.Warnf("UPnP can't get external address: %v", err) - continue out - } - localAddr := addrmgr.NewNetAddressFromIPPort(externalip, - uint16(listenPort), s.services) - err = s.addrManager.AddLocalAddress(localAddr, addrmgr.UpnpPrio) - if err != nil { - srvrLog.Warnf("Failed to add UPnP local address %s: %v", - localAddr, err) - } else { - srvrLog.Warnf("Successfully bound via UPnP to %s", - localAddr) - first = false - } - } - timer.Reset(time.Minute * 15) - - case <-ctx.Done(): - break out - } - } - - timer.Stop() - - err := s.nat.DeletePortMapping("tcp", int(lport), int(lport)) - if err != nil { - srvrLog.Warnf("unable to remove UPnP port mapping: %v", err) - } else { - srvrLog.Debugf("successfully disestablished UPnP port mapping") - } -} - // standardScriptVerifyFlags returns the script flags that should be used when // executing transaction scripts to enforce additional checks which are required // for the script to be considered standard. Note these flags are different @@ -4104,10 +4036,9 @@ func newServer(ctx context.Context, profiler *profileServer, services := defaultServices var listeners []net.Listener - var nat *upnpNAT if !cfg.DisableListen { var err error - listeners, nat, err = initListeners(ctx, chainParams, amgr, listenAddrs, + listeners, err = initListeners(ctx, chainParams, amgr, listenAddrs, services) if err != nil { return nil, err @@ -4131,7 +4062,6 @@ func newServer(ctx context.Context, profiler *profileServer, relayInv: make(chan relayMsg, cfg.MaxPeers), broadcast: make(chan broadcastMsg, cfg.MaxPeers), modifyRebroadcastInv: make(chan any), - nat: nat, db: db, timeSource: blockchain.NewMedianTime(), services: services, @@ -4616,13 +4546,12 @@ func newServer(ctx context.Context, profiler *profileServer, } // initListeners initializes the configured net listeners and adds any bound -// addresses to the address manager. Returns the listeners and a NAT interface, -// which is non-nil if UPnP is in use. -func initListeners(ctx context.Context, params *chaincfg.Params, amgr *addrmgr.AddrManager, listenAddrs []string, services wire.ServiceFlag) ([]net.Listener, *upnpNAT, error) { +// addresses to the address manager. Returns the listeners. +func initListeners(ctx context.Context, params *chaincfg.Params, amgr *addrmgr.AddrManager, listenAddrs []string, services wire.ServiceFlag) ([]net.Listener, error) { // Listen for TCP connections at the configured addresses netAddrs, err := parseListeners(listenAddrs) if err != nil { - return nil, nil, err + return nil, err } var notifyAddrServer boundAddrEventServer @@ -4642,13 +4571,12 @@ func initListeners(ctx context.Context, params *chaincfg.Params, amgr *addrmgr.A notifyAddrServer.notifyP2PAddress(listener.Addr().String()) } - var nat *upnpNAT if len(cfg.ExternalIPs) != 0 { defaultPort, err := strconv.ParseUint(params.DefaultPort, 10, 16) if err != nil { srvrLog.Errorf("Can not parse default port %s for active chain: %v", params.DefaultPort, err) - return nil, nil, err + return nil, err } for _, sip := range cfg.ExternalIPs { @@ -4679,15 +4607,6 @@ func initListeners(ctx context.Context, params *chaincfg.Params, amgr *addrmgr.A } } } else { - if cfg.Upnp { - var err error - nat, err = discover(ctx) - if err != nil { - srvrLog.Warnf("Can't discover upnp: %v", err) - } - // nil nat here is fine, just means no upnp on network. - } - // Add bound addresses to address manager to be advertised to peers. for _, listener := range listeners { addr := listener.Addr().String() @@ -4698,7 +4617,7 @@ func initListeners(ctx context.Context, params *chaincfg.Params, amgr *addrmgr.A } } - return listeners, nat, nil + return listeners, nil } // addrStringToNetAddr takes an address in the form of 'host:port' and returns diff --git a/upnp.go b/upnp.go deleted file mode 100644 index 4f1c0118e..000000000 --- a/upnp.go +++ /dev/null @@ -1,401 +0,0 @@ -package main - -// Upnp code taken from Taipei Torrent license is below: -// Copyright (c) 2010 Jack Palevich. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Just enough UPnP to be able to forward ports -// - -import ( - "bytes" - "context" - "encoding/xml" - "errors" - "fmt" - "net" - "net/http" - "net/url" - "strconv" - "strings" - "time" -) - -type upnpNAT struct { - serviceURL string - ourIP string -} - -// discover searches the local network for a UPnP router returning a NAT -// for the network if so, nil if not. -func discover(ctx context.Context) (*upnpNAT, error) { - ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900") - if err != nil { - return nil, err - } - var l net.ListenConfig - conn, err := l.ListenPacket(ctx, "udp4", ":0") - if err != nil { - return nil, err - } - socket := conn.(*net.UDPConn) - defer socket.Close() - - err = socket.SetDeadline(time.Now().Add(3 * time.Second)) - if err != nil { - return nil, err - } - - 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 < 3; i++ { - _, err = socket.WriteToUDP(message, ssdp) - if err != nil { - return nil, err - } - var n int - n, _, err = socket.ReadFromUDP(answerBytes) - if err != nil { - continue - // socket.Close() - // return - } - answer := string(answerBytes[0:n]) - if !strings.Contains(answer, "\r\n"+st) { - continue - } - // HTTP header field names are case-insensitive. - // https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 - locString := "\r\nlocation: " - locIndex := strings.Index(strings.ToLower(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 nil, err - } - serviceIP := getServiceIP(serviceURL) - var ourIP string - ourIP, err = getOurIP(serviceIP) - if err != nil { - return nil, err - } - return &upnpNAT{serviceURL: serviceURL, ourIP: ourIP}, nil - } - return nil, errors.New("UPnP port discovery failed") -} - -// 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 -} - -// getChildDevice searches the children of device for a device with the given -// type. -func getChildDevice(d *device, deviceType string) *device { - for i := range d.DeviceList.Device { - if d.DeviceList.Device[i].DeviceType == deviceType { - return &d.DeviceList.Device[i] - } - } - return nil -} - -// getChildService searches the service list of device for a service with the -// given type. -func getChildService(d *device, serviceType string) *service { - for i := range d.ServiceList.Service { - if d.ServiceList.Service[i].ServiceType == serviceType { - return &d.ServiceList.Service[i] - } - } - return nil -} - -func getServiceIP(serviceURL string) (routerIP string) { - url, _ := url.Parse(serviceURL) - return url.Hostname() -} - -// getOurIP returns the local IP that is on the same subnet as the serviceIP. -func getOurIP(serviceIP string) (ip string, err error) { - _, serviceNet, _ := net.ParseCIDR(serviceIP + "/24") - addrs, err := net.InterfaceAddrs() - for _, addr := range addrs { - ip, _, _ := net.ParseCIDR(addr.String()) - if serviceNet.Contains(ip) { - return ip.String(), nil - } - } - return -} - -// 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 string, err error) { - r, err := http.Get(rootURL) - if err != nil { - return - } - defer r.Body.Close() - if r.StatusCode >= 400 { - err = fmt.Errorf("%d", 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 internet gateway device") - return - } - b := getChildDevice(a, "urn:schemas-upnp-org:device:WANDevice:1") - if b == nil { - err = errors.New("no WAN device") - return - } - c := getChildDevice(b, "urn:schemas-upnp-org:device:WANConnectionDevice:1") - if c == nil { - err = errors.New("no WAN connection device") - return - } - d := getChildService(c, "urn:schemas-upnp-org:service:WANIPConnection:1") - if d == nil { - err = errors.New("no WAN IP connection") - return - } - url = combineURL(rootURL, d.ControlURL) - return -} - -// combineURL appends subURL onto rootURL. -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 -} - -// soapBody represents the element in a SOAP reply. -// fields we don't care about are elided. -type soapBody struct { - XMLName xml.Name `xml:"Body"` - Data []byte `xml:",innerxml"` -} - -// soapEnvelope represents the element in a SOAP reply. -// fields we don't care about are elided. -type soapEnvelope struct { - XMLName xml.Name `xml:"Envelope"` - Body soapBody `xml:"Body"` -} - -// soapRequest 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 string) (replyXML []byte, err error) { - fullMessage := "" + - "\r\n" + - "" + message + "" - - req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(fullMessage)) - if err != nil { - return nil, err - } - 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 nil, err - } - 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 - } - var reply soapEnvelope - err = xml.NewDecoder(r.Body).Decode(&reply) - if err != nil { - return nil, err - } - return reply.Body.Data, nil -} - -// getExternalIPAddressResponse represents the XML response to a -// GetExternalIPAddress SOAP request. -type getExternalIPAddressResponse struct { - XMLName xml.Name `xml:"GetExternalIPAddressResponse"` - ExternalIPAddress string `xml:"NewExternalIPAddress"` -} - -// GetExternalAddress implements the NAT interface by fetching the external IP -// from the UPnP router. -func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) { - message := "\r\n" - response, err := soapRequest(n.serviceURL, "GetExternalIPAddress", message) - if err != nil { - return nil, err - } - - var reply getExternalIPAddressResponse - err = xml.Unmarshal(response, &reply) - if err != nil { - return nil, err - } - - addr = net.ParseIP(reply.ExternalIPAddress) - if addr == nil { - return nil, errors.New("unable to parse ip address") - } - 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, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) { - // A single concatenation would break ARM compilation. - message := "\r\n" + - "" + strconv.Itoa(externalPort) - message += "" + strings.ToUpper(protocol) + "" - message += "" + strconv.Itoa(internalPort) + "" + - "" + n.ourIP + "" + - "1" - message += description + - "" + strconv.Itoa(timeout) + - "" - - response, err := soapRequest(n.serviceURL, "AddPortMapping", message) - if err != nil { - return - } - - // TODO: check response to see if the port was forwarded - // If the port was not wildcard we don't get a reply with the port in - // it. Not sure about wildcard yet. miniupnpc just checks for error - // codes here. - mappedExternalPort = externalPort - _ = response - return -} - -// 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) { - - message := "\r\n" + - "" + strconv.Itoa(externalPort) + - "" + strings.ToUpper(protocol) + "" + - "" - - response, err := soapRequest(n.serviceURL, "DeletePortMapping", message) - if err != nil { - return - } - - // TODO: check response to see if the port was deleted - // log.Println(message, response) - _ = response - return -}