Merge branch 'tc-mpls-l2-vpn' into next

Guillaume Nault  says:

====================

This patch series adds the possibility for TC to tunnel Ethernet frames
over MPLS.

Patch 1 allows adding or removing the Ethernet header.
Patch 2 allows pushing an MPLS LSE before the MAC header.

By combining these actions, it becomes possible to encapsulate an
entire Ethernet frame into MPLS, then add an outer Ethernet header
and send the resulting frame to the next hop.

====================

Signed-off-by: David Ahern <dsahern@gmail.com>
This commit is contained in:
David Ahern 2020-10-20 08:57:47 -06:00
commit b4edd6a8a6
7 changed files with 337 additions and 19 deletions

View File

@ -80,6 +80,7 @@ __PF(8021Q,802.1Q)
__PF(8021AD,802.1ad)
__PF(MPLS_UC,mpls_uc)
__PF(MPLS_MC,mpls_mc)
__PF(TEB,teb)
{ 0x8100, "802.1Q" },
{ 0x88cc, "LLDP" },

View File

@ -17,7 +17,7 @@ mpls - mpls manipulation module
.ti -8
.IR PUSH " := "
.BR push " [ " protocol
.RB "{ " push " | " mac_push " } [ " protocol
.IR MPLS_PROTO " ]"
.RB " [ " tc
.IR MPLS_TC " ] "
@ -64,7 +64,14 @@ requires no arguments and simply subtracts 1 from the MPLS header TTL field.
Decapsulation mode. Requires the protocol of the next header.
.TP
.B push
Encapsulation mode. Requires at least the
Encapsulation mode. Adds the MPLS header between the MAC and the network
headers. Requires at least the
.B label
option.
.TP
.B mac_push
Encapsulation mode. Adds the MPLS header before the MAC header. Requires at
least the
.B label
option.
.TP
@ -152,5 +159,36 @@ ip packets and output to eth1:
.EE
.RE
Here is another example, where incoming Ethernet frames are encapsulated into
MPLS with label 123 and TTL 64. Then, an outer Ethernet header is added and the
resulting frame is finally sent on eth1:
.RS
.EX
#tc qdisc add dev eth0 ingress
#tc filter add dev eth0 ingress matchall \\
action mpls mac_push label 123 ttl 64 \\
action vlan push_eth \\
dst_mac 02:00:00:00:00:02 \\
src_mac 02:00:00:00:00:01 \\
action mirred egress redirect dev eth1
.EE
.RE
The following example assumes that incoming MPLS packets with label 123
transport Ethernet frames. The outer Ethernet and the MPLS headers are
stripped, then the inner Ethernet frame is sent on eth1:
.RS
.EX
#tc qdisc add dev eth0 ingress
#tc filter add dev eth0 ingress protocol mpls_uc \\
flower mpls_label 123 mpls_bos 1 \\
action vlan pop_eth \\
action mpls pop protocol teb \\
action mirred egress redirect dev eth1
.EE
.RE
.SH SEE ALSO
.BR tc (8)
.BR tc "(8), " tc-mirred "(8), " tc-vlan (8)

View File

