2018-07-04 10:51:47 +00:00
package nat
import (
2019-06-09 07:24:20 +00:00
"context"
2018-07-04 10:51:47 +00:00
"errors"
"fmt"
2023-06-30 13:41:32 +00:00
"net/netip"
2018-07-04 10:51:47 +00:00
"sync"
"time"
2022-04-01 16:16:46 +00:00
logging "github.com/ipfs/go-log/v2"
2018-07-04 10:51:47 +00:00
2022-04-01 16:16:46 +00:00
"github.com/libp2p/go-nat"
2018-07-04 10:51:47 +00:00
)
2022-04-01 16:16:46 +00:00
// ErrNoMapping signals no mapping exists for an address
var ErrNoMapping = errors . New ( "mapping not established" )
2018-07-04 10:51:47 +00:00
var log = logging . Logger ( "nat" )
// MappingDuration is a default port mapping duration.
// Port mappings are renewed every (MappingDuration / 3)
2023-06-30 13:41:32 +00:00
const MappingDuration = time . Minute
2018-07-04 10:51:47 +00:00
// CacheTime is the time a mapping will cache an external address for
2023-06-30 13:41:32 +00:00
const CacheTime = 15 * time . Second
2018-07-04 10:51:47 +00:00
2023-06-30 13:41:32 +00:00
type entry struct {
protocol string
port int
}
// so we can mock it in tests
var discoverGateway = nat . DiscoverGateway
// DiscoverNAT looks for a NAT device in the network and returns an object that can manage port mappings.
2019-06-09 07:24:20 +00:00
func DiscoverNAT ( ctx context . Context ) ( * NAT , error ) {
2023-06-30 13:41:32 +00:00
natInstance , err := discoverGateway ( ctx )
2018-07-04 10:51:47 +00:00
if err != nil {
2019-06-09 07:24:20 +00:00
return nil , err
2018-07-04 10:51:47 +00:00
}
2023-06-30 13:41:32 +00:00
var extAddr netip . Addr
extIP , err := natInstance . GetExternalAddress ( )
if err == nil {
extAddr , _ = netip . AddrFromSlice ( extIP )
}
2019-06-09 07:24:20 +00:00
// Log the device addr.
addr , err := natInstance . GetDeviceAddress ( )
2018-07-04 10:51:47 +00:00
if err != nil {
log . Debug ( "DiscoverGateway address error:" , err )
} else {
log . Debug ( "DiscoverGateway address:" , addr )
}
2019-06-09 07:24:20 +00:00
2023-06-30 13:41:32 +00:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
nat := & NAT {
nat : natInstance ,
extAddr : extAddr ,
mappings : make ( map [ entry ] int ) ,
ctx : ctx ,
ctxCancel : cancel ,
}
nat . refCount . Add ( 1 )
go func ( ) {
defer nat . refCount . Done ( )
nat . background ( )
} ( )
return nat , nil
2018-07-04 10:51:47 +00:00
}
// NAT is an object that manages address port mappings in
// NATs (Network Address Translators). It is a long-running
// service that will periodically renew port mappings,
// and keep an up-to-date list of all the external addresses.
type NAT struct {
natmu sync . Mutex
nat nat . NAT
2023-06-30 13:41:32 +00:00
// External IP of the NAT. Will be renewed periodically (every CacheTime).
extAddr netip . Addr
2022-04-01 16:16:46 +00:00
refCount sync . WaitGroup
ctx context . Context
ctxCancel context . CancelFunc
2018-07-04 10:51:47 +00:00
mappingmu sync . RWMutex // guards mappings
2022-04-01 16:16:46 +00:00
closed bool
2023-06-30 13:41:32 +00:00
mappings map [ entry ] int
2018-07-04 10:51:47 +00:00
}
// Close shuts down all port mappings. NAT can no longer be used.
func ( nat * NAT ) Close ( ) error {
2022-04-01 16:16:46 +00:00
nat . mappingmu . Lock ( )
nat . closed = true
nat . mappingmu . Unlock ( )
2018-07-04 10:51:47 +00:00
2022-04-01 16:16:46 +00:00
nat . ctxCancel ( )
nat . refCount . Wait ( )
return nil
2018-07-04 10:51:47 +00:00
}
2023-06-30 13:41:32 +00:00
func ( nat * NAT ) GetMapping ( protocol string , port int ) ( addr netip . AddrPort , found bool ) {
2018-07-04 10:51:47 +00:00
nat . mappingmu . Lock ( )
2023-06-30 13:41:32 +00:00
defer nat . mappingmu . Unlock ( )
if ! nat . extAddr . IsValid ( ) {
return netip . AddrPort { } , false
2018-07-04 10:51:47 +00:00
}
2023-06-30 13:41:32 +00:00
extPort , found := nat . mappings [ entry { protocol : protocol , port : port } ]
if ! found {
return netip . AddrPort { } , false
}
return netip . AddrPortFrom ( nat . extAddr , uint16 ( extPort ) ) , true
2018-07-04 10:51:47 +00:00
}
2023-06-30 13:41:32 +00:00
// AddMapping attempts to construct a mapping on protocol and internal port.
// It blocks until a mapping was established. Once added, it periodically renews the mapping.
2018-07-04 10:51:47 +00:00
//
// May not succeed, and mappings may change over time;
// NAT devices may not respect our port requests, and even lie.
2023-06-30 13:41:32 +00:00
func ( nat * NAT ) AddMapping ( ctx context . Context , protocol string , port int ) error {
2019-06-09 07:24:20 +00:00
switch protocol {
case "tcp" , "udp" :
2018-07-04 10:51:47 +00:00
default :
2023-06-30 13:41:32 +00:00
return fmt . Errorf ( "invalid protocol: %s" , protocol )
2018-07-04 10:51:47 +00:00
}
2019-06-09 07:24:20 +00:00
2022-04-01 16:16:46 +00:00
nat . mappingmu . Lock ( )
2023-06-30 13:41:32 +00:00
defer nat . mappingmu . Unlock ( )
2022-04-01 16:16:46 +00:00
if nat . closed {
2023-06-30 13:41:32 +00:00
return errors . New ( "closed" )
2022-04-01 16:16:46 +00:00
}
2018-07-04 10:51:47 +00:00
// do it once synchronously, so first mapping is done right away, and before exiting,
// allowing users -- in the optimistic case -- to use results right after.
2023-06-30 13:41:32 +00:00
extPort := nat . establishMapping ( ctx , protocol , port )
nat . mappings [ entry { protocol : protocol , port : port } ] = extPort
return nil
2018-07-04 10:51:47 +00:00
}
2023-06-30 13:41:32 +00:00
// RemoveMapping removes a port mapping.
// It blocks until the NAT has removed the mapping.
func ( nat * NAT ) RemoveMapping ( ctx context . Context , protocol string , port int ) error {
2022-04-01 16:16:46 +00:00
nat . mappingmu . Lock ( )
2023-06-30 13:41:32 +00:00
defer nat . mappingmu . Unlock ( )
switch protocol {
case "tcp" , "udp" :
e := entry { protocol : protocol , port : port }
if _ , ok := nat . mappings [ e ] ; ok {
delete ( nat . mappings , e )
return nat . nat . DeletePortMapping ( ctx , protocol , port )
}
return errors . New ( "unknown mapping" )
default :
return fmt . Errorf ( "invalid protocol: %s" , protocol )
}
2022-04-01 16:16:46 +00:00
}
2023-06-30 13:41:32 +00:00
func ( nat * NAT ) background ( ) {
const mappingUpdate = MappingDuration / 3
now := time . Now ( )
nextMappingUpdate := now . Add ( mappingUpdate )
nextAddrUpdate := now . Add ( CacheTime )
t := time . NewTimer ( minTime ( nextMappingUpdate , nextAddrUpdate ) . Sub ( now ) ) // don't use a ticker here. We don't know how long establishing the mappings takes.
2022-04-01 16:16:46 +00:00
defer t . Stop ( )
2023-06-30 13:41:32 +00:00
var in [ ] entry
var out [ ] int // port numbers
2022-04-01 16:16:46 +00:00
for {
select {
2023-06-30 13:41:32 +00:00
case now := <- t . C :
if now . After ( nextMappingUpdate ) {
in = in [ : 0 ]
out = out [ : 0 ]
nat . mappingmu . Lock ( )
for e := range nat . mappings {
in = append ( in , e )
}
nat . mappingmu . Unlock ( )
// Establishing the mapping involves network requests.
// Don't hold the mutex, just save the ports.
for _ , e := range in {
out = append ( out , nat . establishMapping ( nat . ctx , e . protocol , e . port ) )
}
nat . mappingmu . Lock ( )
for i , p := range in {
if _ , ok := nat . mappings [ p ] ; ! ok {
continue // entry might have been deleted
}
nat . mappings [ p ] = out [ i ]
}
nat . mappingmu . Unlock ( )
nextMappingUpdate = time . Now ( ) . Add ( mappingUpdate )
}
if now . After ( nextAddrUpdate ) {
var extAddr netip . Addr
extIP , err := nat . nat . GetExternalAddress ( )
if err == nil {
extAddr , _ = netip . AddrFromSlice ( extIP )
}
nat . extAddr = extAddr
nextAddrUpdate = time . Now ( ) . Add ( CacheTime )
}
t . Reset ( time . Until ( minTime ( nextAddrUpdate , nextMappingUpdate ) ) )
2022-04-01 16:16:46 +00:00
case <- nat . ctx . Done ( ) :
2023-06-30 13:41:32 +00:00
nat . mappingmu . Lock ( )
ctx , cancel := context . WithTimeout ( context . Background ( ) , 10 * time . Second )
defer cancel ( )
for e := range nat . mappings {
delete ( nat . mappings , e )
nat . nat . DeletePortMapping ( ctx , e . protocol , e . port )
}
nat . mappingmu . Unlock ( )
2022-04-01 16:16:46 +00:00
return
}
}
}
2023-06-30 13:41:32 +00:00
func ( nat * NAT ) establishMapping ( ctx context . Context , protocol string , internalPort int ) ( externalPort int ) {
log . Debugf ( "Attempting port map: %s/%d" , protocol , internalPort )
2022-04-01 16:16:46 +00:00
const comment = "libp2p"
2018-07-04 10:51:47 +00:00
nat . natmu . Lock ( )
2023-06-30 13:41:32 +00:00
var err error
externalPort , err = nat . nat . AddPortMapping ( ctx , protocol , internalPort , comment , MappingDuration )
2018-07-04 10:51:47 +00:00
if err != nil {
// Some hardware does not support mappings with timeout, so try that
2023-06-30 13:41:32 +00:00
externalPort , err = nat . nat . AddPortMapping ( ctx , protocol , internalPort , comment , 0 )
2018-07-04 10:51:47 +00:00
}
nat . natmu . Unlock ( )
2023-06-30 13:41:32 +00:00
if err != nil || externalPort == 0 {
2018-07-04 10:51:47 +00:00
// TODO: log.Event
2022-08-19 16:34:07 +00:00
if err != nil {
log . Warnf ( "failed to establish port mapping: %s" , err )
} else {
log . Warnf ( "failed to establish port mapping: newport = 0" )
}
2018-07-04 10:51:47 +00:00
// we do not close if the mapping failed,
// because it may work again next time.
2023-06-30 13:41:32 +00:00
return 0
2018-07-04 10:51:47 +00:00
}
2023-06-30 13:41:32 +00:00
log . Debugf ( "NAT Mapping: %d --> %d (%s)" , externalPort , internalPort , protocol )
return externalPort
}
func minTime ( a , b time . Time ) time . Time {
if a . Before ( b ) {
return a
2018-07-04 10:51:47 +00:00
}
2023-06-30 13:41:32 +00:00
return b
2018-07-04 10:51:47 +00:00
}