github.com/MetalBlockchain/metalgo@v1.11.9/nat/pmp.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package nat
     5  
     6  import (
     7  	"errors"
     8  	"math"
     9  	"net/netip"
    10  	"time"
    11  
    12  	"github.com/jackpal/gateway"
    13  
    14  	natpmp "github.com/jackpal/go-nat-pmp"
    15  )
    16  
    17  const (
    18  	// pmpProtocol is intentionally lowercase and should not be confused with
    19  	// upnpProtocol.
    20  	// See:
    21  	// - https://github.com/jackpal/go-nat-pmp/blob/v1.0.2/natpmp.go#L82
    22  	pmpProtocol      = "tcp"
    23  	pmpClientTimeout = 500 * time.Millisecond
    24  )
    25  
    26  var (
    27  	_ Router = (*pmpRouter)(nil)
    28  
    29  	errInvalidLifetime = errors.New("invalid mapping duration range")
    30  )
    31  
    32  // pmpRouter adapts the NAT-PMP protocol implementation so it conforms to the
    33  // common interface.
    34  type pmpRouter struct {
    35  	client *natpmp.Client
    36  }
    37  
    38  func (*pmpRouter) SupportsNAT() bool {
    39  	return true
    40  }
    41  
    42  func (r *pmpRouter) MapPort(
    43  	newInternalPort uint16,
    44  	newExternalPort uint16,
    45  	_ string,
    46  	mappingDuration time.Duration,
    47  ) error {
    48  	internalPort := int(newInternalPort)
    49  	externalPort := int(newExternalPort)
    50  
    51  	// go-nat-pmp uses seconds to denote their lifetime
    52  	lifetime := mappingDuration.Seconds()
    53  	// Assumes the architecture is at least 32-bit
    54  	if lifetime < 0 || lifetime > math.MaxInt32 {
    55  		return errInvalidLifetime
    56  	}
    57  
    58  	_, err := r.client.AddPortMapping(pmpProtocol, internalPort, externalPort, int(lifetime))
    59  	return err
    60  }
    61  
    62  func (r *pmpRouter) UnmapPort(internalPort uint16, _ uint16) error {
    63  	internalPortInt := int(internalPort)
    64  
    65  	_, err := r.client.AddPortMapping(pmpProtocol, internalPortInt, 0, 0)
    66  	return err
    67  }
    68  
    69  func (r *pmpRouter) ExternalIP() (netip.Addr, error) {
    70  	response, err := r.client.GetExternalAddress()
    71  	if err != nil {
    72  		return netip.Addr{}, err
    73  	}
    74  	return netip.AddrFrom4(response.ExternalIPAddress), nil
    75  }
    76  
    77  func getPMPRouter() *pmpRouter {
    78  	gatewayIP, err := gateway.DiscoverGateway()
    79  	if err != nil {
    80  		return nil
    81  	}
    82  
    83  	pmp := &pmpRouter{
    84  		client: natpmp.NewClientWithTimeout(gatewayIP, pmpClientTimeout),
    85  	}
    86  	if _, err := pmp.ExternalIP(); err != nil {
    87  		return nil
    88  	}
    89  
    90  	return pmp
    91  }