diff options
author | Justin Pettit <jpettit@ovn.org> | 2016-06-28 02:57:27 -0700 |
---|---|---|
committer | Justin Pettit <jpettit@ovn.org> | 2016-07-29 02:54:57 -0700 |
commit | c34a87b6c5700225733c6fce7f3a678f3ef5ac71 (patch) | |
tree | 80b8751f7ba7e5df26f3bc23ecfe66be3d960322 | |
parent | a63f7235e431415c950734138c62e107caaa2aec (diff) | |
download | openvswitch-c34a87b6c5700225733c6fce7f3a678f3ef5ac71.tar.gz |
ovn: Add support for IPv6 dynamic bindings.
This commit also introduces "get_nd" and "put_nd" logical actions.
Signed-off-by: Justin Pettit <jpettit@ovn.org>
Acked-by: Ben Pfaff <blp@ovn.org>
-rw-r--r-- | include/ovn/actions.h | 13 | ||||
-rw-r--r-- | ovn/controller/lflow.c | 27 | ||||
-rw-r--r-- | ovn/controller/pinctrl.c | 169 | ||||
-rw-r--r-- | ovn/lib/actions.c | 60 | ||||
-rw-r--r-- | ovn/northd/ovn-northd.8.xml | 56 | ||||
-rw-r--r-- | ovn/northd/ovn-northd.c | 17 | ||||
-rw-r--r-- | ovn/ovn-architecture.7.xml | 2 | ||||
-rw-r--r-- | ovn/ovn-sb.xml | 70 | ||||
-rw-r--r-- | tests/ovn.at | 15 | ||||
-rw-r--r-- | tests/test-ovn.c | 2 |
10 files changed, 310 insertions, 121 deletions
diff --git a/include/ovn/actions.h b/include/ovn/actions.h index 2ae9cd216..a395ce9e3 100644 --- a/include/ovn/actions.h +++ b/include/ovn/actions.h @@ -78,6 +78,16 @@ enum action_opcode { * The actions, in OpenFlow 1.3 format, follow the action_header. */ ACTION_OPCODE_ND_NA, + + /* "put_nd(port, ip6, mac)" + * + * Arguments are passed through the packet metadata and data, as follows: + * + * MFF_XXREG0 = ip6 + * MFF_LOG_INPORT = port + * MFF_ETH_SRC = mac + */ + ACTION_OPCODE_PUT_ND, }; /* Header. */ @@ -128,7 +138,8 @@ struct action_params { uint8_t first_ptable; /* First OpenFlow table. */ uint8_t cur_ltable; /* 0 <= cur_ltable < n_tables. */ uint8_t output_ptable; /* OpenFlow table for 'output' to resubmit. */ - uint8_t arp_ptable; /* OpenFlow table for 'get_arp' to resubmit. */ + uint8_t mac_bind_ptable; /* OpenFlow table for 'get_arp'/'get_nd' to + resubmit. */ }; char *actions_parse(struct lexer *, const struct action_params *, diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c index 674b75620..fda10ebac 100644 --- a/ovn/controller/lflow.c +++ b/ovn/controller/lflow.c @@ -528,7 +528,7 @@ consider_logical_flow(const struct lport_index *lports, .first_ptable = first_ptable, .cur_ltable = lflow->table_id, .output_ptable = output_ptable, - .arp_ptable = OFTABLE_MAC_BINDING, + .mac_bind_ptable = OFTABLE_MAC_BINDING, }; error = actions_parse_string(lflow->actions, &ap, &ofpacts, &prereqs); if (error) { @@ -638,16 +638,29 @@ consider_neighbor_flow(const struct lport_index *lports, return; } - ovs_be32 ip; - if (!ip_parse(b->ip, &ip)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip); - return; + + if (strchr(b->ip, '.')) { + ovs_be32 ip; + if (!ip_parse(b->ip, &ip)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip); + return; + } + match_set_reg(match_p, 0, ntohl(ip)); + } else { + struct in6_addr ip6; + if (!ipv6_parse(b->ip, &ip6)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip); + return; + } + ovs_be128 value; + memcpy(&value, &ip6, sizeof(value)); + match_set_xxreg(match_p, 0, ntoh128(value)); } match_set_metadata(match_p, htonll(pb->datapath->tunnel_key)); match_set_reg(match_p, MFF_LOG_OUTPORT - MFF_REG0, pb->tunnel_key); - match_set_reg(match_p, 0, ntohl(ip)); ofpbuf_clear(ofpacts_p); put_load(mac.ea, sizeof mac.ea, MFF_ETH_DST, 0, 48, ofpacts_p); diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c index c03e2cfc0..bd685fe41 100644 --- a/ovn/controller/pinctrl.c +++ b/ovn/controller/pinctrl.c @@ -53,14 +53,15 @@ static struct rconn *swconn; * rconn_get_connection_seqno(rconn), 'swconn' has reconnected. */ static unsigned int conn_seq_no; -static void pinctrl_handle_put_arp(const struct flow *md, - const struct flow *headers); -static void init_put_arps(void); -static void destroy_put_arps(void); -static void run_put_arps(struct controller_ctx *, - const struct lport_index *lports); -static void wait_put_arps(struct controller_ctx *); -static void flush_put_arps(void); +static void pinctrl_handle_put_mac_binding(const struct flow *md, + const struct flow *headers, + bool is_arp); +static void init_put_mac_bindings(void); +static void destroy_put_mac_bindings(void); +static void run_put_mac_bindings(struct controller_ctx *, + const struct lport_index *lports); +static void wait_put_mac_bindings(struct controller_ctx *); +static void flush_put_mac_bindings(void); static void init_send_garps(void); static void destroy_send_garps(void); @@ -75,14 +76,14 @@ static void pinctrl_handle_nd_na(const struct flow *ip_flow, static void reload_metadata(struct ofpbuf *ofpacts, const struct match *md); -COVERAGE_DEFINE(pinctrl_drop_put_arp); +COVERAGE_DEFINE(pinctrl_drop_put_mac_binding); void pinctrl_init(void) { swconn = rconn_create(5, 0, DSCP_DEFAULT, 1 << OFP13_VERSION); conn_seq_no = 0; - init_put_arps(); + init_put_mac_bindings(); init_send_garps(); } @@ -403,7 +404,8 @@ process_packet_in(const struct ofp_header *msg) break; case ACTION_OPCODE_PUT_ARP: - pinctrl_handle_put_arp(&pin.flow_metadata.flow, &headers); + pinctrl_handle_put_mac_binding(&pin.flow_metadata.flow, &headers, + true); break; case ACTION_OPCODE_PUT_DHCP_OPTS: @@ -414,6 +416,11 @@ process_packet_in(const struct ofp_header *msg) pinctrl_handle_nd_na(&headers, &pin.flow_metadata, &userdata); break; + case ACTION_OPCODE_PUT_ND: + pinctrl_handle_put_mac_binding(&pin.flow_metadata.flow, &headers, + false); + break; + default: VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32, ntohl(ah->opcode)); @@ -473,7 +480,7 @@ pinctrl_run(struct controller_ctx *ctx, const struct lport_index *lports, if (conn_seq_no != rconn_get_connection_seqno(swconn)) { pinctrl_setup(swconn); conn_seq_no = rconn_get_connection_seqno(swconn); - flush_put_arps(); + flush_put_mac_bindings(); } /* Process a limited number of messages per call. */ @@ -492,14 +499,14 @@ pinctrl_run(struct controller_ctx *ctx, const struct lport_index *lports, } } - run_put_arps(ctx, lports); + run_put_mac_bindings(ctx, lports); send_garp_run(br_int, chassis_id, lports, local_datapaths); } void pinctrl_wait(struct controller_ctx *ctx) { - wait_put_arps(ctx); + wait_put_mac_bindings(ctx); rconn_run_wait(swconn); rconn_recv_wait(swconn); send_garp_wait(); @@ -509,60 +516,61 @@ void pinctrl_destroy(void) { rconn_destroy(swconn); - destroy_put_arps(); + destroy_put_mac_bindings(); destroy_send_garps(); } -/* Implementation of the "put_arp" OVN action. This action sends a packet to - * ovn-controller, using the flow as an API (see actions.h for details). This - * code implements the action by updating the MAC_Binding table in the - * southbound database. +/* Implementation of the "put_arp" and "put_nd" OVN actions. These + * actions send a packet to ovn-controller, using the flow as an API + * (see actions.h for details). This code implements the actions by + * updating the MAC_Binding table in the southbound database. * * This code could be a lot simpler if the database could always be updated, * but in fact we can only update it when ctx->ovnsb_idl_txn is nonnull. Thus, - * we buffer up a few put_arps (but we don't keep them longer than 1 second) - * and apply them whenever a database transaction is available. */ + * we buffer up a few put_mac_bindings (but we don't keep them longer + * than 1 second) and apply them whenever a database transaction is + * available. */ -/* Buffered "put_arp" operation. */ -struct put_arp { - struct hmap_node hmap_node; /* In 'put_arps'. */ +/* Buffered "put_mac_binding" operation. */ +struct put_mac_binding { + struct hmap_node hmap_node; /* In 'put_mac_bindings'. */ long long int timestamp; /* In milliseconds. */ /* Key. */ uint32_t dp_key; uint32_t port_key; - ovs_be32 ip; + char ip_s[INET6_ADDRSTRLEN + 1]; /* Value. */ struct eth_addr mac; }; -/* Contains "struct put_arp"s. */ -static struct hmap put_arps; +/* Contains "struct put_mac_binding"s. */ +static struct hmap put_mac_bindings; static void -init_put_arps(void) +init_put_mac_bindings(void) { - hmap_init(&put_arps); + hmap_init(&put_mac_bindings); } static void -destroy_put_arps(void) +destroy_put_mac_bindings(void) { - flush_put_arps(); - hmap_destroy(&put_arps); + flush_put_mac_bindings(); + hmap_destroy(&put_mac_bindings); } -static struct put_arp * -pinctrl_find_put_arp(uint32_t dp_key, uint32_t port_key, ovs_be32 ip, - uint32_t hash) +static struct put_mac_binding * +pinctrl_find_put_mac_binding(uint32_t dp_key, uint32_t port_key, + const char *ip_s, uint32_t hash) { - struct put_arp *pa; - HMAP_FOR_EACH_WITH_HASH (pa, hmap_node, hash, &put_arps) { + struct put_mac_binding *pa; + HMAP_FOR_EACH_WITH_HASH (pa, hmap_node, hash, &put_mac_bindings) { if (pa->dp_key == dp_key && pa->port_key == port_key - && pa->ip == ip) { + && !strcmp(pa->ip_s, ip_s)) { return pa; } } @@ -570,64 +578,72 @@ pinctrl_find_put_arp(uint32_t dp_key, uint32_t port_key, ovs_be32 ip, } static void -pinctrl_handle_put_arp(const struct flow *md, const struct flow *headers) +pinctrl_handle_put_mac_binding(const struct flow *md, + const struct flow *headers, bool is_arp) { uint32_t dp_key = ntohll(md->metadata); uint32_t port_key = md->regs[MFF_LOG_INPORT - MFF_REG0]; - ovs_be32 ip = htonl(md->regs[0]); - uint32_t hash = hash_3words(dp_key, port_key, (OVS_FORCE uint32_t) ip); - struct put_arp *pa = pinctrl_find_put_arp(dp_key, port_key, ip, hash); - if (!pa) { - if (hmap_count(&put_arps) >= 1000) { - COVERAGE_INC(pinctrl_drop_put_arp); + char ip_s[INET6_ADDRSTRLEN]; + + if (is_arp) { + ovs_be32 ip = htonl(md->regs[0]); + inet_ntop(AF_INET, &ip, ip_s, sizeof(ip_s)); + } else { + ovs_be128 ip6 = hton128(flow_get_xxreg(md, 0)); + inet_ntop(AF_INET6, &ip6, ip_s, sizeof(ip_s)); + } + uint32_t hash = hash_string(ip_s, hash_2words(dp_key, port_key)); + struct put_mac_binding *pmb + = pinctrl_find_put_mac_binding(dp_key, port_key, ip_s, hash); + if (!pmb) { + if (hmap_count(&put_mac_bindings) >= 1000) { + COVERAGE_INC(pinctrl_drop_put_mac_binding); return; } - pa = xmalloc(sizeof *pa); - hmap_insert(&put_arps, &pa->hmap_node, hash); - pa->dp_key = dp_key; - pa->port_key = port_key; - pa->ip = ip; + pmb = xmalloc(sizeof *pmb); + hmap_insert(&put_mac_bindings, &pmb->hmap_node, hash); + pmb->dp_key = dp_key; + pmb->port_key = port_key; + ovs_strlcpy(pmb->ip_s, ip_s, sizeof pmb->ip_s); } - pa->timestamp = time_msec(); - pa->mac = headers->dl_src; + pmb->timestamp = time_msec(); + pmb->mac = headers->dl_src; } static void -run_put_arp(struct controller_ctx *ctx, const struct lport_index *lports, - const struct put_arp *pa) +run_put_mac_binding(struct controller_ctx *ctx, + const struct lport_index *lports, + const struct put_mac_binding *pmb) { - if (time_msec() > pa->timestamp + 1000) { + if (time_msec() > pmb->timestamp + 1000) { return; } /* Convert logical datapath and logical port key into lport. */ const struct sbrec_port_binding *pb - = lport_lookup_by_key(lports, pa->dp_key, pa->port_key); + = lport_lookup_by_key(lports, pmb->dp_key, pmb->port_key); if (!pb) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); VLOG_WARN_RL(&rl, "unknown logical port with datapath %"PRIu32" " - "and port %"PRIu32, pa->dp_key, pa->port_key); + "and port %"PRIu32, pmb->dp_key, pmb->port_key); return; } - /* Convert arguments to string form for database. */ - char ip_string[INET_ADDRSTRLEN + 1]; - snprintf(ip_string, sizeof ip_string, IP_FMT, IP_ARGS(pa->ip)); - + /* Convert ethernet argument to string form for database. */ char mac_string[ETH_ADDR_STRLEN + 1]; snprintf(mac_string, sizeof mac_string, - ETH_ADDR_FMT, ETH_ADDR_ARGS(pa->mac)); + ETH_ADDR_FMT, ETH_ADDR_ARGS(pmb->mac)); - /* Check for and update an existing IP-MAC binding for this logical + /* Check for an update an existing IP-MAC binding for this logical * port. * * XXX This is not very efficient. */ const struct sbrec_mac_binding *b; SBREC_MAC_BINDING_FOR_EACH (b, ctx->ovnsb_idl) { if (!strcmp(b->logical_port, pb->logical_port) - && !strcmp(b->ip, ip_string)) { + && !strcmp(b->ip, pmb->ip_s)) { if (strcmp(b->mac, mac_string)) { sbrec_mac_binding_set_mac(b, mac_string); } @@ -638,39 +654,40 @@ run_put_arp(struct controller_ctx *ctx, const struct lport_index *lports, /* Add new IP-MAC binding for this logical port. */ b = sbrec_mac_binding_insert(ctx->ovnsb_idl_txn); sbrec_mac_binding_set_logical_port(b, pb->logical_port); - sbrec_mac_binding_set_ip(b, ip_string); + sbrec_mac_binding_set_ip(b, pmb->ip_s); sbrec_mac_binding_set_mac(b, mac_string); sbrec_mac_binding_set_datapath(b, pb->datapath); } static void -run_put_arps(struct controller_ctx *ctx, const struct lport_index *lports) +run_put_mac_bindings(struct controller_ctx *ctx, + const struct lport_index *lports) { if (!ctx->ovnsb_idl_txn) { return; } - const struct put_arp *pa; - HMAP_FOR_EACH (pa, hmap_node, &put_arps) { - run_put_arp(ctx, lports, pa); + const struct put_mac_binding *pmb; + HMAP_FOR_EACH (pmb, hmap_node, &put_mac_bindings) { + run_put_mac_binding(ctx, lports, pmb); } - flush_put_arps(); + flush_put_mac_bindings(); } static void -wait_put_arps(struct controller_ctx *ctx) +wait_put_mac_bindings(struct controller_ctx *ctx) { - if (ctx->ovnsb_idl_txn && !hmap_is_empty(&put_arps)) { + if (ctx->ovnsb_idl_txn && !hmap_is_empty(&put_mac_bindings)) { poll_immediate_wake(); } } static void -flush_put_arps(void) +flush_put_mac_bindings(void) { - struct put_arp *pa; - HMAP_FOR_EACH_POP (pa, hmap_node, &put_arps) { - free(pa); + struct put_mac_binding *pmb; + HMAP_FOR_EACH_POP (pmb, hmap_node, &put_mac_bindings) { + free(pmb); } } diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c index 6b8ea3fe0..b9d1205b0 100644 --- a/ovn/lib/actions.c +++ b/ovn/lib/actions.c @@ -424,7 +424,7 @@ parse_get_arp_action(struct action_context *ctx) setup_args(ctx, args, ARRAY_SIZE(args)); put_load(0, MFF_ETH_DST, 0, 48, ctx->ofpacts); - emit_resubmit(ctx, ctx->ap->arp_ptable); + emit_resubmit(ctx, ctx->ap->mac_bind_ptable); restore_args(ctx, args, ARRAY_SIZE(args)); } @@ -776,6 +776,56 @@ parse_ct_lb_action(struct action_context *ctx) } static void +parse_get_nd_action(struct action_context *ctx) +{ + struct mf_subfield port, ip6; + + if (!action_force_match(ctx, LEX_T_LPAREN) + || !action_parse_field(ctx, 0, &port) + || !action_force_match(ctx, LEX_T_COMMA) + || !action_parse_field(ctx, 128, &ip6) + || !action_force_match(ctx, LEX_T_RPAREN)) { + return; + } + + const struct arg args[] = { + { &port, MFF_LOG_OUTPORT }, + { &ip6, MFF_XXREG0 }, + }; + setup_args(ctx, args, ARRAY_SIZE(args)); + + put_load(0, MFF_ETH_DST, 0, 48, ctx->ofpacts); + emit_resubmit(ctx, ctx->ap->mac_bind_ptable); + + restore_args(ctx, args, ARRAY_SIZE(args)); +} + +static void +parse_put_nd_action(struct action_context *ctx) +{ + struct mf_subfield port, ip6, mac; + + if (!action_force_match(ctx, LEX_T_LPAREN) + || !action_parse_field(ctx, 0, &port) + || !action_force_match(ctx, LEX_T_COMMA) + || !action_parse_field(ctx, 128, &ip6) + || !action_force_match(ctx, LEX_T_COMMA) + || !action_parse_field(ctx, 48, &mac) + || !action_force_match(ctx, LEX_T_RPAREN)) { + return; + } + + const struct arg args[] = { + { &port, MFF_LOG_INPORT }, + { &ip6, MFF_XXREG0 }, + { &mac, MFF_ETH_SRC } + }; + setup_args(ctx, args, ARRAY_SIZE(args)); + put_controller_op(ctx->ofpacts, ACTION_OPCODE_PUT_ND); + restore_args(ctx, args, ARRAY_SIZE(args)); +} + +static void emit_ct(struct action_context *ctx, bool recirc_next, bool commit, int *ct_mark, int *ct_mark_mask, ovs_be128 *ct_label, ovs_be128 *ct_label_mask) @@ -1065,12 +1115,16 @@ parse_action(struct action_context *ctx) parse_ct_lb_action(ctx); } else if (lexer_match_id(ctx->lexer, "arp")) { parse_nested_action(ctx, ACTION_OPCODE_ARP, "ip4"); - } else if (lexer_match_id(ctx->lexer, "nd_na")) { - parse_nested_action(ctx, ACTION_OPCODE_ND_NA, "nd_ns"); } else if (lexer_match_id(ctx->lexer, "get_arp")) { parse_get_arp_action(ctx); } else if (lexer_match_id(ctx->lexer, "put_arp")) { parse_put_arp_action(ctx); + } else if (lexer_match_id(ctx->lexer, "nd_na")) { + parse_nested_action(ctx, ACTION_OPCODE_ND_NA, "nd_ns"); + } else if (lexer_match_id(ctx->lexer, "get_nd")) { + parse_get_nd_action(ctx); + } else if (lexer_match_id(ctx->lexer, "put_nd")) { + parse_put_nd_action(ctx); } else { action_syntax_error(ctx, "expecting action"); } diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index 168c38dc3..77974172f 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -843,7 +843,7 @@ output; </li> <li> - ARP reply handling. These flows use ARP replies to populate the + ARP reply handling. This flow uses ARP replies to populate the logical router's ARP table. A priority-90 flow with match <code>arp.op == 2</code> has actions <code>put_arp(inport, arp.spa, arp.sha);</code>. @@ -851,21 +851,19 @@ output; <li> <p> - Reply to IPv6 Neighbor Solicitations. - </p> - - <p> - These flows reply to Neighbor Solictation requests for the - router's own IPv6 address. For each router port <var>P</var> - that owns IPv6 address <var>A</var>, solicited node address - <var>S</var>, and Ethernet address - <var>E</var>, a priority-90 flow matches <code>inport == - <var>P</var> && nd_ns && ip6.dst == {<var>A</var>, - <var>E</var>} && nd.target == <var>A</var></code> - with the following actions: + Reply to IPv6 Neighbor Solicitations. These flows reply to + Neighbor Solicitation requests for the router's own IPv6 + address and populate the logical router's mac binding table. + For each router port <var>P</var> that owns IPv6 address + <var>A</var>, solicited node address <var>S</var>, and + Ethernet address <var>E</var>, a priority-90 flow matches + <code>inport == <var>P</var> && nd_ns && + ip6.dst == {<var>A</var>, <var>E</var>} && nd.target + == <var>A</var></code> with the following actions: </p> <pre> +put_nd(inport, ip6.src, nd.sll); nd_na { eth.src = <var>E</var>; ip6.src = <var>A</var>; @@ -879,6 +877,23 @@ nd_na { </li> <li> + IPv6 neighbor advertisement handling. This flow uses neighbor + advertisements to populate the logical router's mac binding + table. A priority-90 flow with match <code>nd_na</code> + has actions <code>put_nd(inport, nd.target, nd.tll);</code>. + </li> + + <li> + IPv6 neighbor solicitation for non-hosted addresses handling. + This flow uses neighbor solicitations to populate the logical + router's mac binding table (ones that were directed at the + logical router would have matched the priority-90 neighbor + solicitation flow already). A priority-80 flow with match + <code>nd_ns</code> has actions + <code>put_nd(inport, ip6.src, nd.sll);</code>. + </li> + + <li> <p> UDP port unreachable. Priority-80 flows generate ICMP port unreachable messages in reply to UDP datagrams directed to the @@ -1202,15 +1217,22 @@ next; <li> <p> - Dynamic MAC bindings. This flows resolves MAC-to-IP bindings that - have become known dynamically through ARP. (The next table will - issue an ARP request for cases where the binding is not yet known.) + Dynamic MAC bindings. These flows resolve MAC-to-IP bindings + that have become known dynamically through ARP or neighbor + discovery. (The next table will issue an ARP or neighbor + solicitation request for cases where the binding is not yet + known.) </p> <p> - A priority-0 logical flow with match <code>1</code> has actions + A priority-0 logical flow with match <code>ip4</code> has actions <code>get_arp(outport, reg0); next;</code>. </p> + + <p> + A priority-0 logical flow with match <code>ip6</code> has actions + <code>get_nd(outport, xxreg0); next;</code>. + </p> </li> </ul> diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 14ce1bad7..d6c14cf3e 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -3046,6 +3046,17 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30, ds_cstr(&match), "drop;"); + /* ND advertisement handling. Use advertisements to populate + * the logical router's ARP/ND table. */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 90, "nd_na", + "put_nd(inport, nd.target, nd.tll);"); + + /* Lean from neighbor solicitations that were not directed at + * us. (A priority-90 flow will respond to requests to us and + * learn the sender's mac address. */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 80, "nd_ns", + "put_nd(inport, ip6.src, nd.sll);"); + /* Pass other traffic not already handled to the next table for * routing. */ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;"); @@ -3252,6 +3263,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, ds_clear(&actions); ds_put_format(&actions, + "put_nd(inport, ip6.src, nd.sll); " "nd_na { " "eth.src = %s; " "ip6.src = %s; " @@ -3629,6 +3641,11 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "1", "get_arp(outport, reg0); next;"); + ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4", + "get_arp(outport, reg0); next;"); + + ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6", + "get_nd(outport, xxreg0); next;"); } /* Local router ingress table 6: ARP request. diff --git a/ovn/ovn-architecture.7.xml b/ovn/ovn-architecture.7.xml index b4240a270..092e41857 100644 --- a/ovn/ovn-architecture.7.xml +++ b/ovn/ovn-architecture.7.xml @@ -857,6 +857,7 @@ </dd> <dt><code>get_arp(<var>P</var>, <var>A</var>);</code></dt> + <dt><code>get_nd(<var>P</var>, <var>A</var>);</code></dt> <dd> <p> Implemented by storing arguments into OpenFlow fields, then @@ -875,6 +876,7 @@ </dd> <dt><code>put_arp(<var>P</var>, <var>A</var>, <var>E</var>);</code></dt> + <dt><code>put_nd(<var>P</var>, <var>A</var>, <var>E</var>);</code></dt> <dd> <p> Implemented by storing the arguments into OpenFlow fields, then diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index 91b1cd7cd..13c952696 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -1073,6 +1073,44 @@ <p><b>Prerequisite:</b> <code>ip4</code></p> </dd> + <dt><code>get_arp(<var>P</var>, <var>A</var>);</code></dt> + + <dd> + <p> + <b>Parameters</b>: logical port string field <var>P</var>, 32-bit + IP address field <var>A</var>. + </p> + + <p> + Looks up <var>A</var> in <var>P</var>'s mac binding table. + If an entry is found, stores its Ethernet address in + <code>eth.dst</code>, otherwise stores + <code>00:00:00:00:00:00</code> in <code>eth.dst</code>. + </p> + + <p><b>Example:</b> <code>get_arp(outport, ip4.dst);</code></p> + </dd> + + <dt> + <code>put_arp(<var>P</var>, <var>A</var>, <var>E</var>);</code> + </dt> + + <dd> + <p> + <b>Parameters</b>: logical port string field <var>P</var>, 32-bit + IP address field <var>A</var>, 48-bit Ethernet address field + <var>E</var>. + </p> + + <p> + Adds or updates the entry for IP address <var>A</var> in + logical port <var>P</var>'s mac binding table, setting its + Ethernet address to <var>E</var>. + </p> + + <p><b>Example:</b> <code>put_arp(inport, arp.spa, arp.sha);</code></p> + </dd> + <dt> <code>nd_na { <var>action</var>; </code>...<code> };</code> </dt> @@ -1113,42 +1151,42 @@ </p> </dd> - <dt><code>get_arp(<var>P</var>, <var>A</var>);</code></dt> + <dt><code>get_nd(<var>P</var>, <var>A</var>);</code></dt> <dd> <p> - <b>Parameters</b>: logical port string field <var>P</var>, 32-bit - IP address field <var>A</var>. + <b>Parameters</b>: logical port string field <var>P</var>, 128-bit + IPv6 address field <var>A</var>. </p> <p> - Looks up <var>A</var> in <var>P</var>'s ARP table. If an entry is - found, stores its Ethernet address in <code>eth.dst</code>, - otherwise stores <code>00:00:00:00:00:00</code> in - <code>eth.dst</code>. + Looks up <var>A</var> in <var>P</var>'s mac binding table. + If an entry is found, stores its Ethernet address in + <code>eth.dst</code>, otherwise stores + <code>00:00:00:00:00:00</code> in <code>eth.dst</code>. </p> - <p><b>Example:</b> <code>get_arp(outport, ip4.dst);</code></p> + <p><b>Example:</b> <code>get_nd(outport, ip6.dst);</code></p> </dd> <dt> - <code>put_arp(<var>P</var>, <var>A</var>, <var>E</var>);</code> + <code>put_nd(<var>P</var>, <var>A</var>, <var>E</var>);</code> </dt> <dd> <p> - <b>Parameters</b>: logical port string field <var>P</var>, 32-bit - IP address field <var>A</var>, 48-bit Ethernet address field - <var>E</var>. + <b>Parameters</b>: logical port string field <var>P</var>, + 128-bit IPv6 address field <var>A</var>, 48-bit Ethernet + address field <var>E</var>. </p> <p> - Adds or updates the entry for IP address <var>A</var> in logical - port <var>P</var>'s ARP table, setting its Ethernet address to - <var>E</var>. + Adds or updates the entry for IPv6 address <var>A</var> in + logical port <var>P</var>'s mac binding table, setting its + Ethernet address to <var>E</var>. </p> - <p><b>Example:</b> <code>put_arp(inport, arp.spa, arp.sha);</code></p> + <p><b>Example:</b> <code>put_nd(inport, nd.target, nd.tll);</code></p> </dd> <dt> diff --git a/tests/ovn.at b/tests/ovn.at index 33b901032..54fa8c53c 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -688,6 +688,21 @@ reg1[0] = put_dhcp_opts(offerip=1.2.3.4, domain=1.2.3.4); => DHCP option domain # nd_na nd_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.1c.04.00.01.1e.04.00.19.00.10.00.01.1c.04.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_ns +# get_nd +get_nd(outport, ip6.dst); => actions=push:NXM_NX_XXREG0[],push:NXM_NX_IPV6_DST[],pop:NXM_NX_XXREG0[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,65),pop:NXM_NX_XXREG0[], prereqs=eth.type == 0x86dd +get_nd(inport, xxreg0); => actions=push:NXM_NX_REG15[],push:NXM_NX_REG14[],pop:NXM_NX_REG15[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,65),pop:NXM_NX_REG15[], prereqs=1 +get_nd; => Syntax error at `;' expecting `('. +get_nd(); => Syntax error at `)' expecting field name. +get_nd(inport); => Syntax error at `)' expecting `,'. +get_nd(inport ip6.dst); => Syntax error at `ip6.dst' expecting `,'. +get_nd(inport, ip6.dst; => Syntax error at `;' expecting `)'. +get_nd(inport, eth.dst); => Cannot use 48-bit field eth.dst[0..47] where 128-bit field is required. +get_nd(inport, outport); => Cannot use string field outport where numeric field is required. +get_nd(xxreg0, ip6.dst); => Cannot use numeric field xxreg0 where string field is required. + +# put_nd +put_nd(inport, nd.target, nd.sll); => actions=push:NXM_NX_XXREG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ND_SLL[],push:NXM_NX_ND_TARGET[],pop:NXM_NX_XXREG0[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.04.00.00.00.00),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_XXREG0[], prereqs=((icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd)) || (icmp6.type == 0x88 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd))) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd) + # 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 diff --git a/tests/test-ovn.c b/tests/test-ovn.c index af197099f..acb6a99c5 100644 --- a/tests/test-ovn.c +++ b/tests/test-ovn.c @@ -1320,7 +1320,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED) .first_ptable = 16, .cur_ltable = 10, .output_ptable = 64, - .arp_ptable = 65, + .mac_bind_ptable = 65, }; error = actions_parse_string(ds_cstr(&input), &ap, &ofpacts, &prereqs); if (!error) { |