Browse code

libnet/portmapperapi: add PortMapper interface, registry

Signed-off-by: Albin Kerouanton <albinker@gmail.com>

Albin Kerouanton authored on 2025/06/28 02:37:55
Showing 11 changed files
... ...
@@ -86,6 +86,7 @@ type Controller struct {
86 86
 	id               string
87 87
 	drvRegistry      drvregistry.Networks
88 88
 	ipamRegistry     drvregistry.IPAMs
89
+	pmRegistry       drvregistry.PortMappers
89 90
 	sandboxes        map[string]*Sandbox
90 91
 	cfg              *config.Config
91 92
 	store            *datastore.Store
... ...
@@ -173,13 +174,20 @@ func New(ctx context.Context, cfgOptions ...config.Option) (_ *Controller, retEr
173 173
 	}
174 174
 	c.drvRegistry.Notify = c
175 175
 
176
+	// Register portmappers before network drivers to make sure they can
177
+	// restore existing sandboxes (with port mappings) during their
178
+	// initialization, if the daemon is started in live restore mode.
179
+	if err := registerPortMappers(ctx, &c.pmRegistry, c.cfg); err != nil {
180
+		return nil, err
181
+	}
182
+
176 183
 	// External plugins don't need config passed through daemon. They can
177 184
 	// bootstrap themselves.
178 185
 	if err := remotedriver.Register(&c.drvRegistry, c.cfg.PluginGetter); err != nil {
179 186
 		return nil, err
180 187
 	}
181 188
 
182
-	if err := registerNetworkDrivers(&c.drvRegistry, c.store, c.makeDriverConfig); err != nil {
189
+	if err := registerNetworkDrivers(&c.drvRegistry, c.store, &c.pmRegistry, c.makeDriverConfig); err != nil {
183 190
 		return nil, err
184 191
 	}
185 192
 
... ...
@@ -22,6 +22,7 @@ import (
22 22
 	"github.com/docker/docker/daemon/libnetwork/drivers/bridge/internal/iptabler"
23 23
 	"github.com/docker/docker/daemon/libnetwork/drivers/bridge/internal/nftabler"
24 24
 	"github.com/docker/docker/daemon/libnetwork/drivers/bridge/internal/rlkclient"
25
+	"github.com/docker/docker/daemon/libnetwork/drvregistry"
25 26
 	"github.com/docker/docker/daemon/libnetwork/internal/netiputil"
26 27
 	"github.com/docker/docker/daemon/libnetwork/internal/nftables"
27 28
 	"github.com/docker/docker/daemon/libnetwork/iptables"
... ...
@@ -178,6 +179,7 @@ type driver struct {
178 178
 	portDriverClient portDriverClient
179 179
 	configNetwork    sync.Mutex
180 180
 	firewaller       firewaller.Firewaller
181
+	portmappers      *drvregistry.PortMappers
181 182
 	sync.Mutex
182 183
 }
183 184
 
... ...
@@ -192,17 +194,18 @@ const (
192 192
 )
193 193
 
194 194
 // New constructs a new bridge driver
195
-func newDriver(store *datastore.Store) *driver {
195
+func newDriver(store *datastore.Store, pms *drvregistry.PortMappers) *driver {
196 196
 	return &driver{
197
-		store:    store,
198
-		nlh:      ns.NlHandle(),
199
-		networks: map[string]*bridgeNetwork{},
197
+		store:       store,
198
+		nlh:         ns.NlHandle(),
199
+		networks:    map[string]*bridgeNetwork{},
200
+		portmappers: pms,
200 201
 	}
201 202
 }
202 203
 
203 204
 // Register registers a new instance of bridge driver.
204
-func Register(r driverapi.Registerer, store *datastore.Store, config map[string]interface{}) error {
205
-	d := newDriver(store)
205
+func Register(r driverapi.Registerer, store *datastore.Store, pms *drvregistry.PortMappers, config map[string]interface{}) error {
206
+	d := newDriver(store, pms)
206 207
 	if err := d.configure(config); err != nil {
207 208
 		return err
208 209
 	}
... ...
@@ -15,6 +15,7 @@ import (
15 15
 
16 16
 	"github.com/docker/docker/daemon/libnetwork/driverapi"
17 17
 	"github.com/docker/docker/daemon/libnetwork/drivers/bridge/internal/firewaller"
18
+	"github.com/docker/docker/daemon/libnetwork/drvregistry"
18 19
 	"github.com/docker/docker/daemon/libnetwork/internal/netiputil"
19 20
 	"github.com/docker/docker/daemon/libnetwork/ipamapi"
20 21
 	"github.com/docker/docker/daemon/libnetwork/ipams/defaultipam"
... ...
@@ -280,7 +281,7 @@ func getIPv6Data(t *testing.T) []driverapi.IPAMData {
280 280
 
281 281
 func TestCreateFullOptions(t *testing.T) {
282 282
 	defer netnsutils.SetupTestOSContext(t)()
283
-	d := newDriver(storeutils.NewTempStore(t))
283
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
284 284
 
285 285
 	config := &configuration{
286 286
 		EnableIPForwarding: true,
... ...
@@ -335,7 +336,7 @@ func TestCreateFullOptions(t *testing.T) {
335 335
 
336 336
 func TestCreateNoConfig(t *testing.T) {
337 337
 	defer netnsutils.SetupTestOSContext(t)()
338
-	d := newDriver(storeutils.NewTempStore(t))
338
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
339 339
 	err := d.configure(nil)
340 340
 	assert.NilError(t, err)
341 341
 
... ...
@@ -350,7 +351,7 @@ func TestCreateNoConfig(t *testing.T) {
350 350
 
351 351
 func TestCreateFullOptionsLabels(t *testing.T) {
352 352
 	defer netnsutils.SetupTestOSContext(t)()
353
-	d := newDriver(storeutils.NewTempStore(t))
353
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
354 354
 
355 355
 	config := &configuration{
356 356
 		EnableIPForwarding: true,
... ...
@@ -537,7 +538,7 @@ func TestCreateVeth(t *testing.T) {
537 537
 func TestCreate(t *testing.T) {
538 538
 	defer netnsutils.SetupTestOSContext(t)()
539 539
 
540
-	d := newDriver(storeutils.NewTempStore(t))
540
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
541 541
 
542 542
 	if err := d.configure(nil); err != nil {
543 543
 		t.Fatalf("Failed to setup driver config: %v", err)
... ...
@@ -563,7 +564,7 @@ func TestCreate(t *testing.T) {
563 563
 func TestCreateFail(t *testing.T) {
564 564
 	defer netnsutils.SetupTestOSContext(t)()
565 565
 
566
-	d := newDriver(storeutils.NewTempStore(t))
566
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
567 567
 
568 568
 	if err := d.configure(nil); err != nil {
569 569
 		t.Fatalf("Failed to setup driver config: %v", err)
... ...
@@ -582,7 +583,7 @@ func TestCreateMultipleNetworks(t *testing.T) {
582 582
 	defer netnsutils.SetupTestOSContext(t)()
583 583
 	useStubFirewaller(t)
584 584
 
585
-	d := newDriver(storeutils.NewTempStore(t))
585
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
586 586
 
587 587
 	checkFirewallerNetworks := func() {
588 588
 		t.Helper()
... ...
@@ -795,7 +796,7 @@ func testQueryEndpointInfo(t *testing.T, ulPxyEnabled bool) {
795 795
 	defer netnsutils.SetupTestOSContext(t)()
796 796
 	useStubFirewaller(t)
797 797
 
798
-	d := newDriver(storeutils.NewTempStore(t))
798
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
799 799
 	portallocator.Get().ReleaseAll()
800 800
 
801 801
 	var proxyBinary string
... ...
@@ -909,7 +910,7 @@ func TestLinkContainers(t *testing.T) {
909 909
 	defer netnsutils.SetupTestOSContext(t)()
910 910
 	useStubFirewaller(t)
911 911
 
912
-	d := newDriver(storeutils.NewTempStore(t))
912
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
913 913
 
914 914
 	config := &configuration{
915 915
 		EnableIPTables: true,
... ...
@@ -1178,7 +1179,7 @@ func TestValidateFixedCIDRV6(t *testing.T) {
1178 1178
 func TestSetDefaultGw(t *testing.T) {
1179 1179
 	defer netnsutils.SetupTestOSContext(t)()
1180 1180
 
1181
-	d := newDriver(storeutils.NewTempStore(t))
1181
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
1182 1182
 
1183 1183
 	if err := d.configure(nil); err != nil {
1184 1184
 		t.Fatalf("Failed to setup driver config: %v", err)
... ...
@@ -1228,7 +1229,7 @@ func TestSetDefaultGw(t *testing.T) {
1228 1228
 
1229 1229
 func TestCreateWithExistingBridge(t *testing.T) {
1230 1230
 	defer netnsutils.SetupTestOSContext(t)()
1231
-	d := newDriver(storeutils.NewTempStore(t))
1231
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
1232 1232
 
1233 1233
 	if err := d.configure(nil); err != nil {
1234 1234
 		t.Fatalf("Failed to setup driver config: %v", err)
... ...
@@ -1300,7 +1301,7 @@ func TestCreateParallel(t *testing.T) {
1300 1300
 	c := netnsutils.SetupTestOSContextEx(t)
1301 1301
 	defer c.Cleanup(t)
1302 1302
 
1303
-	d := newDriver(storeutils.NewTempStore(t))
1303
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
1304 1304
 	portallocator.Get().ReleaseAll()
1305 1305
 
1306 1306
 	if err := d.configure(nil); err != nil {
... ...
@@ -1351,7 +1352,7 @@ func useStubFirewaller(t *testing.T) {
1351 1351
 // Regression test for https://github.com/moby/moby/issues/46445
1352 1352
 func TestSetupIP6TablesWithHostIPv4(t *testing.T) {
1353 1353
 	defer netnsutils.SetupTestOSContext(t)()
1354
-	d := newDriver(storeutils.NewTempStore(t))
1354
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
1355 1355
 	dc := &configuration{
1356 1356
 		EnableIPTables:  true,
1357 1357
 		EnableIP6Tables: true,
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"testing"
6 6
 
7 7
 	cerrdefs "github.com/containerd/errdefs"
8
+	"github.com/docker/docker/daemon/libnetwork/drvregistry"
8 9
 	"github.com/docker/docker/daemon/libnetwork/netlabel"
9 10
 	"github.com/docker/docker/internal/nlwrap"
10 11
 	"github.com/docker/docker/internal/testutils/netnsutils"
... ...
@@ -15,7 +16,7 @@ import (
15 15
 
16 16
 func TestLinkCreate(t *testing.T) {
17 17
 	defer netnsutils.SetupTestOSContext(t)()
18
-	d := newDriver(storeutils.NewTempStore(t))
18
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
19 19
 	err := d.configure(nil)
20 20
 	assert.NilError(t, err)
21 21
 
... ...
@@ -78,7 +79,7 @@ func TestLinkCreate(t *testing.T) {
78 78
 
79 79
 func TestLinkCreateTwo(t *testing.T) {
80 80
 	defer netnsutils.SetupTestOSContext(t)()
81
-	d := newDriver(storeutils.NewTempStore(t))
81
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
82 82
 	err := d.configure(nil)
83 83
 	assert.NilError(t, err)
84 84
 
... ...
@@ -106,7 +107,7 @@ func TestLinkCreateTwo(t *testing.T) {
106 106
 
107 107
 func TestLinkCreateNoEnableIPv6(t *testing.T) {
108 108
 	defer netnsutils.SetupTestOSContext(t)()
109
-	d := newDriver(storeutils.NewTempStore(t))
109
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
110 110
 	err := d.configure(nil)
111 111
 	assert.NilError(t, err)
112 112
 
... ...
@@ -131,7 +132,7 @@ func TestLinkCreateNoEnableIPv6(t *testing.T) {
131 131
 
132 132
 func TestLinkDelete(t *testing.T) {
133 133
 	defer netnsutils.SetupTestOSContext(t)()
134
-	d := newDriver(storeutils.NewTempStore(t))
134
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
135 135
 	err := d.configure(nil)
136 136
 	assert.NilError(t, err)
137 137
 
... ...
@@ -14,6 +14,7 @@ import (
14 14
 
15 15
 	"github.com/containerd/log"
16 16
 	"github.com/docker/docker/daemon/libnetwork/drivers/bridge/internal/firewaller"
17
+	"github.com/docker/docker/daemon/libnetwork/drvregistry"
17 18
 	"github.com/docker/docker/daemon/libnetwork/netlabel"
18 19
 	"github.com/docker/docker/daemon/libnetwork/ns"
19 20
 	"github.com/docker/docker/daemon/libnetwork/portallocator"
... ...
@@ -31,7 +32,7 @@ func TestPortMappingConfig(t *testing.T) {
31 31
 	defer netnsutils.SetupTestOSContext(t)()
32 32
 	useStubFirewaller(t)
33 33
 
34
-	d := newDriver(storeutils.NewTempStore(t))
34
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
35 35
 
36 36
 	config := &configuration{
37 37
 		EnableIPTables: true,
... ...
@@ -116,7 +117,7 @@ func TestPortMappingV6Config(t *testing.T) {
116 116
 		t.Fatalf("Could not bring loopback iface up: %v", err)
117 117
 	}
118 118
 
119
-	d := newDriver(storeutils.NewTempStore(t))
119
+	d := newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{})
120 120
 
121 121
 	config := &configuration{
122 122
 		EnableIPTables:  true,
... ...
@@ -791,7 +792,7 @@ func TestAddPortMappings(t *testing.T) {
791 791
 					GwModeIPv6: tc.gwMode6,
792 792
 				},
793 793
 				bridge: &bridgeInterface{},
794
-				driver: newDriver(storeutils.NewTempStore(t)),
794
+				driver: newDriver(storeutils.NewTempStore(t), &drvregistry.PortMappers{}),
795 795
 			}
796 796
 			genericOption := map[string]interface{}{
797 797
 				netlabel.GenericData: &configuration{
... ...
@@ -1,8 +1,10 @@
1 1
 package libnetwork
2 2
 
3 3
 import (
4
+	"context"
4 5
 	"fmt"
5 6
 
7
+	"github.com/docker/docker/daemon/libnetwork/config"
6 8
 	"github.com/docker/docker/daemon/libnetwork/datastore"
7 9
 	"github.com/docker/docker/daemon/libnetwork/driverapi"
8 10
 	"github.com/docker/docker/daemon/libnetwork/drivers/bridge"
... ...
@@ -11,14 +13,17 @@ import (
11 11
 	"github.com/docker/docker/daemon/libnetwork/drivers/macvlan"
12 12
 	"github.com/docker/docker/daemon/libnetwork/drivers/null"
13 13
 	"github.com/docker/docker/daemon/libnetwork/drivers/overlay"
14
+	"github.com/docker/docker/daemon/libnetwork/drvregistry"
14 15
 )
15 16
 
16
-func registerNetworkDrivers(r driverapi.Registerer, store *datastore.Store, driverConfig func(string) map[string]interface{}) error {
17
+func registerNetworkDrivers(r driverapi.Registerer, store *datastore.Store, pms *drvregistry.PortMappers, driverConfig func(string) map[string]interface{}) error {
17 18
 	for _, nr := range []struct {
18 19
 		ntype    string
19 20
 		register func(driverapi.Registerer, *datastore.Store, map[string]interface{}) error
20 21
 	}{
21
-		{ntype: bridge.NetworkType, register: bridge.Register},
22
+		{ntype: bridge.NetworkType, register: func(r driverapi.Registerer, store *datastore.Store, cfg map[string]interface{}) error {
23
+			return bridge.Register(r, store, pms, cfg)
24
+		}},
22 25
 		{ntype: host.NetworkType, register: func(r driverapi.Registerer, _ *datastore.Store, _ map[string]interface{}) error {
23 26
 			return host.Register(r)
24 27
 		}},
... ...
@@ -38,3 +43,7 @@ func registerNetworkDrivers(r driverapi.Registerer, store *datastore.Store, driv
38 38
 
39 39
 	return nil
40 40
 }
41
+
42
+func registerPortMappers(ctx context.Context, r *drvregistry.PortMappers, cfg *config.Config) error {
43
+	return nil
44
+}
... ...
@@ -1,16 +1,19 @@
1 1
 package libnetwork
2 2
 
3 3
 import (
4
+	"context"
4 5
 	"fmt"
5 6
 
7
+	"github.com/docker/docker/daemon/libnetwork/config"
6 8
 	"github.com/docker/docker/daemon/libnetwork/datastore"
7 9
 	"github.com/docker/docker/daemon/libnetwork/driverapi"
8 10
 	"github.com/docker/docker/daemon/libnetwork/drivers/null"
9 11
 	"github.com/docker/docker/daemon/libnetwork/drivers/windows"
10 12
 	"github.com/docker/docker/daemon/libnetwork/drivers/windows/overlay"
13
+	"github.com/docker/docker/daemon/libnetwork/drvregistry"
11 14
 )
12 15
 
13
-func registerNetworkDrivers(r driverapi.Registerer, store *datastore.Store, _ func(string) map[string]interface{}) error {
16
+func registerNetworkDrivers(r driverapi.Registerer, store *datastore.Store, _ *drvregistry.PortMappers, _ func(string) map[string]interface{}) error {
14 17
 	for _, nr := range []struct {
15 18
 		ntype    string
16 19
 		register func(driverapi.Registerer) error
... ...
@@ -25,3 +28,7 @@ func registerNetworkDrivers(r driverapi.Registerer, store *datastore.Store, _ fu
25 25
 
26 26
 	return windows.RegisterBuiltinLocalDrivers(r, store)
27 27
 }
28
+
29
+func registerPortMappers(ctx context.Context, r *drvregistry.PortMappers, cfg *config.Config) error {
30
+	return nil
31
+}
28 32
new file mode 100644
... ...
@@ -0,0 +1,41 @@
0
+package drvregistry
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"strings"
6
+
7
+	"github.com/docker/docker/daemon/libnetwork/portmapperapi"
8
+)
9
+
10
+type PortMappers struct {
11
+	drivers map[string]portmapperapi.PortMapper
12
+}
13
+
14
+// Register a portmapper with the registry.
15
+func (r *PortMappers) Register(name string, pm portmapperapi.PortMapper) error {
16
+	if strings.TrimSpace(name) == "" {
17
+		return errors.New("portmapper name cannot be empty")
18
+	}
19
+
20
+	if _, ok := r.drivers[name]; ok {
21
+		return errors.New("portmapper already registered")
22
+	}
23
+
24
+	if r.drivers == nil {
25
+		r.drivers = make(map[string]portmapperapi.PortMapper)
26
+	}
27
+
28
+	r.drivers[name] = pm
29
+
30
+	return nil
31
+}
32
+
33
+// Get retrieves a portmapper by name from the registry.
34
+func (r *PortMappers) Get(name string) (portmapperapi.PortMapper, error) {
35
+	pm, ok := r.drivers[name]
36
+	if !ok {
37
+		return nil, fmt.Errorf("portmapper %s not found", name)
38
+	}
39
+	return pm, nil
40
+}
0 41
new file mode 100644
... ...
@@ -0,0 +1,67 @@
0
+package drvregistry
1
+
2
+import (
3
+	"context"
4
+	"testing"
5
+
6
+	"github.com/docker/docker/daemon/libnetwork/portmapperapi"
7
+	"gotest.tools/v3/assert"
8
+)
9
+
10
+type fakePortMapper struct{}
11
+
12
+func (f fakePortMapper) MapPorts(_ context.Context, _ []portmapperapi.PortBindingReq, _ portmapperapi.Firewaller) ([]portmapperapi.PortBinding, error) {
13
+	return nil, nil
14
+}
15
+
16
+func (f fakePortMapper) UnmapPorts(_ context.Context, _ []portmapperapi.PortBinding, _ portmapperapi.Firewaller) error {
17
+	return nil
18
+}
19
+
20
+func TestRegisterPortMappers(t *testing.T) {
21
+	t.Run("register port mapper", func(t *testing.T) {
22
+		var pms PortMappers
23
+
24
+		pm := fakePortMapper{}
25
+		err := pms.Register("test", pm)
26
+		assert.NilError(t, err)
27
+	})
28
+
29
+	t.Run("empty name", func(t *testing.T) {
30
+		var pms PortMappers
31
+
32
+		err := pms.Register("", nil)
33
+		assert.ErrorContains(t, err, "portmapper name cannot be empty")
34
+	})
35
+
36
+	t.Run("duplicate port mapper", func(t *testing.T) {
37
+		var pms PortMappers
38
+
39
+		err := pms.Register("test", nil)
40
+		assert.NilError(t, err)
41
+
42
+		err = pms.Register("test", nil)
43
+		assert.ErrorContains(t, err, "portmapper already registered")
44
+	})
45
+}
46
+
47
+func TestGetPortMapper(t *testing.T) {
48
+	t.Run("get existing port mapper", func(t *testing.T) {
49
+		var pms PortMappers
50
+
51
+		pm := fakePortMapper{}
52
+		err := pms.Register("test", pm)
53
+		assert.NilError(t, err)
54
+
55
+		retrieved, err := pms.Get("test")
56
+		assert.NilError(t, err)
57
+		assert.Equal(t, retrieved, pm)
58
+	})
59
+
60
+	t.Run("get nonexistent port mapper", func(t *testing.T) {
61
+		var pms PortMappers
62
+
63
+		_, err := pms.Get("nonexistent")
64
+		assert.ErrorContains(t, err, "portmapper nonexistent not found")
65
+	})
66
+}
... ...
@@ -1,6 +1,7 @@
1 1
 package portmapperapi
2 2
 
3 3
 import (
4
+	"context"
4 5
 	"net"
5 6
 	"net/netip"
6 7
 	"os"
... ...
@@ -8,6 +9,31 @@ import (
8 8
 	"github.com/docker/docker/daemon/libnetwork/types"
9 9
 )
10 10
 
11
+// Registerer provides a callback interface for registering port-mappers.
12
+type Registerer interface {
13
+	// Register provides a way for port-mappers to dynamically register with libnetwork.
14
+	Register(name string, driver PortMapper) error
15
+}
16
+
17
+// PortMapper maps / unmaps container ports to host ports.
18
+type PortMapper interface {
19
+	// MapPorts takes a list of port binding requests, and returns a list of
20
+	// PortBinding. Both lists MUST have the same size.
21
+	//
22
+	// Multiple port bindings are passed when they're all requesting the
23
+	// same port range, or an ephemeral port, over multiple IP addresses and
24
+	// all pointing to the same container port. In that case, the PortMapper
25
+	// MUST assign the same HostPort for all IP addresses.
26
+	//
27
+	// When an ephemeral port, or a single port from a range is requested
28
+	// MapPorts should attempt a few times to find a free port available
29
+	// across all IP addresses.
30
+	MapPorts(ctx context.Context, reqs []PortBindingReq, fwn Firewaller) ([]PortBinding, error)
31
+
32
+	// UnmapPorts takes a list of port bindings to unmap.
33
+	UnmapPorts(ctx context.Context, pbs []PortBinding, fwn Firewaller) error
34
+}
35
+
11 36
 type PortBindingReq struct {
12 37
 	types.PortBinding
13 38
 	// ChildHostIP is a temporary field used to pass the host IP address as
14 39
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+package portmapperapi
1
+
2
+import (
3
+	"context"
4
+
5
+	"github.com/docker/docker/daemon/libnetwork/types"
6
+)
7
+
8
+type Firewaller interface {
9
+	// AddPorts adds the configuration needed for NATing ports.
10
+	AddPorts(ctx context.Context, pbs []types.PortBinding) error
11
+	// DelPorts deletes the configuration needed for NATing ports.
12
+	DelPorts(ctx context.Context, pbs []types.PortBinding) error
13
+}