Skip to content

Commit

Permalink
Pass vlan tagged packets back to the kernel stack.
Browse files Browse the repository at this point in the history
VLAN packets will be catched with bpf on main interface first. We
need to passs it back to the kernel stack. VLAN info/tag will be stripped and
packet will be reenqueued on proper interface or dropped.

Also we do not need to process VLAN packets on egress, cause either
we've already processed such packets in case bpf program is attached
to VLAN interface or we do not need to process such packets at all in
other way.

We will reject all unknown vlan tags for security reasons.
By default will be allowed only vlan tags of vlan devices controlled by
cilium. Additional vlan tags may be added using `vlan-bpf-bypass` option.
'0' tag (i.e. `--vlan-bpf-bypass 0`) can be used to allow all vlan tags without filtering.

Fixes: #14579

Signed-off-by: Viktor Kuzmin <kvaster@gmail.com>
  • Loading branch information
kvaster authored and joestringer committed Jul 23, 2021
1 parent 090794e commit 9086640
Show file tree
Hide file tree
Showing 14 changed files with 408 additions and 156 deletions.
1 change: 1 addition & 0 deletions Documentation/cmdref/cilium-agent.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

317 changes: 161 additions & 156 deletions api/v1/flow/flow.pb.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions api/v1/flow/flow.proto
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ enum DropReason {
SOCKET_LOOKUP_FAILED = 178;
SOCKET_ASSIGN_FAILED = 179;
PROXY_REDIRECTION_NOT_SUPPORTED_FOR_PROTOCOL = 180;
VLAN_FILTERED = 182;
}

