Skip to content

Commit fa125a3

Browse files
authored
Merge pull request #2604 from arkodg/fix-port-forwarding
Fix IPv6 Port Forwarding for the Bridge Driver
2 parents 5c6a95b + 28576a4 commit fa125a3

10 files changed

Lines changed: 210 additions & 68 deletions

File tree

cmd/proxy/proxy.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ import (
88
"github.com/ishidawataru/sctp"
99
)
1010

11+
// ipVersion refers to IP version - v4 or v6
12+
type ipVersion string
13+
14+
const (
15+
// IPv4 is version 4
16+
ipv4 ipVersion = "4"
17+
// IPv4 is version 6
18+
ipv6 ipVersion = "6"
19+
)
20+
1121
// Proxy defines the behavior of a proxy. It forwards traffic back and forth
1222
// between two endpoints : the frontend and the backend.
1323
// It can be used to do software port-mapping between two addresses.

cmd/proxy/sctp_proxy.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ type SCTPProxy struct {
1919

2020
// NewSCTPProxy creates a new SCTPProxy.
2121
func NewSCTPProxy(frontendAddr, backendAddr *sctp.SCTPAddr) (*SCTPProxy, error) {
22-
listener, err := sctp.ListenSCTP("sctp", frontendAddr)
22+
// detect version of hostIP to bind only to correct version
23+
ipVersion := ipv4
24+
if frontendAddr.IPAddrs[0].IP.To4() == nil {
25+
ipVersion = ipv6
26+
}
27+
listener, err := sctp.ListenSCTP("sctp"+string(ipVersion), frontendAddr)
2328
if err != nil {
2429
return nil, err
2530
}

cmd/proxy/tcp_proxy.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ type TCPProxy struct {
1717

1818
// NewTCPProxy creates a new TCPProxy.
1919
func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) {
20-
listener, err := net.ListenTCP("tcp", frontendAddr)
20+
// detect version of hostIP to bind only to correct version
21+
ipVersion := ipv4
22+
if frontendAddr.IP.To4() == nil {
23+
ipVersion = ipv6
24+
}
25+
listener, err := net.ListenTCP("tcp"+string(ipVersion), frontendAddr)
2126
if err != nil {
2227
return nil, err
2328
}

cmd/proxy/udp_proxy.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ type UDPProxy struct {
5555

5656
// NewUDPProxy creates a new UDPProxy.
5757
func NewUDPProxy(frontendAddr, backendAddr *net.UDPAddr) (*UDPProxy, error) {
58-
listener, err := net.ListenUDP("udp", frontendAddr)
58+
// detect version of hostIP to bind only to correct version
59+
ipVersion := ipv4
60+
if frontendAddr.IP.To4() == nil {
61+
ipVersion = ipv6
62+
}
63+
listener, err := net.ListenUDP("udp"+string(ipVersion), frontendAddr)
5964
if err != nil {
6065
return nil, err
6166
}

drivers/bridge/port_mapping.go

Lines changed: 81 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,71 +11,113 @@ import (
1111
"github.com/sirupsen/logrus"
1212
)
1313

14-
var (
15-
defaultBindingIP = net.IPv4(0, 0, 0, 0)
16-
defaultBindingIPV6 = net.ParseIP("::")
17-
)
18-
1914
func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
2015
if ep.extConnConfig == nil || ep.extConnConfig.PortBindings == nil {
2116
return nil, nil
2217
}
2318

24-
defHostIP := defaultBindingIP
19+
defHostIP := net.IPv4zero // 0.0.0.0
2520
if reqDefBindIP != nil {
2621
defHostIP = reqDefBindIP
2722
}
2823

29-
// IPv4 port binding including user land proxy
30-
pb, err := n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addr.IP, defHostIP, ulPxyEnabled)
31-
if err != nil {
32-
return nil, err
24+
var containerIPv6 net.IP
25+
if ep.addrv6 != nil {
26+
containerIPv6 = ep.addrv6.IP
3327
}
3428

35-
// IPv6 port binding excluding user land proxy
36-
if n.driver.config.EnableIP6Tables && ep.addrv6 != nil {
37-
// TODO IPv6 custom default binding IP
38-
pbv6, err := n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addrv6.IP, defaultBindingIPV6, false)
39-
if err != nil {
40-
// ensure we clear the previous allocated IPv4 ports
41-
n.releasePortsInternal(pb)
42-
return nil, err
43-
}
44-
45-
pb = append(pb, pbv6...)
29+
pb, err := n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addr.IP, containerIPv6, defHostIP, ulPxyEnabled)
30+
if err != nil {
31+
return nil, err
4632
}
4733
return pb, nil
4834
}
4935

50-
func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
36+
func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIPv4, containerIPv6, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
5137
bs := make([]types.PortBinding, 0, len(bindings))
5238
for _, c := range bindings {
53-
b := c.GetCopy()
54-
if err := n.allocatePort(&b, containerIP, defHostIP, ulPxyEnabled); err != nil {
55-
// On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
56-
if cuErr := n.releasePortsInternal(bs); cuErr != nil {
57-
logrus.Warnf("Upon allocation failure for %v, failed to clear previously allocated port bindings: %v", b, cuErr)
39+
bIPv4 := c.GetCopy()
40+
bIPv6 := c.GetCopy()
41+
// Allocate IPv4 Port mappings
42+
if ok := n.validatePortBindingIPv4(&bIPv4, containerIPv4, defHostIP); ok {
43+
if err := n.allocatePort(&bIPv4, ulPxyEnabled); err != nil {
44+
// On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
45+
if cuErr := n.releasePortsInternal(bs); cuErr != nil {
46+
logrus.Warnf("allocation failure for %v, failed to clear previously allocated ipv4 port bindings: %v", bIPv4, cuErr)
47+
}
48+
return nil, err
5849
}
59-
return nil, err
50+
bs = append(bs, bIPv4)
51+
}
52+
// Allocate IPv6 Port mappings
53+
if ok := n.validatePortBindingIPv6(&bIPv6, containerIPv6, defHostIP); ok {
54+
if err := n.allocatePort(&bIPv6, ulPxyEnabled); err != nil {
55+
// On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
56+
if cuErr := n.releasePortsInternal(bs); cuErr != nil {
57+
logrus.Warnf("allocation failure for %v, failed to clear previously allocated ipv6 port bindings: %v", bIPv6, cuErr)
58+
}
59+
return nil, err
60+
}
61+
bs = append(bs, bIPv6)
6062
}
61-
bs = append(bs, b)
6263
}
6364
return bs, nil
6465
}
6566

66-
func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) error {
67-
var (
68-
host net.Addr
69-
err error
70-
)
71-
72-
// Store the container interface address in the operational binding
73-
bnd.IP = containerIP
74-
67+
// validatePortBindingIPv4 validates the port binding, populates the missing Host IP field and returns true
68+
// if this is a valid IPv4 binding, else returns false
69+
func (n *bridgeNetwork) validatePortBindingIPv4(bnd *types.PortBinding, containerIPv4, defHostIP net.IP) bool {
70+
//Return early if there is a valid Host IP, but its not a IPv6 address
71+
if len(bnd.HostIP) > 0 && bnd.HostIP.To4() == nil {
72+
return false
73+
}
7574
// Adjust the host address in the operational binding
7675
if len(bnd.HostIP) == 0 {
76+
// Return early if the default binding address is an IPv6 address
77+
if defHostIP.To4() == nil {
78+
return false
79+
}
7780
bnd.HostIP = defHostIP
7881
}
82+
bnd.IP = containerIPv4
83+
return true
84+
85+
}
86+
87+
// validatePortBindingIPv6 validates the port binding, populates the missing Host IP field and returns true
88+
// if this is a valid IP6v binding, else returns false
89+
func (n *bridgeNetwork) validatePortBindingIPv6(bnd *types.PortBinding, containerIPv6, defHostIP net.IP) bool {
90+
// Return early if there is no IPv6 container endpoint
91+
if containerIPv6 == nil {
92+
return false
93+
}
94+
// Return early if there is a valid Host IP, which is a IPv4 address
95+
if len(bnd.HostIP) > 0 && bnd.HostIP.To4() != nil {
96+
return false
97+
}
98+
99+
// Setup a binding to "::" if Host IP is empty and the default binding IP is 0.0.0.0
100+
if len(bnd.HostIP) == 0 {
101+
if defHostIP.Equal(net.IPv4zero) {
102+
bnd.HostIP = net.IPv6zero
103+
// If the default binding IP is an IPv6 address, use it
104+
} else if defHostIP.To4() == nil {
105+
bnd.HostIP = defHostIP
106+
// Return false if default binding ip is an IPv4 address
107+
} else {
108+
return false
109+
}
110+
}
111+
bnd.IP = containerIPv6
112+
return true
113+
114+
}
115+
116+
func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, ulPxyEnabled bool) error {
117+
var (
118+
host net.Addr
119+
err error
120+
)
79121

80122
// Adjust HostPortEnd if this is not a range.
81123
if bnd.HostPortEnd == 0 {
@@ -90,7 +132,7 @@ func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, containerIP, defHos
90132

91133
portmapper := n.portMapper
92134

93-
if containerIP.To4() == nil {
135+
if bnd.IP.To4() == nil {
94136
portmapper = n.portMapperV6
95137
}
96138

drivers/bridge/port_mapping_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,74 @@ func TestPortMappingConfig(t *testing.T) {
9595
t.Fatal(err)
9696
}
9797
}
98+
99+
func TestPortMappingV6Config(t *testing.T) {
100+
defer testutils.SetupTestOSContext(t)()
101+
d := newDriver()
102+
103+
config := &configuration{
104+
EnableIPTables: true,
105+
EnableIP6Tables: true,
106+
}
107+
genericOption := make(map[string]interface{})
108+
genericOption[netlabel.GenericData] = config
109+
110+
if err := d.configure(genericOption); err != nil {
111+
t.Fatalf("Failed to setup driver config: %v", err)
112+
}
113+
114+
portBindings := []types.PortBinding{
115+
{Proto: types.UDP, Port: uint16(400), HostPort: uint16(54000)},
116+
{Proto: types.TCP, Port: uint16(500), HostPort: uint16(65000)},
117+
{Proto: types.SCTP, Port: uint16(500), HostPort: uint16(65000)},
118+
}
119+
120+
sbOptions := make(map[string]interface{})
121+
sbOptions[netlabel.PortMap] = portBindings
122+
netConfig := &networkConfiguration{
123+
BridgeName: DefaultBridgeName,
124+
EnableIPv6: true,
125+
}
126+
netOptions := make(map[string]interface{})
127+
netOptions[netlabel.GenericData] = netConfig
128+
129+
ipdList := getIPv4Data(t, "")
130+
err := d.CreateNetwork("dummy", netOptions, nil, ipdList, nil)
131+
if err != nil {
132+
t.Fatalf("Failed to create bridge: %v", err)
133+
}
134+
135+
te := newTestEndpoint(ipdList[0].Pool, 11)
136+
err = d.CreateEndpoint("dummy", "ep1", te.Interface(), nil)
137+
if err != nil {
138+
t.Fatalf("Failed to create the endpoint: %s", err.Error())
139+
}
140+
141+
if err = d.Join("dummy", "ep1", "sbox", te, sbOptions); err != nil {
142+
t.Fatalf("Failed to join the endpoint: %v", err)
143+
}
144+
145+
if err = d.ProgramExternalConnectivity("dummy", "ep1", sbOptions); err != nil {
146+
t.Fatalf("Failed to program external connectivity: %v", err)
147+
}
148+
149+
network, ok := d.networks["dummy"]
150+
if !ok {
151+
t.Fatalf("Cannot find network %s inside driver", "dummy")
152+
}
153+
ep, _ := network.endpoints["ep1"]
154+
if len(ep.portMapping) != 6 {
155+
t.Fatalf("Failed to store the port bindings into the sandbox info. Found: %v", ep.portMapping)
156+
}
157+
158+
// release host mapped ports
159+
err = d.Leave("dummy", "ep1")
160+
if err != nil {
161+
t.Fatal(err)
162+
}
163+
164+
err = d.RevokeExternalConnectivity("dummy", "ep1")
165+
if err != nil {
166+
t.Fatal(err)
167+
}
168+
}

libnetwork_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ func TestBridge(t *testing.T) {
199199
if !ok {
200200
t.Fatalf("Unexpected format for port mapping in endpoint operational data")
201201
}
202-
if len(pm) != 5 {
202+
if len(pm) != 10 {
203203
t.Fatalf("Incomplete data for port mapping in endpoint operational data: %d", len(pm))
204204
}
205205
}

portmapper/mapper.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,20 +151,16 @@ func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart,
151151
}
152152

153153
containerIP, containerPort := getIPAndPort(m.container)
154-
if pm.checkIP(hostIP) {
155-
if err := pm.AppendForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
156-
return nil, err
157-
}
154+
if err := pm.AppendForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
155+
return nil, err
158156
}
159157

160158
cleanup := func() error {
161159
// need to undo the iptables rules before we return
162160
m.userlandProxy.Stop()
163-
if pm.checkIP(hostIP) {
164-
pm.DeleteForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
165-
if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
166-
return err
167-
}
161+
pm.DeleteForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
162+
if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
163+
return err
168164
}
169165

170166
return nil

portmapper/mapper_linux.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,3 @@ func (pm *PortMapper) forward(action iptables.Action, proto string, sourceIP net
4444
}
4545
return pm.chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort, pm.bridgeName)
4646
}
47-
48-
// checkIP checks if IP is valid and matching to chain version
49-
func (pm *PortMapper) checkIP(ip net.IP) bool {
50-
if pm.chain == nil || pm.chain.IPTable.Version == iptables.IPv4 {
51-
return ip.To4() != nil
52-
}
53-
return ip.To16() != nil
54-
}

0 commit comments

Comments
 (0)