An upcoming commit will use this as a building block in adding ARP support to the OVN L3 logical router implementation.
Signed-off-by: Ben Pfaff <b...@ovn.org> --- lib/ofp-actions.h | 30 +++---- lib/packets.c | 38 +++++---- lib/packets.h | 3 +- ovn/controller/ovn-controller.c | 4 +- ovn/controller/pinctrl.c | 174 ++++++++++++++++++++++++++++++++-------- ovn/controller/pinctrl.h | 5 +- ovn/lib/actions.c | 57 ++++++++++++- ovn/lib/actions.h | 18 ++++- ovn/ovn-sb.xml | 21 +++-- tests/ovn.at | 3 + 10 files changed, 277 insertions(+), 76 deletions(-) diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h index 24143d3..55058ef 100644 --- a/lib/ofp-actions.h +++ b/lib/ofp-actions.h @@ -254,20 +254,22 @@ struct ofpact_output { * * Used for NXAST_CONTROLLER. */ struct ofpact_controller { - struct ofpact ofpact; - uint16_t max_len; /* Maximum length to send to controller. */ - uint16_t controller_id; /* Controller ID to send packet-in. */ - enum ofp_packet_in_reason reason; /* Reason to put in packet-in. */ - - /* If true, this action freezes packet traversal of the OpenFlow tables and - * adds a continuation to the packet-in message, that a controller can use - * to resume that traversal. */ - bool pause; - - /* Arbitrary data to include in the packet-in message (currently, only in - * NXT_PACKET_IN2). */ - uint16_t userdata_len; - uint8_t userdata[]; + OFPACT_PADDED_MEMBERS( + struct ofpact ofpact; + uint16_t max_len; /* Max length to send to controller. */ + uint16_t controller_id; /* Controller ID to send packet-in. */ + enum ofp_packet_in_reason reason; /* Reason to put in packet-in. */ + + /* If true, this action freezes packet traversal of the OpenFlow + * tables and adds a continuation to the packet-in message, that + * a controller can use to resume that traversal. */ + bool pause; + + /* Arbitrary data to include in the packet-in message (currently, + * only in NXT_PACKET_IN2). */ + uint16_t userdata_len; + ); + uint8_t userdata[0]; }; /* OFPACT_ENQUEUE. diff --git a/lib/packets.c b/lib/packets.c index d82341d..b693f78 100644 --- a/lib/packets.c +++ b/lib/packets.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc. + * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1191,35 +1191,45 @@ packet_format_tcp_flags(struct ds *s, uint16_t tcp_flags) * 'arp_op', 'arp_sha', 'arp_tha', 'arp_spa', and 'arp_tpa'. The outer * Ethernet frame is initialized with Ethernet source 'arp_sha' and destination * 'arp_tha', except that destination ff:ff:ff:ff:ff:ff is used instead if - * 'broadcast' is true. */ + * 'broadcast' is true. Points the L3 header to the ARP header. */ void compose_arp(struct dp_packet *b, uint16_t arp_op, const struct eth_addr arp_sha, const struct eth_addr arp_tha, bool broadcast, ovs_be32 arp_spa, ovs_be32 arp_tpa) { - struct eth_header *eth; - struct arp_eth_header *arp; + compose_arp__(b); + + struct eth_header *eth = dp_packet_l2(b); + eth->eth_dst = broadcast ? eth_addr_broadcast : arp_tha; + eth->eth_src = arp_sha; + + struct arp_eth_header *arp = dp_packet_l3(b); + arp->ar_op = htons(arp_op); + arp->ar_sha = arp_sha; + arp->ar_tha = arp_tha; + put_16aligned_be32(&arp->ar_spa, arp_spa); + put_16aligned_be32(&arp->ar_tpa, arp_tpa); +} +/* Clears 'b' and replaces its contents by an ARP frame. Sets the fields in + * the Ethernet and ARP headers that are fixed for ARP frames to those fixed + * values, and zeroes the other fields. Points the L3 header to the ARP + * header. */ +void +compose_arp__(struct dp_packet *b) +{ dp_packet_clear(b); dp_packet_prealloc_tailroom(b, ARP_PACKET_SIZE); dp_packet_reserve(b, 2 + VLAN_HEADER_LEN); - eth = dp_packet_put_uninit(b, sizeof *eth); - eth->eth_dst = broadcast ? eth_addr_broadcast : arp_tha; - eth->eth_src = arp_sha; + struct eth_header *eth = dp_packet_put_zeros(b, sizeof *eth); eth->eth_type = htons(ETH_TYPE_ARP); - arp = dp_packet_put_uninit(b, sizeof *arp); + struct arp_eth_header *arp = dp_packet_put_zeros(b, sizeof *arp); arp->ar_hrd = htons(ARP_HRD_ETHERNET); arp->ar_pro = htons(ARP_PRO_IP); arp->ar_hln = sizeof arp->ar_sha; arp->ar_pln = sizeof arp->ar_spa; - arp->ar_op = htons(arp_op); - arp->ar_sha = arp_sha; - arp->ar_tha = arp_tha; - - put_16aligned_be32(&arp->ar_spa, arp_spa); - put_16aligned_be32(&arp->ar_tpa, arp_tpa); dp_packet_reset_offsets(b); dp_packet_set_l3(b, arp); diff --git a/lib/packets.h b/lib/packets.h index f1445de..119c4b7 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc. + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1062,6 +1062,7 @@ void packet_set_nd(struct dp_packet *, const ovs_be32 target[4], void packet_format_tcp_flags(struct ds *, uint16_t); const char *packet_tcp_flag_to_string(uint32_t flag); +void compose_arp__(struct dp_packet *); void compose_arp(struct dp_packet *, uint16_t arp_op, const struct eth_addr arp_sha, const struct eth_addr arp_tha, bool broadcast, diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c index 3638342..9799a19 100644 --- a/ovn/controller/ovn-controller.c +++ b/ovn/controller/ovn-controller.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2015 Nicira, Inc. +/* Copyright (c) 2015, 2016 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -297,7 +297,7 @@ main(int argc, char *argv[]) enum mf_field_id mff_ovn_geneve = ofctrl_run(br_int); - pinctrl_run(&ctx, br_int); + pinctrl_run(br_int); struct hmap flow_table = HMAP_INITIALIZER(&flow_table); lflow_run(&ctx, &flow_table, &ct_zones, &local_datapaths); diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c index 9e94425..70088f6 100644 --- a/ovn/controller/pinctrl.c +++ b/ovn/controller/pinctrl.c @@ -15,11 +15,16 @@ */ #include <config.h> -#include "dirs.h" + #include "pinctrl.h" + +#include "dirs.h" +#include "dp-packet.h" +#include "ofp-actions.h" #include "ofp-msgs.h" #include "ofp-print.h" #include "ofp-util.h" +#include "ovn/lib/actions.h" #include "rconn.h" #include "openvswitch/vlog.h" #include "socket-util.h" @@ -51,14 +56,19 @@ queue_msg(struct ofpbuf *msg) return xid; } +/* Sets up 'swconn', a newly (re)connected connection to a switch. */ static void -get_switch_config(struct rconn *swconn) +pinctrl_setup(struct rconn *swconn) { - struct ofpbuf *request; + /* Fetch the switch configuration. The response later will allow us to + * change the miss_send_len to UINT16_MAX, so that we can enable + * asynchronous messages. */ + queue_msg(ofpraw_alloc(OFPRAW_OFPT_GET_CONFIG_REQUEST, + rconn_get_version(swconn), 0)); - request = ofpraw_alloc(OFPRAW_OFPT_GET_CONFIG_REQUEST, - rconn_get_version(swconn), 0); - queue_msg(request); + /* Set a packet-in format that supports userdata. */ + queue_msg(ofputil_make_set_packet_in_format(rconn_get_version(swconn), + NXPIF_NXT_PACKET_IN2)); } static void @@ -71,35 +81,135 @@ set_switch_config(struct rconn *swconn, } static void -process_packet_in(struct controller_ctx *ctx OVS_UNUSED, - const struct ofp_header *msg) +pinctrl_handle_arp(const struct flow *ip_flow, struct ofpbuf *userdata) { - struct ofputil_packet_in pin; + /* Compose an ARP packet. */ + uint64_t packet_stub[128 / 8]; + struct dp_packet packet; + dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); + compose_arp__(&packet); + + struct eth_header *eth = dp_packet_l2(&packet); + eth->eth_dst = ip_flow->dl_dst; + eth->eth_src = ip_flow->dl_src; + + struct arp_eth_header *arp = dp_packet_l3(&packet); + arp->ar_op = htons(ARP_OP_REQUEST); + arp->ar_sha = ip_flow->dl_src; + put_16aligned_be32(&arp->ar_spa, ip_flow->nw_src); + arp->ar_tha = eth_addr_zero; + put_16aligned_be32(&arp->ar_tpa, ip_flow->nw_dst); + + if (ip_flow->vlan_tci) { + eth_push_vlan(&packet, htons(ETH_TYPE_VLAN_8021Q), ip_flow->vlan_tci); + } + + /* Compose actions. + * + * First, add actions to restore the metadata, then add actions from + * 'userdata'. + */ + uint64_t ofpacts_stub[1024 / 8]; + struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); + enum ofp_version version = rconn_get_version(swconn); + + for (int id = 0; id < MFF_N_IDS; id++) { + const struct mf_field *field = mf_from_id(id); + + if (field->prereqs == MFP_NONE + && field->writable + && id != MFF_IN_PORT && id != MFF_IN_PORT_OXM + && mf_is_set(field, ip_flow)) + { + struct ofpact_set_field *sf = ofpact_put_SET_FIELD(&ofpacts); + sf->field = field; + sf->flow_has_vlan = false; + mf_get_value(field, ip_flow, &sf->value); + bitwise_one(&sf->mask, sizeof sf->mask, 0, field->n_bits); + } + } + 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 arp actions (%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, + }; + enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); + queue_msg(ofputil_encode_packet_out(&po, proto)); + +exit: + dp_packet_uninit(&packet); + ofpbuf_uninit(&ofpacts); +} - if (ofputil_decode_packet_in(msg, true, &pin, NULL, NULL, NULL) != 0) { +static void +process_packet_in(const struct ofp_header *msg) +{ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + + struct ofputil_packet_in pin; + enum ofperr error = ofputil_decode_packet_in(msg, true, &pin, + NULL, NULL, NULL); + if (error) { + VLOG_WARN_RL(&rl, "error decoding packet-in: %s", + ofperr_to_string(error)); return; } if (pin.reason != OFPR_ACTION) { return; } - /* XXX : process the received packet */ + struct ofpbuf userdata = ofpbuf_const_initializer(pin.userdata, + pin.userdata_len); + const struct action_header *ah = ofpbuf_pull(&userdata, sizeof *ah); + if (!ah) { + VLOG_WARN_RL(&rl, "packet-in userdata lacks action header"); + return; + } + + struct dp_packet packet; + dp_packet_use_const(&packet, pin.packet, pin.packet_len); + struct flow headers; + flow_extract(&packet, &headers); + + const struct flow *md = &pin.flow_metadata.flow; + switch (ntohl(ah->opcode)) { + case ACTION_OPCODE_ARP: + pinctrl_handle_arp(&headers, &userdata); + break; + + default: + VLOG_WARN_RL(&rl, "unrecognized packet-in command %#"PRIx32, + md->regs[0]); + break; + } } static void -pinctrl_recv(struct controller_ctx *ctx, const struct ofp_header *oh, - enum ofptype type) +pinctrl_recv(const struct ofp_header *oh, enum ofptype type) { if (type == OFPTYPE_ECHO_REQUEST) { queue_msg(make_echo_reply(oh)); } else if (type == OFPTYPE_GET_CONFIG_REPLY) { + /* Enable asynchronous messages. */ struct ofputil_switch_config config; ofputil_decode_get_config_reply(oh, &config); config.miss_send_len = UINT16_MAX; set_switch_config(swconn, &config); } else if (type == OFPTYPE_PACKET_IN) { - process_packet_in(ctx, oh); + process_packet_in(oh); } else if (type != OFPTYPE_ECHO_REPLY && type != OFPTYPE_BARRIER_REPLY) { if (VLOG_IS_DBG_ENABLED()) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300); @@ -113,7 +223,7 @@ pinctrl_recv(struct controller_ctx *ctx, const struct ofp_header *oh, } void -pinctrl_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int) +pinctrl_run(const struct ovsrec_bridge *br_int) { if (br_int) { char *target; @@ -130,27 +240,27 @@ pinctrl_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int) rconn_run(swconn); - if (!rconn_is_connected(swconn)) { - return; - } + if (rconn_is_connected(swconn)) { + if (conn_seq_no != rconn_get_connection_seqno(swconn)) { + pinctrl_setup(swconn); + conn_seq_no = rconn_get_connection_seqno(swconn); + } - if (conn_seq_no != rconn_get_connection_seqno(swconn)) { - get_switch_config(swconn); - conn_seq_no = rconn_get_connection_seqno(swconn); - } + /* Process a limited number of messages per call. */ + for (int i = 0; i < 50; i++) { + struct ofpbuf *msg = rconn_recv(swconn); + if (!msg) { + break; + } - struct ofpbuf *msg = rconn_recv(swconn); + const struct ofp_header *oh = msg->data; + enum ofptype type; - if (!msg) { - return; + ofptype_decode(&type, oh); + pinctrl_recv(oh, type); + ofpbuf_delete(msg); + } } - - const struct ofp_header *oh = msg->data; - enum ofptype type; - - ofptype_decode(&type, oh); - pinctrl_recv(ctx, oh, type); - ofpbuf_delete(msg); } void diff --git a/ovn/controller/pinctrl.h b/ovn/controller/pinctrl.h index fd279ff..fca6b52 100644 --- a/ovn/controller/pinctrl.h +++ b/ovn/controller/pinctrl.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2015 Nicira, Inc. +/* Copyright (c) 2015, 2016 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,8 +25,7 @@ struct controller_ctx; /* Interface for OVN main loop. */ void pinctrl_init(void); -void pinctrl_run(struct controller_ctx *ctx, - const struct ovsrec_bridge *br_int); +void pinctrl_run(const struct ovsrec_bridge *br_int); void pinctrl_wait(void); void pinctrl_destroy(void); diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c index 42e7f3b..f9f7ef7 100644 --- a/ovn/lib/actions.c +++ b/ovn/lib/actions.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Nicira, Inc. + * Copyright (c) 2015, 2016 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -184,6 +184,59 @@ add_prerequisite(struct action_context *ctx, const char *prerequisite) } static void +parse_arp_action(struct action_context *ctx) +{ + if (!lexer_match(ctx->lexer, LEX_T_LCURLY)) { + action_syntax_error(ctx, "expecting `{'"); + return; + } + + struct ofpbuf *outer_ofpacts = ctx->ofpacts; + uint64_t inner_ofpacts_stub[1024 / 8]; + struct ofpbuf inner_ofpacts = OFPBUF_STUB_INITIALIZER(inner_ofpacts_stub); + ctx->ofpacts = &inner_ofpacts; + + /* Save prerequisites. (XXX What is the right treatment for prereqs?) */ + struct expr *outer_prereqs = ctx->prereqs; + ctx->prereqs = NULL; + + /* Parse inner actions. */ + while (!lexer_match(ctx->lexer, LEX_T_RCURLY)) { + if (!parse_action(ctx)) { + break; + } + } + + ctx->ofpacts = outer_ofpacts; + + /* controller. */ + size_t oc_offset = ctx->ofpacts->size; + ofpact_put_CONTROLLER(ctx->ofpacts); + + struct action_header ah = { .opcode = htonl(ACTION_OPCODE_ARP) }; + ofpbuf_put(ctx->ofpacts, &ah, sizeof ah); + + ofpacts_put_openflow_actions(inner_ofpacts.data, inner_ofpacts.size, + ctx->ofpacts, OFP13_VERSION); + + struct ofpact_controller *oc = ofpbuf_at_assert(ctx->ofpacts, oc_offset, + sizeof *oc); + ctx->ofpacts->header = oc; + oc->max_len = UINT16_MAX; + oc->reason = OFPR_ACTION; + oc->userdata_len = ctx->ofpacts->size - (oc_offset + sizeof *oc); + ofpact_finish(ctx->ofpacts, &oc->ofpact); + + /* Restore prerequisites. */ + expr_destroy(ctx->prereqs); + ctx->prereqs = outer_prereqs; + add_prerequisite(ctx, "ip4"); + + /* Free memory. */ + ofpbuf_uninit(&inner_ofpacts); +} + +static void emit_ct(struct action_context *ctx, bool recirc_next, bool commit) { struct ofpact_conntrack *ct = ofpact_put_CT(ctx->ofpacts); @@ -239,6 +292,8 @@ parse_action(struct action_context *ctx) emit_ct(ctx, true, false); } else if (lexer_match_id(ctx->lexer, "ct_commit")) { emit_ct(ctx, false, true); + } else if (lexer_match_id(ctx->lexer, "arp")) { + parse_arp_action(ctx); } else { action_syntax_error(ctx, "expecting action"); } diff --git a/ovn/lib/actions.h b/ovn/lib/actions.h index 2c3644a..5b2367c 100644 --- a/ovn/lib/actions.h +++ b/ovn/lib/actions.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Nicira, Inc. + * Copyright (c) 2015, 2016 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ #include <stdint.h> #include "compiler.h" +#include "util.h" struct expr; struct lexer; @@ -26,6 +27,21 @@ struct ofpbuf; struct shash; struct simap; +enum action_opcode { + /* "arp { ...actions... }". + * + * The actions, in OpenFlow 1.3 format, follow the action_header. + */ + ACTION_OPCODE_ARP, +}; + +/* Header. */ +struct action_header { + ovs_be32 opcode; /* One of ACTION_OPCODE_* */ + uint8_t pad[4]; +}; +BUILD_ASSERT_DECL(sizeof(struct action_header) == 8); + struct action_params { /* A table of "struct expr_symbol"s to support (as one would provide to * expr_parse()). */ diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index 1ea35d5..dd248b4 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -907,14 +907,6 @@ Commit the flow to the connection tracking entry associated with it by a previous call to <code>ct_next</code>. </dd> - </dl> - - <p> - The following actions will likely be useful later, but they have not - been thought out carefully. - </p> - - <dl> <dt><code>arp { <var>action</var>; </code>...<code> };</code></dt> <dd> @@ -942,9 +934,22 @@ <li><code>arp.tpa</code> copied from <code>ip4.dst</code></li> </ul> + <p> + The ARP packet has the same VLAN header, if any, as the IP packet + it replaces. + </p> + <p><b>Prerequisite:</b> <code>ip4</code></p> </dd> + </dl> + + <p> + The following actions will likely be useful later, but they have not + been thought out carefully. + </p> + + <dl> <dt><code>icmp4 { <var>action</var>; </code>...<code> };</code></dt> <dd> <p> diff --git a/tests/ovn.at b/tests/ovn.at index 0917e8d..5e49767 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -507,6 +507,9 @@ ip.ttl => Syntax error at end of input expecting `--'. ct_next; => actions=ct(table=27,zone=NXM_NX_REG5[0..15]), prereqs=ip ct_commit; => actions=ct(commit,zone=NXM_NX_REG5[0..15]), prereqs=ip +# arp +arp { eth.dst = ff:ff:ff:ff:ff:ff; output; }; => actions=controller(userdata=00.00.00.00.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00), prereqs=ip4 + # 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 -- 2.1.3 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev