Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 36 additions & 17 deletions external_ip_mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ func validateIPString(ipStr string) (net.IP, bool, error) {

// ipMapping holds the mapping of local and external IP address for a particular IP family
type ipMapping struct {
ipSole net.IP // When non-nil, this is the sole external IP for one local IP assumed
ipMap map[string]net.IP // Local-to-external IP mapping (k: local, v: external)
valid bool // If not set any external IP, valid is false
// When non-nil, this is the sole external IP for one local IP assumed
ipSole net.IP
// Local-to-external IP mapping (k: local, v: []external). We allow an
// additional external IP if it matches the local IP.
ipMap map[string][]net.IP
// If not set any external IP, valid is false
valid bool
}

func (m *ipMapping) setSoleIP(ip net.IP) error {
Expand All @@ -41,32 +45,47 @@ func (m *ipMapping) addIPMapping(locIP, extIP net.IP) error {

locIPStr := locIP.String()

// Check if dup of local IP
if _, ok := m.ipMap[locIPStr]; ok {
extIPs, ok := m.ipMap[locIPStr]
// We only allow mapping a local address to either a single external address,
// itself or both.
if ok && len(extIPs) > 1 {
return ErrInvalidNAT1To1IPMapping
}

m.ipMap[locIPStr] = extIP
// De-duplication check.
for _, ip := range extIPs {
// If the address is external we only allow one.
if locIPStr != extIP.String() && ip.String() != locIPStr {
return ErrInvalidNAT1To1IPMapping
}

// Otherwise the local IP can only map to itself once.
if ip.String() == extIP.String() {
return ErrInvalidNAT1To1IPMapping
}
}

m.ipMap[locIPStr] = append(m.ipMap[locIPStr], extIP)
m.valid = true

return nil
}

func (m *ipMapping) findExternalIP(locIP net.IP) (net.IP, error) {
func (m *ipMapping) findExternalIPs(locIP net.IP) ([]net.IP, error) {
if !m.valid {
return locIP, nil
return []net.IP{locIP}, nil
}

if m.ipSole != nil {
return m.ipSole, nil
return []net.IP{m.ipSole}, nil
}

extIP, ok := m.ipMap[locIP.String()]
if !ok {
extIPs, ok := m.ipMap[locIP.String()]
if !ok || len(extIPs) == 0 {
return nil, ErrExternalMappedIPNotFound
}

return extIP, nil
return extIPs, nil
}

type externalIPMapper struct {
Expand All @@ -86,8 +105,8 @@ func newExternalIPMapper(candidateType CandidateType, ips []string) (*externalIP
}

m := &externalIPMapper{
ipv4Mapping: ipMapping{ipMap: map[string]net.IP{}},
ipv6Mapping: ipMapping{ipMap: map[string]net.IP{}},
ipv4Mapping: ipMapping{ipMap: map[string][]net.IP{}},
ipv6Mapping: ipMapping{ipMap: map[string][]net.IP{}},
candidateType: candidateType,
}

Expand Down Expand Up @@ -139,15 +158,15 @@ func newExternalIPMapper(candidateType CandidateType, ips []string) (*externalIP
return m, nil
}

func (m *externalIPMapper) findExternalIP(localIPStr string) (net.IP, error) {
func (m *externalIPMapper) findExternalIPs(localIPStr string) ([]net.IP, error) {
locIP, isLocIPv4, err := validateIPString(localIPStr)
if err != nil {
return nil, err
}

if isLocIPv4 {
return m.ipv4Mapping.findExternalIP(locIP)
return m.ipv4Mapping.findExternalIPs(locIP)
}

return m.ipv6Mapping.findExternalIP(locIP)
return m.ipv6Mapping.findExternalIPs(locIP)
}
62 changes: 39 additions & 23 deletions external_ip_mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,22 @@ func TestExternalIPMapper(t *testing.T) {
})
assert.Error(t, err, "should fail")
assert.Nil(t, m, "should be nil")

// Cannot have duplicates local IPv4
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"10.0.0.1/10.0.0.1",
"10.0.0.1/10.0.0.1",
})
assert.Error(t, err, "should fail")
assert.Nil(t, m, "should be nil")

// Cannot have duplicates local IPv6
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"2200::1/fe80::1",
"2200::1/fe80::1",
})
assert.Error(t, err, "should fail")
assert.Nil(t, m, "should be nil")
})

