Skip to content

Commit

Permalink
bridge: Add option to enable port isolation
Browse files Browse the repository at this point in the history
Enable bridge CNI plugin setting port-isolation [1] the interface.
When port-isolation is enabled, containers connected to the network
cannot communicate with each other over the linux-bridge.
Communication will be enable depending on the gateway appliance according
to its restrictions / policies.

For example: in a scenario the env connected to smart switch, enabling
port-isolation ensure traffic will go outbound, allowing the
smart-switch routing the traffic according to policies.

Add "portIsolation" flag to bridge plugin.
When true, configure the node interface with port-isolation [1].
Default is false.

[1] https://man7.org/linux/man-pages/man8/bridge.8.html (see "isolated" option)

Signed-off-by: Or Mergi <[email protected]>
  • Loading branch information
ormergi committed Jan 19, 2025
1 parent ba8bc7d commit 6f92d18
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 3 deletions.
23 changes: 20 additions & 3 deletions plugins/main/bridge/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type NetConf struct {
MacSpoofChk bool `json:"macspoofchk,omitempty"`
EnableDad bool `json:"enabledad,omitempty"`
DisableContainerInterface bool `json:"disableContainerInterface,omitempty"`
PortIsolation bool `json:"portIsolation,omitempty"`

Args struct {
Cni BridgeArgs `json:"cni,omitempty"`
Expand Down Expand Up @@ -387,7 +388,7 @@ func ensureVlanInterface(br *netlink.Bridge, vlanID int, preserveDefaultVlan boo
return nil, fmt.Errorf("faild to find host namespace: %v", err)
}

_, brGatewayIface, err := setupVeth(hostNS, br, name, br.MTU, false, vlanID, nil, preserveDefaultVlan, "")
_, brGatewayIface, err := setupVeth(hostNS, br, name, br.MTU, false, vlanID, nil, preserveDefaultVlan, "", false)
if err != nil {
return nil, fmt.Errorf("faild to create vlan gateway %q: %v", name, err)
}
Expand All @@ -406,7 +407,18 @@ func ensureVlanInterface(br *netlink.Bridge, vlanID int, preserveDefaultVlan boo
return brGatewayVeth, nil
}

func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool, vlanID int, vlans []int, preserveDefaultVlan bool, mac string) (*current.Interface, *current.Interface, error) {
func setupVeth(
netns ns.NetNS,
br *netlink.Bridge,
ifName string,
mtu int,
hairpinMode bool,
vlanID int,
vlans []int,
preserveDefaultVlan bool,
mac string,
portIsolation bool,
) (*current.Interface, *current.Interface, error) {
contIface := &current.Interface{}
hostIface := &current.Interface{}

Expand Down Expand Up @@ -443,6 +455,11 @@ func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairp
return nil, nil, fmt.Errorf("failed to setup hairpin mode for %v: %v", hostVeth.Attrs().Name, err)
}

// set isolation mode
if err = netlink.LinkSetIsolated(hostVeth, portIsolation); err != nil {
return nil, nil, fmt.Errorf("failed to set isolated on for %v: %v", hostVeth.Attrs().Name, err)
}

if (vlanID != 0 || len(vlans) > 0) && !preserveDefaultVlan {
err = removeDefaultVlan(hostVeth)
if err != nil {
Expand Down Expand Up @@ -549,7 +566,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
defer netns.Close()

hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode, n.Vlan, n.vlans, n.PreserveDefaultVlan, n.mac)
hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode, n.Vlan, n.vlans, n.PreserveDefaultVlan, n.mac, n.PortIsolation)
if err != nil {
return err
}
Expand Down
42 changes: 42 additions & 0 deletions plugins/main/bridge/bridge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ type testCase struct {
ipMasqBackend string
macspoofchk bool
disableContIface bool
portIsolation bool

AddErr020 string
DelErr020 string
Expand Down Expand Up @@ -162,6 +163,9 @@ const (
disableContainerInterface = `,
"disableContainerInterface": true`

portIsolation = `,
"portIsolation": true`

ipamStartStr = `,
"ipam": {
"type": "host-local"`
Expand Down Expand Up @@ -266,6 +270,10 @@ func (tc testCase) netConfJSON(dataDir string) string {
conf += disableContainerInterface
}

if tc.portIsolation {
conf += portIsolation
}

if !tc.isLayer2 {
conf += netDefault
if tc.subnet != "" || tc.ranges != nil {
Expand Down Expand Up @@ -649,6 +657,10 @@ func (tester *testerV10x) cmdAddTest(tc testCase, dataDir string) (types.Result,
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
tester.vethName = result.Interfaces[1].Name

protInfo, err := netlink.LinkGetProtinfo(link)
Expect(err).NotTo(HaveOccurred())
Expect(protInfo.Isolated).To(Equal(tc.portIsolation), "link isolation should be on when portIsolation is set")

// check vlan exist on the veth interface
if tc.vlan != 0 {
interfaceMap, err := netlink.BridgeVlanList()
Expand Down Expand Up @@ -2588,6 +2600,36 @@ var _ = Describe("bridge Operations", func() {
return nil
})).To(Succeed())
})

FIt(fmt.Sprintf("[%s] when port-isolation is off, should set the veth peer on node with isolation off", ver), func() {
Expect(originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
tc := testCase{
cniVersion: ver,
portIsolation: false,
isLayer2: true,
AddErr020: "cannot convert: no valid IP addresses",
AddErr010: "cannot convert: no valid IP addresses",
}
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
return nil
})).To(Succeed())
})

FIt(fmt.Sprintf("[%s] when port-isolation is on, should set the veth peer on node with isolation on", ver), func() {
Expect(originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
tc := testCase{
cniVersion: ver,
portIsolation: true,
isLayer2: true,
AddErr020: "cannot convert: no valid IP addresses",
AddErr010: "cannot convert: no valid IP addresses",
}
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
return nil
})).To(Succeed())
})
}

It("check vlan id when loading net conf", func() {
Expand Down

0 comments on commit 6f92d18

Please sign in to comment.