diff options
author | Numan Siddique <nusiddiq@redhat.com> | 2016-03-03 00:38:42 +0530 |
---|---|---|
committer | Ben Pfaff <blp@ovn.org> | 2016-03-18 16:49:21 -0700 |
commit | 685f4dfe09f2b0ed859dfcbc7e454c6f7196cefb (patch) | |
tree | 11d462dc47dfa482e65bf32afd943e4bec5fb651 /ovn | |
parent | 8743fa8cb61b1672cc506d515efd5703e3795c23 (diff) | |
download | openvswitch-685f4dfe09f2b0ed859dfcbc7e454c6f7196cefb.tar.gz |
ovn: Add l3 port security for IPv4 and IPv6
This patch extends the port security to support L3.
The ingress stage 'ls_in_port_sec' is renamed to 'ls_in_port_sec_l2'
and 2 new stages 'ls_in_port_sec_ip' (table 1) and 'ls_in_port_sec_nd'
(table 2) are added. 'ls_in_port_sec_ip' adds flows to restrict
the IPv4 and IPv6 traffic to valid IPv4 and IPv6 addresses of the port.
'ls_in_port_sec_nd' adds flows to restricts the ARP and IPv6 ND
packets.
For egress pipeline, 'ls_out_port_sec' is renamed to 'ls_out_port_sec_l2'
and a new stage 'ls_out_port_sec_ip' is added before 'ls_out_port_sec_l2'
to restrict the IPv4 and IPv6 traffic for valid IPs.
Signed-off-by: Numan Siddique <nusiddiq@redhat.com>
Co-authored-by: Ben Pfaff <blp@ovn.org>
Signed-off-by: Ben Pfaff <blp@ovn.org>
Diffstat (limited to 'ovn')
-rw-r--r-- | ovn/northd/ovn-northd.8.xml | 123 | ||||
-rw-r--r-- | ovn/northd/ovn-northd.c | 313 | ||||
-rw-r--r-- | ovn/ovn-nb.xml | 120 |
3 files changed, 503 insertions, 53 deletions
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index 79fe153f5..7954e228e 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -111,7 +111,7 @@ <h2>Logical Switch Datapaths</h2> - <h3>Ingress Table 0: Admission Control and Ingress Port Security</h3> + <h3>Ingress Table 0: Admission Control and Ingress Port Security - L2</h3> <p> Ingress table 0 contains these logical flows: @@ -139,17 +139,101 @@ be dropped. </p> - <h3>Ingress Table 1: <code>from-lport</code> Pre-ACLs</h3> + <h3>Ingress Table 1: Ingress Port Security - IP</h3> <p> - Ingress table 1 prepares flows for possible stateful ACL processing - in table 2. It contains a priority-0 flow that simply moves - traffic to table 2. If stateful ACLs are used in the logical + Ingress table 1 contains these logical flows: + </p> + + <ul> + <li> + <p> + For each element in the port security set having one or more IPv4 or + IPv6 addresses (or both), + </p> + + <ul> + <li> + Priority 90 flow to allow IPv4 traffic if it has IPv4 addresses + which match the <code>inport</code>, valid <code>eth.src</code> + and valid <code>ip4.src</code> address(es). + </li> + + <li> + Priority 90 flow to allow IPv6 traffic if it has IPv6 addresses + which match the <code>inport</code>, valid <code>eth.src</code> and + valid <code>ip6.src</code> address(es). + </li> + + <li> + Priority 80 flow to drop IP (both IPv4 and IPv6) traffic which + match the <code>inport</code> and valid <code>eth.src</code>. + </li> + </ul> + </li> + + <li> + One priority-0 fallback flow that matches all packets and advances to + table 2. + </li> + </ul> + + <h3>Ingress Table 2: Ingress Port Security - Neighbor discovery</h3> + + <p> + Ingress table 2 contains these logical flows: + </p> + + <ul> + <li> + <p> + For each element in the port security set, + </p> + + <ul> + <li> + Priority 90 flow to allow ARP traffic which match the + <code>inport</code> and valid <code>eth.src</code> and + <code>arp.sha</code>. If the element has one or more + IPv4 addresses, then it also matches the valid + <code>arp.spa</code>. + </li> + + <li> + Priority 90 flow to allow IPv6 Neighbor Solicitation and + Advertisement traffic which match the <code>inport</code>, + valid <code>eth.src</code> and + <code>nd.sll</code>/<code>nd.tll</code>. + If the element has one or more IPv6 addresses, then it also + matches the valid <code>nd.target</code> address(es) for Neighbor + Advertisement traffic. + </li> + + <li> + Priority 80 flow to drop ARP and IPv6 Neighbor Solicitation and + Advertisement traffic which match the <code>inport</code> and + valid <code>eth.src</code>. + </li> + </ul> + </li> + + <li> + One priority-0 fallback flow that matches all packets and advances to + table 3. + </li> + </ul> + + <h3>Ingress Table 3: <code>from-lport</code> Pre-ACLs</h3> + + <p> + Ingress table 3 prepares flows for possible stateful ACL processing + in table 4. It contains a priority-0 flow that simply moves + traffic to table 4. If stateful ACLs are used in the logical datapath, a priority-100 flow is added that sends IP packets to - the connection tracker before advancing to table 2. + the connection tracker before advancing to table 4. </p> - <h3>Ingress table 2: <code>from-lport</code> ACLs</h3> + <h3>Ingress table 4: <code>from-lport</code> ACLs</h3> <p> Logical flows in this table closely reproduce those in the @@ -163,7 +247,7 @@ </p> <p> - Ingress table 2 also contains a priority 0 flow with action + Ingress table 4 also contains a priority 0 flow with action <code>next;</code>, so that ACLs allow packets by default. If the logical datapath has a statetful ACL, the following flows will also be added: @@ -195,7 +279,7 @@ </li> </ul> - <h3>Ingress Table 3: ARP responder</h3> + <h3>Ingress Table 5: ARP responder</h3> <p> This table implements ARP responder for known IPs. It contains these @@ -205,7 +289,7 @@ <ul> <li> Priority-100 flows to skip ARP responder if inport is of type - <code>localnet</code>, and advances directly to table 3. + <code>localnet</code>, and advances directly to table 6. </li> <li> @@ -236,11 +320,11 @@ output; <li> One priority-0 fallback flow that matches all packets and advances to - table 4. + table 6. </li> </ul> - <h3>Ingress Table 4: Destination Lookup</h3> + <h3>Ingress Table 6: Destination Lookup</h3> <p> This table implements switching behavior. It contains these logical @@ -274,17 +358,26 @@ output; <h3>Egress Table 0: <code>to-lport</code> Pre-ACLs</h3> <p> - This is similar to ingress table 1 except for <code>to-lport</code> + This is similar to ingress table 3 except for <code>to-lport</code> traffic. </p> <h3>Egress Table 1: <code>to-lport</code> ACLs</h3> <p> - This is similar to ingress table 2 except for <code>to-lport</code> ACLs. + This is similar to ingress table 4 except for <code>to-lport</code> ACLs. + </p> + + <h3>Egress Table 2: Egress Port Security - IP</h3> + + <p> + This is similar to the ingress port security logic in table 1 except + that <code>outport</code>, <code>eth.dst</code>, <code>ip4.dst</code> + and <code>ip6.dst</code> are checked instead of <code>inport</code>, + <code>eth.src</code>, <code>ip4.src</code> and <code>ip6.src</code> </p> - <h3>Egress Table 2: Egress Port Security</h3> + <h3>Egress Table 3: Egress Port Security - L2</h3> <p> This is similar to the ingress port security logic in ingress table 0, diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index d70796562..68003e320 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -80,21 +80,24 @@ enum ovn_datapath_type { * An "enum ovn_stage" indicates whether the stage is part of a logical switch * or router, whether the stage is part of the ingress or egress pipeline, and * the table within that pipeline. The first three components are combined to - * form the stage's full name, e.g. S_SWITCH_IN_PORT_SEC, + * form the stage's full name, e.g. S_SWITCH_IN_PORT_SEC_L2, * S_ROUTER_OUT_DELIVERY. */ enum ovn_stage { #define PIPELINE_STAGES \ /* Logical switch ingress stages. */ \ - PIPELINE_STAGE(SWITCH, IN, PORT_SEC, 0, "ls_in_port_sec") \ - PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 1, "ls_in_pre_acl") \ - PIPELINE_STAGE(SWITCH, IN, ACL, 2, "ls_in_acl") \ - PIPELINE_STAGE(SWITCH, IN, ARP_RSP, 3, "ls_in_arp_rsp") \ - PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 4, "ls_in_l2_lkup") \ + PIPELINE_STAGE(SWITCH, IN, PORT_SEC_L2, 0, "ls_in_port_sec_l2") \ + PIPELINE_STAGE(SWITCH, IN, PORT_SEC_IP, 1, "ls_in_port_sec_ip") \ + PIPELINE_STAGE(SWITCH, IN, PORT_SEC_ND, 2, "ls_in_port_sec_nd") \ + PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 3, "ls_in_pre_acl") \ + PIPELINE_STAGE(SWITCH, IN, ACL, 4, "ls_in_acl") \ + PIPELINE_STAGE(SWITCH, IN, ARP_RSP, 5, "ls_in_arp_rsp") \ + PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 6, "ls_in_l2_lkup") \ \ /* Logical switch egress stages. */ \ PIPELINE_STAGE(SWITCH, OUT, PRE_ACL, 0, "ls_out_pre_acl") \ PIPELINE_STAGE(SWITCH, OUT, ACL, 1, "ls_out_acl") \ - PIPELINE_STAGE(SWITCH, OUT, PORT_SEC, 2, "ls_out_port_sec") \ + PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP, 2, "ls_out_port_sec_ip") \ + PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 3, "ls_out_port_sec_l2") \ \ /* Logical router ingress stages. */ \ PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \ @@ -1049,9 +1052,9 @@ extract_lport_addresses(char *address, struct lport_addresses *laddrs, * 'n_port_security' elements, is the collection of port_security constraints * from an OVN_NB Logical_Port row. */ static void -build_port_security(const char *eth_addr_field, - char **port_security, size_t n_port_security, - struct ds *match) +build_port_security_l2(const char *eth_addr_field, + char **port_security, size_t n_port_security, + struct ds *match) { size_t base_len = match->length; ds_put_format(match, " && %s == {", eth_addr_field); @@ -1074,6 +1077,227 @@ build_port_security(const char *eth_addr_field, } } +static void +build_port_security_ipv6_nd_flow( + struct ds *match, struct eth_addr ea, struct ipv6_netaddr *ipv6_addrs, + int n_ipv6_addrs) +{ + ds_put_format(match, " && ip6 && nd && ((nd.sll == "ETH_ADDR_FMT" || " + "nd.sll == "ETH_ADDR_FMT") || ((nd.tll == "ETH_ADDR_FMT" || " + "nd.tll == "ETH_ADDR_FMT")", ETH_ADDR_ARGS(eth_addr_zero), + ETH_ADDR_ARGS(ea), ETH_ADDR_ARGS(eth_addr_zero), + ETH_ADDR_ARGS(ea)); + if (!n_ipv6_addrs) { + ds_put_cstr(match, "))"); + return; + } + + char ip6_str[INET6_ADDRSTRLEN + 1]; + struct in6_addr lla; + in6_generate_lla(ea, &lla); + memset(ip6_str, 0, sizeof(ip6_str)); + ipv6_string_mapped(ip6_str, &lla); + ds_put_format(match, " && (nd.target == %s", ip6_str); + + for(int i = 0; i < n_ipv6_addrs; i++) { + memset(ip6_str, 0, sizeof(ip6_str)); + ipv6_string_mapped(ip6_str, &ipv6_addrs[i].addr); + ds_put_format(match, " || nd.target == %s", ip6_str); + } + + ds_put_format(match, ")))"); +} + +static void +build_port_security_ipv6_flow( + enum ovn_pipeline pipeline, struct ds *match, struct eth_addr ea, + struct ipv6_netaddr *ipv6_addrs, int n_ipv6_addrs) +{ + char ip6_str[INET6_ADDRSTRLEN + 1]; + + ds_put_format(match, " && %s == {", + pipeline == P_IN ? "ip6.src" : "ip6.dst"); + + /* Allow link-local address. */ + struct in6_addr lla; + in6_generate_lla(ea, &lla); + ipv6_string_mapped(ip6_str, &lla); + ds_put_format(match, "%s, ", ip6_str); + + /* Allow ip6.src=:: and ip6.dst=ff00::/8 for ND packets */ + ds_put_cstr(match, pipeline == P_IN ? "::" : "ff00::/8"); + for(int i = 0; i < n_ipv6_addrs; i++) { + ipv6_string_mapped(ip6_str, &ipv6_addrs[i].addr); + ds_put_format(match, ", %s", ip6_str); + } + ds_put_cstr(match, "}"); +} + +/** + * Build port security constraints on ARP and IPv6 ND fields + * and add logical flows to S_SWITCH_IN_PORT_SEC_ND stage. + * + * For each port security of the logical port, following + * logical flows are added + * - If the port security has no IP (both IPv4 and IPv6) or + * if it has IPv4 address(es) + * - Priority 90 flow to allow ARP packets for known MAC addresses + * in the eth.src and arp.spa fields. If the port security + * has IPv4 addresses, allow known IPv4 addresses in the arp.tpa field. + * + * - If the port security has no IP (both IPv4 and IPv6) or + * if it has IPv6 address(es) + * - Priority 90 flow to allow IPv6 ND packets for known MAC addresses + * in the eth.src and nd.sll/nd.tll fields. If the port security + * has IPv6 addresses, allow known IPv6 addresses in the nd.target field + * for IPv6 Neighbor Advertisement packet. + * + * - Priority 80 flow to drop ARP and IPv6 ND packets. + */ +static void +build_port_security_nd(struct ovn_port *op, struct hmap *lflows) +{ + for (size_t i = 0; i < op->nbs->n_port_security; i++) { + struct lport_addresses ps; + if (!extract_lport_addresses(op->nbs->port_security[i], &ps, true)) { + 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", op->nbs->port_security[i]); + continue; + } + + bool no_ip = !(ps.n_ipv4_addrs || ps.n_ipv6_addrs); + struct ds match = DS_EMPTY_INITIALIZER; + + if (ps.n_ipv4_addrs || no_ip) { + ds_put_format( + &match, "inport == %s && eth.src == "ETH_ADDR_FMT" && arp.sha == " + ETH_ADDR_FMT, op->json_key, ETH_ADDR_ARGS(ps.ea), + ETH_ADDR_ARGS(ps.ea)); + + if (ps.n_ipv4_addrs) { + ds_put_cstr(&match, " && ("); + for (size_t i = 0; i < ps.n_ipv4_addrs; i++) { + ds_put_format(&match, "arp.spa == "IP_FMT" || ", + IP_ARGS(ps.ipv4_addrs[i].addr)); + } + ds_chomp(&match, ' '); + ds_chomp(&match, '|'); + ds_chomp(&match, '|'); + ds_put_cstr(&match, ")"); + } + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_ND, 90, + ds_cstr(&match), "next;"); + ds_destroy(&match); + } + + if (ps.n_ipv6_addrs || no_ip) { + ds_init(&match); + ds_put_format(&match, "inport == %s && eth.src == "ETH_ADDR_FMT, + op->json_key, ETH_ADDR_ARGS(ps.ea)); + build_port_security_ipv6_nd_flow(&match, ps.ea, ps.ipv6_addrs, + ps.n_ipv6_addrs); + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_ND, 90, + ds_cstr(&match), "next;"); + ds_destroy(&match); + } + free(ps.ipv4_addrs); + free(ps.ipv6_addrs); + } + + char *match = xasprintf("inport == %s && (arp || nd)", op->json_key); + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_ND, 80, + match, "drop;"); + free(match); +} + +/** + * Build port security constraints on IPv4 and IPv6 src and dst fields + * and add logical flows to S_SWITCH_(IN/OUT)_PORT_SEC_IP stage. + * + * For each port security of the logical port, following + * logical flows are added + * - If the port security has IPv4 addresses, + * - Priority 90 flow to allow IPv4 packets for known IPv4 addresses + * + * - If the port security has IPv6 addresses, + * - Priority 90 flow to allow IPv6 packets for known IPv6 addresses + * + * - If the port security has IPv4 addresses or IPv6 addresses or both + * - Priority 80 flow to drop all IPv4 and IPv6 traffic + */ +static void +build_port_security_ip(enum ovn_pipeline pipeline, struct ovn_port *op, + struct hmap *lflows) +{ + char *port_direction; + enum ovn_stage stage; + if (pipeline == P_IN) { + port_direction = "inport"; + stage = S_SWITCH_IN_PORT_SEC_IP; + } else { + port_direction = "outport"; + stage = S_SWITCH_OUT_PORT_SEC_IP; + } + + for (size_t i = 0; i < op->nbs->n_port_security; i++) { + struct lport_addresses ps; + if (!extract_lport_addresses(op->nbs->port_security[i], &ps, true)) { + continue; + } + + if (!(ps.n_ipv4_addrs || ps.n_ipv6_addrs)) { + continue; + } + + if (ps.n_ipv4_addrs) { + struct ds match = DS_EMPTY_INITIALIZER; + if (pipeline == P_IN) { + ds_put_format(&match, "inport == %s && eth.src == "ETH_ADDR_FMT + " && ip4.src == {0.0.0.0, ", op->json_key, + ETH_ADDR_ARGS(ps.ea)); + } else { + ds_put_format(&match, "outport == %s && eth.dst == "ETH_ADDR_FMT + " && ip4.dst == {255.255.255.255, 224.0.0.0/4, ", + op->json_key, ETH_ADDR_ARGS(ps.ea)); + } + + for (int i = 0; i < ps.n_ipv4_addrs; i++) { + ds_put_format(&match, IP_FMT", ", IP_ARGS(ps.ipv4_addrs[i].addr)); + } + + /* Replace ", " by "}". */ + ds_chomp(&match, ' '); + ds_chomp(&match, ','); + ds_put_cstr(&match, "}"); + ovn_lflow_add(lflows, op->od, stage, 90, ds_cstr(&match), "next;"); + ds_destroy(&match); + free(ps.ipv4_addrs); + } + + if (ps.n_ipv6_addrs) { + struct ds match = DS_EMPTY_INITIALIZER; + ds_put_format(&match, "%s == %s && %s == "ETH_ADDR_FMT"", + port_direction, op->json_key, + pipeline == P_IN ? "eth.src" : "eth.dst", + ETH_ADDR_ARGS(ps.ea)); + build_port_security_ipv6_flow(pipeline, &match, ps.ea, + ps.ipv6_addrs, ps.n_ipv6_addrs); + ovn_lflow_add(lflows, op->od, stage, 90, + ds_cstr(&match), "next;"); + ds_destroy(&match); + free(ps.ipv6_addrs); + } + + char *match = xasprintf( + "%s == %s && %s == "ETH_ADDR_FMT" && ip", port_direction, + op->json_key, pipeline == P_IN ? "eth.src" : "eth.dst", + ETH_ADDR_ARGS(ps.ea)); + ovn_lflow_add(lflows, op->od, stage, 80, match, "drop;"); + free(match); + } +} + static bool lport_is_enabled(const struct nbrec_logical_port *lport) { @@ -1255,7 +1479,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, * update ovn-northd.8.xml if you change anything. */ /* Build pre-ACL and ACL tables for both ingress and egress. - * Ingress tables 1 and 2. Egress tables 0 and 1. */ + * Ingress tables 3 and 4. Egress tables 0 and 1. */ struct ovn_datapath *od; HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { @@ -1273,18 +1497,22 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } /* Logical VLANs not supported. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC, 100, "vlan.present", + ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, "vlan.present", "drop;"); /* Broadcast/multicast source address is invalid. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC, 100, "eth.src[40]", + ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, "eth.src[40]", "drop;"); /* Port security flows have priority 50 (see below) and will continue * to the next table if packet source is acceptable. */ } - /* Logical switch ingress table 0: Ingress port security (priority 50). */ + /* Logical switch ingress table 0: Ingress port security - L2 + * (priority 50). + * Ingress table 1: Ingress port security - IP (priority 90 and 80) + * Ingress table 2: Ingress port security - ND (priority 90 and 80) + */ struct ovn_port *op; HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { @@ -1299,12 +1527,28 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, struct ds match = DS_EMPTY_INITIALIZER; ds_put_format(&match, "inport == %s", op->json_key); - build_port_security("eth.src", - op->nbs->port_security, op->nbs->n_port_security, - &match); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC, 50, + build_port_security_l2( + "eth.src", op->nbs->port_security, op->nbs->n_port_security, + &match); + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_L2, 50, ds_cstr(&match), "next;"); ds_destroy(&match); + + if (op->nbs->n_port_security) { + build_port_security_ip(P_IN, op, lflows); + build_port_security_nd(op, lflows); + } + } + + /* Ingress table 1 and 2: Port security - IP and ND, by default goto next. + * (priority 0)*/ + HMAP_FOR_EACH (od, key_node, datapaths) { + if (!od->nbs) { + continue; + } + + ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_ND, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_IP, 0, "1", "next;"); } /* Ingress table 3: ARP responder, skip requests coming from localnet ports. @@ -1322,7 +1566,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 3: ARP responder, reply for known IPs. + /* Ingress table 5: ARP responder, reply for known IPs. * (priority 50). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { @@ -1372,7 +1616,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 3: ARP responder, by default goto next. + /* Ingress table 5: ARP responder, by default goto next. * (priority 0)*/ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { @@ -1382,7 +1626,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_RSP, 0, "1", "next;"); } - /* Ingress table 4: Destination lookup, broadcast and multicast handling + /* Ingress table 6: Destination lookup, broadcast and multicast handling * (priority 100). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { @@ -1402,7 +1646,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, "outport = \""MC_FLOOD"\"; output;"); } - /* Ingress table 4: Destination lookup, unicast handling (priority 50), */ + /* Ingress table 6: Destination lookup, unicast handling (priority 50), */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { continue; @@ -1439,7 +1683,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 4: Destination lookup for unknown MACs (priority 0). */ + /* Ingress table 6: Destination lookup for unknown MACs (priority 0). */ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { continue; @@ -1451,18 +1695,23 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Egress table 2: Egress port security multicast/broadcast (priority + /* Egress table 2: Egress port security - IP (priority 0) + * port security L2 - multicast/broadcast (priority * 100). */ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { continue; } - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC, 100, "eth.mcast", + ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_IP, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_L2, 100, "eth.mcast", "output;"); } - /* Egress table 2: Egress port security (priorities 50 and 150). + /* Egress table 2: Egress port security - IP (priorities 90 and 80) + * if port security enabled. + * + * Egress table 3: Egress port security - L2 (priorities 50 and 150). * * Priority 50 rules implement port security for enabled logical port. * @@ -1476,16 +1725,20 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, struct ds match = DS_EMPTY_INITIALIZER; ds_put_format(&match, "outport == %s", op->json_key); if (lport_is_enabled(op->nbs)) { - build_port_security("eth.dst", op->nbs->port_security, - op->nbs->n_port_security, &match); - ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PORT_SEC, 50, + build_port_security_l2("eth.dst", op->nbs->port_security, + op->nbs->n_port_security, &match); + ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2, 50, ds_cstr(&match), "output;"); } else { - ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PORT_SEC, 150, + ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2, 150, ds_cstr(&match), "drop;"); } ds_destroy(&match); + + if (op->nbs->n_port_security) { + build_port_security_ip(P_OUT, op, lflows); + } } } diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index 5c8e94220..e65bc3a9a 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -362,21 +362,125 @@ <column name="port_security"> <p> - A set of L2 (Ethernet) addresses from which the logical port is - allowed to send packets and to which it is allowed to receive - packets. If this column is empty, all addresses are permitted. - Logical ports are always allowed to receive packets addressed to - multicast and broadcast addresses. + This column controls the addresses from which the host attached to the + logical port (``the host'') is allowed to send packets and to which it + is allowed to receive packets. If this column is empty, all addresses + are permitted. </p> <p> - Each member of the set is an Ethernet address in the form - <var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>:<var>xx</var>. + Each element in the set must begin with one Ethernet address. + This would restrict the host to sending packets from and receiving + packets to the ethernet addresses defined in the logical port's + <ref column="port_security"/> column. It also restricts the inner + source MAC addresses that the host may send in ARP and IPv6 + Neighbor Discovery packets. The host is always allowed to receive packets + to multicast and broadcast Ethernet addresses. </p> <p> - This specification will be extended to support L3 port security. + Each element in the set may additionally contain one or more IPv4 or + IPv6 addresses (or both), with optional masks. If a mask is given, it + must be a CIDR mask. In addition to the restrictions described for + Ethernet addresses above, such an element restricts the IPv4 or IPv6 + addresses from which the host may send and to which it may receive + packets to the specified addresses. A masked address, if the host part + is zero, indicates that the host is allowed to use any address in the + subnet; if the host part is nonzero, the mask simply indicates the size + of the subnet. In addition: </p> + + <ul> + <li> + <p> + If any IPv4 address is given, the host is also allowed to receive + packets to the IPv4 local broadcast address 255.255.255.255 and to + IPv4 multicast addresses (224.0.0.0/4). If an IPv4 address with a + mask is given, the host is also allowed to receive packets to the + broadcast address in that specified subnet. + </p> + + <p> + If any IPv4 address is given, the host is additionally restricted + to sending ARP packets with the specified source IPv4 address. + (RARP is not restricted.) + </p> + </li> + + <li> + <p> + If any IPv6 address is given, the host is also allowed to receive + packets to IPv6 multicast addresses (ff00::/8). + </p> + + <p> + If any IPv6 address is given, the host is additionally restricted + to sending IPv6 Neighbor Discovery Solicitation or Advertisement + packets with the specified source address or, for solicitations, + the unspecified address. + </p> + </li> + </ul> + + <p> + If an element includes an IPv4 address, but no IPv6 addresses, then + IPv6 traffic is not allowed. If an element includes an IPv6 address, + but no IPv4 address, then IPv4 and ARP traffic is not allowed. + </p> + + <p> + This column uses the same lexical syntax as the <ref column="match" + table="Pipeline" db="OVN_Southbound"/> column in the OVN Southbound + database's <ref table="Pipeline" db="OVN_Southbound"/> table. Multiple + addresses within an element may be space or comma separated. + </p> + + <p> + This column is provided as a convenience to cloud management systems, + but all of the features that it implements can be implemented as ACLs + using the <ref table="ACL"/> table. + </p> + + <p> + Examples: + </p> + + <dl> + <dt><code>80:fa:5b:06:72:b7</code></dt> + <dd> + The host may send traffic from and receive traffic to the specified + MAC address, and to receive traffic to Ethernet multicast and + broadcast addresses, but not otherwise. The host may not send ARP or + IPv6 Neighbor Discovery packets with inner source Ethernet addresses + other than the one specified. + </dd> + + <dt><code>80:fa:5b:06:72:b7 192.168.1.10/24</code></dt> + <dd> + This adds further restrictions to the first example. The host may + send IPv4 packets from or receive IPv4 packets to only 192.168.1.10, + except that it may also receive IPv4 packets to 192.168.1.255 (based + on the subnet mask), 255.255.255.255, and any address in 224.0.0.0/4. + The host may not send ARPs with a source Ethernet address other than + 80:fa:5b:06:72:b7 or source IPv4 address other than 192.168.1.10. + The host may not send or receive any IPv6 (including IPv6 Neighbor + Discovery) traffic. + </dd> + + <dt><code>"80:fa:5b:12:42:ba", "80:fa:5b:06:72:b7 192.168.1.10/24"</code></dt> + <dd> + The host may send traffic from and receive traffic to the + specified MAC addresses, and + to receive traffic to Ethernet multicast and broadcast addresses, + but not otherwise. With MAC 80:fa:5b:12:42:ba, the host may + send traffic from and receive traffic to any L3 address. + With MAC 80:fa:5b:06:72:b7, the host may send IPv4 packets from or + receive IPv4 packets to only 192.168.1.10, except that it may also + receive IPv4 packets to 192.168.1.255 (based on the subnet mask), + 255.255.255.255, and any address in 224.0.0.0/4. The host may not + send or receive any IPv6 (including IPv6 Neighbor Discovery) traffic. + </dd> + </dl> </column> </group> |