summaryrefslogtreecommitdiff
path: root/vendor/github.com/pion/transport/vnet/nat.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/pion/transport/vnet/nat.go')
-rw-r--r--vendor/github.com/pion/transport/vnet/nat.go338
1 files changed, 338 insertions, 0 deletions
diff --git a/vendor/github.com/pion/transport/vnet/nat.go b/vendor/github.com/pion/transport/vnet/nat.go
new file mode 100644
index 0000000..4ece5fa
--- /dev/null
+++ b/vendor/github.com/pion/transport/vnet/nat.go
@@ -0,0 +1,338 @@
+package vnet
+
+import (
+ "errors"
+ "fmt"
+ "net"
+ "sync"
+ "time"
+
+ "github.com/pion/logging"
+)
+
+var (
+ errNATRequriesMapping = errors.New("1:1 NAT requires more than one mapping")
+ errMismatchLengthIP = errors.New("length mismtach between mappedIPs and localIPs")
+ errNonUDPTranslationNotSupported = errors.New("non-udp translation is not supported yet")
+ errNoAssociatedLocalAddress = errors.New("no associated local address")
+ errNoNATBindingFound = errors.New("no NAT binding found")
+ errHasNoPermission = errors.New("has no permission")
+)
+
+// EndpointDependencyType defines a type of behavioral dependendency on the
+// remote endpoint's IP address or port number. This is used for the two
+// kinds of behaviors:
+// - Port mapping behavior
+// - Filtering behavior
+// See: https://tools.ietf.org/html/rfc4787
+type EndpointDependencyType uint8
+
+const (
+ // EndpointIndependent means the behavior is independent of the endpoint's address or port
+ EndpointIndependent EndpointDependencyType = iota
+ // EndpointAddrDependent means the behavior is dependent on the endpoint's address
+ EndpointAddrDependent
+ // EndpointAddrPortDependent means the behavior is dependent on the endpoint's address and port
+ EndpointAddrPortDependent
+)
+
+// NATMode defines basic behavior of the NAT
+type NATMode uint8
+
+const (
+ // NATModeNormal means the NAT behaves as a standard NAPT (RFC 2663).
+ NATModeNormal NATMode = iota
+ // NATModeNAT1To1 exhibits 1:1 DNAT where the external IP address is statically mapped to
+ // a specific local IP address with port number is preserved always between them.
+ // When this mode is selected, MappingBehavior, FilteringBehavior, PortPreservation and
+ // MappingLifeTime of NATType are ignored.
+ NATModeNAT1To1
+)
+
+const (
+ defaultNATMappingLifeTime = 30 * time.Second
+)
+
+// NATType has a set of parameters that define the behavior of NAT.
+type NATType struct {
+ Mode NATMode
+ MappingBehavior EndpointDependencyType
+ FilteringBehavior EndpointDependencyType
+ Hairpining bool // Not implemented yet
+ PortPreservation bool // Not implemented yet
+ MappingLifeTime time.Duration
+}
+
+type natConfig struct {
+ name string
+ natType NATType
+ mappedIPs []net.IP // mapped IPv4
+ localIPs []net.IP // local IPv4, required only when the mode is NATModeNAT1To1
+ loggerFactory logging.LoggerFactory
+}
+
+type mapping struct {
+ proto string // "udp" or "tcp"
+ local string // "<local-ip>:<local-port>"
+ mapped string // "<mapped-ip>:<mapped-port>"
+ bound string // key: "[<remote-ip>[:<remote-port>]]"
+ filters map[string]struct{} // key: "[<remote-ip>[:<remote-port>]]"
+ expires time.Time // time to expire
+}
+
+type networkAddressTranslator struct {
+ name string
+ natType NATType
+ mappedIPs []net.IP // mapped IPv4
+ localIPs []net.IP // local IPv4, required only when the mode is NATModeNAT1To1
+ outboundMap map[string]*mapping // key: "<proto>:<local-ip>:<local-port>[:remote-ip[:remote-port]]
+ inboundMap map[string]*mapping // key: "<proto>:<mapped-ip>:<mapped-port>"
+ udpPortCounter int
+ mutex sync.RWMutex
+ log logging.LeveledLogger
+}
+
+func newNAT(config *natConfig) (*networkAddressTranslator, error) {
+ natType := config.natType
+
+ if natType.Mode == NATModeNAT1To1 {
+ // 1:1 NAT behavior
+ natType.MappingBehavior = EndpointIndependent
+ natType.FilteringBehavior = EndpointIndependent
+ natType.PortPreservation = true
+ natType.MappingLifeTime = 0
+
+ if len(config.mappedIPs) == 0 {
+ return nil, errNATRequriesMapping
+ }
+ if len(config.mappedIPs) != len(config.localIPs) {
+ return nil, errMismatchLengthIP
+ }
+ } else {
+ // Normal (NAPT) behavior
+ natType.Mode = NATModeNormal
+ if natType.MappingLifeTime == 0 {
+ natType.MappingLifeTime = defaultNATMappingLifeTime
+ }
+ }
+
+ return &networkAddressTranslator{
+ name: config.name,
+ natType: natType,
+ mappedIPs: config.mappedIPs,
+ localIPs: config.localIPs,
+ outboundMap: map[string]*mapping{},
+ inboundMap: map[string]*mapping{},
+ log: config.loggerFactory.NewLogger("vnet"),
+ }, nil
+}
+
+func (n *networkAddressTranslator) getPairedMappedIP(locIP net.IP) net.IP {
+ for i, ip := range n.localIPs {
+ if ip.Equal(locIP) {
+ return n.mappedIPs[i]
+ }
+ }
+ return nil
+}
+
+func (n *networkAddressTranslator) getPairedLocalIP(mappedIP net.IP) net.IP {
+ for i, ip := range n.mappedIPs {
+ if ip.Equal(mappedIP) {
+ return n.localIPs[i]
+ }
+ }
+ return nil
+}
+
+func (n *networkAddressTranslator) translateOutbound(from Chunk) (Chunk, error) {
+ n.mutex.Lock()
+ defer n.mutex.Unlock()
+
+ to := from.Clone()
+
+ if from.Network() == udpString {
+ if n.natType.Mode == NATModeNAT1To1 {
+ // 1:1 NAT behavior
+ srcAddr := from.SourceAddr().(*net.UDPAddr)
+ srcIP := n.getPairedMappedIP(srcAddr.IP)
+ if srcIP == nil {
+ n.log.Debugf("[%s] drop outbound chunk %s with not route", n.name, from.String())
+ return nil, nil // silently discard
+ }
+ srcPort := srcAddr.Port
+ if err := to.setSourceAddr(fmt.Sprintf("%s:%d", srcIP.String(), srcPort)); err != nil {
+ return nil, err
+ }
+ } else {
+ // Normal (NAPT) behavior
+ var bound, filterKey string
+ switch n.natType.MappingBehavior {
+ case EndpointIndependent:
+ bound = ""
+ case EndpointAddrDependent:
+ bound = from.getDestinationIP().String()
+ case EndpointAddrPortDependent:
+ bound = from.DestinationAddr().String()
+ }
+
+ switch n.natType.FilteringBehavior {
+ case EndpointIndependent:
+ filterKey = ""
+ case EndpointAddrDependent:
+ filterKey = from.getDestinationIP().String()
+ case EndpointAddrPortDependent:
+ filterKey = from.DestinationAddr().String()
+ }
+
+ oKey := fmt.Sprintf("udp:%s:%s", from.SourceAddr().String(), bound)
+
+ m := n.findOutboundMapping(oKey)
+ if m == nil {
+ // Create a new mapping
+ mappedPort := 0xC000 + n.udpPortCounter
+ n.udpPortCounter++
+
+ m = &mapping{
+ proto: from.SourceAddr().Network(),
+ local: from.SourceAddr().String(),
+ bound: bound,
+ mapped: fmt.Sprintf("%s:%d", n.mappedIPs[0].String(), mappedPort),
+ filters: map[string]struct{}{},
+ expires: time.Now().Add(n.natType.MappingLifeTime),
+ }
+
+ n.outboundMap[oKey] = m
+
+ iKey := fmt.Sprintf("udp:%s", m.mapped)
+
+ n.log.Debugf("[%s] created a new NAT binding oKey=%s iKey=%s\n",
+ n.name,
+ oKey,
+ iKey)
+
+ m.filters[filterKey] = struct{}{}
+ n.log.Debugf("[%s] permit access from %s to %s\n", n.name, filterKey, m.mapped)
+ n.inboundMap[iKey] = m
+ } else if _, ok := m.filters[filterKey]; !ok {
+ n.log.Debugf("[%s] permit access from %s to %s\n", n.name, filterKey, m.mapped)
+ m.filters[filterKey] = struct{}{}
+ }
+
+ if err := to.setSourceAddr(m.mapped); err != nil {
+ return nil, err
+ }
+ }
+
+ n.log.Debugf("[%s] translate outbound chunk from %s to %s", n.name, from.String(), to.String())
+
+ return to, nil
+ }
+
+ return nil, errNonUDPTranslationNotSupported
+}
+
+func (n *networkAddressTranslator) translateInbound(from Chunk) (Chunk, error) {
+ n.mutex.Lock()
+ defer n.mutex.Unlock()
+
+ to := from.Clone()
+
+ if from.Network() == udpString {
+ if n.natType.Mode == NATModeNAT1To1 {
+ // 1:1 NAT behavior
+ dstAddr := from.DestinationAddr().(*net.UDPAddr)
+ dstIP := n.getPairedLocalIP(dstAddr.IP)
+ if dstIP == nil {
+ return nil, fmt.Errorf("drop %s as %w", from.String(), errNoAssociatedLocalAddress)
+ }
+ dstPort := from.DestinationAddr().(*net.UDPAddr).Port
+ if err := to.setDestinationAddr(fmt.Sprintf("%s:%d", dstIP, dstPort)); err != nil {
+ return nil, err
+ }
+ } else {
+ // Normal (NAPT) behavior
+ iKey := fmt.Sprintf("udp:%s", from.DestinationAddr().String())
+ m := n.findInboundMapping(iKey)
+ if m == nil {
+ return nil, fmt.Errorf("drop %s as %w", from.String(), errNoNATBindingFound)
+ }
+
+ var filterKey string
+ switch n.natType.FilteringBehavior {
+ case EndpointIndependent:
+ filterKey = ""
+ case EndpointAddrDependent:
+ filterKey = from.getSourceIP().String()
+ case EndpointAddrPortDependent:
+ filterKey = from.SourceAddr().String()
+ }
+
+ if _, ok := m.filters[filterKey]; !ok {
+ return nil, fmt.Errorf("drop %s as the remote %s %w", from.String(), filterKey, errHasNoPermission)
+ }
+
+ // See RFC 4847 Section 4.3. Mapping Refresh
+ // a) Inbound refresh may be useful for applications with no outgoing
+ // UDP traffic. However, allowing inbound refresh may allow an
+ // external attacker or misbehaving application to keep a mapping
+ // alive indefinitely. This may be a security risk. Also, if the
+ // process is repeated with different ports, over time, it could
+ // use up all the ports on the NAT.
+
+ if err := to.setDestinationAddr(m.local); err != nil {
+ return nil, err
+ }
+ }
+
+ n.log.Debugf("[%s] translate inbound chunk from %s to %s", n.name, from.String(), to.String())
+
+ return to, nil
+ }
+
+ return nil, errNonUDPTranslationNotSupported
+}
+
+// caller must hold the mutex
+func (n *networkAddressTranslator) findOutboundMapping(oKey string) *mapping {
+ now := time.Now()
+
+ m, ok := n.outboundMap[oKey]
+ if ok {
+ // check if this mapping is expired
+ if now.After(m.expires) {
+ n.removeMapping(m)
+ m = nil // expired
+ } else {
+ m.expires = time.Now().Add(n.natType.MappingLifeTime)
+ }
+ }
+
+ return m
+}
+
+// caller must hold the mutex
+func (n *networkAddressTranslator) findInboundMapping(iKey string) *mapping {
+ now := time.Now()
+ m, ok := n.inboundMap[iKey]
+ if !ok {
+ return nil
+ }
+
+ // check if this mapping is expired
+ if now.After(m.expires) {
+ n.removeMapping(m)
+ return nil
+ }
+
+ return m
+}
+
+// caller must hold the mutex
+func (n *networkAddressTranslator) removeMapping(m *mapping) {
+ oKey := fmt.Sprintf("%s:%s:%s", m.proto, m.local, m.bound)
+ iKey := fmt.Sprintf("%s:%s", m.proto, m.mapped)
+
+ delete(n.outboundMap, oKey)
+ delete(n.inboundMap, iKey)
+}