Skip to content

Commit 55e2272

Browse files
authored
Refactor NAT to support TCP, fix typos (#388)
1 parent a4a04ed commit 55e2272

2 files changed

Lines changed: 711 additions & 860 deletions

File tree

vnet/nat.go

Lines changed: 173 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import (
1414
)
1515

1616
var (
17-
errNATRequriesMapping = errors.New("1:1 NAT requires more than one mapping")
18-
errMismatchLengthIP = errors.New("length mismtach between mappedIPs and localIPs")
19-
errNonUDPTranslationNotSupported = errors.New("non-udp translation is not supported yet")
20-
errNoAssociatedLocalAddress = errors.New("no associated local address")
21-
errNoNATBindingFound = errors.New("no NAT binding found")
22-
errHasNoPermission = errors.New("has no permission")
17+
errNATRequiresMapping = errors.New("1:1 NAT requires at least one mapping")
18+
errMismatchLengthIP = errors.New("length mismatch between mappedIPs and localIPs")
19+
errTranslationNotSupported = errors.New("translation is not supported for this protocol")
20+
errNoAssociatedLocalAddress = errors.New("no associated local address")
21+
errNoNATBindingFound = errors.New("no NAT binding found")
22+
errHasNoPermission = errors.New("has no permission")
2323
)
2424

2525
// EndpointDependencyType defines a type of behavioral dependendency on the
@@ -92,6 +92,7 @@ type networkAddressTranslator struct {
9292
outboundMap map[string]*mapping // key: "<proto>:<local-ip>:<local-port>[:remote-ip[:remote-port]]
9393
inboundMap map[string]*mapping // key: "<proto>:<mapped-ip>:<mapped-port>"
9494
udpPortCounter int
95+
tcpPortCounter int
9596
mutex sync.RWMutex
9697
log logging.LeveledLogger
9798
}
@@ -107,7 +108,7 @@ func newNAT(config *natConfig) (*networkAddressTranslator, error) {
107108
natType.MappingLifeTime = 0
108109

109110
if len(config.mappedIPs) == 0 {
110-
return nil, errNATRequriesMapping
111+
return nil, errNATRequiresMapping
111112
}
112113
if len(config.mappedIPs) != len(config.localIPs) {
113114
return nil, errMismatchLengthIP
@@ -151,13 +152,73 @@ func (n *networkAddressTranslator) getPairedLocalIP(mappedIP net.IP) net.IP {
151152
return nil
152153
}
153154

154-
func (n *networkAddressTranslator) translateOutbound(from Chunk) (Chunk, error) { //nolint:cyclop
155+
func (n *networkAddressTranslator) translateOutbound(from Chunk) (Chunk, error) { //nolint:cyclop,gocognit
155156
n.mutex.Lock()
156157
defer n.mutex.Unlock()
157158

158159
to := from.Clone()
159160

160-
if from.Network() == udp { //nolint:nestif
161+
translateOutboundNAPT := func(proto string, portBase int, portCounter *int) (Chunk, error) {
162+
var bound, filterKey string
163+
switch n.natType.MappingBehavior {
164+
case EndpointIndependent:
165+
bound = ""
166+
case EndpointAddrDependent:
167+
bound = from.getDestinationIP().String()
168+
case EndpointAddrPortDependent:
169+
bound = from.DestinationAddr().String()
170+
}
171+
172+
switch n.natType.FilteringBehavior {
173+
case EndpointIndependent:
174+
filterKey = ""
175+
case EndpointAddrDependent:
176+
filterKey = from.getDestinationIP().String()
177+
case EndpointAddrPortDependent:
178+
filterKey = from.DestinationAddr().String()
179+
}
180+
181+
oKey := fmt.Sprintf("%s:%s:%s", proto, from.SourceAddr().String(), bound)
182+
183+
mapp := n.findOutboundMapping(oKey)
184+
if mapp == nil {
185+
mappedPort := portBase + *portCounter
186+
if mappedPort > 65535 {
187+
return nil, errPortSpaceExhausted
188+
}
189+
(*portCounter)++
190+
191+
mapp = &mapping{
192+
proto: from.SourceAddr().Network(),
193+
local: from.SourceAddr().String(),
194+
bound: bound,
195+
mapped: fmt.Sprintf("%s:%d", n.mappedIPs[0].String(), mappedPort),
196+
filters: map[string]struct{}{},
197+
expires: time.Now().Add(n.natType.MappingLifeTime),
198+
}
199+
200+
n.outboundMap[oKey] = mapp
201+
iKey := fmt.Sprintf("%s:%s", proto, mapp.mapped)
202+
203+
n.log.Debugf("[%s] created a new NAT binding oKey=%s iKey=%s", n.name, oKey, iKey)
204+
205+
mapp.filters[filterKey] = struct{}{}
206+
n.log.Debugf("[%s] permit access from %s to %s", n.name, filterKey, mapp.mapped)
207+
n.inboundMap[iKey] = mapp
208+
} else if _, ok := mapp.filters[filterKey]; !ok {
209+
n.log.Debugf("[%s] permit access from %s to %s", n.name, filterKey, mapp.mapped)
210+
mapp.filters[filterKey] = struct{}{}
211+
}
212+
213+
if err := to.setSourceAddr(mapp.mapped); err != nil {
214+
return nil, err
215+
}
216+
217+
return to, nil
218+
}
219+
220+
switch from.Network() {
221+
case udp:
161222
if n.natType.Mode == NATModeNAT1To1 {
162223
// 1:1 NAT behavior
163224
srcAddr := from.SourceAddr().(*net.UDPAddr) //nolint:forcetypeassert
@@ -172,132 +233,145 @@ func (n *networkAddressTranslator) translateOutbound(from Chunk) (Chunk, error)
172233
return nil, err
173234
}
174235
} else {
175-
// Normal (NAPT) behavior
176-
var bound, filterKey string
177-
switch n.natType.MappingBehavior {
178-
case EndpointIndependent:
179-
bound = ""
180-
case EndpointAddrDependent:
181-
bound = from.getDestinationIP().String()
182-
case EndpointAddrPortDependent:
183-
bound = from.DestinationAddr().String()
236+
var err error
237+
to, err = translateOutboundNAPT("udp", 0xC000, &n.udpPortCounter)
238+
if err != nil {
239+
return nil, err
184240
}
241+
}
185242

186-
switch n.natType.FilteringBehavior {
187-
case EndpointIndependent:
188-
filterKey = ""
189-
case EndpointAddrDependent:
190-
filterKey = from.getDestinationIP().String()
191-
case EndpointAddrPortDependent:
192-
filterKey = from.DestinationAddr().String()
193-
}
243+
n.log.Debugf("[%s] translate outbound chunk from %s to %s", n.name, from.String(), to.String())
194244

195-
oKey := fmt.Sprintf("udp:%s:%s", from.SourceAddr().String(), bound)
196-
197-
mapp := n.findOutboundMapping(oKey)
198-
if mapp == nil {
199-
// Create a new mapping
200-
mappedPort := 0xC000 + n.udpPortCounter
201-
n.udpPortCounter++
202-
203-
mapp = &mapping{
204-
proto: from.SourceAddr().Network(),
205-
local: from.SourceAddr().String(),
206-
bound: bound,
207-
mapped: fmt.Sprintf("%s:%d", n.mappedIPs[0].String(), mappedPort),
208-
filters: map[string]struct{}{},
209-
expires: time.Now().Add(n.natType.MappingLifeTime),
210-
}
211-
212-
n.outboundMap[oKey] = mapp
213-
214-
iKey := fmt.Sprintf("udp:%s", mapp.mapped)
215-
216-
n.log.Debugf("[%s] created a new NAT binding oKey=%s iKey=%s",
217-
n.name,
218-
oKey,
219-
iKey)
220-
221-
mapp.filters[filterKey] = struct{}{}
222-
n.log.Debugf("[%s] permit access from %s to %s", n.name, filterKey, mapp.mapped)
223-
n.inboundMap[iKey] = mapp
224-
} else if _, ok := mapp.filters[filterKey]; !ok {
225-
n.log.Debugf("[%s] permit access from %s to %s", n.name, filterKey, mapp.mapped)
226-
mapp.filters[filterKey] = struct{}{}
227-
}
245+
return to, nil
228246

229-
if err := to.setSourceAddr(mapp.mapped); err != nil {
247+
case tcp:
248+
if n.natType.Mode == NATModeNAT1To1 {
249+
srcAddr := from.SourceAddr().(*net.TCPAddr) //nolint:forcetypeassert
250+
srcIP := n.getPairedMappedIP(srcAddr.IP)
251+
if srcIP == nil {
252+
n.log.Debugf("[%s] drop outbound chunk %s with not route", n.name, from.String())
253+
254+
return nil, nil // nolint:nilnil
255+
}
256+
srcPort := srcAddr.Port
257+
if err := to.setSourceAddr(fmt.Sprintf("%s:%d", srcIP.String(), srcPort)); err != nil {
258+
return nil, err
259+
}
260+
} else {
261+
var err error
262+
to, err = translateOutboundNAPT("tcp", 0x8000, &n.tcpPortCounter)
263+
if err != nil {
230264
return nil, err
231265
}
232266
}
233267

234268
n.log.Debugf("[%s] translate outbound chunk from %s to %s", n.name, from.String(), to.String())
235269

236270
return to, nil
237-
}
238271

239-
return nil, errNonUDPTranslationNotSupported
272+
default:
273+
return nil, errTranslationNotSupported
274+
}
240275
}
241276

242-
func (n *networkAddressTranslator) translateInbound(from Chunk) (Chunk, error) { //nolint:cyclop
277+
func (n *networkAddressTranslator) translateInbound(from Chunk) (Chunk, error) { //nolint:cyclop,gocognit
243278
n.mutex.Lock()
244279
defer n.mutex.Unlock()
245280

246281
to := from.Clone()
247282

248-
if from.Network() == udp { //nolint:nestif
283+
translateInboundNAT1To1 := func(dstPort int) (Chunk, error) {
284+
dstIP := n.getPairedLocalIP(from.getDestinationIP())
285+
if dstIP == nil {
286+
return nil, fmt.Errorf("drop %s as %w", from.String(), errNoAssociatedLocalAddress)
287+
}
288+
if err := to.setDestinationAddr(fmt.Sprintf("%s:%d", dstIP, dstPort)); err != nil {
289+
return nil, err
290+
}
291+
292+
return to, nil
293+
}
294+
295+
translateInboundNAPT := func(proto string) (Chunk, error) {
296+
iKey := fmt.Sprintf("%s:%s", proto, from.DestinationAddr().String())
297+
mapp := n.findInboundMapping(iKey)
298+
if mapp == nil {
299+
return nil, fmt.Errorf("drop %s as %w", from.String(), errNoNATBindingFound)
300+
}
301+
302+
var filterKey string
303+
switch n.natType.FilteringBehavior {
304+
case EndpointIndependent:
305+
filterKey = ""
306+
case EndpointAddrDependent:
307+
filterKey = from.getSourceIP().String()
308+
case EndpointAddrPortDependent:
309+
filterKey = from.SourceAddr().String()
310+
}
311+
312+
if _, ok := mapp.filters[filterKey]; !ok {
313+
return nil, fmt.Errorf("drop %s as the remote %s %w", from.String(), filterKey, errHasNoPermission)
314+
}
315+
316+
// See RFC 4847 Section 4.3. Mapping Refresh
317+
// a) Inbound refresh may be useful for applications with no outgoing
318+
// UDP traffic. However, allowing inbound refresh may allow an
319+
// external attacker or misbehaving application to keep a mapping
320+
// alive indefinitely. This may be a security risk. Also, if the
321+
// process is repeated with different ports, over time, it could
322+
// use up all the ports on the NAT.
323+
324+
if err := to.setDestinationAddr(mapp.local); err != nil {
325+
return nil, err
326+
}
327+
328+
return to, nil
329+
}
330+
331+
switch from.Network() {
332+
case udp:
249333
if n.natType.Mode == NATModeNAT1To1 {
250-
// 1:1 NAT behavior
251334
dstAddr := from.DestinationAddr().(*net.UDPAddr) //nolint:forcetypeassert
252-
dstIP := n.getPairedLocalIP(dstAddr.IP)
253-
if dstIP == nil {
254-
return nil, fmt.Errorf("drop %s as %w", from.String(), errNoAssociatedLocalAddress)
255-
}
256-
dstPort := from.DestinationAddr().(*net.UDPAddr).Port //nolint:forcetypeassert
257-
if err := to.setDestinationAddr(fmt.Sprintf("%s:%d", dstIP, dstPort)); err != nil {
335+
var err error
336+
to, err = translateInboundNAT1To1(dstAddr.Port)
337+
if err != nil {
258338
return nil, err
259339
}
260340
} else {
261-
// Normal (NAPT) behavior
262-
iKey := fmt.Sprintf("udp:%s", from.DestinationAddr().String())
263-
mapping := n.findInboundMapping(iKey)
264-
if mapping == nil {
265-
return nil, fmt.Errorf("drop %s as %w", from.String(), errNoNATBindingFound)
266-
}
267-
268-
var filterKey string
269-
switch n.natType.FilteringBehavior {
270-
case EndpointIndependent:
271-
filterKey = ""
272-
case EndpointAddrDependent:
273-
filterKey = from.getSourceIP().String()
274-
case EndpointAddrPortDependent:
275-
filterKey = from.SourceAddr().String()
341+
var err error
342+
to, err = translateInboundNAPT(udp)
343+
if err != nil {
344+
return nil, err
276345
}
346+
}
277347

278-
if _, ok := mapping.filters[filterKey]; !ok {
279-
return nil, fmt.Errorf("drop %s as the remote %s %w", from.String(), filterKey, errHasNoPermission)
280-
}
348+
n.log.Debugf("[%s] translate inbound chunk from %s to %s", n.name, from.String(), to.String())
281349

282-
// See RFC 4847 Section 4.3. Mapping Refresh
283-
// a) Inbound refresh may be useful for applications with no outgoing
284-
// UDP traffic. However, allowing inbound refresh may allow an
285-
// external attacker or misbehaving application to keep a mapping
286-
// alive indefinitely. This may be a security risk. Also, if the
287-
// process is repeated with different ports, over time, it could
288-
// use up all the ports on the NAT.
350+
return to, nil
289351

290-
if err := to.setDestinationAddr(mapping.local); err != nil {
352+
case tcp:
353+
if n.natType.Mode == NATModeNAT1To1 {
354+
dstAddr := from.DestinationAddr().(*net.TCPAddr) //nolint:forcetypeassert
355+
var err error
356+
to, err = translateInboundNAT1To1(dstAddr.Port)
357+
if err != nil {
358+
return nil, err
359+
}
360+
} else {
361+
var err error
362+
to, err = translateInboundNAPT(tcp)
363+
if err != nil {
291364
return nil, err
292365
}
293366
}
294367

295368
n.log.Debugf("[%s] translate inbound chunk from %s to %s", n.name, from.String(), to.String())
296369

297370
return to, nil
298-
}
299371

300-
return nil, errNonUDPTranslationNotSupported
372+
default:
373+
return nil, errTranslationNotSupported
374+
}
301375
}
302376

303377
// caller must hold the mutex.

0 commit comments

Comments
 (0)