1
- import { isIPv4 , isIPv6 } from '@chainsafe/is-ip'
1
+ import { isIPv4 } from '@chainsafe/is-ip'
2
2
import { InvalidParametersError , start , stop } from '@libp2p/interface'
3
+ import { isLinkLocal } from '@libp2p/utils/multiaddr/is-link-local'
3
4
import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback'
4
5
import { isPrivate } from '@libp2p/utils/multiaddr/is-private'
5
6
import { isPrivateIp } from '@libp2p/utils/private-ip'
6
7
import { QUICV1 , TCP , WebSockets , WebSocketsSecure , WebTransport } from '@multiformats/multiaddr-matcher'
7
8
import { dynamicExternalAddress } from './check-external-address.js'
8
- import { DoubleNATError , InvalidIPAddressError } from './errors.js'
9
+ import { DoubleNATError } from './errors.js'
9
10
import type { ExternalAddress } from './check-external-address.js'
10
11
import type { Gateway } from '@achingbrain/nat-port-mapper'
11
12
import type { ComponentLogger , Logger } from '@libp2p/interface'
@@ -98,12 +99,20 @@ export class UPnPPortMapper {
98
99
/**
99
100
* Return any eligible multiaddrs that are not mapped on the detected gateway
100
101
*/
101
- private getUnmappedAddresses ( multiaddrs : Multiaddr [ ] , ipType : 4 | 6 ) : Multiaddr [ ] {
102
+ private getUnmappedAddresses ( multiaddrs : Multiaddr [ ] , publicAddresses : string [ ] ) : Multiaddr [ ] {
102
103
const output : Multiaddr [ ] = [ ]
103
104
104
105
for ( const ma of multiaddrs ) {
105
- // ignore public addresses
106
- if ( ! isPrivate ( ma ) ) {
106
+ const stringTuples = ma . stringTuples ( )
107
+ const address = `${ stringTuples [ 0 ] [ 1 ] } `
108
+
109
+ // ignore public IPv4 addresses
110
+ if ( isIPv4 ( address ) && ! isPrivate ( ma ) ) {
111
+ continue
112
+ }
113
+
114
+ // ignore any addresses that match the interface on the network gateway
115
+ if ( publicAddresses . includes ( address ) ) {
107
116
continue
108
117
}
109
118
@@ -112,6 +121,11 @@ export class UPnPPortMapper {
112
121
continue
113
122
}
114
123
124
+ // ignore link-local addresses
125
+ if ( isLinkLocal ( ma ) ) {
126
+ continue
127
+ }
128
+
115
129
// only IP based addresses
116
130
if ( ! (
117
131
TCP . exactMatch ( ma ) ||
@@ -123,13 +137,9 @@ export class UPnPPortMapper {
123
137
continue
124
138
}
125
139
126
- const { port, host , family , transport } = ma . toOptions ( )
140
+ const { port, transport } = ma . toOptions ( )
127
141
128
- if ( family !== ipType ) {
129
- continue
130
- }
131
-
132
- if ( this . mappedPorts . has ( `${ host } -${ port } -${ transport } ` ) ) {
142
+ if ( this . mappedPorts . has ( `${ port } -${ transport } ` ) ) {
133
143
continue
134
144
}
135
145
@@ -143,62 +153,49 @@ export class UPnPPortMapper {
143
153
try {
144
154
const externalHost = await this . externalAddress . getPublicIp ( )
145
155
146
- let ipType : 4 | 6 = 4
147
-
148
- if ( isIPv4 ( externalHost ) ) {
149
- ipType = 4
150
- } else if ( isIPv6 ( externalHost ) ) {
151
- ipType = 6
152
- } else {
153
- throw new InvalidIPAddressError ( `Public address ${ externalHost } was not an IPv4 address` )
154
- }
155
-
156
156
// filter addresses to get private, non-relay, IP based addresses that we
157
157
// haven't mapped yet
158
- const addresses = this . getUnmappedAddresses ( this . addressManager . getAddresses ( ) , ipType )
158
+ const addresses = this . getUnmappedAddresses ( this . addressManager . getAddresses ( ) , [ externalHost ] )
159
159
160
160
if ( addresses . length === 0 ) {
161
161
this . log ( 'no private, non-relay, unmapped, IP based addresses found' )
162
162
return
163
163
}
164
164
165
- this . log ( '%s public IP %s' , this . externalAddress != null ? 'using configured' : 'discovered ', externalHost )
165
+ this . log ( 'discovered public IP %s' , externalHost )
166
166
167
167
this . assertNotBehindDoubleNAT ( externalHost )
168
168
169
169
for ( const addr of addresses ) {
170
170
// try to open uPnP ports for each thin waist address
171
- const { family , host, port , transport } = addr . toOptions ( )
171
+ const { port , host, transport , family } = addr . toOptions ( )
172
172
173
- if ( family === 6 ) {
174
- // only support IPv4 addresses
173
+ // don't try to open port on IPv6 host via IPv4 gateway
174
+ if ( family === 4 && this . gateway . family !== ' IPv4' ) {
175
175
continue
176
176
}
177
177
178
- if ( this . mappedPorts . has ( ` ${ host } - ${ port } - ${ transport } ` ) ) {
179
- // already mapped this port
178
+ // don't try to open port on IPv4 host via IPv6 gateway
179
+ if ( family === 6 && this . gateway . family !== 'IPv6' ) {
180
180
continue
181
181
}
182
182
183
- try {
184
- const key = `${ host } -${ port } -${ transport } `
185
- this . log ( 'creating mapping of key %s' , key )
183
+ const key = `${ host } -${ port } -${ transport } `
186
184
187
- const externalPort = await this . gateway . map ( port , {
188
- localAddress : host ,
189
- protocol : transport === 'tcp' ? 'tcp' : 'udp'
190
- } )
185
+ if ( this . mappedPorts . has ( key ) ) {
186
+ // already mapped this port
187
+ continue
188
+ }
191
189
192
- this . mappedPorts . set ( key , {
193
- externalHost ,
194
- externalPort
190
+ try {
191
+ const mapping = await this . gateway . map ( port , host , {
192
+ protocol : transport === 'tcp' ? 'TCP' : 'UDP'
195
193
} )
196
-
197
- this . log ( 'created mapping of %s:%s to %s:%s' , externalHost , externalPort , host , port )
198
-
199
- this . addressManager . addPublicAddressMapping ( host , port , externalHost , externalPort , transport === 'tcp' ? 'tcp' : 'udp' )
194
+ this . mappedPorts . set ( key , mapping )
195
+ this . addressManager . addPublicAddressMapping ( mapping . internalHost , mapping . internalPort , mapping . externalHost , mapping . externalPort , transport === 'tcp' ? 'tcp' : 'udp' )
196
+ this . log ( 'created mapping of %s:%s to %s:%s for protocol %s' , mapping . internalHost , mapping . internalPort , mapping . externalHost , mapping . externalPort , transport )
200
197
} catch ( err ) {
201
- this . log . error ( 'failed to create mapping of %s:%s - %e' , host , port , err )
198
+ this . log . error ( 'failed to create mapping for %s:%d for protocol - %e' , host , port , transport , err )
202
199
}
203
200
}
204
201
} catch ( err : any ) {
0 commit comments