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 }