@ -5,8 +5,8 @@ vlan - vlan manipulation module
.SH SYNOPSIS
.in +8
.ti -8
.BR tc " ... " "action vlan" " { " pop " |"
.IR PUSH " | " MODIFY " } [ " CONTROL " ]"
.BR tc " ... " "action vlan" " { " pop " | " pop_eth " |"
.IR PUSH " | " MODIFY " | " PUSH_ETH " } [ " CONTROL " ]"
.ti -8
.IR PUSH " := "
@ -24,6 +24,11 @@ vlan - vlan manipulation module
.IR VLANPRIO " ] "
.BI id " VLANID"
.ti -8
.IR PUSH_ETH " := "
.B push_eth
.BI dst_mac " LLADDR " src_mac " LLADDR "
.ti -8
.IR CONTROL " := { "
.BR reclassify " | " pipe " | " drop " | " continue " | " pass " | " goto " " chain " " CHAIN_INDEX " }"
@ -43,6 +48,20 @@ modes require at least a
and allow to optionally choose the
.I VLANPROTO
to use.
The
.B vlan
action can also be used to add or remove the base Ethernet header. The
.B pop_eth
mode, which takes no argument, is used to remove the base Ethernet header. All
existing VLANs must have been previously dropped. The opposite operation,
adding a base Ethernet header, is done with the
.B push_eth
mode. In that case, the packet must have no MAC header (stacking MAC headers is
not permitted). This mode is mostly useful when a previous action has
encapsulated the whole original frame behind a network header and one needs
to prepend an Ethernet header before forwarding the resulting packet.
.SH OPTIONS
.TP
.B pop
@ -58,6 +77,16 @@ Replace mode. Existing 802.1Q tag is replaced. Requires at least
.B id
option.
.TP
.B pop_eth
Ethernet header decapsulation mode. Only works on a plain Ethernet header:
VLANs, if any, must be removed first.
.TP
.B push_eth
Ethernet header encapsulation mode. The Ethertype is automatically set
using the network header type. Chaining Ethernet headers is not allowed: the
packet must have no MAC header when using this mode. Requires the
.BR "dst_mac " and " src_mac " options.
.TP
.BI id " VLANID"
Specify the VLAN ID to encapsulate into.
.I VLANID
@ -73,6 +102,12 @@ Choose the VLAN protocol to use. At the time of writing, the kernel accepts only
.BI priority " VLANPRIO"
Choose the VLAN priority to use. Decimal number in range of 0-7.
.TP
.BI dst_mac " LLADDR"
Choose the destination MAC address to use.
.TP
.BI src_mac " LLADDR"
Choose the source MAC address to use.
.TP
.I CONTROL
How to continue after executing this action.
.RS
@ -122,5 +157,8 @@ process then restarted for the plain packet:
.EE
.RE
For an example of the
.BR pop_eth " and " push_eth " modes, see " tc-mpls (8).
.SH SEE ALSO
.BR tc (8)
.BR tc "(8), " tc-mpls (8)

View File