enum TrafficDirection {
Expand Down
1 change: 1 addition & 0 deletions api/v1/observer/observer.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions bpf/bpf_host.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
*/
#define SKIP_ICMPV6_ECHO_HANDLING

#ifndef VLAN_FILTER
# define VLAN_FILTER(ifindex, vlan_id) return false;
#endif

#include "lib/common.h"
#include "lib/edt.h"
#include "lib/arp.h"
Expand All @@ -61,6 +65,10 @@
#include "lib/overloadable.h"
#include "lib/encrypt.h"

static __always_inline bool allow_vlan(__u32 __maybe_unused ifindex, __u32 __maybe_unused vlan_id) {
VLAN_FILTER(ifindex, vlan_id);
}

#if defined(ENABLE_IPV4) || defined(ENABLE_IPV6)
static __always_inline int rewrite_dmac_to_host(struct __ctx_buff *ctx,
__u32 src_identity)
Expand Down Expand Up @@ -968,6 +976,21 @@ handle_netdev(struct __ctx_buff *ctx, const bool from_host)
__section("from-netdev")
int from_netdev(struct __ctx_buff *ctx)
{
__u32 __maybe_unused vlan_id;

/* Filter allowed vlan id's and pass them back to kernel.
*/
if (ctx->vlan_present) {
vlan_id = ctx->vlan_tci & 0xfff;
if (vlan_id) {
if (allow_vlan(ctx->ifindex, vlan_id))
return CTX_ACT_OK;
else
return send_drop_notify_error(ctx, 0, DROP_VLAN_FILTERED,
CTX_ACT_DROP, METRIC_INGRESS);
}
}

return handle_netdev(ctx, false);
}

Expand Down Expand Up @@ -997,8 +1020,22 @@ int to_netdev(struct __ctx_buff *ctx __maybe_unused)
__u32 __maybe_unused src_id = 0;
__u16 __maybe_unused proto = 0;
__u32 monitor = 0;
__u32 __maybe_unused vlan_id;
int ret = CTX_ACT_OK;

/* Filter allowed vlan id's and pass them back to kernel.
*/
if (ctx->vlan_present) {
vlan_id = ctx->vlan_tci & 0xfff;
if (vlan_id) {
if (allow_vlan(ctx->ifindex, vlan_id))
return CTX_ACT_OK;
else
return send_drop_notify_error(ctx, 0, DROP_VLAN_FILTERED,
CTX_ACT_DROP, METRIC_EGRESS);
}
}

#ifdef ENABLE_HOST_FIREWALL
if (!proto && !validate_ethertype(ctx, &proto)) {
ret = DROP_UNSUPPORTED_L2;
Expand Down
1 change: 1 addition & 0 deletions bpf/lib/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ enum {
#define DROP_PROXY_SET_FAILED -179
#define DROP_PROXY_UNKNOWN_PROTO -180
#define DROP_POLICY_DENY -181
#define DROP_VLAN_FILTERED -182

#define NAT_PUNT_TO_STACK DROP_NAT_NOT_NEEDED

Expand Down
19 changes: 19 additions & 0 deletions bpf/node_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,22 @@ DEFINE_IPV6(HOST_IP, 0xbe, 0xef, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0xa, 0x
#define IPCACHE4_PREFIXES 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, \
4, 3, 2, 1
#define IPCACHE6_PREFIXES 4, 3, 2, 1

#define VLAN_FILTER(ifindex, vlan_id) switch (ifindex) { \
case 116: \
switch (vlan_id) { \
case 4000: \
case 4001: \
return true; \
} \
break; \
case 117: \
switch (vlan_id) { \
case 4003: \
case 4004: \
case 4005: \
return true; \
} \
break; \
} \
return false;
3 changes: 3 additions & 0 deletions daemon/cmd/daemon_main.go
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,9 @@ func init() {
flags.Bool(option.ExternalClusterIPName, false, "Enable external access to ClusterIP services (default false)")
option.BindEnv(option.ExternalClusterIPName)

flags.IntSlice(option.VLANBPFBypass, []int{}, "List of explicitly allowed VLAN IDs, '0' id will allow all VLAN IDs")
option.BindEnv(option.VLANBPFBypass)

viper.BindPFlags(flags)
}

Expand Down
5 changes: 5 additions & 0 deletions install/kubernetes/cilium/templates/cilium-configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,11 @@ data:
cgroup-root: {{ .Values.cgroup.hostRoot | quote }}
{{- end }}

{{- if hasKey .Values.bpf "vlanBypass" }}
# A space separated list of explicitly allowed vlan id's
vlan-bpf-bypass: {{ .Values.bpf.vlanBypass | join " " | quote }}
{{- end }}

{{- if .Values.extraConfig }}
{{ toYaml .Values.extraConfig | indent 2 }}
{{- end }}
Expand Down
4 changes: 4 additions & 0 deletions install/kubernetes/cilium/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ bpf:
# NAT handling.
# lbBypassFIBLookup: true

# -- Configure explicitly allowed VLAN id's for bpf logic bypass.
# [0] will allow all VLAN id's without any filtering.
# vlan-bpf-bypass: []

# -- Clean all eBPF datapath state from the initContainer of the cilium-agent
# DaemonSet.
#
Expand Down
74 changes: 74 additions & 0 deletions pkg/datapath/linux/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,12 @@ func (h *HeaderfileWriter) WriteNodeConfig(w io.Writer, cfg *datapath.LocalNodeC
cDefinesMap["ENABLE_CUSTOM_CALLS"] = "1"
}

vlanFilter, err := vlanFilterMacros()
if err != nil {
return err
}
cDefinesMap["VLAN_FILTER(ifindex, vlan_id)"] = vlanFilter

// Since golang maps are unordered, we sort the keys in the map
// to get a consistent writtern format to the writer. This maintains
// the consistency when we try to calculate hash for a datapath after
Expand Down Expand Up @@ -549,6 +555,74 @@ func (h *HeaderfileWriter) WriteNodeConfig(w io.Writer, cfg *datapath.LocalNodeC
return fw.Flush()
}

// vlanFilterMacros generates VLAN_FILTER macros which
// are written to node_config.h
func vlanFilterMacros() (string, error) {
devices := make(map[int]bool)
for _, device := range option.Config.Devices {
ifindex, err := link.GetIfIndex(device)
if err != nil {
return "", err
}
devices[int(ifindex)] = true
}

allowedVlans := make(map[int]bool)
for _, vlanId := range option.Config.VLANBPFBypass {
allowedVlans[vlanId] = true
}

// allow all vlan id's
if allowedVlans[0] {
return "return true", nil
}

vlansByIfIndex := make(map[int][]int)

links, err := netlink.LinkList()
if err != nil {
return "", err
}

for _, l := range links {
vlan, ok := l.(*netlink.Vlan)
// if it's vlan device and we're controlling vlan main device
// and either all vlans are allowed, or we're controlling vlan device or vlan is explicitly allowed
if ok && devices[vlan.ParentIndex] && (devices[vlan.Index] || allowedVlans[vlan.VlanId]) {
vlansByIfIndex[vlan.ParentIndex] = append(vlansByIfIndex[vlan.ParentIndex], vlan.VlanId)
}
}

vlansCount := 0
for _, v := range vlansByIfIndex {
vlansCount += len(v)
}

if vlansCount == 0 {
return "return false", nil
} else if vlansCount > 5 {
return "", fmt.Errorf("allowed VLAN list is too big - %d entries, please use '--vlan-bpf-bypass 0' in order to allow all available VLANs", vlansCount)
} else {
vlanFilterTmpl := template.Must(template.New("vlanFilter").Parse(
`switch (ifindex) { \
{{range $ifindex,$vlans := . -}} case {{$ifindex}}: \
switch (vlan_id) { \
{{range $vlan := $vlans -}} case {{$vlan}}: \
{{end}}return true; \
} \
break; \
{{end}}} \
return false;`))

var vlanFilterMacro bytes.Buffer
if err := vlanFilterTmpl.Execute(&vlanFilterMacro, vlansByIfIndex); err != nil {
return "", fmt.Errorf("failed to execute template: %q", err)
}

return vlanFilterMacro.String(), nil
}
}

// devMacros generates NATIVE_DEV_MAC_BY_IFINDEX and IS_L3_DEV macros which
// are written to node_config.h.
func devMacros() (string, string, error) {
Expand Down
92 changes: 92 additions & 0 deletions pkg/datapath/linux/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package config
import (
"bytes"
"errors"
"fmt"
"io"
"strings"
"testing"
Expand All @@ -31,6 +32,8 @@ import (
"github.com/cilium/cilium/pkg/option"
"github.com/cilium/cilium/pkg/testutils"

"github.com/vishvananda/netlink"

. "gopkg.in/check.v1"
)

Expand Down Expand Up @@ -245,3 +248,92 @@ func assertKeysInsideMap(c *C, m map[string]uint32, keys []string, want bool) {
c.Assert(ok, Equals, want)
}
}

func createMainLink(name string, c *C) *netlink.Dummy {
link := &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: name,
},
}
err := netlink.LinkAdd(link)
c.Assert(err, IsNil)

return link
}

func createVlanLink(vlanId int, mainLink *netlink.Dummy, c *C) *netlink.Vlan {
link := &netlink.Vlan{
LinkAttrs: netlink.LinkAttrs{
Name: fmt.Sprintf("%s.%d", mainLink.Name, vlanId),
ParentIndex: mainLink.Index,
},
VlanProtocol: netlink.VLAN_PROTOCOL_8021Q,
VlanId: vlanId,
}
err := netlink.LinkAdd(link)
c.Assert(err, IsNil)

return link
}

func (s *ConfigSuite) TestVLANBypassConfig(c *C) {
oldDevices := option.Config.Devices
defer func() {
option.Config.Devices = oldDevices
}()

main1 := createMainLink("dummy0", c)
defer func() {
netlink.LinkDel(main1)
}()

for i := 4000; i < 4003; i++ {
vlan := createVlanLink(i, main1, c)
defer func() {
netlink.LinkDel(vlan)
}()
}

main2 := createMainLink("dummy1", c)
defer func() {
netlink.LinkDel(main2)
}()

for i := 4003; i < 4006; i++ {
vlan := createVlanLink(i, main2, c)
defer func() {
netlink.LinkDel(vlan)
}()
}

option.Config.Devices = []string{"dummy0", "dummy0.4000", "dummy0.4001", "dummy1", "dummy1.4003"}
option.Config.VLANBPFBypass = []int{4004}
m, err := vlanFilterMacros()
c.Assert(err, Equals, nil)
c.Assert(m, Equals, fmt.Sprintf(`switch (ifindex) { \
case %d: \
switch (vlan_id) { \
case 4000: \
case 4001: \
return true; \
} \
break; \
case %d: \
switch (vlan_id) { \
case 4003: \
case 4004: \
return true; \
} \
break; \
} \
return false;`, main1.Index, main2.Index))

option.Config.VLANBPFBypass = []int{4002, 4004, 4005}
_, err = vlanFilterMacros()
c.Assert(err, NotNil)

option.Config.VLANBPFBypass = []int{0}
m, err = vlanFilterMacros()
c.Assert(err, IsNil)
c.Assert(m, Equals, "return true")
}
1 change: 1 addition & 0 deletions pkg/monitor/api/drop.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ var errors = map[uint8]string{
179: "Socket assign failed",
180: "Proxy redirection not supported for protocol",
181: "Policy denied by denylist",
182: "VLAN traffic disallowed by VLAN filter",
}

// DropReason prints the drop reason in a human readable string
Expand Down
8 changes: 8 additions & 0 deletions pkg/option/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,9 @@ const (
// ExternalClusterIPName is the name of the option to enable
// cluster external access to ClusterIP services.
ExternalClusterIPName = "bpf-lb-external-clusterip"

// VLANBPFBypass instructs Cilium to bypass bpf logic for vlan tagged packets
VLANBPFBypass = "vlan-bpf-bypass"
)

// Default string arguments
Expand Down Expand Up @@ -1949,6 +1952,9 @@ type DaemonConfig struct {

// ARPPingRefreshPeriod is the ARP entries refresher period.
ARPPingRefreshPeriod time.Duration

// VLANBPFBypass list of explicitly allowed VLAN id's for bpf logic bypass
VLANBPFBypass []int
}

var (
Expand Down Expand Up @@ -2514,6 +2520,8 @@ func (c *DaemonConfig) Populate() {
c.populateDevices()
c.EgressMultiHomeIPRuleCompat = viper.GetBool(EgressMultiHomeIPRuleCompat)

c.VLANBPFBypass = viper.GetIntSlice(VLANBPFBypass)

nativeRoutingCIDR := viper.GetString(NativeRoutingCIDR)
ipv4NativeRoutingCIDR := viper.GetString(IPv4NativeRoutingCIDR)

Expand Down

0 comments on commit 9086640

Please sign in to comment.