Message ID | 20191127125516.5135-9-roid@mellanox.com |
---|---|
State | Superseded |
Headers | show |
Series | Add support for offloading CT datapath rules to TC | expand |
On Wed, Nov 27, 2019 at 02:55:14PM +0200, Roi Dayan wrote: > From: Paul Blakey <paulb@mellanox.com> > > Changelog: > V1->V2: > Missing ntohs/htons on nat port range. > > Signed-off-by: Paul Blakey <paulb@mellanox.com> > --- > lib/netdev-offload-tc.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ > lib/tc.c | 91 ++++++++++++++++++++++++++++++++++++++++++ > lib/tc.h | 24 +++++++++++ > 3 files changed, 219 insertions(+) > > diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c > index 2b4ecfb9ca48..3a9b9e144311 100644 > --- a/lib/netdev-offload-tc.c > +++ b/lib/netdev-offload-tc.c > @@ -814,6 +814,40 @@ parse_tc_flower_to_match(struct tc_flower *flower, > ct_label->mask = action->ct.label_mask; > } > > + if (action->ct.nat_type) { > + size_t nat_offset = nl_msg_start_nested(buf, > + OVS_CT_ATTR_NAT); > + > + if (action->ct.nat_type == TC_NAT_SRC) { > + nl_msg_put_flag(buf, OVS_NAT_ATTR_SRC); > + } else if (action->ct.nat_type == TC_NAT_DST) { > + nl_msg_put_flag(buf, OVS_NAT_ATTR_DST); > + } > + > + if (action->ct.range.ip_family == AF_INET) { > + nl_msg_put_be32(buf, OVS_NAT_ATTR_IP_MIN, ^^^^^^^^^^^ > + action->ct.range.min_addr.ipv4); ^^^^^^^^ Sorry, more bikeshedding here. OVS seems to use _MIN and _MAX as suffix, and tc act ct also uses it that way: TCA_CT_NAT_IPV4_MIN, etc. I'm wondering, should we do the same for tc_action members here? > + nl_msg_put_be32(buf, OVS_NAT_ATTR_IP_MAX, > + action->ct.range.max_addr.ipv4); > + } else if (action->ct.range.ip_family == AF_INET6) { > + nl_msg_put_in6_addr(buf, OVS_NAT_ATTR_IP_MIN, > + &action->ct.range.min_addr.ipv6); > + nl_msg_put_in6_addr(buf, OVS_NAT_ATTR_IP_MAX, > + &action->ct.range.max_addr.ipv6); > + } > + > + if (action->ct.range.min_port) { > + nl_msg_put_u16(buf, OVS_NAT_ATTR_PROTO_MIN, > + ntohs(action->ct.range.min_port)); > + if (action->ct.range.max_port) { > + nl_msg_put_u16(buf, OVS_NAT_ATTR_PROTO_MAX, > + ntohs(action->ct.range.max_port)); > + } > + } > + > + nl_msg_end_nested(buf, nat_offset); > + } > + > nl_msg_end_nested(buf, ct_offset); > } > break; > @@ -906,6 +940,66 @@ parse_mpls_set_action(struct tc_flower *flower, struct tc_action *action, > } > > static int > +parse_put_flow_nat_action(struct tc_action *action, > + const struct nlattr *nat, > + size_t nat_len) > +{ > + const struct nlattr *nat_attr; > + size_t nat_left; > + > + action->ct.nat_type = TC_NAT_RESTORE; > + NL_ATTR_FOR_EACH_UNSAFE (nat_attr, nat_left, nat, nat_len) { > + switch (nl_attr_type(nat_attr)) { > + case OVS_NAT_ATTR_SRC: { > + action->ct.nat_type = TC_NAT_SRC; > + }; > + break; > + case OVS_NAT_ATTR_DST: { > + action->ct.nat_type = TC_NAT_DST; > + }; > + break; > + case OVS_NAT_ATTR_IP_MIN: { > + if (nl_attr_get_size(nat_attr) == sizeof(ovs_be32)) { > + ovs_be32 addr = nl_attr_get_be32(nat_attr); > + > + action->ct.range.min_addr.ipv4 = addr; > + action->ct.range.ip_family = AF_INET; > + } else { > + struct in6_addr addr = nl_attr_get_in6_addr(nat_attr); > + > + action->ct.range.min_addr.ipv6 = addr; > + action->ct.range.ip_family = AF_INET6; > + } > + }; > + break; > + case OVS_NAT_ATTR_IP_MAX: { > + if (nl_attr_get_size(nat_attr) == sizeof(ovs_be32)) { > + ovs_be32 addr = nl_attr_get_be32(nat_attr); > + > + action->ct.range.max_addr.ipv4 = addr; > + action->ct.range.ip_family = AF_INET; > + } else { > + struct in6_addr addr = nl_attr_get_in6_addr(nat_attr); > + > + action->ct.range.max_addr.ipv6 = addr; > + action->ct.range.ip_family = AF_INET6; > + } > + }; > + break; > + case OVS_NAT_ATTR_PROTO_MIN: { > + action->ct.range.min_port = htons(nl_attr_get_u16(nat_attr)); > + }; > + break; > + case OVS_NAT_ATTR_PROTO_MAX: { > + action->ct.range.max_port = htons(nl_attr_get_u16(nat_attr)); > + }; > + break; > + } > + } > + return 0; > +} > + > +static int > parse_put_flow_ct_action(struct tc_flower *flower, > struct tc_action *action, > const struct nlattr *ct, > @@ -925,6 +1019,16 @@ parse_put_flow_ct_action(struct tc_flower *flower, > action->ct.zone = nl_attr_get_u16(ct_attr); > } > break; > + case OVS_CT_ATTR_NAT: { > + const struct nlattr *nat = nl_attr_get(ct_attr); > + const size_t nat_len = nl_attr_get_size(ct_attr); > + > + err = parse_put_flow_nat_action(action, nat, nat_len); > + if (err) { > + return err; > + } > + } > + break; > case OVS_CT_ATTR_MARK: { > const struct { > uint32_t key; > diff --git a/lib/tc.c b/lib/tc.c > index 1062326d4b86..83ee0f6473aa 100644 > --- a/lib/tc.c > +++ b/lib/tc.c > @@ -1282,6 +1282,18 @@ static const struct nl_policy ct_policy[] = { > .optional = true, }, > [TCA_CT_LABELS_MASK] = { .type = NL_A_UNSPEC, > .optional = true, }, > + [TCA_CT_NAT_IPV4_MIN] = { .type = NL_A_U32, > + .optional = true, }, > + [TCA_CT_NAT_IPV4_MAX] = { .type = NL_A_U32, > + .optional = true, }, > + [TCA_CT_NAT_IPV6_MIN] = { .min_len = sizeof(struct in6_addr), > + .optional = true, }, > + [TCA_CT_NAT_IPV6_MAX] = { .min_len = sizeof(struct in6_addr), > + .optional = true, }, > + [TCA_CT_NAT_PORT_MIN] = { .type = NL_A_U16, > + .optional = true, }, > + [TCA_CT_NAT_PORT_MAX] = { .type = NL_A_U16, > + .optional = true, }, > }; > > static int > @@ -1325,6 +1337,47 @@ nl_parse_act_ct(struct nlattr *options, struct tc_flower *flower) > action->ct.label_mask = label_mask ? > nl_attr_get_u128(label_mask) : OVS_U128_ZERO; > > + if (ct_action & TCA_CT_ACT_NAT) { > + struct nlattr *ipv4_min = ct_attrs[TCA_CT_NAT_IPV4_MIN]; > + struct nlattr *ipv4_max = ct_attrs[TCA_CT_NAT_IPV4_MAX]; > + struct nlattr *ipv6_min = ct_attrs[TCA_CT_NAT_IPV6_MIN]; > + struct nlattr *ipv6_max = ct_attrs[TCA_CT_NAT_IPV6_MAX]; > + struct nlattr *min_port = ct_attrs[TCA_CT_NAT_PORT_MIN]; > + struct nlattr *max_port = ct_attrs[TCA_CT_NAT_PORT_MAX]; Ditto here. > + > + action->ct.nat_type = TC_NAT_RESTORE; > + if (ct_action & TCA_CT_ACT_NAT_SRC) { > + action->ct.nat_type = TC_NAT_SRC; > + } else if (ct_action & TCA_CT_ACT_NAT_DST) { > + action->ct.nat_type = TC_NAT_DST; > + } > + > + if (ipv4_min) { > + action->ct.range.ip_family = AF_INET; > + action->ct.range.min_addr.ipv4 = nl_attr_get_be32(ipv4_min); > + if (ipv4_max) { > + ovs_be32 port = nl_attr_get_be32(ipv4_max); > + > + action->ct.range.max_addr.ipv4 = port; > + } > + } else if (ipv6_min) { > + action->ct.range.ip_family = AF_INET6; > + action->ct.range.min_addr.ipv6 > + = nl_attr_get_in6_addr(ipv6_min); > + if (ipv6_max) { > + struct in6_addr addr = nl_attr_get_in6_addr(ipv6_max); > + > + action->ct.range.max_addr.ipv6 = addr; > + } > + } > + > + if (min_port) { > + action->ct.range.min_port = nl_attr_get_be16(min_port); > + if (max_port) { > + action->ct.range.max_port = nl_attr_get_be16(max_port); > + } > + } > + } > } > action->type = TC_ACT_CT; > > @@ -2056,6 +2109,44 @@ nl_msg_put_act_ct(struct ofpbuf *request, struct tc_action *action) > ct_action |= TCA_CT_ACT_FORCE; > } > } > + > + if (action->ct.nat_type) { > + ct_action |= TCA_CT_ACT_NAT; > + > + if (action->ct.nat_type == TC_NAT_SRC) { > + ct_action |= TCA_CT_ACT_NAT_SRC; > + } else if (action->ct.nat_type == TC_NAT_DST) { > + ct_action |= TCA_CT_ACT_NAT_DST; > + } > + > + if (action->ct.range.ip_family == AF_INET) { > + nl_msg_put_be32(request, TCA_CT_NAT_IPV4_MIN, > + action->ct.range.min_addr.ipv4); > + if (action->ct.range.max_addr.ipv4) { > + nl_msg_put_be32(request, TCA_CT_NAT_IPV4_MAX, > + action->ct.range.max_addr.ipv4); > + } > + } else if (action->ct.range.ip_family == AF_INET6) { > + size_t ipv6_sz = sizeof(action->ct.range.max_addr.ipv6); > + > + nl_msg_put_in6_addr(request, TCA_CT_NAT_IPV6_MIN, > + &action->ct.range.min_addr.ipv6); > + if (!is_all_zeros(&action->ct.range.max_addr.ipv6, > + ipv6_sz)) { > + nl_msg_put_in6_addr(request, TCA_CT_NAT_IPV6_MAX, > + &action->ct.range.max_addr.ipv6); > + } > + } > + > + if (action->ct.range.min_port) { > + nl_msg_put_be16(request, TCA_CT_NAT_PORT_MIN, > + action->ct.range.min_port); > + if (action->ct.range.max_port) { > + nl_msg_put_be16(request, TCA_CT_NAT_PORT_MAX, > + action->ct.range.max_port); > + } > + } > + } > } else { > ct_action = TCA_CT_ACT_CLEAR; > } > diff --git a/lib/tc.h b/lib/tc.h > index e853aeb77468..15e58f88acb9 100644 > --- a/lib/tc.h > +++ b/lib/tc.h > @@ -165,6 +165,13 @@ enum tc_action_type { > TC_ACT_CT, > }; > > +enum nat_type { > + TC_NO_NAT = 0, > + TC_NAT_SRC, > + TC_NAT_DST, > + TC_NAT_RESTORE, > +}; > + > struct tc_action { > union { > int chain; > @@ -213,6 +220,23 @@ struct tc_action { > uint32_t mark_mask; > ovs_u128 label; > ovs_u128 label_mask; > + uint8_t nat_type; > + struct { > + uint8_t ip_family; > + > + union { > + struct in6_addr ipv6; > + ovs_be32 ipv4; > + } min_addr; > + > + union { > + struct in6_addr ipv6; > + ovs_be32 ipv4; > + } max_addr; > + > + ovs_be16 min_port; > + ovs_be16 max_port; > + } range; > bool clear; > bool force; > bool commit; > -- > 2.8.4 >
On 11/27/2019 5:27 PM, Marcelo Ricardo Leitner wrote: > On Wed, Nov 27, 2019 at 02:55:14PM +0200, Roi Dayan wrote: >> From: Paul Blakey <paulb@mellanox.com> >> >> Changelog: >> V1->V2: >> Missing ntohs/htons on nat port range. >> >> Signed-off-by: Paul Blakey <paulb@mellanox.com> >> --- >> lib/netdev-offload-tc.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ >> lib/tc.c | 91 ++++++++++++++++++++++++++++++++++++++++++ >> lib/tc.h | 24 +++++++++++ >> 3 files changed, 219 insertions(+) >> >> diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c >> index 2b4ecfb9ca48..3a9b9e144311 100644 >> --- a/lib/netdev-offload-tc.c >> +++ b/lib/netdev-offload-tc.c >> @@ -814,6 +814,40 @@ parse_tc_flower_to_match(struct tc_flower *flower, >> ct_label->mask = action->ct.label_mask; >> } >> >> + if (action->ct.nat_type) { >> + size_t nat_offset = nl_msg_start_nested(buf, >> + OVS_CT_ATTR_NAT); >> + >> + if (action->ct.nat_type == TC_NAT_SRC) { >> + nl_msg_put_flag(buf, OVS_NAT_ATTR_SRC); >> + } else if (action->ct.nat_type == TC_NAT_DST) { >> + nl_msg_put_flag(buf, OVS_NAT_ATTR_DST); >> + } >> + >> + if (action->ct.range.ip_family == AF_INET) { >> + nl_msg_put_be32(buf, OVS_NAT_ATTR_IP_MIN, > ^^^^^^^^^^^ >> + action->ct.range.min_addr.ipv4); > ^^^^^^^^ > > Sorry, more bikeshedding here. > OVS seems to use _MIN and _MAX as suffix, and tc act ct also uses it > that way: TCA_CT_NAT_IPV4_MIN, etc. > > I'm wondering, should we do the same for tc_action members here? > Sure, will do.
diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c index 2b4ecfb9ca48..3a9b9e144311 100644 --- a/lib/netdev-offload-tc.c +++ b/lib/netdev-offload-tc.c @@ -814,6 +814,40 @@ parse_tc_flower_to_match(struct tc_flower *flower, ct_label->mask = action->ct.label_mask; } + if (action->ct.nat_type) { + size_t nat_offset = nl_msg_start_nested(buf, + OVS_CT_ATTR_NAT); + + if (action->ct.nat_type == TC_NAT_SRC) { + nl_msg_put_flag(buf, OVS_NAT_ATTR_SRC); + } else if (action->ct.nat_type == TC_NAT_DST) { + nl_msg_put_flag(buf, OVS_NAT_ATTR_DST); + } + + if (action->ct.range.ip_family == AF_INET) { + nl_msg_put_be32(buf, OVS_NAT_ATTR_IP_MIN, + action->ct.range.min_addr.ipv4); + nl_msg_put_be32(buf, OVS_NAT_ATTR_IP_MAX, + action->ct.range.max_addr.ipv4); + } else if (action->ct.range.ip_family == AF_INET6) { + nl_msg_put_in6_addr(buf, OVS_NAT_ATTR_IP_MIN, + &action->ct.range.min_addr.ipv6); + nl_msg_put_in6_addr(buf, OVS_NAT_ATTR_IP_MAX, + &action->ct.range.max_addr.ipv6); + } + + if (action->ct.range.min_port) { + nl_msg_put_u16(buf, OVS_NAT_ATTR_PROTO_MIN, + ntohs(action->ct.range.min_port)); + if (action->ct.range.max_port) { + nl_msg_put_u16(buf, OVS_NAT_ATTR_PROTO_MAX, + ntohs(action->ct.range.max_port)); + } + } + + nl_msg_end_nested(buf, nat_offset); + } + nl_msg_end_nested(buf, ct_offset); } break; @@ -906,6 +940,66 @@ parse_mpls_set_action(struct tc_flower *flower, struct tc_action *action, } static int +parse_put_flow_nat_action(struct tc_action *action, + const struct nlattr *nat, + size_t nat_len) +{ + const struct nlattr *nat_attr; + size_t nat_left; + + action->ct.nat_type = TC_NAT_RESTORE; + NL_ATTR_FOR_EACH_UNSAFE (nat_attr, nat_left, nat, nat_len) { + switch (nl_attr_type(nat_attr)) { + case OVS_NAT_ATTR_SRC: { + action->ct.nat_type = TC_NAT_SRC; + }; + break; + case OVS_NAT_ATTR_DST: { + action->ct.nat_type = TC_NAT_DST; + }; + break; + case OVS_NAT_ATTR_IP_MIN: { + if (nl_attr_get_size(nat_attr) == sizeof(ovs_be32)) { + ovs_be32 addr = nl_attr_get_be32(nat_attr); + + action->ct.range.min_addr.ipv4 = addr; + action->ct.range.ip_family = AF_INET; + } else { + struct in6_addr addr = nl_attr_get_in6_addr(nat_attr); + + action->ct.range.min_addr.ipv6 = addr; + action->ct.range.ip_family = AF_INET6; + } + }; + break; + case OVS_NAT_ATTR_IP_MAX: { + if (nl_attr_get_size(nat_attr) == sizeof(ovs_be32)) { + ovs_be32 addr = nl_attr_get_be32(nat_attr); + + action->ct.range.max_addr.ipv4 = addr; + action->ct.range.ip_family = AF_INET; + } else { + struct in6_addr addr = nl_attr_get_in6_addr(nat_attr); + + action->ct.range.max_addr.ipv6 = addr; + action->ct.range.ip_family = AF_INET6; + } + }; + break; + case OVS_NAT_ATTR_PROTO_MIN: { + action->ct.range.min_port = htons(nl_attr_get_u16(nat_attr)); + }; + break; + case OVS_NAT_ATTR_PROTO_MAX: { + action->ct.range.max_port = htons(nl_attr_get_u16(nat_attr)); + }; + break; + } + } + return 0; +} + +static int parse_put_flow_ct_action(struct tc_flower *flower, struct tc_action *action, const struct nlattr *ct, @@ -925,6 +1019,16 @@ parse_put_flow_ct_action(struct tc_flower *flower, action->ct.zone = nl_attr_get_u16(ct_attr); } break; + case OVS_CT_ATTR_NAT: { + const struct nlattr *nat = nl_attr_get(ct_attr); + const size_t nat_len = nl_attr_get_size(ct_attr); + + err = parse_put_flow_nat_action(action, nat, nat_len); + if (err) { + return err; + } + } + break; case OVS_CT_ATTR_MARK: { const struct { uint32_t key; diff --git a/lib/tc.c b/lib/tc.c index 1062326d4b86..83ee0f6473aa 100644 --- a/lib/tc.c +++ b/lib/tc.c @@ -1282,6 +1282,18 @@ static const struct nl_policy ct_policy[] = { .optional = true, }, [TCA_CT_LABELS_MASK] = { .type = NL_A_UNSPEC, .optional = true, }, + [TCA_CT_NAT_IPV4_MIN] = { .type = NL_A_U32, + .optional = true, }, + [TCA_CT_NAT_IPV4_MAX] = { .type = NL_A_U32, + .optional = true, }, + [TCA_CT_NAT_IPV6_MIN] = { .min_len = sizeof(struct in6_addr), + .optional = true, }, + [TCA_CT_NAT_IPV6_MAX] = { .min_len = sizeof(struct in6_addr), + .optional = true, }, + [TCA_CT_NAT_PORT_MIN] = { .type = NL_A_U16, + .optional = true, }, + [TCA_CT_NAT_PORT_MAX] = { .type = NL_A_U16, + .optional = true, }, }; static int @@ -1325,6 +1337,47 @@ nl_parse_act_ct(struct nlattr *options, struct tc_flower *flower) action->ct.label_mask = label_mask ? nl_attr_get_u128(label_mask) : OVS_U128_ZERO; + if (ct_action & TCA_CT_ACT_NAT) { + struct nlattr *ipv4_min = ct_attrs[TCA_CT_NAT_IPV4_MIN]; + struct nlattr *ipv4_max = ct_attrs[TCA_CT_NAT_IPV4_MAX]; + struct nlattr *ipv6_min = ct_attrs[TCA_CT_NAT_IPV6_MIN]; + struct nlattr *ipv6_max = ct_attrs[TCA_CT_NAT_IPV6_MAX]; + struct nlattr *min_port = ct_attrs[TCA_CT_NAT_PORT_MIN]; + struct nlattr *max_port = ct_attrs[TCA_CT_NAT_PORT_MAX]; + + action->ct.nat_type = TC_NAT_RESTORE; + if (ct_action & TCA_CT_ACT_NAT_SRC) { + action->ct.nat_type = TC_NAT_SRC; + } else if (ct_action & TCA_CT_ACT_NAT_DST) { + action->ct.nat_type = TC_NAT_DST; + } + + if (ipv4_min) { + action->ct.range.ip_family = AF_INET; + action->ct.range.min_addr.ipv4 = nl_attr_get_be32(ipv4_min); + if (ipv4_max) { + ovs_be32 port = nl_attr_get_be32(ipv4_max); + + action->ct.range.max_addr.ipv4 = port; + } + } else if (ipv6_min) { + action->ct.range.ip_family = AF_INET6; + action->ct.range.min_addr.ipv6 + = nl_attr_get_in6_addr(ipv6_min); + if (ipv6_max) { + struct in6_addr addr = nl_attr_get_in6_addr(ipv6_max); + + action->ct.range.max_addr.ipv6 = addr; + } + } + + if (min_port) { + action->ct.range.min_port = nl_attr_get_be16(min_port); + if (max_port) { + action->ct.range.max_port = nl_attr_get_be16(max_port); + } + } + } } action->type = TC_ACT_CT; @@ -2056,6 +2109,44 @@ nl_msg_put_act_ct(struct ofpbuf *request, struct tc_action *action) ct_action |= TCA_CT_ACT_FORCE; } } + + if (action->ct.nat_type) { + ct_action |= TCA_CT_ACT_NAT; + + if (action->ct.nat_type == TC_NAT_SRC) { + ct_action |= TCA_CT_ACT_NAT_SRC; + } else if (action->ct.nat_type == TC_NAT_DST) { + ct_action |= TCA_CT_ACT_NAT_DST; + } + + if (action->ct.range.ip_family == AF_INET) { + nl_msg_put_be32(request, TCA_CT_NAT_IPV4_MIN, + action->ct.range.min_addr.ipv4); + if (action->ct.range.max_addr.ipv4) { + nl_msg_put_be32(request, TCA_CT_NAT_IPV4_MAX, + action->ct.range.max_addr.ipv4); + } + } else if (action->ct.range.ip_family == AF_INET6) { + size_t ipv6_sz = sizeof(action->ct.range.max_addr.ipv6); + + nl_msg_put_in6_addr(request, TCA_CT_NAT_IPV6_MIN, + &action->ct.range.min_addr.ipv6); + if (!is_all_zeros(&action->ct.range.max_addr.ipv6, + ipv6_sz)) { + nl_msg_put_in6_addr(request, TCA_CT_NAT_IPV6_MAX, + &action->ct.range.max_addr.ipv6); + } + } + + if (action->ct.range.min_port) { + nl_msg_put_be16(request, TCA_CT_NAT_PORT_MIN, + action->ct.range.min_port); + if (action->ct.range.max_port) { + nl_msg_put_be16(request, TCA_CT_NAT_PORT_MAX, + action->ct.range.max_port); + } + } + } } else { ct_action = TCA_CT_ACT_CLEAR; } diff --git a/lib/tc.h b/lib/tc.h index e853aeb77468..15e58f88acb9 100644 --- a/lib/tc.h +++ b/lib/tc.h @@ -165,6 +165,13 @@ enum tc_action_type { TC_ACT_CT, }; +enum nat_type { + TC_NO_NAT = 0, + TC_NAT_SRC, + TC_NAT_DST, + TC_NAT_RESTORE, +}; + struct tc_action { union { int chain; @@ -213,6 +220,23 @@ struct tc_action { uint32_t mark_mask; ovs_u128 label; ovs_u128 label_mask; + uint8_t nat_type; + struct { + uint8_t ip_family; + + union { + struct in6_addr ipv6; + ovs_be32 ipv4; + } min_addr; + + union { + struct in6_addr ipv6; + ovs_be32 ipv4; + } max_addr; + + ovs_be16 min_port; + ovs_be16 max_port; + } range; bool clear; bool force; bool commit;