@ -17,6 +17,7 @@ static const char * const action_names[] = {
[TCA_MPLS_ACT_PUSH] = "push",
[TCA_MPLS_ACT_MODIFY] = "modify",
[TCA_MPLS_ACT_DEC_TTL] = "dec_ttl",
[TCA_MPLS_ACT_MAC_PUSH] = "mac_push",
};
static void explain(void)
@ -25,9 +26,11 @@ static void explain(void)
"Usage: mpls pop [ protocol MPLS_PROTO ]\n"
" mpls push [ protocol MPLS_PROTO ] [ label MPLS_LABEL ] [ tc MPLS_TC ]\n"
" [ ttl MPLS_TTL ] [ bos MPLS_BOS ] [CONTROL]\n"
" mpls mac_push [ protocol MPLS_PROTO ] [ label MPLS_LABEL ] [ tc MPLS_TC ]\n"
" [ ttl MPLS_TTL ] [ bos MPLS_BOS ] [CONTROL]\n"
" mpls modify [ label MPLS_LABEL ] [ tc MPLS_TC ] [ ttl MPLS_TTL ] [CONTROL]\n"
" for pop MPLS_PROTO is next header of packet - e.g. ip or mpls_uc\n"
" for push MPLS_PROTO is one of mpls_uc or mpls_mc\n"
" for pop, MPLS_PROTO is next header of packet - e.g. ip or mpls_uc\n"
" for push and mac_push, MPLS_PROTO is one of mpls_uc or mpls_mc\n"
" with default: mpls_uc\n"
" CONTROL := reclassify | pipe | drop | continue | pass |\n"
" goto chain <CHAIN_INDEX>\n");
@ -41,12 +44,14 @@ static void usage(void)
static bool can_modify_mpls_fields(unsigned int action)
{
return action == TCA_MPLS_ACT_PUSH || action == TCA_MPLS_ACT_MODIFY;
return action == TCA_MPLS_ACT_PUSH || action == TCA_MPLS_ACT_MAC_PUSH ||
action == TCA_MPLS_ACT_MODIFY;
}
static bool can_modify_ethtype(unsigned int action)
static bool can_set_ethtype(unsigned int action)
{
return action == TCA_MPLS_ACT_PUSH || action == TCA_MPLS_ACT_POP;
return action == TCA_MPLS_ACT_PUSH || action == TCA_MPLS_ACT_MAC_PUSH ||
action == TCA_MPLS_ACT_POP;
}
static bool is_valid_label(__u32 label)
@ -94,6 +99,10 @@ static int parse_mpls(struct action_util *a, int *argc_p, char ***argv_p,
if (check_double_action(action, *argv))
return -1;
action = TCA_MPLS_ACT_PUSH;
} else if (matches(*argv, "mac_push") == 0) {
if (check_double_action(action, *argv))
return -1;
action = TCA_MPLS_ACT_MAC_PUSH;
} else if (matches(*argv, "modify") == 0) {
if (check_double_action(action, *argv))
return -1;
@ -104,31 +113,36 @@ static int parse_mpls(struct action_util *a, int *argc_p, char ***argv_p,
action = TCA_MPLS_ACT_DEC_TTL;
} else if (matches(*argv, "label") == 0) {
if (!can_modify_mpls_fields(action))
invarg("only valid for push/modify", *argv);
invarg("only valid for push, mac_push and modify",
*argv);
NEXT_ARG();
if (get_u32(&label, *argv, 0) || !is_valid_label(label))
invarg("label must be <=0xFFFFF", *argv);
} else if (matches(*argv, "tc") == 0) {
if (!can_modify_mpls_fields(action))
invarg("only valid for push/modify", *argv);
invarg("only valid for push, mac_push and modify",
*argv);
NEXT_ARG();
if (get_u8(&tc, *argv, 0) || (tc & ~0x7))
invarg("tc field is 3 bits max", *argv);
} else if (matches(*argv, "ttl") == 0) {
if (!can_modify_mpls_fields(action))
invarg("only valid for push/modify", *argv);
invarg("only valid for push, mac_push and modify",
*argv);
NEXT_ARG();
if (get_u8(&ttl, *argv, 0) || !ttl)
invarg("ttl must be >0 and <=255", *argv);
} else if (matches(*argv, "bos") == 0) {
if (!can_modify_mpls_fields(action))
invarg("only valid for push/modify", *argv);
invarg("only valid for push, mac_push and modify",
*argv);
NEXT_ARG();
if (get_u8(&bos, *argv, 0) || (bos & ~0x1))
invarg("bos must be 0 or 1", *argv);
} else if (matches(*argv, "protocol") == 0) {
if (!can_modify_ethtype(action))
invarg("only valid for push/pop", *argv);
if (!can_set_ethtype(action))
invarg("only valid for push, mac_push and pop",
*argv);
NEXT_ARG();
if (ll_proto_a2n(&proto, *argv))
invarg("protocol is invalid", *argv);
@ -159,10 +173,12 @@ static int parse_mpls(struct action_util *a, int *argc_p, char ***argv_p,
if (action == TCA_MPLS_ACT_PUSH && label == 0xffffffff)
missarg("label");
if (action == TCA_MPLS_ACT_PUSH && proto &&
if ((action == TCA_MPLS_ACT_PUSH || action == TCA_MPLS_ACT_MAC_PUSH) &&
proto &&
proto != htons(ETH_P_MPLS_UC) && proto != htons(ETH_P_MPLS_MC)) {
fprintf(stderr,
"invalid push protocol \"0x%04x\" - use mpls_(uc|mc)\n",
"invalid %spush protocol \"0x%04x\" - use mpls_(uc|mc)\n",
action == TCA_MPLS_ACT_MAC_PUSH ? "mac_" : "",
ntohs(proto));
return -1;
}
@ -223,6 +239,7 @@ static int print_mpls(struct action_util *au, FILE *f, struct rtattr *arg)
}
break;
case TCA_MPLS_ACT_PUSH:
case TCA_MPLS_ACT_MAC_PUSH:
if (tb[TCA_MPLS_PROTO]) {
__u16 proto;

View File

@ -23,6 +23,8 @@ static const char * const action_names[] = {
[TCA_VLAN_ACT_POP] = "pop",
[TCA_VLAN_ACT_PUSH] = "push",
[TCA_VLAN_ACT_MODIFY] = "modify",
[TCA_VLAN_ACT_POP_ETH] = "pop_eth",
[TCA_VLAN_ACT_PUSH_ETH] = "push_eth",
};
static void explain(void)
@ -31,6 +33,8 @@ static void explain(void)
"Usage: vlan pop\n"
" vlan push [ protocol VLANPROTO ] id VLANID [ priority VLANPRIO ] [CONTROL]\n"
" vlan modify [ protocol VLANPROTO ] id VLANID [ priority VLANPRIO ] [CONTROL]\n"
" vlan pop_eth [CONTROL]\n"
" vlan push_eth dst_mac LLADDR src_mac LLADDR [CONTROL]\n"
" VLANPROTO is one of 802.1Q or 802.1AD\n"
" with default: 802.1Q\n"
" CONTROL := reclassify | pipe | drop | continue | pass |\n"
@ -63,6 +67,10 @@ static int parse_vlan(struct action_util *a, int *argc_p, char ***argv_p,
char **argv = *argv_p;
struct rtattr *tail;
int action = 0;
char dst_mac[ETH_ALEN] = {};
int dst_mac_set = 0;
char src_mac[ETH_ALEN] = {};
int src_mac_set = 0;
__u16 id;
int id_set = 0;
__u16 proto;
@ -95,6 +103,18 @@ static int parse_vlan(struct action_util *a, int *argc_p, char ***argv_p,
return -1;
}
action = TCA_VLAN_ACT_MODIFY;
} else if (matches(*argv, "pop_eth") == 0) {
if (action) {
unexpected(*argv);
return -1;
}
action = TCA_VLAN_ACT_POP_ETH;
} else if (matches(*argv, "push_eth") == 0) {
if (action) {
unexpected(*argv);
return -1;
}
action = TCA_VLAN_ACT_PUSH_ETH;
} else if (matches(*argv, "id") == 0) {
if (!has_push_attribs(action))
invarg("only valid for push/modify", *argv);
@ -119,6 +139,22 @@ static int parse_vlan(struct action_util *a, int *argc_p, char ***argv_p,
if (get_u8(&prio, *argv, 0) || (prio & ~0x7))
invarg("prio is invalid", *argv);
prio_set = 1;
} else if (matches(*argv, "dst_mac") == 0) {
if (action != TCA_VLAN_ACT_PUSH_ETH)
invarg("only valid for push_eth", *argv);
NEXT_ARG();
if (ll_addr_a2n(dst_mac, sizeof(dst_mac), *argv) < 0)
invarg("dst_mac is invalid", *argv);
dst_mac_set = 1;
} else if (matches(*argv, "src_mac") == 0) {
if (action != TCA_VLAN_ACT_PUSH_ETH)
invarg("only valid for push_eth", *argv);
NEXT_ARG();
if (ll_addr_a2n(src_mac, sizeof(src_mac), *argv) < 0)
invarg("src_mac is invalid", *argv);
src_mac_set = 1;
} else if (matches(*argv, "help") == 0) {
usage();
} else {
@ -150,6 +186,20 @@ static int parse_vlan(struct action_util *a, int *argc_p, char ***argv_p,
return -1;
}
if (action == TCA_VLAN_ACT_PUSH_ETH) {
if (!dst_mac_set) {
fprintf(stderr, "dst_mac needs to be set for %s\n",
action_names[action]);
explain();
return -1;
} else if (!src_mac_set) {
fprintf(stderr, "src_mac needs to be set for %s\n",
action_names[action]);
explain();
return -1;
}
}
parm.v_action = action;
tail = addattr_nest(n, MAX_MSG, tca_id);
addattr_l(n, MAX_MSG, TCA_VLAN_PARMS, &parm, sizeof(parm));
@ -167,6 +217,12 @@ static int parse_vlan(struct action_util *a, int *argc_p, char ***argv_p,
}
if (prio_set)
addattr8(n, MAX_MSG, TCA_VLAN_PUSH_VLAN_PRIORITY, prio);
if (dst_mac_set)
addattr_l(n, MAX_MSG, TCA_VLAN_PUSH_ETH_DST, dst_mac,
sizeof(dst_mac));
if (src_mac_set)
addattr_l(n, MAX_MSG, TCA_VLAN_PUSH_ETH_SRC, src_mac,
sizeof(src_mac));
addattr_nest_end(n, tail);
@ -216,6 +272,19 @@ static int print_vlan(struct action_util *au, FILE *f, struct rtattr *arg)
print_uint(PRINT_ANY, "priority", " priority %u", val);
}
break;
case TCA_VLAN_ACT_PUSH_ETH:
if (tb[TCA_VLAN_PUSH_ETH_DST] &&
RTA_PAYLOAD(tb[TCA_VLAN_PUSH_ETH_DST]) == ETH_ALEN) {
ll_addr_n2a(RTA_DATA(tb[TCA_VLAN_PUSH_ETH_DST]),
ETH_ALEN, 0, b1, sizeof(b1));
print_string(PRINT_ANY, "dst_mac", " dst_mac %s", b1);
}
if (tb[TCA_VLAN_PUSH_ETH_SRC &&
RTA_PAYLOAD(tb[TCA_VLAN_PUSH_ETH_SRC]) == ETH_ALEN]) {
ll_addr_n2a(RTA_DATA(tb[TCA_VLAN_PUSH_ETH_SRC]),
ETH_ALEN, 0, b1, sizeof(b1));
print_string(PRINT_ANY, "src_mac", " src_mac %s", b1);
}
}
print_action_control(f, " ", parm->action, "");

69
testsuite/tests/tc/mpls.t Executable file
View File

@ -0,0 +1,69 @@
#!/bin/sh
. lib/generic.sh
DEV="$(rand_dev)"
ts_ip "$0" "Add $DEV dummy interface" link add dev $DEV up type dummy
ts_tc "$0" "Add ingress qdisc" qdisc add dev $DEV ingress
reset_qdisc()
{
ts_tc "$0" "Remove ingress qdisc" qdisc del dev $DEV ingress
ts_tc "$0" "Add ingress qdisc" qdisc add dev $DEV ingress
}
ts_tc "$0" "Add mpls action pop" \
filter add dev $DEV ingress protocol mpls_uc matchall \
action mpls pop protocol ip
ts_tc "$0" "Show ingress filters" filter show dev $DEV ingress
test_on "mpls"
test_on "pop protocol ip pipe"
reset_qdisc
ts_tc "$0" "Add mpls action push" \
filter add dev $DEV ingress protocol ip matchall \
action mpls push protocol mpls_uc label 20 tc 3 bos 1 ttl 64
ts_tc "$0" "Show ingress filters" filter show dev $DEV ingress
test_on "mpls"
test_on "push"
test_on "protocol mpls_uc"
test_on "label 20"
test_on "tc 3"
test_on "bos 1"
test_on "ttl 64"
test_on "pipe"
reset_qdisc
ts_tc "$0" "Add mpls action mac_push" \
filter add dev $DEV ingress matchall \
action mpls mac_push protocol mpls_uc label 20 tc 3 bos 1 ttl 64
ts_tc "$0" "Show ingress filters" filter show dev $DEV ingress
test_on "mpls"
test_on "mac_push"
test_on "protocol mpls_uc"
test_on "label 20"
test_on "tc 3"
test_on "bos 1"
test_on "ttl 64"
test_on "pipe"
reset_qdisc
ts_tc "$0" "Add mpls action modify" \
filter add dev $DEV ingress protocol mpls_uc matchall \
action mpls modify label 20 tc 3 ttl 64
ts_tc "$0" "Show ingress filters" filter show dev $DEV ingress
test_on "mpls"
test_on "modify"
test_on "label 20"
test_on "tc 3"
test_on "ttl 64"
test_on "pipe"
reset_qdisc
ts_tc "$0" "Add mpls action dec_ttl" \
filter add dev $DEV ingress protocol mpls_uc matchall \
action mpls dec_ttl
ts_tc "$0" "Show ingress filters" filter show dev $DEV ingress
test_on "mpls"
test_on "dec_ttl"
test_on "pipe"

86
testsuite/tests/tc/vlan.t Executable file
View File

@ -0,0 +1,86 @@
#!/bin/sh
. lib/generic.sh
DEV="$(rand_dev)"
ts_ip "$0" "Add $DEV dummy interface" link add dev $DEV up type dummy
ts_tc "$0" "Add ingress qdisc" qdisc add dev $DEV ingress
reset_qdisc()
{
ts_tc "$0" "Remove ingress qdisc" qdisc del dev $DEV ingress
ts_tc "$0" "Add ingress qdisc" qdisc add dev $DEV ingress
}
ts_tc "$0" "Add vlan action pop" \
filter add dev $DEV ingress matchall action vlan pop
ts_tc "$0" "Show ingress filters" filter show dev $DEV ingress
test_on "vlan"
test_on "pop"
test_on "pipe"
reset_qdisc
ts_tc "$0" "Add vlan action push (default parameters)" \
filter add dev $DEV ingress matchall action vlan push id 5
ts_tc "$0" "Show ingress filters" filter show dev $DEV ingress
test_on "vlan"
test_on "push"
test_on "id 5"
test_on "protocol 802.1Q"
test_on "priority 0"
test_on "pipe"
reset_qdisc
ts_tc "$0" "Add vlan action push (explicit parameters)" \
filter add dev $DEV ingress matchall \
action vlan push id 5 protocol 802.1ad priority 2
ts_tc "$0" "Show ingress filters" filter show dev $DEV ingress
test_on "vlan"
test_on "push"
test_on "id 5"
test_on "protocol 802.1ad"
test_on "priority 2"
test_on "pipe"
reset_qdisc
ts_tc "$0" "Add vlan action modify (default parameters)" \
filter add dev $DEV ingress matchall action vlan modify id 5
ts_tc "$0" "Show ingress filters" filter show dev $DEV ingress
test_on "vlan"
test_on "modify"
test_on "id 5"
test_on "protocol 802.1Q"
test_on "priority 0"
test_on "pipe"
reset_qdisc
ts_tc "$0" "Add vlan action modify (explicit parameters)" \
filter add dev $DEV ingress matchall \
action vlan modify id 5 protocol 802.1ad priority 2
ts_tc "$0" "Show ingress filters" filter show dev $DEV ingress
test_on "vlan"
test_on "modify"
test_on "id 5"
test_on "protocol 802.1ad"
test_on "priority 2"
test_on "pipe"
reset_qdisc
ts_tc "$0" "Add vlan action pop_eth" \
filter add dev $DEV ingress matchall action vlan pop_eth
ts_tc "$0" "Show ingress filters" filter show dev $DEV ingress
test_on "vlan"
test_on "pop_eth"
test_on "pipe"
reset_qdisc
ts_tc "$0" "Add vlan action push_eth" \
filter add dev $DEV ingress matchall \
action vlan push_eth dst_mac 02:00:00:00:00:02 \
src_mac 02:00:00:00:00:01
ts_tc "$0" "Show ingress filters" filter show dev $DEV ingress
test_on "vlan"
test_on "push_eth"
test_on "dst_mac 02:00:00:00:00:02"
test_on "src_mac 02:00:00:00:00:01"
test_on "pipe"