t.Run("newExternalIPMapper with implicit and explicit local IP", func(t *testing.T) {
Expand All @@ -208,10 +224,10 @@ func TestExternalIPMapper(t *testing.T) {
assert.Error(t, err, "should fail")
})

t.Run("findExternalIP without explicit local IP", func(t *testing.T) {
t.Run("findExternalIPs without explicit local IP", func(t *testing.T) {
var m *externalIPMapper
var err error
var extIP net.IP
var extIPs []net.IP

// IPv4 with explicit local IP, defaults to CandidateTypeHost
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
Expand All @@ -224,24 +240,24 @@ func TestExternalIPMapper(t *testing.T) {
assert.NotNil(t, m.ipv6Mapping.ipSole)

// Find external IPv4
extIP, err = m.findExternalIP("10.0.0.1")
extIPs, err = m.findExternalIPs("10.0.0.1")
assert.NoError(t, err, "should succeed")
assert.Equal(t, "1.2.3.4", extIP.String(), "should match")
assert.Equal(t, "1.2.3.4", extIPs[0].String(), "should match")

// Find external IPv6
extIP, err = m.findExternalIP("fe80::0001") // Use '0001' instead of '1' on purpose
extIPs, err = m.findExternalIPs("fe80::0001") // Use '0001' instead of '1' on purpose
assert.NoError(t, err, "should succeed")
assert.Equal(t, "2200::1", extIP.String(), "should match")
assert.Equal(t, "2200::1", extIPs[0].String(), "should match")

// Bad local IP string
_, err = m.findExternalIP("really.bad")
_, err = m.findExternalIPs("really.bad")
assert.Error(t, err, "should fail")
})

t.Run("findExternalIP with explicit local IP", func(t *testing.T) {
var m *externalIPMapper
var err error
var extIP net.IP
var extIPs []net.IP

// IPv4 with explicit local IP, defaults to CandidateTypeHost
m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
Expand All @@ -254,31 +270,31 @@ func TestExternalIPMapper(t *testing.T) {
assert.NotNil(t, m, "should not be nil")

// Find external IPv4
extIP, err = m.findExternalIP("10.0.0.1")
extIPs, err = m.findExternalIPs("10.0.0.1")
assert.NoError(t, err, "should succeed")
assert.Equal(t, "1.2.3.4", extIP.String(), "should match")
assert.Equal(t, "1.2.3.4", extIPs[0].String(), "should match")

extIP, err = m.findExternalIP("10.0.0.2")
extIPs, err = m.findExternalIPs("10.0.0.2")
assert.NoError(t, err, "should succeed")
assert.Equal(t, "1.2.3.5", extIP.String(), "should match")
assert.Equal(t, "1.2.3.5", extIPs[0].String(), "should match")

_, err = m.findExternalIP("10.0.0.3")
_, err = m.findExternalIPs("10.0.0.3")
assert.Error(t, err, "should fail")

// Find external IPv6
extIP, err = m.findExternalIP("fe80::0001") // Use '0001' instead of '1' on purpose
extIPs, err = m.findExternalIPs("fe80::0001") // Use '0001' instead of '1' on purpose
assert.NoError(t, err, "should succeed")
assert.Equal(t, "2200::1", extIP.String(), "should match")
assert.Equal(t, "2200::1", extIPs[0].String(), "should match")

extIP, err = m.findExternalIP("fe80::0002") // Use '0002' instead of '2' on purpose
extIPs, err = m.findExternalIPs("fe80::0002") // Use '0002' instead of '2' on purpose
assert.NoError(t, err, "should succeed")
assert.Equal(t, "2200::2", extIP.String(), "should match")
assert.Equal(t, "2200::2", extIPs[0].String(), "should match")

_, err = m.findExternalIP("fe80::3")
_, err = m.findExternalIPs("fe80::3")
assert.Error(t, err, "should fail")

// Bad local IP string
_, err = m.findExternalIP("really.bad")
_, err = m.findExternalIPs("really.bad")
assert.Error(t, err, "should fail")
})

Expand All @@ -292,18 +308,18 @@ func TestExternalIPMapper(t *testing.T) {
assert.NoError(t, err, "should succeed")

// Attempt to find IPv6 that does not exist in the map
extIP, err := m.findExternalIP("fe80::1")
extIPs, err := m.findExternalIPs("fe80::1")
assert.NoError(t, err, "should succeed")
assert.Equal(t, "fe80::1", extIP.String(), "should match")
assert.Equal(t, "fe80::1", extIPs[0].String(), "should match")

m, err = newExternalIPMapper(CandidateTypeUnspecified, []string{
"2200::1",
})
assert.NoError(t, err, "should succeed")

// Attempt to find IPv4 that does not exist in the map
extIP, err = m.findExternalIP("10.0.0.1")
extIPs, err = m.findExternalIPs("10.0.0.1")
assert.NoError(t, err, "should succeed")
assert.Equal(t, "10.0.0.1", extIP.String(), "should match")
assert.Equal(t, "10.0.0.1", extIPs[0].String(), "should match")
})
}
Loading