diff options
-rw-r--r-- | lib/packets.c | 29 | ||||
-rw-r--r-- | lib/packets.h | 4 | ||||
-rw-r--r-- | ovn/controller/pinctrl.c | 126 | ||||
-rw-r--r-- | ovn/lib/actions.c | 21 | ||||
-rw-r--r-- | ovn/lib/actions.h | 6 | ||||
-rw-r--r-- | ovn/northd/ovn-northd.c | 65 | ||||
-rw-r--r-- | ovn/ovn-sb.xml | 40 | ||||
-rw-r--r-- | tests/ovn.at | 101 | ||||
-rw-r--r-- | tutorial/OVN-Tutorial.md | 6 |
9 files changed, 353 insertions, 45 deletions
diff --git a/lib/packets.c b/lib/packets.c index 43b5a70f3..a27264c93 100644 --- a/lib/packets.c +++ b/lib/packets.c @@ -1355,6 +1355,35 @@ compose_nd(struct dp_packet *b, const struct eth_addr eth_src, ND_MSG_LEN + ND_OPT_LEN)); } +void +compose_na(struct dp_packet *b, + const struct eth_addr eth_src, const struct eth_addr eth_dst, + const ovs_be32 ipv6_src[4], const ovs_be32 ipv6_dst[4], + ovs_be32 rco_flags) +{ + struct ovs_nd_msg *na; + struct ovs_nd_opt *nd_opt; + uint32_t icmp_csum; + + eth_compose(b, eth_dst, eth_src, ETH_TYPE_IPV6, IPV6_HEADER_LEN); + na = compose_ipv6(b, IPPROTO_ICMPV6, ipv6_src, ipv6_dst, 0, 0, 255, + ND_MSG_LEN + ND_OPT_LEN); + + na->icmph.icmp6_type = ND_NEIGHBOR_ADVERT; + na->icmph.icmp6_code = 0; + put_16aligned_be32(&na->rco_flags, rco_flags); + + nd_opt = &na->options[0]; + nd_opt->nd_opt_type = ND_OPT_TARGET_LINKADDR; + nd_opt->nd_opt_len = 1; + + packet_set_nd(b, ipv6_src, eth_addr_zero, eth_src); + na->icmph.icmp6_cksum = 0; + icmp_csum = packet_csum_pseudoheader6(dp_packet_l3(b)); + na->icmph.icmp6_cksum = csum_finish(csum_continue(icmp_csum, na, + ND_MSG_LEN + ND_OPT_LEN)); +} + uint32_t packet_csum_pseudoheader(const struct ip_header *ip) { diff --git a/lib/packets.h b/lib/packets.h index 594594053..077ccfafa 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -1069,6 +1069,10 @@ void compose_arp(struct dp_packet *, uint16_t arp_op, ovs_be32 arp_spa, ovs_be32 arp_tpa); void compose_nd(struct dp_packet *, const struct eth_addr eth_src, struct in6_addr *, struct in6_addr *); +void compose_na(struct dp_packet *, + const struct eth_addr eth_src, const struct eth_addr eth_dst, + const ovs_be32 ipv6_src[4], const ovs_be32 ipv6_dst[4], + ovs_be32 rco_flags); uint32_t packet_csum_pseudoheader(const struct ip_header *); void IP_ECN_set_ce(struct dp_packet *pkt, bool is_ipv6); diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c index 200505a66..f960ea9e1 100644 --- a/ovn/controller/pinctrl.c +++ b/ovn/controller/pinctrl.c @@ -25,6 +25,7 @@ #include "lport.h" #include "nx-match.h" #include "ovn-controller.h" +#include "lib/packets.h" #include "lib/sset.h" #include "openvswitch/ofp-actions.h" #include "openvswitch/ofp-msgs.h" @@ -68,6 +69,11 @@ static void send_garp_run(const struct ovsrec_bridge *, const char *chassis_id, const struct lport_index *lports, struct hmap *local_datapaths); +static void pinctrl_handle_na(const struct flow *ip_flow, + const struct match *md, + struct ofpbuf *userdata); +static void reload_metadata(struct ofpbuf *ofpacts, + const struct match *md); COVERAGE_DEFINE(pinctrl_drop_put_arp); @@ -157,31 +163,7 @@ pinctrl_handle_arp(const struct flow *ip_flow, const struct match *md, struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); enum ofp_version version = rconn_get_version(swconn); - enum mf_field_id md_fields[] = { -#if FLOW_N_REGS == 8 - MFF_REG0, - MFF_REG1, - MFF_REG2, - MFF_REG3, - MFF_REG4, - MFF_REG5, - MFF_REG6, - MFF_REG7, -#else -#error -#endif - MFF_METADATA, - }; - for (size_t i = 0; i < ARRAY_SIZE(md_fields); i++) { - const struct mf_field *field = mf_from_id(md_fields[i]); - if (!mf_is_all_wild(field, &md->wc)) { - struct ofpact_set_field *sf = ofpact_put_SET_FIELD(&ofpacts); - sf->field = field; - sf->flow_has_vlan = false; - mf_get_value(field, &md->flow, &sf->value); - memset(&sf->mask, 0xff, field->n_bytes); - } - } + reload_metadata(&ofpacts, md); enum ofperr error = ofpacts_pull_openflow_actions(userdata, userdata->size, version, &ofpacts); if (error) { @@ -428,6 +410,10 @@ process_packet_in(const struct ofp_header *msg) pinctrl_handle_put_dhcp_opts(&packet, &pin, &userdata, &continuation); break; + case ACTION_OPCODE_NA: + pinctrl_handle_na(&headers, &pin.flow_metadata, &userdata); + break; + default: VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32, ntohl(ah->opcode)); @@ -920,3 +906,93 @@ send_garp_run(const struct ovsrec_bridge *br_int, const char *chassis_id, sset_destroy(&localnet_vifs); simap_destroy(&localnet_ofports); } + +static void +reload_metadata(struct ofpbuf *ofpacts, const struct match *md) +{ + enum mf_field_id md_fields[] = { +#if FLOW_N_REGS == 8 + MFF_REG0, + MFF_REG1, + MFF_REG2, + MFF_REG3, + MFF_REG4, + MFF_REG5, + MFF_REG6, + MFF_REG7, +#else +#error +#endif + MFF_METADATA, + }; + for (size_t i = 0; i < ARRAY_SIZE(md_fields); i++) { + const struct mf_field *field = mf_from_id(md_fields[i]); + if (!mf_is_all_wild(field, &md->wc)) { + struct ofpact_set_field *sf = ofpact_put_SET_FIELD(ofpacts); + sf->field = field; + sf->flow_has_vlan = false; + mf_get_value(field, &md->flow, &sf->value); + memset(&sf->mask, 0xff, field->n_bytes); + } + } +} + +static void +pinctrl_handle_na(const struct flow *ip_flow, + const struct match *md, + struct ofpbuf *userdata) +{ + /* This action only works for IPv6 ND packets, and the switch should only + * send us ND packets this way, but check here just to be sure. */ + if (!is_nd(ip_flow, NULL)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "NA action on non-ND packet"); + return; + } + + enum ofp_version version = rconn_get_version(swconn); + enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); + + uint64_t packet_stub[128 / 8]; + struct dp_packet packet; + dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); + + ovs_be32 ipv6_src[4], ipv6_dst[4]; + memcpy(ipv6_dst, &ip_flow->ipv6_src, sizeof ipv6_src); + memcpy(ipv6_src, &ip_flow->nd_target, sizeof ipv6_dst); + + /* Frame the NA packet with RSO=011. */ + compose_na(&packet, + ip_flow->dl_dst, ip_flow->dl_src, + ipv6_src, ipv6_dst, + htonl(0x60000000)); + + /* Reload previous packet metadata. */ + uint64_t ofpacts_stub[4096 / 8]; + struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); + reload_metadata(&ofpacts, md); + + enum ofperr error = ofpacts_pull_openflow_actions(userdata, userdata->size, + version, &ofpacts); + if (error) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "failed to parse actions for 'na' (%s)", + ofperr_to_string(error)); + goto exit; + } + + struct ofputil_packet_out po = { + .packet = dp_packet_data(&packet), + .packet_len = dp_packet_size(&packet), + .buffer_id = UINT32_MAX, + .in_port = OFPP_CONTROLLER, + .ofpacts = ofpacts.data, + .ofpacts_len = ofpacts.size, + }; + + queue_msg(ofputil_encode_packet_out(&po, proto)); + +exit: + dp_packet_uninit(&packet); + ofpbuf_uninit(&ofpacts); +} diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c index 52c74e6e9..550bb87b7 100644 --- a/ovn/lib/actions.c +++ b/ovn/lib/actions.c @@ -246,8 +246,11 @@ put_controller_op(struct ofpbuf *ofpacts, enum action_opcode opcode) finish_controller_op(ofpacts, ofs); } +/* Implements the "arp" and "na" actions, which execute nested actions on a + * packet derived from the one being processed. */ static void -parse_arp_action(struct action_context *ctx) +parse_nested_action(struct action_context *ctx, enum action_opcode opcode, + const char *prereq) { if (!lexer_match(ctx->lexer, LEX_T_LCURLY)) { action_syntax_error(ctx, "expecting `{'"); @@ -272,13 +275,11 @@ parse_arp_action(struct action_context *ctx) ctx->ofpacts = outer_ofpacts; - /* Add a "controller" action with the actions nested inside "arp {...}", + /* Add a "controller" action with the actions nested inside "{...}", * converted to OpenFlow, as its userdata. ovn-controller will convert the - * packet to an ARP and then send the packet and actions back to the switch - * inside an OFPT_PACKET_OUT message. */ - /* controller. */ - size_t oc_offset = start_controller_op(ctx->ofpacts, ACTION_OPCODE_ARP, - false); + * packet to ARP or NA and then send the packet and actions back to the + * switch inside an OFPT_PACKET_OUT message. */ + size_t oc_offset = start_controller_op(ctx->ofpacts, opcode, false); ofpacts_put_openflow_actions(inner_ofpacts.data, inner_ofpacts.size, ctx->ofpacts, OFP13_VERSION); finish_controller_op(ctx->ofpacts, oc_offset); @@ -286,7 +287,7 @@ parse_arp_action(struct action_context *ctx) /* Restore prerequisites. */ expr_destroy(ctx->prereqs); ctx->prereqs = outer_prereqs; - add_prerequisite(ctx, "ip4"); + add_prerequisite(ctx, prereq); /* Free memory. */ ofpbuf_uninit(&inner_ofpacts); @@ -908,7 +909,9 @@ parse_action(struct action_context *ctx) } else if (lexer_match_id(ctx->lexer, "ct_snat")) { parse_ct_nat(ctx, true); } else if (lexer_match_id(ctx->lexer, "arp")) { - parse_arp_action(ctx); + parse_nested_action(ctx, ACTION_OPCODE_ARP, "ip4"); + } else if (lexer_match_id(ctx->lexer, "na")) { + parse_nested_action(ctx, ACTION_OPCODE_NA, "nd"); } else if (lexer_match_id(ctx->lexer, "get_arp")) { parse_get_arp_action(ctx); } else if (lexer_match_id(ctx->lexer, "put_arp")) { diff --git a/ovn/lib/actions.h b/ovn/lib/actions.h index f49e15ec6..6b9cd248e 100644 --- a/ovn/lib/actions.h +++ b/ovn/lib/actions.h @@ -54,6 +54,12 @@ enum action_opcode { * - Any number of DHCP options. */ ACTION_OPCODE_PUT_DHCP_OPTS, + + /* "na { ...actions... }". + * + * The actions, in OpenFlow 1.3 format, follow the action_header. + */ + ACTION_OPCODE_NA, }; /* Header. */ diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index c2cf15eba..d36398e10 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -93,7 +93,7 @@ enum ovn_stage { 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, ARP_ND_RSP, 5, "ls_in_arp_nd_rsp") \ PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 6, "ls_in_l2_lkup") \ \ /* Logical switch egress stages. */ \ @@ -1386,6 +1386,12 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows, struct hmap *ports) ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 100, "ip", "ct_next;"); ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 100, "ip", "ct_next;"); + /* Ingress and Egress Pre-ACL Table (Priority 110). + * + * Not to do conntrack on ND packets. */ + ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, "nd", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, "nd", "next;"); + /* Ingress and Egress ACL Table (Priority 1). * * By default, traffic is allowed. This is partially handled by @@ -1436,6 +1442,12 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows, struct hmap *ports) ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, "!ct.est && ct.rel && !ct.new && !ct.inv", "next;"); + + /* Ingress and Egress ACL Table (Priority 65535). + * + * Not to do conntrack on ND packets. */ + ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, "nd", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, "nd", "next;"); } /* Ingress or Egress ACL Table (Various priorities). */ @@ -1569,13 +1581,13 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, if (!strcmp(op->nbs->type, "localnet")) { char *match = xasprintf("inport == %s", op->json_key); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_RSP, 100, + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100, match, "next;"); free(match); } } - /* Ingress table 5: ARP responder, reply for known IPs. + /* Ingress table 5: ARP/ND responder, reply for known IPs. * (priority 50). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { @@ -1583,7 +1595,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } /* - * Add ARP reply flows if either the + * Add ARP/ND reply flows if either the * - port is up or * - port type is router */ @@ -1594,7 +1606,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, for (size_t i = 0; i < op->nbs->n_addresses; i++) { struct lport_addresses laddrs; if (!extract_lsp_addresses(op->nbs->addresses[i], &laddrs, - false)) { + true)) { continue; } for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) { @@ -1615,24 +1627,61 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ETH_ADDR_ARGS(laddrs.ea), ETH_ADDR_ARGS(laddrs.ea), IP_ARGS(laddrs.ipv4_addrs[j].addr)); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_RSP, 50, + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 50, match, actions); free(match); free(actions); } + if (laddrs.n_ipv6_addrs > 0) { + char ip6_str[INET6_ADDRSTRLEN + 1]; + struct ds match = DS_EMPTY_INITIALIZER; + ds_put_cstr(&match, "icmp6 && icmp6.type == 135 && "); + if (laddrs.n_ipv6_addrs == 1) { + ipv6_string_mapped(ip6_str, + &(laddrs.ipv6_addrs[0].addr)); + ds_put_format(&match, "nd.target == %s", ip6_str); + } else { + ds_put_cstr(&match, "("); + for (size_t j = 0; j < laddrs.n_ipv6_addrs; j++) { + ipv6_string_mapped(ip6_str, + &(laddrs.ipv6_addrs[j].addr)); + ds_put_format(&match, "nd.target == %s || ", ip6_str); + } + ds_chomp(&match, ' '); + ds_chomp(&match, '|'); + ds_chomp(&match, '|'); + ds_chomp(&match, ' '); + ds_put_cstr(&match, ")"); + } + char *actions = xasprintf( + "na { eth.src = "ETH_ADDR_FMT"; " + "nd.tll = "ETH_ADDR_FMT"; " + "outport = inport; " + "inport = \"\"; /* Allow sending out inport. */ " + "output; };", + ETH_ADDR_ARGS(laddrs.ea), + ETH_ADDR_ARGS(laddrs.ea)); + + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 50, + ds_cstr(&match), actions); + + ds_destroy(&match); + } + free(laddrs.ipv4_addrs); + free(laddrs.ipv6_addrs); } } - /* Ingress table 5: ARP responder, by default goto next. + /* Ingress table 5: ARP/ND responder, 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_ARP_RSP, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 0, "1", "next;"); } /* Ingress table 6: Destination lookup, broadcast and multicast handling diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index 4814b0a6c..ebc5cf06d 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -1042,6 +1042,46 @@ <p><b>Prerequisite:</b> <code>ip4</code></p> </dd> + <dt> + <code>na { <var>action</var>; </code>...<code> };</code> + </dt> + + <dd> + <p> + Temporarily replaces the IPv6 packet being processed by an IPv6 + neighbor advertisement (NA) packet and executes each nested + <var>action</var> on the NA packet. Actions following the + <var>na</var> action, if any, apply to the original, unmodified + packet. + </p> + + <p> + The NA packet that this action operates on is initialized based on + the IPv6 packet being processed, as follows. These are default + values that the nested actions will probably want to change: + </p> + + <ul> + <li><code>eth.dst</code> exchanged with <code>eth.src</code></li> + <li><code>eth.type = 0x86dd</code></li> + <li><code>ip6.dst</code> copied from <code>ip6.src</code></li> + <li><code>ip6.src</code> copied from <code>nd.target</code></li> + <li><code>icmp6.type = 136</code> (Neighbor Advertisement)</li> + <li><code>nd.target</code> unchanged</li> + <li><code>nd.sll = 00:00:00:00:00:00</code></li> + <li><code>nd.tll</code> copied from <code>eth.dst</code></li> + </ul> + + <p> + The ND packet has the same VLAN header, if any, as the IPv6 packet + it replaces. + </p> + + <p> + <b>Prerequisite:</b> <code>nd</code> + </p> + </dd> + <dt><code>get_arp(<var>P</var>, <var>A</var>);</code></dt> <dd> diff --git a/tests/ovn.at b/tests/ovn.at index 37888bfa8..778cac7bc 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -573,6 +573,9 @@ reg1[0] = put_dhcp_opts(offerip=1.2.3.4, xyzzy); => Syntax error at `xyzzy' expe reg1[0] = put_dhcp_opts(offerip="xyzzy"); => DHCP option offerip requires numeric value. reg1[0] = put_dhcp_opts(offerip=1.2.3.4, domain=1.2.3.4); => DHCP option domain requires string value. +# na +na { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; /* Allow sending out inport. */ output; }; => actions=controller(userdata=00.00.00.03.00.00.00.00.00.19.00.10.80.00.08.06.12.34.56.78.9a.bc.00.00.00.19.00.10.80.00.42.06.12.34.56.78.9a.bc.00.00.ff.ff.00.18.00.00.23.20.00.06.00.20.00.00.00.00.00.01.0c.04.00.01.0e.04.00.19.00.10.00.01.0c.04.00.00.00.00.00.00.00.00.00.19.00.10.00.00.00.02.00.00.00.00.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00), prereqs=nd + # Contradictionary prerequisites (allowed but not useful): ip4.src = ip6.src[0..31]; => actions=move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[], prereqs=eth.type == 0x800 && eth.type == 0x86dd ip4.src <-> ip6.src[0..31]; => actions=push:NXM_NX_IPV6_SRC[0..31],push:NXM_OF_IP_SRC[],pop:NXM_NX_IPV6_SRC[0..31],pop:NXM_OF_IP_SRC[], prereqs=eth.type == 0x800 && eth.type == 0x86dd @@ -3393,3 +3396,101 @@ as main OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) OVS_APP_EXIT_AND_WAIT([ovsdb-server]) AT_CLEANUP + +AT_SETUP([ovn -- nd ]) +AT_KEYWORDS([ovn-nd]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +ovn_start + +#TODO: since patch port for IPv6 logical router port is not ready not, +# so we are not going to test vifs on different lswitches cases. Try +# to update for that once relevant stuff implemented. + +# In this test cases we create 1 lswitch, it has 2 VIF ports attached +# with. NS packet we test, from one VIF for another VIF, will be replied +# by local ovn-controller, but not by target VIF. + +# Create hypervisors and logical switch lsw0. +ovn-nbctl ls-add lsw0 +net_add n1 +sim_add hv1 +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.2 + +# Add vif1 to hv1 and lsw0, turn on l2 port security on vif1. +ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1 +ovn-nbctl lsp-add lsw0 lp1 +ovn-nbctl lsp-set-addresses lp1 "fa:16:3e:94:05:98 192.168.0.3 fd81:ce49:a948:0:f816:3eff:fe94:598" +ovn-nbctl lsp-set-port-security lp1 "fa:16:3e:94:05:98 192.168.0.3 fd81:ce49:a948:0:f816:3eff:fe94:598" + +# Add vif2 to hv1 and lsw0, turn on l2 port security on vif2. +ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv1/vif2-tx.pcap options:rxq_pcap=hv1/vif2-rx.pcap ofport-request=2 +ovn-nbctl lsp-add lsw0 lp2 +ovn-nbctl lsp-set-addresses lp2 "fa:16:3e:a1:f9:ae 192.168.0.4 fd81:ce49:a948:0:f816:3eff:fea1:f9ae" +ovn-nbctl lsp-set-port-security lp2 "fa:16:3e:a1:f9:ae 192.168.0.4 fd81:ce49:a948:0:f816:3eff:fea1:f9ae" + +# Add ACL rule for ICMPv6 on lsw0 +ovn-nbctl acl-add lsw0 from-lport 1002 'ip6 && icmp6' allow-related +ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp1" && ip6 && icmp6' allow-related +ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp2" && ip6 && icmp6' allow-related + +# Allow some time for ovn-northd and ovn-controller to catch up. +# XXX This should be more systematic. +sleep 1 + +# Given the name of a logical port, prints the name of the hypervisor +# on which it is located. +vif_to_hv() { + echo hv1${1%?} +} +trim_zeros() { + sed 's/\(00\)\{1,\}$//' +} +for i in 1 2; do + : > $i.expected +done + +# Complete Neighbor Solicitation packet and Neighbor Advertisement packet +# vif1 -> NS -> vif2. vif1 <- NA <- ovn-controller. +# vif2 will not receive NS packet, since ovn-controller will reply for it. +ns_packet=3333ffa1f9aefa163e94059886dd6000000000203afffd81ce49a9480000f8163efffe940598fd81ce49a9480000f8163efffea1f9ae8700e01160000000fd81ce49a9480000f8163efffea1f9ae0101fa163e940598 +na_packet=fa163e940598fa163ea1f9ae86dd6000000000203afffd81ce49a9480000f8163efffea1f9aefd81ce49a9480000f8163efffe9405988800e9ed60000000fd81ce49a9480000f8163efffea1f9ae0201fa163ea1f9ae + +as hv1 ovs-appctl netdev-dummy/receive vif1 $ns_packet +echo $na_packet | trim_zeros >> 1.expected + +sleep 1 + +echo "------ hv1 dump ------" +as hv1 ovs-vsctl show +as hv1 ovs-ofctl -O OpenFlow13 show br-int +as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int + +for i in 1 2; do + file=hv1/vif$i-tx.pcap + echo $file + $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $file | trim_zeros > $i.packets + cat $i.expected > expout + AT_CHECK([cat $i.packets], [0], [expout]) +done + +as hv1 +OVS_APP_EXIT_AND_WAIT([ovn-controller]) +OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as ovn-sb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as ovn-nb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as northd +OVS_APP_EXIT_AND_WAIT([ovn-northd]) + +as main +OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +AT_CLEANUP diff --git a/tutorial/OVN-Tutorial.md b/tutorial/OVN-Tutorial.md index c4bcbae42..811224db3 100644 --- a/tutorial/OVN-Tutorial.md +++ b/tutorial/OVN-Tutorial.md @@ -104,7 +104,7 @@ show the logical flows. table=2(ls_in_port_sec_nd), priority= 0, match=(1), action=(next;) table=3( ls_in_pre_acl), priority= 0, match=(1), action=(next;) table=4( ls_in_acl), priority= 0, match=(1), action=(next;) - table=5( ls_in_arp_rsp), priority= 0, match=(1), action=(next;) + table=5(ls_in_arp_nd_rsp), priority= 0, match=(1), action=(next;) table=6( ls_in_l2_lkup), priority= 100, match=(eth.mcast), action=(outport = "_MC_flood"; output;) table=6( ls_in_l2_lkup), priority= 50, match=(eth.dst == 00:00:00:00:00:01), action=(outport = "sw0-port1"; output;) table=6( ls_in_l2_lkup), priority= 50, match=(eth.dst == 00:00:00:00:00:02), action=(outport = "sw0-port2"; output;) @@ -277,7 +277,7 @@ OVN creates separate logical flows for each logical switch. table=2(ls_in_port_sec_nd), priority= 0, match=(1), action=(next;) table=3( ls_in_pre_acl), priority= 0, match=(1), action=(next;) table=4( ls_in_acl), priority= 0, match=(1), action=(next;) - table=5( ls_in_arp_rsp), priority= 0, match=(1), action=(next;) + table=5(ls_in_arp_nd_rsp), priority= 0, match=(1), action=(next;) table=6( ls_in_l2_lkup), priority= 100, match=(eth.mcast), action=(outport = "_MC_flood"; output;) table=6( ls_in_l2_lkup), priority= 50, match=(eth.dst == 00:00:00:00:00:03), action=(outport = "sw1-port1"; output;) table=6( ls_in_l2_lkup), priority= 50, match=(eth.dst == 00:00:00:00:00:04), action=(outport = "sw1-port2"; output;) @@ -303,7 +303,7 @@ OVN creates separate logical flows for each logical switch. table=2(ls_in_port_sec_nd), priority= 0, match=(1), action=(next;) table=3( ls_in_pre_acl), priority= 0, match=(1), action=(next;) table=4( ls_in_acl), priority= 0, match=(1), action=(next;) - table=5( ls_in_arp_rsp), priority= 0, match=(1), action=(next;) + table=5(ls_in_arp_nd_rsp), priority= 0, match=(1), action=(next;) table=6( ls_in_l2_lkup), priority= 100, match=(eth.mcast), action=(outport = "_MC_flood"; output;) table=6( ls_in_l2_lkup), priority= 50, match=(eth.dst == 00:00:00:00:00:01), action=(outport = "sw0-port1"; output;) table=6( ls_in_l2_lkup), priority= 50, match=(eth.dst == 00:00:00:00:00:02), action=(outport = "sw0-port2"; output;) |