2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-22 09:58:01 +00:00

ovn: Add 'na' action and lflow for ND

This patch tries to support ND versus ARP for OVN.

It adds a new OVN action 'na' in ovn-controller side, and modify lflows
for 'na' action and relevant packets in ovn-northd.

First, for ovn-northd, it will generate lflows per each lport with its
IPv6 addresses and mac addresss, with 'na' action, such as:
  match=(icmp6 && icmp6.type == 135 &&
         (nd.target == fd81:ce49:a948:0:f816:3eff:fe46:8a42 ||
          nd.target == fd81:ce49:b123:0:f816:3eff:fe46:8a42)),
  action=(na { eth.src = fa:16:3e:46:8a:42; nd.tll = fa:16:3e:46:8a:42;
               outport = inport;
               inport = ""; /* Allow sending out inport. */ output; };)

and new lflows will be set in tabel ls_in_arp_nd_rsp, which is renamed
from previous ls_in_arp_rsp.

Later, for ovn-controller, when it received a ND packet, it frames a
template NA packet for reply. The NA packet will be initialized based on
ND packet, such as NA packet will use:
 - ND packet eth.src as eth.dst,
 - ND packet eth.dst as eth.src,
 - ND packet ip6.src as ip6.dst,
 - ND packet nd.target as ip6.src,
 - ND packet eth.dst as nd.tll.

Finally, nested actions in 'na' action will update necessary fileds
for NA packet, such as:
 - eth.src, nd.tll
 - inport, outport

Since patch port for IPv6 router interface is not ready yet, this
patch will only try to deal with ND from VM. This patch will set
RSO flags to 011 for NA packets.

This patch also modified current ACL lflows for ND, not to do conntrack
on ND and NA packets in following tables:
 - S_SWITCH_IN_PRE_ACL
 - S_SWITCH_OUT_PRE_ACL
 - S_SWITCH_IN_ACL
 - S_SWITCH_OUT_ACL

Signed-off-by: Zong Kai LI <zealokii@gmail.com>
[blp@ovn.org made several minor simplifications and improvements]
Signed-off-by: Ben Pfaff <blp@ovn.org>
This commit is contained in:
Zong Kai LI 2016-06-27 14:54:52 +08:00 committed by Ben Pfaff
parent c17fcc0aed
commit e75451fef9
9 changed files with 353 additions and 45 deletions

View File

@ -1355,6 +1355,35 @@ compose_nd(struct dp_packet *b, const struct eth_addr eth_src,
ND_MSG_LEN + ND_OPT_LEN)); 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 uint32_t
packet_csum_pseudoheader(const struct ip_header *ip) packet_csum_pseudoheader(const struct ip_header *ip)
{ {

View File

@ -1069,6 +1069,10 @@ void compose_arp(struct dp_packet *, uint16_t arp_op,
ovs_be32 arp_spa, ovs_be32 arp_tpa); ovs_be32 arp_spa, ovs_be32 arp_tpa);
void compose_nd(struct dp_packet *, const struct eth_addr eth_src, void compose_nd(struct dp_packet *, const struct eth_addr eth_src,
struct in6_addr *, struct in6_addr *); 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 *); uint32_t packet_csum_pseudoheader(const struct ip_header *);
void IP_ECN_set_ce(struct dp_packet *pkt, bool is_ipv6); void IP_ECN_set_ce(struct dp_packet *pkt, bool is_ipv6);

View File

@ -25,6 +25,7 @@
#include "lport.h" #include "lport.h"
#include "nx-match.h" #include "nx-match.h"
#include "ovn-controller.h" #include "ovn-controller.h"
#include "lib/packets.h"
#include "lib/sset.h" #include "lib/sset.h"
#include "openvswitch/ofp-actions.h" #include "openvswitch/ofp-actions.h"
#include "openvswitch/ofp-msgs.h" #include "openvswitch/ofp-msgs.h"
@ -68,6 +69,11 @@ static void send_garp_run(const struct ovsrec_bridge *,
const char *chassis_id, const char *chassis_id,
const struct lport_index *lports, const struct lport_index *lports,
struct hmap *local_datapaths); 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); 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); struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
enum ofp_version version = rconn_get_version(swconn); enum ofp_version version = rconn_get_version(swconn);
enum mf_field_id md_fields[] = { reload_metadata(&ofpacts, md);
#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);
}
}
enum ofperr error = ofpacts_pull_openflow_actions(userdata, userdata->size, enum ofperr error = ofpacts_pull_openflow_actions(userdata, userdata->size,
version, &ofpacts); version, &ofpacts);
if (error) { if (error) {
@ -428,6 +410,10 @@ process_packet_in(const struct ofp_header *msg)
pinctrl_handle_put_dhcp_opts(&packet, &pin, &userdata, &continuation); pinctrl_handle_put_dhcp_opts(&packet, &pin, &userdata, &continuation);
break; break;
case ACTION_OPCODE_NA:
pinctrl_handle_na(&headers, &pin.flow_metadata, &userdata);
break;
default: default:
VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32, VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
ntohl(ah->opcode)); ntohl(ah->opcode));
@ -920,3 +906,93 @@ send_garp_run(const struct ovsrec_bridge *br_int, const char *chassis_id,
sset_destroy(&localnet_vifs); sset_destroy(&localnet_vifs);
simap_destroy(&localnet_ofports); 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);
}

View File

@ -246,8 +246,11 @@ put_controller_op(struct ofpbuf *ofpacts, enum action_opcode opcode)
finish_controller_op(ofpacts, ofs); 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 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)) { if (!lexer_match(ctx->lexer, LEX_T_LCURLY)) {
action_syntax_error(ctx, "expecting `{'"); action_syntax_error(ctx, "expecting `{'");
@ -272,13 +275,11 @@ parse_arp_action(struct action_context *ctx)
ctx->ofpacts = outer_ofpacts; 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 * 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 * packet to ARP or NA and then send the packet and actions back to the
* inside an OFPT_PACKET_OUT message. */ * switch inside an OFPT_PACKET_OUT message. */
/* controller. */ size_t oc_offset = start_controller_op(ctx->ofpacts, opcode, false);
size_t oc_offset = start_controller_op(ctx->ofpacts, ACTION_OPCODE_ARP,
false);
ofpacts_put_openflow_actions(inner_ofpacts.data, inner_ofpacts.size, ofpacts_put_openflow_actions(inner_ofpacts.data, inner_ofpacts.size,
ctx->ofpacts, OFP13_VERSION); ctx->ofpacts, OFP13_VERSION);
finish_controller_op(ctx->ofpacts, oc_offset); finish_controller_op(ctx->ofpacts, oc_offset);
@ -286,7 +287,7 @@ parse_arp_action(struct action_context *ctx)
/* Restore prerequisites. */ /* Restore prerequisites. */
expr_destroy(ctx->prereqs); expr_destroy(ctx->prereqs);
ctx->prereqs = outer_prereqs; ctx->prereqs = outer_prereqs;
add_prerequisite(ctx, "ip4"); add_prerequisite(ctx, prereq);
/* Free memory. */ /* Free memory. */
ofpbuf_uninit(&inner_ofpacts); ofpbuf_uninit(&inner_ofpacts);
@ -908,7 +909,9 @@ parse_action(struct action_context *ctx)
} else if (lexer_match_id(ctx->lexer, "ct_snat")) { } else if (lexer_match_id(ctx->lexer, "ct_snat")) {
parse_ct_nat(ctx, true); parse_ct_nat(ctx, true);
} else if (lexer_match_id(ctx->lexer, "arp")) { } 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")) { } else if (lexer_match_id(ctx->lexer, "get_arp")) {
parse_get_arp_action(ctx); parse_get_arp_action(ctx);
} else if (lexer_match_id(ctx->lexer, "put_arp")) { } else if (lexer_match_id(ctx->lexer, "put_arp")) {

View File

@ -54,6 +54,12 @@ enum action_opcode {
* - Any number of DHCP options. * - Any number of DHCP options.
*/ */
ACTION_OPCODE_PUT_DHCP_OPTS, ACTION_OPCODE_PUT_DHCP_OPTS,
/* "na { ...actions... }".
*
* The actions, in OpenFlow 1.3 format, follow the action_header.
*/
ACTION_OPCODE_NA,
}; };
/* Header. */ /* Header. */

