@@ -116,6 +116,8 @@ struct ovn_extend_table;
OVNACT(PUT_FDB, ovnact_put_fdb) \
OVNACT(GET_FDB, ovnact_get_fdb) \
OVNACT(LOOKUP_FDB, ovnact_lookup_fdb) \
+ OVNACT(CHECK_IN_PORT_SEC, ovnact_result) \
+ OVNACT(CHECK_OUT_PORT_SEC, ovnact_result) \
/* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
enum OVS_PACKED_ENUM ovnact_type {
@@ -4004,19 +4004,20 @@ format_CHK_LB_HAIRPIN_REPLY(const struct ovnact_result *res, struct ds *s)
}
static void
-encode_chk_lb_hairpin__(const struct ovnact_result *res,
- uint8_t hairpin_table,
- struct ofpbuf *ofpacts)
+encode_result_action__(const struct ovnact_result *res,
+ uint8_t resubmit_table,
+ int log_flags_result_bit,
+ struct ofpbuf *ofpacts)
{
struct mf_subfield dst = expr_resolve_field(&res->dst);
ovs_assert(dst.field);
- put_load(0, MFF_LOG_FLAGS, MLF_LOOKUP_LB_HAIRPIN_BIT, 1, ofpacts);
- emit_resubmit(ofpacts, hairpin_table);
+ put_load(0, MFF_LOG_FLAGS, log_flags_result_bit, 1, ofpacts);
+ emit_resubmit(ofpacts, resubmit_table);
struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts);
orm->dst = dst;
orm->src.field = mf_from_id(MFF_LOG_FLAGS);
- orm->src.ofs = MLF_LOOKUP_LB_HAIRPIN_BIT;
+ orm->src.ofs = log_flags_result_bit;
orm->src.n_bits = 1;
}
@@ -4025,7 +4026,8 @@ encode_CHK_LB_HAIRPIN(const struct ovnact_result *res,
const struct ovnact_encode_params *ep,
struct ofpbuf *ofpacts)
{
- encode_chk_lb_hairpin__(res, ep->lb_hairpin_ptable, ofpacts);
+ encode_result_action__(res, ep->lb_hairpin_ptable,
+ MLF_LOOKUP_LB_HAIRPIN_BIT, ofpacts);
}
static void
@@ -4033,7 +4035,8 @@ encode_CHK_LB_HAIRPIN_REPLY(const struct ovnact_result *res,
const struct ovnact_encode_params *ep,
struct ofpbuf *ofpacts)
{
- encode_chk_lb_hairpin__(res, ep->lb_hairpin_reply_ptable, ofpacts);
+ encode_result_action__(res, ep->lb_hairpin_reply_ptable,
+ MLF_LOOKUP_LB_HAIRPIN_BIT, ofpacts);
}
static void
@@ -4216,6 +4219,54 @@ ovnact_lookup_fdb_free(struct ovnact_lookup_fdb *get_fdb OVS_UNUSED)
{
}
+static void
+parse_check_in_port_sec(struct action_context *ctx,
+ const struct expr_field *dst,
+ struct ovnact_result *dl)
+{
+ parse_ovnact_result(ctx, "check_in_port_sec", NULL, dst, dl);
+}
+
+static void
+format_CHECK_IN_PORT_SEC(const struct ovnact_result *dl, struct ds *s)
+{
+ expr_field_format(&dl->dst, s);
+ ds_put_cstr(s, " = check_in_port_sec();");
+}
+
+static void
+encode_CHECK_IN_PORT_SEC(const struct ovnact_result *dl,
+ const struct ovnact_encode_params *ep,
+ struct ofpbuf *ofpacts)
+{
+ encode_result_action__(dl, ep->in_port_sec_ptable,
+ MLF_CHECK_PORT_SEC_BIT, ofpacts);
+}
+
+static void
+parse_check_out_port_sec(struct action_context *ctx,
+ const struct expr_field *dst,
+ struct ovnact_result *dl)
+{
+ parse_ovnact_result(ctx, "check_out_port_sec", NULL, dst, dl);
+}
+
+static void
+format_CHECK_OUT_PORT_SEC(const struct ovnact_result *dl, struct ds *s)
+{
+ expr_field_format(&dl->dst, s);
+ ds_put_cstr(s, " = check_out_port_sec();");
+}
+
+static void
+encode_CHECK_OUT_PORT_SEC(const struct ovnact_result *dl,
+ const struct ovnact_encode_params *ep,
+ struct ofpbuf *ofpacts)
+{
+ encode_result_action__(dl, ep->out_port_sec_ptable,
+ MLF_CHECK_PORT_SEC_BIT, ofpacts);
+}
+
/* Parses an assignment or exchange or put_dhcp_opts action. */
static void
parse_set_action(struct action_context *ctx)
@@ -4284,6 +4335,14 @@ parse_set_action(struct action_context *ctx)
&& lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
parse_lookup_fdb(
ctx, &lhs, ovnact_put_LOOKUP_FDB(ctx->ovnacts));
+ } else if (!strcmp(ctx->lexer->token.s, "check_in_port_sec")
+ && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
+ parse_check_in_port_sec(
+ ctx, &lhs, ovnact_put_CHECK_IN_PORT_SEC(ctx->ovnacts));
+ } else if (!strcmp(ctx->lexer->token.s, "check_out_port_sec")
+ && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
+ parse_check_out_port_sec(
+ ctx, &lhs, ovnact_put_CHECK_OUT_PORT_SEC(ctx->ovnacts));
} else {
parse_assignment_action(ctx, false, &lhs);
}
@@ -2514,6 +2514,45 @@ tcp.flags = RST;
action to advance the packet to the next stage.
</p>
</dd>
+
+ <dt><code><var>R</var> = check_in_port_sec();</code></dt>
+ <dd>
+ <p>
+ This action checks if the packet under consideration passes the
+ inport port security checks. If the packet fails the port security
+ checks, then <code>1</code> is stored in the destination register
+ <var>R</var>. Else 0 is stored. The port security values to check
+ are retrieved from the the <code>inport</code> logical port.
+ </p>
+
+ <p>
+ This action should be used in the ingress logical switch pipeline.
+
+ </p>
+ <p>
+ <b>Example:</b> <code>reg8[0..7] = check_in_port_sec();</code>
+ </p>
+ </dd>
+
+ <dt><code><var>R</var> = check_out_port_sec();</code></dt>
+ <dd>
+ <p>
+ This action checks if the packet under consideration passes the
+ outport port security checks. If the packet fails the port
+ security checks, then <code>1</code> is stored in the destination
+ register <var>R</var>. Else 0 is stored. The port security
+ values to check are retrieved from the the <code>outport</code>
+ logical port.
+ </p>
+
+ <p>
+ This action should be used in the egress logical switch pipeline.
+
+ </p>
+ <p>
+ <b>Example:</b> <code>reg8[0..7] = check_out_port_sec();</code>
+ </p>
+ </dd>
</dl>
</column>
@@ -2037,6 +2037,38 @@ reg1[1] = lookup_fdb(outport, ip4.src);
reg1[1] = lookup_fdb(ip4.src, eth.src);
Cannot use numeric field ip4.src where string field is required.
+# check_in_port_sec
+reg0[0] = check_in_port_sec();
+ encodes as set_field:0/0x1000->reg10,resubmit(,73),move:NXM_NX_REG10[12]->NXM_NX_XXREG0[96]
+
+reg2[2] = check_in_port_sec();
+ encodes as set_field:0/0x1000->reg10,resubmit(,73),move:NXM_NX_REG10[12]->NXM_NX_XXREG0[34]
+
+reg0 = check_in_port_sec();
+ Cannot use 32-bit field reg0[0..31] where 1-bit field is required.
+
+reg0[0] = check_in_port_sec(foo);
+ check_in_port_sec doesn't take any parameters
+
+check_in_port_sec;
+ Syntax error at `check_in_port_sec' expecting action.
+
+# check_out_port_sec
+reg0[0] = check_out_port_sec();
+ encodes as set_field:0/0x1000->reg10,resubmit(,75),move:NXM_NX_REG10[12]->NXM_NX_XXREG0[96]
+
+reg2[2..3] = check_out_port_sec();
+ Cannot use 2-bit field reg2[2..3] where 1-bit field is required.
+
+reg0 = check_out_port_sec();
+ Cannot use 32-bit field reg0[0..31] where 1-bit field is required.
+
+reg0[0] = check_out_port_sec(foo);
+ check_out_port_sec doesn't take any parameters
+
+check_out_port_sec;
+ Syntax error at `check_out_port_sec' expecting action.
+
# push/pop
push(xxreg0);push(xxreg1[10..20]);push(eth.src);pop(xxreg0[0..47]);pop(xxreg0[48..57]);pop(xxreg1);
formats as push(xxreg0); push(xxreg1[10..20]); push(eth.src); pop(xxreg0[0..47]); pop(xxreg0[48..57]); pop(xxreg1);
@@ -1352,6 +1352,8 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
.fdb_ptable = OFTABLE_GET_FDB,
.fdb_lookup_ptable = OFTABLE_LOOKUP_FDB,
.common_nat_ct_zone = MFF_LOG_DNAT_ZONE,
+ .in_port_sec_ptable = OFTABLE_CHK_IN_PORT_SEC,
+ .out_port_sec_ptable = OFTABLE_CHK_OUT_PORT_SEC,
};
struct ofpbuf ofpacts;
ofpbuf_init(&ofpacts, 0);
@@ -431,6 +431,8 @@ struct ovntrace_port {
uint16_t tunnel_key;
struct ovntrace_port *peer; /* Patch ports only. */
struct ovntrace_port *distributed_port; /* chassisredirect ports only. */
+ struct lport_addresses *ps_addrs; /* Port security addresses. */
+ size_t n_ps_addrs;
};
struct ovntrace_mcgroup {
@@ -747,6 +749,22 @@ read_ports(void)
}
}
}
+
+ port->n_ps_addrs = 0;
+ port->ps_addrs =
+ xmalloc(sizeof *port->ps_addrs * sbpb->n_port_security);
+ for (size_t i = 0; i < sbpb->n_port_security; i++) {
+ if (!extract_lsp_addresses(sbpb->port_security[i],
+ &port->ps_addrs[port->n_ps_addrs])) {
+ static struct vlog_rate_limit rl
+ = VLOG_RATE_LIMIT_INIT(1, 1);
+ VLOG_INFO_RL(&rl, "invalid syntax '%s' in port "
+ "security. No MAC address found",
+ sbpb->port_security[i]);
+ continue;
+ }
+ port->n_ps_addrs++;
+ }
}
SBREC_PORT_BINDING_FOR_EACH (sbpb, ovnsb_idl) {
@@ -2621,6 +2639,291 @@ execute_ct_snat_to_vip(struct flow *uflow OVS_UNUSED, struct ovs_list *super)
"*** ct_snat_to_vip action not implemented");
}
+static bool
+check_in_port_sec_arp(const struct flow *uflow,
+ struct lport_addresses *ps_addr)
+{
+ /* arp.sha should match the ps_addr's ea. */
+ if (!eth_addr_equals(uflow->arp_sha, ps_addr->ea)) {
+ return false;
+ }
+
+ if (!ps_addr->n_ipv4_addrs) {
+ return true;
+ }
+
+ /* arp.spa should match the allowed IPv4 addresses. */
+ for (size_t i = 0; i < ps_addr->n_ipv4_addrs; i++) {
+ if (uflow->nw_src == ps_addr->ipv4_addrs[i].addr) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool
+check_in_port_sec_ip4(const struct flow *uflow,
+ struct lport_addresses *ps_addr)
+{
+ /* No IPs present in ps_addr. Allow all IPs. */
+ if (!ps_addr->n_ipv4_addrs && !ps_addr->n_ipv6_addrs) {
+ return true;
+ }
+
+ /* ip4.src should match the allowed IPv4 addresses. */
+ for (size_t i = 0; i < ps_addr->n_ipv4_addrs; i++) {
+ if (uflow->nw_src == ps_addr->ipv4_addrs[i].addr) {
+ return true;
+ }
+ }
+
+ /* If its dhcp packet, then the ip4.src == 0.0.0.0 is allowed if
+ * ip4.dst == 255.255.255.255. */
+ if (uflow->nw_proto == IPPROTO_UDP && uflow->tp_src == htons(68) &&
+ uflow->tp_dst == htons(67) && uflow->nw_src == htonl(0) &&
+ uflow->nw_dst == htonl(0xffffffff)) {
+ return true;
+ }
+
+ return false;
+}
+
+static bool
+check_in_port_sec_ip6(const struct flow *uflow,
+ struct lport_addresses *ps_addr)
+{
+ /* No IPs present in ps_addr. Allow all IPs. */
+ if (!ps_addr->n_ipv4_addrs && !ps_addr->n_ipv6_addrs) {
+ return true;
+ }
+
+ /* ip6.src should match the allowed IPv6 addresses. */
+ bool passed = false;
+ for (size_t i = 0; i < ps_addr->n_ipv6_addrs; i++) {
+ if (ipv6_addr_equals(&uflow->ipv6_src, &ps_addr->ipv6_addrs[i].addr)) {
+ passed = true;
+ break;
+ }
+ }
+
+ struct in6_addr lla;
+ in6_generate_lla(ps_addr->ea, &lla);
+
+ if (!passed && !ipv6_addr_equals(&uflow->ipv6_src, &lla)) {
+ return false;
+ }
+
+ if (uflow->nw_proto == IPPROTO_ICMPV6) {
+ if (ntohs(uflow->tp_src) == 135) {
+ if (!eth_addr_equals(uflow->arp_sha, eth_addr_zero) &&
+ !eth_addr_equals(uflow->arp_sha, ps_addr->ea)) {
+ return false;
+ }
+ }
+
+ if (ntohs(uflow->tp_src) == 136) {
+ if (!eth_addr_equals(uflow->arp_tha, eth_addr_zero) &&
+ !eth_addr_equals(uflow->arp_tha, ps_addr->ea)) {
+ return false;
+ }
+
+ if (ps_addr->n_ipv6_addrs) {
+ passed = false;
+ for (size_t i = 0; i < ps_addr->n_ipv6_addrs; i++) {
+ if (ipv6_addr_equals(&uflow->nd_target,
+ &ps_addr->ipv6_addrs[i].addr)) {
+ passed = true;
+ break;
+ }
+ }
+
+ if (!passed && !ipv6_addr_equals(&uflow->nd_target, &lla)) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+static void
+execute_check_in_port_sec(const struct ovnact_result *dl,
+ const struct ovntrace_datapath *dp,
+ struct flow *uflow)
+{
+ struct mf_subfield sf = expr_resolve_field(&dl->dst);
+ union mf_subvalue sv = { .u8_val = 1 };
+
+ /* Get the input port .*/
+ uint32_t in_key = uflow->regs[MFF_LOG_INPORT - MFF_REG0];
+ ovs_assert(in_key);
+ const struct ovntrace_port *inport = ovntrace_port_find_by_key(dp, in_key);
+ ovs_assert(inport);
+
+ /* No port security is enabled on the input port.
+ * Set result bit 'sv' to 0. */
+ if (!inport->n_ps_addrs) {
+ sv.u8_val = 0;
+ mf_write_subfield_flow(&sf, &sv, uflow);
+ return;
+ }
+
+ bool allow = false;
+ for (size_t i = 0; i < inport->n_ps_addrs; i++) {
+ struct lport_addresses *ps_addr = &inport->ps_addrs[i];
+
+ /* Check L2 first. */
+ if (!eth_addr_equals(uflow->dl_src, ps_addr->ea)) {
+ continue;
+ }
+
+ if (uflow->dl_type == htons(ETH_TYPE_IP)) {
+ allow = check_in_port_sec_ip4(uflow, ps_addr);
+ } else if (uflow->dl_type == htons(ETH_TYPE_ARP)) {
+ allow = check_in_port_sec_arp(uflow, ps_addr);
+ } else if (uflow->dl_type == htons(ETH_TYPE_IPV6)) {
+ allow = check_in_port_sec_ip6(uflow, ps_addr);
+ } else {
+ allow = true;
+ }
+ break;
+ }
+
+ if (allow) {
+ sv.u8_val = 0;
+ }
+
+ mf_write_subfield_flow(&sf, &sv, uflow);
+}
+
+static bool
+check_out_port_sec_ip4(const struct flow *uflow,
+ struct lport_addresses *ps_addr)
+{
+ /* No IPs present in ps_addr. Allow all IPs. */
+ if (!ps_addr->n_ipv4_addrs && !ps_addr->n_ipv6_addrs) {
+ return true;
+ }
+
+ if (!ps_addr->n_ipv4_addrs) {
+ /* Only IPv6 is allowed. */
+ return false;
+ }
+
+ /* ip4.dst should match from the allowed IPv4 addresses. */
+ for (size_t i = 0; i < ps_addr->n_ipv4_addrs; i++) {
+ if (uflow->nw_dst == ps_addr->ipv4_addrs[i].addr) {
+ return true;
+ }
+ }
+
+ if (uflow->nw_dst == htonl(0xffffffff)) {
+ return true;
+ }
+
+ ovs_be32 mcast_network, mcast_mask;
+ mcast_network = htonl(0xe0000000);
+ mcast_mask = htonl(0xf0000000);
+ if (!((mcast_network ^ uflow->nw_dst) & mcast_mask)) {
+ return true;
+ }
+
+ return false;
+}
+
+static bool
+check_out_port_sec_ip6(const struct flow *uflow,
+ struct lport_addresses *ps_addr)
+{
+ /* No IPs present in ps_addr. Allow all IPs. */
+ if (!ps_addr->n_ipv4_addrs && !ps_addr->n_ipv6_addrs) {
+ return true;
+ }
+
+ if (!ps_addr->n_ipv6_addrs) {
+ /* Only IPv4 is allowed. */
+ return false;
+ }
+
+ /* ip6.dst should match from the allowed IPv6 addresses. */
+ for (size_t i = 0; i < ps_addr->n_ipv6_addrs; i++) {
+ if (ipv6_addr_equals(&uflow->ipv6_dst, &ps_addr->ipv6_addrs[i].addr)) {
+ return true;
+ }
+ }
+
+ struct in6_addr lla;
+ in6_generate_lla(ps_addr->ea, &lla);
+
+ if (ipv6_addr_equals(&uflow->ipv6_dst, &lla)) {
+ return true;
+ }
+
+ struct in6_addr mcast6_network, mcast6_mask;
+ char *error = ipv6_parse_masked("ff00::/8", &mcast6_network, &mcast6_mask);
+ ovs_assert(!error);
+
+ struct in6_addr ip6_mask = ipv6_addr_bitxor(&uflow->ipv6_dst,
+ &mcast6_network);
+ ip6_mask = ipv6_addr_bitand(&ip6_mask, &mcast6_mask);
+ if (ipv6_mask_is_any(&ip6_mask)) {
+ return true;
+ }
+
+ return false;
+}
+
+static void
+execute_check_out_port_sec(const struct ovnact_result *dl,
+ const struct ovntrace_datapath *dp,
+ struct flow *uflow)
+{
+ struct mf_subfield sf = expr_resolve_field(&dl->dst);
+ union mf_subvalue sv = { .u8_val = 1 };
+
+ /* Get the output port .*/
+ uint32_t out_key = uflow->regs[MFF_LOG_OUTPORT - MFF_REG0];
+ ovs_assert(out_key);
+ const struct ovntrace_port *outport =
+ ovntrace_port_find_by_key(dp, out_key);
+ ovs_assert(outport);
+
+ /* No port security is enabled on the output port.
+ * Set result bit 'sv' to 0. */
+ if (!outport->n_ps_addrs) {
+ sv.u8_val = 0;
+ mf_write_subfield_flow(&sf, &sv, uflow);
+ return;
+ }
+
+ bool allow = false;
+ for (size_t i = 0; i < outport->n_ps_addrs; i++) {
+ struct lport_addresses *ps_addr = &outport->ps_addrs[i];
+
+ /* Check L2 first. */
+ if (!eth_addr_equals(uflow->dl_dst, ps_addr->ea)) {
+ continue;
+ }
+
+ if (uflow->dl_type == htons(ETH_TYPE_IP)) {
+ allow = check_out_port_sec_ip4(uflow, ps_addr);
+ } else if (uflow->dl_type == htons(ETH_TYPE_IPV6)) {
+ allow = check_out_port_sec_ip6(uflow, ps_addr);
+ } else {
+ allow = true;
+ }
+ break;
+ }
+
+ if (allow) {
+ sv.u8_val = 0;
+ }
+
+ mf_write_subfield_flow(&sf, &sv, uflow);
+}
+
static void
trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
const struct ovntrace_datapath *dp, struct flow *uflow,
@@ -2911,6 +3214,16 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
case OVNACT_LOOKUP_FDB:
execute_lookup_fdb(ovnact_get_LOOKUP_FDB(a), dp, uflow, super);
break;
+
+ case OVNACT_CHECK_IN_PORT_SEC:
+ execute_check_in_port_sec(ovnact_get_CHECK_IN_PORT_SEC(a),
+ dp, uflow);
+ break;
+
+ case OVNACT_CHECK_OUT_PORT_SEC:
+ execute_check_out_port_sec(ovnact_get_CHECK_OUT_PORT_SEC(a),
+ dp, uflow);
+ break;
}
}
ofpbuf_uninit(&stack);