github.com/ethereum/go-ethereum@v1.16.1/p2p/server_nat.go (about)

     1  // Copyright 2023 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package p2p
    18  
    19  import (
    20  	"net"
    21  	"time"
    22  
    23  	"github.com/ethereum/go-ethereum/common/mclock"
    24  	"github.com/ethereum/go-ethereum/log"
    25  	"github.com/ethereum/go-ethereum/p2p/enr"
    26  	"github.com/ethereum/go-ethereum/p2p/nat"
    27  )
    28  
    29  const (
    30  	portMapDuration        = 10 * time.Minute
    31  	portMapRefreshInterval = 8 * time.Minute
    32  	portMapRetryInterval   = 5 * time.Minute
    33  	extipRetryInterval     = 2 * time.Minute
    34  	maxRetries             = 5 // max number of failed attempts to refresh the mapping
    35  )
    36  
    37  type portMapping struct {
    38  	protocol string
    39  	name     string
    40  	port     int
    41  	retries  int // number of failed attempts to refresh the mapping
    42  
    43  	// for use by the portMappingLoop goroutine:
    44  	extPort  int // the mapped port returned by the NAT interface
    45  	nextTime mclock.AbsTime
    46  }
    47  
    48  // setupPortMapping starts the port mapping loop if necessary.
    49  // Note: this needs to be called after the LocalNode instance has been set on the server.
    50  func (srv *Server) setupPortMapping() {
    51  	// portMappingRegister will receive up to two values: one for the TCP port if
    52  	// listening is enabled, and one more for enabling UDP port mapping if discovery is
    53  	// enabled. We make it buffered to avoid blocking setup while a mapping request is in
    54  	// progress.
    55  	srv.portMappingRegister = make(chan *portMapping, 2)
    56  
    57  	switch srv.NAT.(type) {
    58  	case nil:
    59  		// No NAT interface configured.
    60  		srv.loopWG.Add(1)
    61  		go srv.consumePortMappingRequests()
    62  
    63  	case nat.ExtIP:
    64  		// ExtIP doesn't block, set the IP right away.
    65  		ip, _ := srv.NAT.ExternalIP()
    66  		srv.localnode.SetStaticIP(ip)
    67  		srv.loopWG.Add(1)
    68  		go srv.consumePortMappingRequests()
    69  
    70  	default:
    71  		srv.loopWG.Add(1)
    72  		go srv.portMappingLoop()
    73  	}
    74  }
    75  
    76  func (srv *Server) consumePortMappingRequests() {
    77  	defer srv.loopWG.Done()
    78  	for {
    79  		select {
    80  		case <-srv.quit:
    81  			return
    82  		case <-srv.portMappingRegister:
    83  		}
    84  	}
    85  }
    86  
    87  // portMappingLoop manages port mappings for UDP and TCP.
    88  func (srv *Server) portMappingLoop() {
    89  	defer srv.loopWG.Done()
    90  
    91  	newLogger := func(p string, e int, i int) log.Logger {
    92  		return log.New("proto", p, "extport", e, "intport", i, "interface", srv.NAT)
    93  	}
    94  
    95  	var (
    96  		mappings  = make(map[string]*portMapping, 2)
    97  		refresh   = mclock.NewAlarm(srv.clock)
    98  		extip     = mclock.NewAlarm(srv.clock)
    99  		lastExtIP net.IP
   100  	)
   101  	extip.Schedule(srv.clock.Now())
   102  	defer func() {
   103  		refresh.Stop()
   104  		extip.Stop()
   105  		for _, m := range mappings {
   106  			if m.extPort != 0 {
   107  				log := newLogger(m.protocol, m.extPort, m.port)
   108  				log.Debug("Deleting port mapping")
   109  				srv.NAT.DeleteMapping(m.protocol, m.extPort, m.port)
   110  			}
   111  		}
   112  	}()
   113  
   114  	for {
   115  		// Schedule refresh of existing mappings.
   116  		for _, m := range mappings {
   117  			refresh.Schedule(m.nextTime)
   118  		}
   119  
   120  		select {
   121  		case <-srv.quit:
   122  			return
   123  
   124  		case <-extip.C():
   125  			extip.Schedule(srv.clock.Now().Add(extipRetryInterval))
   126  			ip, err := srv.NAT.ExternalIP()
   127  			if err != nil {
   128  				log.Debug("Couldn't get external IP", "err", err, "interface", srv.NAT)
   129  			} else if !ip.Equal(lastExtIP) {
   130  				log.Debug("External IP changed", "ip", ip, "interface", srv.NAT)
   131  			} else {
   132  				continue
   133  			}
   134  			// Here, we either failed to get the external IP, or it has changed.
   135  			lastExtIP = ip
   136  			srv.localnode.SetStaticIP(ip)
   137  			// Ensure port mappings are refreshed in case we have moved to a new network.
   138  			for _, m := range mappings {
   139  				m.nextTime = srv.clock.Now()
   140  			}
   141  
   142  		case m := <-srv.portMappingRegister:
   143  			if m.protocol != "TCP" && m.protocol != "UDP" {
   144  				panic("unknown NAT protocol name: " + m.protocol)
   145  			}
   146  			mappings[m.protocol] = m
   147  			m.nextTime = srv.clock.Now()
   148  
   149  		case <-refresh.C():
   150  			for _, m := range mappings {
   151  				if srv.clock.Now() < m.nextTime {
   152  					continue
   153  				}
   154  
   155  				log := newLogger(m.protocol, m.extPort, m.port)
   156  				log.Trace("Attempting port mapping")
   157  				p, err := srv.NAT.AddMapping(m.protocol, m.extPort, m.port, m.name, portMapDuration)
   158  				if err != nil {
   159  					// Failed to add or refresh port mapping.
   160  					if m.extPort == 0 {
   161  						log.Debug("Couldn't add port mapping", "err", err)
   162  					} else {
   163  						// Failed refresh. Since UPnP implementation are often buggy,
   164  						// and lifetime is larger than the retry interval, this does not
   165  						// mean we lost our existing mapping. We do not reset the external
   166  						// port, as it is still our best chance, but we do retry soon.
   167  						// We could check the error code, but UPnP implementations are buggy.
   168  						log.Debug("Couldn't refresh port mapping", "err", err)
   169  						m.retries++
   170  						if m.retries > maxRetries {
   171  							m.retries = 0
   172  							err := srv.NAT.DeleteMapping(m.protocol, m.extPort, m.port)
   173  							log.Debug("Couldn't refresh port mapping, trying to delete it:", "err", err)
   174  							m.extPort = 0
   175  						}
   176  					}
   177  					m.nextTime = srv.clock.Now().Add(portMapRetryInterval)
   178  					// Note ENR is not updated here, i.e. we keep the last port.
   179  					continue
   180  				}
   181  
   182  				// It was mapped!
   183  				m.retries = 0
   184  				log = newLogger(m.protocol, int(p), m.port)
   185  				if int(p) != m.extPort {
   186  					m.extPort = int(p)
   187  					if m.port != m.extPort {
   188  						log.Info("NAT mapped alternative port")
   189  					} else {
   190  						log.Info("NAT mapped port")
   191  					}
   192  
   193  					// Update port in local ENR.
   194  					switch m.protocol {
   195  					case "TCP":
   196  						srv.localnode.Set(enr.TCP(m.extPort))
   197  					case "UDP":
   198  						srv.localnode.SetFallbackUDP(m.extPort)
   199  					}
   200  				}
   201  				m.nextTime = srv.clock.Now().Add(portMapRefreshInterval)
   202  			}
   203  		}
   204  	}
   205  }