@@ -986,6 +986,22 @@ in6_addr_solicited_node(struct in6_addr *addr, const struct in6_addr *ip6)
memcpy(&addr->s6_addr[13], &ip6->s6_addr[13], 3);
}
+/*
+ * Generates ipv6 link local address from the given eth addr
+ * with prefix 'fe80::/64' and stores it in 'lla'
+ */
+static inline void
+in6_generate_lla(struct eth_addr ea, struct in6_addr *lla)
+{
+ union ovs_16aligned_in6_addr *taddr = (void *) lla;
+ memset(taddr->be16, 0, sizeof(taddr->be16));
+ taddr->be16[0] = htons(0xfe80);
+ taddr->be16[4] = htons(((ea.ea[0] ^ 0x02) << 8) | ea.ea[1]);
+ taddr->be16[5] = htons(ea.ea[2] << 8 | 0x00ff);
+ taddr->be16[6] = htons(0xfe << 8 | ea.ea[3]);
+ taddr->be16[7] = ea.be16[2];
+}
+
static inline void
ipv6_multicast_to_ethernet(struct eth_addr *eth, const struct in6_addr *ip6)
{
@@ -124,12 +124,64 @@
</li>
<li>
- Priority 50 flows that implement ingress port security for each enabled
- logical port. For logical ports on which port security is enabled,
- these match the <code>inport</code> and the valid <code>eth.src</code>
- address(es) and advance only those packets to the next flow table. For
- logical ports on which port security is not enabled, these advance all
- packets that match the <code>inport</code>.
+ <p>
+ Port security flows to allow traffic for each enabled logical port.
+ For logical ports on which port security is not enabled, a priority 50
+ flow is added to allow all packets that match the <code>inport</code>.
+ </p>
+
+ <p>
+ For logical ports on which port security is enabled, following flows
+ are added.
+ </p>
+
+ <p>
+ For each element in the port security set,
+ </p>
+
+ <ul>
+ <li>
+ Priority 90 flow is added 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 is added to allow arp traffic if it has ipv4
+ addresses which match the <code>inport</code>, valid
+ <code>eth.src</code>, valid <code>arp.sha</code> and valid
+ <code>arp.spa</code> address(es).
+ </li>
+
+ <li>
+ Priority 90 flow is added to allow ipv6 traffic if it has
+ ipv6 address(es) which match the <code>inport</code>, valid
+ <code>eth.src</code> and valid <code>ip6.src</code> address(es).
+ </li>
+
+ <li>
+ Priority 80 flow is added to drop all ipv4 traffic if it has
+ ipv4/ipv6 address(es) which match the <code>inport</code> and
+ <code>eth.src</code>
+ </li>
+
+ <li>
+ Priority 80 flow is added to drop all arp traffic if it has ipv4
+ addresses which match the <code>inport</code> and
+ <code>eth.src</code>
+ </li>
+
+ <li>
+ Priority 80 flow is added to drop all ipv6 traffic if it has
+ ipv4/ipv6 address(es) which match the <code>inport</code> and
+ <code>eth.src</code>
+ </li>
+
+ <li>
+ Priority 50 flow is added to allow all traffic which match the
+ <code>inport</code> and the valid <code>eth.src</code>
+ </li>
+ </ul>
</li>
</ul>
@@ -270,15 +322,17 @@ output;
<p>
This is similar to the ingress port security logic in ingress table 0,
but with important differences. Most obviously, <code>outport</code> and
- <code>eth.dst</code> are checked instead of <code>inport</code> and
- <code>eth.src</code>. Second, packets directed to broadcast or multicast
- <code>eth.dst</code> are always accepted instead of being subject to the
- port security rules; this is implemented through a priority-100 flow that
- matches on <code>eth.mcast</code> with action <code>output;</code>.
- Finally, to ensure that even broadcast and multicast packets are not
- delivered to disabled logical ports, a priority-150 flow for each
- disabled logical <code>outport</code> overrides the priority-100 flow
- with a <code>drop;</code> action.
+ <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>.
+ Second, packets directed to broadcast or multicast <code>eth.dst</code>
+ are always accepted instead of being subject to the port security rules;
+ this is implemented through a priority-100 flow that matches on
+ <code>eth.mcast</code> with action <code>output;</code>. Third, flows
+ related to <code>arp</code> are not added. Finally, to ensure that even
+ broadcast and multicast packets are not delivered to disabled logical
+ ports, a priority-150 flow for each disabled logical <code>outport</code>
+ overrides the priority-100 flow with a <code>drop;</code> action.
</p>
<h2>Logical Router Datapaths</h2>
@@ -1020,33 +1020,216 @@ extract_lport_addresses(char *address, struct lport_addresses *laddrs,
return true;
}
-/* Appends port security constraints on L2 address field 'eth_addr_field'
- * (e.g. "eth.src" or "eth.dst") to 'match'. 'port_security', with
- * '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_arp_flow(
+ struct ds *match, char *port_name, struct eth_addr ea,
+ struct ipv4_netaddr *ipv4_addrs, int n_ipv4_addrs)
{
- size_t base_len = match->length;
- ds_put_format(match, " && %s == {", eth_addr_field);
+ ds_put_format(match, "inport == %s && eth.src == "ETH_ADDR_FMT" && arp"
+ " && arp.sha == "ETH_ADDR_FMT" && (", port_name,
+ ETH_ADDR_ARGS(ea), ETH_ADDR_ARGS(ea));
+ for(int i = 0; i < n_ipv4_addrs; i++) {
+ if (i) {
+ ds_put_cstr(match, " || ");
+ }
+ ds_put_format(match, "arp.spa == "IP_FMT, IP_ARGS(ipv4_addrs[i].addr));
+ }
+ ds_put_cstr(match, ")");
+}
- size_t n = 0;
- for (size_t i = 0; i < n_port_security; i++) {
- struct eth_addr ea;
+static void
+build_port_security_ipv4_flow(enum ovn_pipeline pipeline, struct ds *match,
+ struct ipv4_netaddr *ipv4_addrs,
+ int n_ipv4_addrs)
+{
+ char *ipv4_addr_field;
+ if (pipeline == P_IN) {
+ ipv4_addr_field = "ip4.src";
+ /* For ingress pipeline allow broadcast traffic (for dhcp) */
+ ds_put_cstr(match, " && ip4 && ((ip4.src == 0.0.0.0 &&"
+ " ip4.dst == 255.255.255.255) || ");
+ }
+ else {
+ ipv4_addr_field = "ip4.dst";
+ ds_put_cstr(match, " && ip4 && (");
+ }
- if (eth_addr_from_string(port_security[i], &ea)) {
- ds_put_format(match, ETH_ADDR_FMT, ETH_ADDR_ARGS(ea));
- ds_put_char(match, ' ');
- n++;
+ for(int i = 0; i < n_ipv4_addrs; i++) {
+ if (i) {
+ ds_put_cstr(match, " || ");
}
+ ds_put_format(match, "%s == "IP_FMT, ipv4_addr_field,
+ IP_ARGS(ipv4_addrs[i].addr));
+ }
+ ds_put_cstr(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_addr_field;
+ if (pipeline == P_IN) {
+ ip6_addr_field = "ip6.src";
+ }
+ else {
+ ip6_addr_field = "ip6.dst";
+ }
+
+ char ip6_str[INET6_ADDRSTRLEN + 1];
+ /* Allow link local address */
+ 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, " && ip6 && (%s == %s", ip6_addr_field, ip6_str);
+
+ /* Allow ip6.src=:: and ip6.dst=ff00::/8 for ND packets */
+ if (pipeline == P_IN) {
+ ds_put_cstr(match, " || ip6.src == ::");
+ }
+ else {
+ ds_put_cstr(match, " || ip6.dst == ff00::/8");
+ }
+ 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, " || %s == %s", ip6_addr_field, ip6_str);
+ }
+ ds_put_cstr(match, ")");
+}
+
+/*
+ * Build port security constraints on L2 and L3 address fields and add
+ * logical flows to S_SWITCH_(IN/OUT)_PORT_SEC stage.
+ *
+ * If there is no port security enabled for the logical port add a flow
+ * to allow all the traffic.
+ * For each port security of the logical port
+ * - For ingress pipine, add a priority 90 flow to allow arp packets for
+ * known ipv4 addresses.
+ * - Add a priority 90 flow to allow ipv4 packets for known ipv4 addresses
+ * - Add a priority 80 flow to drop arp (for ingress) and ip packets
+ * - Add a priority 90 flow to allow all ipv6 packets if port security
+ * has ipv6 address(es).
+ * - Add a priority 80 flow to drop ipv6 packets.
+ * - Add a priority 50 flow to allow all other traffic with the matching
+ * ethernet address.
+ */
+static void
+build_port_security(enum ovn_pipeline pipeline, struct ovn_port *op,
+ struct hmap *lflows)
+{
+ char *eth_addr_field;
+ char *port_direction;
+ enum ovn_stage stage;
+ char *action;
+ if (pipeline == P_IN) {
+ eth_addr_field = "eth.src";
+ port_direction = "inport";
+ stage = S_SWITCH_IN_PORT_SEC;
+ action = "next;";
+ }
+ else {
+ eth_addr_field = "eth.dst";
+ port_direction = "outport";
+ stage = S_SWITCH_OUT_PORT_SEC;
+ action = "output;";
+ }
+
+ if (!op->nbs->n_port_security) {
+ /* port security is disabled on this lport. */
+ char *match = xasprintf("%s == %s", port_direction, op->json_key);
+ ovn_lflow_add(lflows, op->od, stage, 50, match, action);
+ free(match);
+ return;
}
- ds_chomp(match, ' ');
- ds_put_cstr(match, "}");
- if (!n) {
- match->length = base_len;
+ struct ds match = DS_EMPTY_INITIALIZER;
+ 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;
+ }
+
+ /* Allow all traffic with priority 50 */
+ ds_init(&match);
+ ds_put_format(&match, "%s == %s && %s == "ETH_ADDR_FMT,
+ port_direction, op->json_key, eth_addr_field,
+ ETH_ADDR_ARGS(ps.ea));
+ ovn_lflow_add(lflows, op->od, stage, 50, ds_cstr(&match), action);
+ ds_destroy(&match);
+
+ if (!ps.n_ipv4_addrs && !ps.n_ipv6_addrs) {
+ /* port security option has only l2 address.
+ * Add port security constraints only on eth address which the above
+ * priority 50 flow takes care of*/
+ continue;
+ }
+
+ if (ps.n_ipv4_addrs) {
+ if (pipeline == P_IN) {
+ ds_init(&match);
+ /* Allow arp traffic for known ipv4 addresses */
+ build_port_security_arp_flow(&match, op->json_key, ps.ea,
+ ps.ipv4_addrs, ps.n_ipv4_addrs);
+ ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC, 90,
+ ds_cstr(&match), "next;");
+ ds_destroy(&match);
+ }
+
+ /* Allow traffic for known ipv4 addresses */
+ ds_init(&match);
+ ds_put_format(&match, "%s == %s && %s == "ETH_ADDR_FMT,
+ port_direction, op->json_key, eth_addr_field,
+ ETH_ADDR_ARGS(ps.ea));
+ build_port_security_ipv4_flow(pipeline, &match, ps.ipv4_addrs,
+ ps.n_ipv4_addrs);
+ ovn_lflow_add(lflows, op->od, stage, 90, ds_cstr(&match),
+ action);
+ ds_destroy(&match);
+ free(ps.ipv4_addrs);
+ }
+
+ /* Drop all ip and arp packets (for ingress pipline) with
+ * priority 80. */
+ ds_init(&match);
+ ds_put_format(&match, "%s == %s && %s == "ETH_ADDR_FMT,
+ port_direction, op->json_key, eth_addr_field,
+ ETH_ADDR_ARGS(ps.ea));
+ if (pipeline == P_IN) {
+ ds_put_cstr(&match, " && (arp || ip4)");
+ }
+ else {
+ ds_put_cstr(&match, " && ip4");
+ }
+ ovn_lflow_add(lflows, op->od, stage, 80, ds_cstr(&match), "drop;");
+ ds_destroy(&match);
+
+ if (ps.n_ipv6_addrs) {
+ /* XXX Need to add port security flows for ipv6 ND */
+ ds_init(&match);
+ ds_put_format(&match, "%s == %s && %s == "ETH_ADDR_FMT,
+ port_direction, op->json_key, eth_addr_field,
+ 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), action);
+ ds_destroy(&match);
+ free(ps.ipv6_addrs);
+ }
+
+ /* Drop all ipv6 traffic with priority 80. */
+ ds_init(&match);
+ ds_put_format(&match, "%s == %s && %s == "ETH_ADDR_FMT" && ip6",
+ port_direction, op->json_key, eth_addr_field,
+ ETH_ADDR_ARGS(ps.ea));
+ ovn_lflow_add(lflows, op->od, stage, 80, ds_cstr(&match), "drop;");
+ ds_destroy(&match);
}
}
@@ -1273,14 +1456,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
continue;
}
- 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,
- ds_cstr(&match), "next;");
- ds_destroy(&match);
+ build_port_security(P_IN, op, lflows);
}
/* Ingress table 3: Destination lookup, ARP reply for known IPs.
@@ -1424,19 +1600,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
continue;
}
- 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,
- ds_cstr(&match), "output;");
+ build_port_security(P_OUT, op, lflows);
} else {
+ struct ds match = DS_EMPTY_INITIALIZER;
+ ds_put_format(&match, "outport == %s", op->json_key);
+
ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PORT_SEC, 150,
ds_cstr(&match), "drop;");
+ ds_destroy(&match);
}
-
- ds_destroy(&match);
}
}
@@ -348,23 +348,134 @@
</column>
<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.
- </p>
+ <p>
+ 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>.
- </p>
+ <p>
+ Each element in the set must contain one Ethernet address optionally
+ masked followed by zero or more IPv4 or IPv6 addresses (or both).
+ It 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.
- </p>
- </column>
+ <p>
+ 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 the host may send and to which it may receive to packets
+ to the specified addresses. A masked address, if the host part is
+ zero, indicates that the host is allowed to use any addresses 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>00:23:20:00:00:00/ff:ff:ff:00:00:00</code></dt>
+ <dd>
+ Similar to the first example, except that any Ethernet address in the
+ Nicira OUI is allowed.
+ </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 n 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>
+ In this case, the host may send traffic from and receive traffic to the
+ specified MAC addresses - "80:fa:5b:12:42:ba" and "80:fa:5b:06:72:b7", and
+ to receive traffic to Ethernet multicast and broadcast addresses,
+ but not otherwise. With mac "80:fa:5b:12:42:ba", 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 n 224.0.0.0/4 and the host may not
+ send or receive any IPv6 (including IPv6 Neighbor Discovery) traffic.
+ </dd>
+ </dl>
+ </column>
</group>
<group title="Common Columns">
@@ -1345,3 +1345,251 @@ for i in 1 2 3; do
done
done
AT_CLEANUP
+
+# 3 hypervisors, one logical switch, 3 logical ports per hypervisor
+AT_SETUP([ovn -- 3 HVs, 1 LS, 3 lports/HV])
+AT_KEYWORDS([portsecurity])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+# Create hypervisors hv[123].
+# Add vif1[123] to hv1, vif2[123] to hv2, vif3[123] to hv3.
+# Add all of the vifs to a single logical switch lsw0.
+# Turn off port security on vifs vif[123]1
+# Turn on l2 port security on vifs vif[123]2
+# Turn of l2 and l3 port security on vifs vif[123]3
+# Make vif13, vif2[23], vif3[123] destinations for unknown MACs.
+ovn-nbctl lswitch-add lsw0
+net_add n1
+for i in 1 2 3; do
+ sim_add hv$i
+ as hv$i
+ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.$i
+
+ for j in 1 2 3; do
+ ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j
+ ovn-nbctl lport-add lsw0 lp$i$j
+ if test $j = 1; then
+ ovn-nbctl lport-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" unknown
+ elif test $j = 2; then
+ ovn-nbctl lport-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j"
+ ovn-nbctl lport-set-port-security lp$i$j f0:00:00:00:00:$i$j
+ else
+ ovn-nbctl lport-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j"
+ ovn-nbctl lport-set-port-security lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j"
+ fi
+ done
+done
+
+
+# Pre-populate the hypervisors' ARP tables so that we don't lose any
+# packets for ARP resolution (native tunneling doesn't queue packets
+# for ARP resolution).
+ovn_populate_arp
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+# XXX This should be more systematic.
+sleep 1
+ovn-sbctl dump-flows -- list multicast_group
+
+# Given the name of a logical port, prints the name of the hypervisor
+# on which it is located.
+vif_to_hv() {
+ echo hv${1%?}
+}
+
+
+trim_zeros() {
+ sed 's/\(00\)\{1,\}$//'
+}
+for i in 1 2 3; do
+ for j in 1 2 3; do
+ : > $i$j.expected
+ done
+done
+
+# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
+#
+# This shell function causes an ip packet to be received on INPORT.
+# The packet's content has Ethernet destination DST and source SRC
+# (each exactly 12 hex digits) and Ethernet type ETHTYPE (4 hex digits).
+# The OUTPORTs (zero or more) list the VIFs on which the packet should
+# be received. INPORT and the OUTPORTs are specified as lport numbers,
+# e.g. 11 for vif11.
+test_ip() {
+ # This packet has bad checksums but logical L3 routing doesn't check.
+ local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5
+ local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}003511110008
+ shift; shift; shift; shift; shift
+ hv=`vif_to_hv $inport`
+ as $hv ovs-appctl netdev-dummy/receive vif$inport $packet
+ #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet
+ for outport; do
+ echo $packet | trim_zeros >> $outport.expected
+ done
+}
+
+# test_arp INPORT SHA SPA TPA DROP [REPLY_HA]
+#
+# Causes a packet to be received on INPORT. The packet is an ARP
+# request with SHA, SPA, and TPA as specified. If REPLY_HA is provided, then
+# it should be the hardware address of the target to expect to receive in an
+# ARP reply; otherwise no reply is expected.
+#
+# INPORT is an lport number, e.g. 11 for vif11.
+# SHA and REPLY_HA are each 12 hex digits.
+# SPA and TPA are each 8 hex digits.
+test_arp() {
+ local inport=$1 sha=$2 spa=$3 tpa=$4 drop=$5 reply_ha=$6
+ local request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa}
+ hv=`vif_to_hv $inport`
+ as $hv ovs-appctl netdev-dummy/receive vif$inport $request
+ #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $request
+ if test $drop != 1; then
+ if test X$reply_ha == X; then
+ # Expect to receive the broadcast ARP on the other logical switch ports
+ # if no reply is expected.
+ local i j
+ for i in 1 2 3; do
+ for j in 1 2 3; do
+ if test $i$j != $inport; then
+ echo $request >> $i$j.expected
+ fi
+ done
+ done
+ else
+ # Expect to receive the reply, if any.
+ local reply=${sha}${reply_ha}08060001080006040002${reply_ha}${tpa}${sha}${spa}
+ echo $reply >> $inport.expected
+ fi
+ fi
+}
+
+# test_ipv6 INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
+# This function is similar to test_ip() except that it sends
+# ipv6 packet
+test_ipv6() {
+ local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5
+ local packet=${dst_mac}${src_mac}86dd6000000000083aff${src_ip}${dst_ip}0000000000000000
+ shift; shift; shift; shift; shift
+ hv=`vif_to_hv $inport`
+ as $hv ovs-appctl netdev-dummy/receive vif$inport $packet
+ #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet
+ for outport; do
+ echo $packet | trim_zeros >> $outport.expected
+ done
+}
+
+ip_to_hex() {
+ printf "%02x%02x%02x%02x" "$@"
+}
+
+# no port security
+sip=`ip_to_hex 192 168 0 12`
+tip=`ip_to_hex 192 168 0 13`
+# the arp packet should be allowed even if lp[123]1 is
+# not configured with mac f00000000023 and ip 192.168.0.12
+for i in 1 2 3; do
+ test_arp ${i}1 f00000000023 $sip $tip 0 f00000000013
+ for j in 1 2 3; do
+ if test $i != $j; then
+ test_ip ${i}1 f000000000${i}1 f000000000${j}1 $sip $tip ${j}1
+ fi
+ done
+done
+
+# l2 port security
+sip=`ip_to_hex 192 168 0 12`
+tip=`ip_to_hex 192 168 0 13`
+
+# arp packet should be allowed since lp22 is configured with
+# mac f00000000022
+test_arp 22 f00000000022 $sip $tip 0 f00000000013
+
+# arp packet should not be allowed since lp32 is not configured with
+# mac f00000000021
+test_arp 32 f00000000021 $sip $tip 1
+
+# ip packets should be allowed and received since lp[123]2 do not
+# have l3 port security
+sip=`ip_to_hex 192 168 0 55`
+tip=`ip_to_hex 192 168 0 66`
+for i in 1 2 3; do
+ for j in 1 2 3; do
+ if test $i != $j; then
+ test_ip ${i}2 f000000000${i}2 f000000000${j}2 $sip $tip ${j}2
+ fi
+ done
+done
+
+# ipv6 packets should be received by lp[123]2
+# lp[123]1 can send ipv6 traffic as there is no port security
+sip=fe800000000000000000000000000000
+tip=ff020000000000000000000000000000
+
+for i in 1 2 3; do
+ test_ipv6 ${i}1 f000000000${i}2 f000000000{i}2 $sip $tip {i}2
+done
+
+
+# l2 and l3 port security
+sip=`ip_to_hex 192 168 0 13`
+tip=`ip_to_hex 192 168 0 22`
+# arp packet should be allowed since lp13 is configured with
+# f00000000013 and 192.168.0.13
+test_arp 13 f00000000013 $sip $tip 0 f00000000022
+
+# the arp packet should be dropped because lp23 is not configured
+# with mac f00000000022
+sip=`ip_to_hex 192 168 0 13`
+tip=`ip_to_hex 192 168 0 22`
+test_arp 23 f00000000022 $sip $tip 1
+
+# the arp packet should be dropped because lp33 is not configured
+# with ip 192.168.0.55
+spa=`ip_to_hex 192 168 0 55`
+tpa=`ip_to_hex 192 168 0 22`
+test_arp 33 f00000000031 $spa $tpa 1
+
+# ip packets should not be received by lp[123]3 since
+# l3 port security is enabled
+sip=`ip_to_hex 192 168 0 55`
+tip=`ip_to_hex 192 168 0 66`
+for i in 1 2 3; do
+ for j in 1 2 3; do
+ test_ip ${i}2 f000000000${i}2 f000000000${j}3 $sip $tip
+ done
+done
+
+# ipv6 packets should be dropped for lp[123]3 since
+# it is configured with only ipv4 address
+sip=fe800000000000000000000000000000
+tip=ff020000000000000000000000000000
+
+for i in 1 2 3; do
+ test_ipv6 ${i}3 f000000000${i}3 f00000000022 $sip $tip
+done
+
+# ipv6 packets should not be received by lp[123]3
+# lp[123]1 can send ipv6 traffic as there is no port security
+for i in 1 2 3; do
+ test_ipv6 ${i}1 f000000000${i}1 f000000000${i}3 $sip $tip
+done
+
+# Allow some time for packet forwarding.
+# XXX This can be improved.
+sleep 1
+
+# Now check the packets actually received against the ones expected.
+for i in 1 2 3; do
+ for j in 1 2 3; do
+ file=hv$i/vif$i$j-tx.pcap
+ echo $file
+ $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $file | trim_zeros > $i$j.packets
+ sort $i$j.expected > expout
+ AT_CHECK([sort $i$j.packets], [0], [expout])
+ echo
+ done
+done
+AT_CLEANUP
For every port security defined for a logical port, add following lflows in "ls_in_port_sec" and "ls_out_port_sec" stage - A priority 90 flow to allow ipv4 traffic for known ip addresses and (broadcast ip - for ingress, mainly for dhcp) - A priority 80 flow to drop all ipv4 traffic. - For ingress, a priority 90 flow to allow arp traffic for known ip addresses and priority 80 flow to drop all arp traffic - A priority 90 flow to allow ipv6 traffic for known ipv6 addresses if port security has ipv6 address(es) defined - A priority 80 flow to drop all ipv6 traffic. - A priority 50 flow to allow all traffic on that port with the matching eth address Eg. if the port security is "00:00:00:00:00:01 10.0.0.2 fe80::eaff:fe28:3390" priority=90, match=(inport == "portname" && eth.src == 00:00:00:00:00:01 && arp && arp.sha == 00:00:00:00:00:01 && (arp.spa == 10.0.0.2)), action=(next;) priority=90, match=(inport == "portname" && eth.src == 00:00:00:00:00:01 && ip4 && ((ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255) || ip4.src == 10.0.0.3)), action=(next;) priority=90, match=(inport == "portname" && eth.src == 00:00:00:00:00:01 && ip6 && (ip6.src == fe80::200:ff:fe00:1 || ip6.src == :: || ip6.src == fe80::eaff:fe28:3390)), action=(next;) priority=80, match=(inport == "portname" && eth.src == 00:00:00:00:00:01 && (arp || ip4)), action=(drop;) priority=80, match=(inport == "portname" && eth.src == 00:00:00:00:00:01 && ip6), action=(drop;) priority=50, match=(inport == "portname" && eth.src == 00:00:00:00:00:01), action=(next;) Co-Authored-by: Ben Pfaff <blp@ovn.org> Signed-off-by: Numan Siddique <nusiddiq@redhat.com> --- lib/packets.h | 16 +++ ovn/northd/ovn-northd.8.xml | 84 ++++++++++++--- ovn/northd/ovn-northd.c | 245 ++++++++++++++++++++++++++++++++++++------- ovn/ovn-nb.xml | 141 ++++++++++++++++++++++--- tests/ovn.at | 248 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 668 insertions(+), 66 deletions(-)