View File

@ -93,7 +93,7 @@ enum ovn_stage {
PIPELINE_STAGE(SWITCH, IN, PORT_SEC_ND, 2, "ls_in_port_sec_nd") \ 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, PRE_ACL, 3, "ls_in_pre_acl") \
PIPELINE_STAGE(SWITCH, IN, ACL, 4, "ls_in_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") \ PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 6, "ls_in_l2_lkup") \
\ \
/* Logical switch egress stages. */ \ /* 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_IN_PRE_ACL, 100, "ip", "ct_next;");
ovn_lflow_add(lflows, od, S_SWITCH_OUT_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). /* Ingress and Egress ACL Table (Priority 1).
* *
* By default, traffic is allowed. This is partially handled by * 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, ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX,
"!ct.est && ct.rel && !ct.new && !ct.inv", "!ct.est && ct.rel && !ct.new && !ct.inv",
"next;"); "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). */ /* 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")) { if (!strcmp(op->nbs->type, "localnet")) {
char *match = xasprintf("inport == %s", op->json_key); 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;"); match, "next;");
free(match); free(match);
} }
} }
/* Ingress table 5: ARP responder, reply for known IPs. /* Ingress table 5: ARP/ND responder, reply for known IPs.
* (priority 50). */ * (priority 50). */
HMAP_FOR_EACH (op, key_node, ports) { HMAP_FOR_EACH (op, key_node, ports) {
if (!op->nbs) { 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 is up or
* - port type is router * - 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++) { for (size_t i = 0; i < op->nbs->n_addresses; i++) {
struct lport_addresses laddrs; struct lport_addresses laddrs;
if (!extract_lsp_addresses(op->nbs->addresses[i], &laddrs, if (!extract_lsp_addresses(op->nbs->addresses[i], &laddrs,
false)) { true)) {
continue; continue;
} }
for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) { 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),
ETH_ADDR_ARGS(laddrs.ea), ETH_ADDR_ARGS(laddrs.ea),
IP_ARGS(laddrs.ipv4_addrs[j].addr)); 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); match, actions);
free(match); free(match);
free(actions); 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.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)*/ * (priority 0)*/
HMAP_FOR_EACH (od, key_node, datapaths) { HMAP_FOR_EACH (od, key_node, datapaths) {
if (!od->nbs) { if (!od->nbs) {
continue; 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 /* Ingress table 6: Destination lookup, broadcast and multicast handling

View File

@ -1042,6 +1042,46 @@
<p><b>Prerequisite:</b> <code>ip4</code></p> <p><b>Prerequisite:</b> <code>ip4</code></p>
</dd> </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> <dt><code>get_arp(<var>P</var>, <var>A</var>);</code></dt>
<dd> <dd>

View File

@ -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="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. 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): # 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=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 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([ovs-vswitchd])
OVS_APP_EXIT_AND_WAIT([ovsdb-server]) OVS_APP_EXIT_AND_WAIT([ovsdb-server])
AT_CLEANUP 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

View File

@ -104,7 +104,7 @@ show the logical flows.
table=2(ls_in_port_sec_nd), priority= 0, match=(1), action=(next;) 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=3( ls_in_pre_acl), priority= 0, match=(1), action=(next;)
table=4( ls_in_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= 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: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;) 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=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=3( ls_in_pre_acl), priority= 0, match=(1), action=(next;)
table=4( ls_in_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= 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: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;) 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=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=3( ls_in_pre_acl), priority= 0, match=(1), action=(next;)
table=4( ls_in_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= 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: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;) table=6( ls_in_l2_lkup), priority= 50, match=(eth.dst == 00:00:00:00:00:02), action=(outport = "sw0-port2"; output;)