int main(void) { struct ds in; ds_init(&in); while (!ds_get_line(&in, stdin)) { struct ofpbuf odp_key; struct flow flow; struct ds out; int error; char *s; /* Delete comments, skip blank lines. */ s = ds_cstr(&in); if (*s == '#') { puts(s); continue; } if (strchr(s, '#')) { *strchr(s, '#') = '\0'; } if (s[strspn(s, " ")] == '\0') { putchar('\n'); continue; } /* Convert string to OVS DP key. */ ofpbuf_init(&odp_key, 0); error = odp_flow_key_from_string(ds_cstr(&in), &odp_key); if (error) { printf("odp_flow_key_from_string: error\n"); goto next; } /* Convert odp_key to flow. */ error = odp_flow_key_to_flow(odp_key.data, odp_key.size, &flow); if (error) { printf("odp_flow_key_to_flow: error\n"); goto next; } /* Convert cls_rule back to odp_key. */ ofpbuf_uninit(&odp_key); ofpbuf_init(&odp_key, 0); odp_flow_key_from_flow(&odp_key, &flow); /* Convert odp_key to string. */ ds_init(&out); odp_flow_key_format(odp_key.data, odp_key.size, &out); puts(ds_cstr(&out)); ds_destroy(&out); next: ofpbuf_uninit(&odp_key); } ds_destroy(&in); return 0; }
struct ofpbuf * flow_mod_match_conj(enum ofputil_protocol proto) { struct ofputil_flow_mod fm; struct ofpbuf acts; struct ofpact_ipv4 *a_set_field; struct ofpact_goto_table *a_goto; memset(&fm, 0, sizeof(fm)); fm.command = OFPFC_ADD; fm.table_id = 3; fm.new_cookie = htonll(0x123456789abcdef0); fm.cookie_mask = OVS_BE64_MAX; fm.importance = 0x9878; match_init_catchall(&fm.match); match_set_conj_id(&fm.match, 0xabcdef); ofpbuf_init(&acts, 64); ofpact_put_STRIP_VLAN(&acts); a_set_field = ofpact_put_SET_IPV4_DST(&acts); a_set_field->ipv4 = inet_addr("192.168.2.9"); a_goto = ofpact_put_GOTO_TABLE(&acts); a_goto->table_id = 100; fm.ofpacts = acts.data; fm.ofpacts_len = acts.size; return ofputil_encode_flow_mod(&fm, proto); }
static int lookup_brc_multicast_group(int *multicast_group) { struct nl_sock *sock; struct ofpbuf request, *reply; struct nlattr *attrs[ARRAY_SIZE(brc_multicast_policy)]; int retval; retval = nl_sock_create(NETLINK_GENERIC, &sock); if (retval) { return retval; } ofpbuf_init(&request, 0); nl_msg_put_genlmsghdr(&request, 0, brc_family, NLM_F_REQUEST, BRC_GENL_C_QUERY_MC, 1); retval = nl_sock_transact(sock, &request, &reply); ofpbuf_uninit(&request); if (retval) { nl_sock_destroy(sock); return retval; } if (!nl_policy_parse(reply, NLMSG_HDRLEN + GENL_HDRLEN, brc_multicast_policy, attrs, ARRAY_SIZE(brc_multicast_policy))) { nl_sock_destroy(sock); ofpbuf_delete(reply); return EPROTO; } *multicast_group = nl_attr_get_u32(attrs[BRC_GENL_A_MC_GROUP]); nl_sock_destroy(sock); ofpbuf_delete(reply); return 0; }
struct ofpbuf * flow_mod_conjunction(enum ofputil_protocol proto) { struct ofputil_flow_mod fm; struct ofpbuf acts; struct ofpact_conjunction *a_conj; memset(&fm, 0, sizeof(fm)); fm.command = OFPFC_ADD; fm.table_id = 4; fm.new_cookie = htonll(0x123456789abcdef0); fm.cookie_mask = OVS_BE64_MAX; fm.importance = 0x9878; fill_match(&fm.match); ofpbuf_init(&acts, 64); a_conj = ofpact_put_CONJUNCTION(&acts); a_conj->id = 0xabcdef; a_conj->clause = 1; a_conj->n_clauses = 2; fm.ofpacts = acts.data; fm.ofpacts_len = acts.size; return ofputil_encode_flow_mod(&fm, proto); }
static int route_table_reset(void) { struct nl_dump dump; struct rtgenmsg *rtmsg; uint64_t reply_stub[NL_DUMP_BUFSIZE / 8]; struct ofpbuf request, reply, buf; route_map_clear(); route_table_valid = true; rt_change_seq++; ofpbuf_init(&request, 0); nl_msg_put_nlmsghdr(&request, sizeof *rtmsg, RTM_GETROUTE, NLM_F_REQUEST); rtmsg = ofpbuf_put_zeros(&request, sizeof *rtmsg); rtmsg->rtgen_family = AF_UNSPEC; nl_dump_start(&dump, NETLINK_ROUTE, &request); ofpbuf_uninit(&request); ofpbuf_use_stub(&buf, reply_stub, sizeof reply_stub); while (nl_dump_next(&dump, &reply, &buf)) { struct route_table_msg msg; if (route_table_parse(&reply, &msg)) { route_table_handle_msg(&msg); } } ofpbuf_uninit(&buf); return nl_dump_done(&dump); }
static int parse_actions(void) { struct ds in; ds_init(&in); vlog_set_levels_from_string_assert("odp_util:console:dbg"); while (!ds_get_test_line(&in, stdin)) { struct ofpbuf odp_actions; struct ds out; int error; /* Convert string to OVS DP actions. */ ofpbuf_init(&odp_actions, 0); error = odp_actions_from_string(ds_cstr(&in), NULL, &odp_actions); if (error) { printf("odp_actions_from_string: error\n"); goto next; } /* Convert odp_actions back to string. */ ds_init(&out); format_odp_actions(&out, odp_actions.data, odp_actions.size); puts(ds_cstr(&out)); ds_destroy(&out); next: ofpbuf_uninit(&odp_actions); } ds_destroy(&in); return 0; }
/* Parses 'string' as an OFPT_FLOW_MOD or NXT_FLOW_MOD with command 'command' * (one of OFPFC_*) and appends the parsed OpenFlow message to 'packets'. * '*cur_format' should initially contain the flow format currently configured * on the connection; this function will add a message to change the flow * format and update '*cur_format', if this is necessary to add the parsed * flow. */ void parse_ofp_flow_mod_str(struct list *packets, enum nx_flow_format *cur_format, char *string, uint16_t command) { bool is_del = command == OFPFC_DELETE || command == OFPFC_DELETE_STRICT; enum nx_flow_format min_format, next_format; struct ofpbuf actions; struct ofpbuf *ofm; struct flow_mod fm; ofpbuf_init(&actions, 64); parse_ofp_str(&fm, NULL, is_del ? NULL : &actions, string); fm.command = command; min_format = ofputil_min_flow_format(&fm.cr, true, fm.cookie); next_format = MAX(*cur_format, min_format); if (next_format != *cur_format) { struct ofpbuf *sff = ofputil_make_set_flow_format(next_format); list_push_back(packets, &sff->list_node); *cur_format = next_format; } ofm = ofputil_encode_flow_mod(&fm, *cur_format); list_push_back(packets, &ofm->list_node); ofpbuf_uninit(&actions); }
static int parse_actions(const char *in) { struct ofpbuf odp_actions; struct ds out; int error; /* Convert string to OVS DP actions. */ ofpbuf_init(&odp_actions, 0); error = odp_actions_from_string(in, NULL, &odp_actions); if (error) { printf("odp_actions_from_string: error\n"); goto next; } /* Convert odp_actions back to string. */ ds_init(&out); format_odp_actions(&out, odp_actions.data, odp_actions.size, NULL); puts(ds_cstr(&out)); ds_destroy(&out); next: ofpbuf_uninit(&odp_actions); return 0; }
/* Creates and returns a new ofpbuf with an initial capacity of 'size' * bytes. */ struct ofpbuf * ofpbuf_new(size_t size) { struct ofpbuf *b = xmalloc(sizeof *b); ofpbuf_init(b, size); return b; }
struct ofpbuf * group_mod(enum ofputil_protocol proto) { struct ofputil_group_mod gm; struct ofpbuf acts; struct ofpact_ipv4 *a_set_field; struct ofpact_goto_table *a_goto; struct ofputil_bucket bckt; memset(&gm, 0, sizeof(gm)); gm.command = OFPGC15_INSERT_BUCKET; gm.type = OFPGT11_SELECT; gm.group_id = 0xaaaaaaaa; gm.command_bucket_id = 0xbbbbbbbb; ofpbuf_init(&acts, 0x18); ofpact_put_STRIP_VLAN(&acts); a_set_field = ofpact_put_SET_IPV4_DST(&acts); a_set_field->ipv4 = inet_addr("192.168.2.9"); bckt.weight = 0xcccc; bckt.watch_port = 0xdddd; bckt.watch_group = 0xeeeeeeee; bckt.bucket_id = 0x12345678; bckt.ofpacts = acts.data; bckt.ofpacts_len = acts.size; list_init(&(gm.buckets)); list_push_back(&(gm.buckets), &(bckt.list_node)); return ofputil_encode_group_mod( ofputil_protocol_to_ofp_version(proto), &gm); }
/* Sends the given 'command' to datapath 'dp', related to the local datapath * numbered 'dp_idx'. If 'arg' is nonnull, adds it to the command as the * datapath or port name attribute depending on the requested operation. * Returns 0 if successful, otherwise a positive errno value. */ static int send_mgmt_command(struct dpif *dp, int dp_idx, int command, const char *arg) { struct ofpbuf request, *reply; int retval; ofpbuf_init(&request, 0); nl_msg_put_genlmsghdr(&request, dp->sock, 32, openflow_family, NLM_F_REQUEST | NLM_F_ACK, command, 1); if (dp_idx != -1) { nl_msg_put_u32(&request, DP_GENL_A_DP_IDX, dp_idx); } if (arg) { if ((command == DP_GENL_C_ADD_DP) || (command == DP_GENL_C_DEL_DP)) { nl_msg_put_string(&request, DP_GENL_A_DP_NAME, arg); } else { nl_msg_put_string(&request, DP_GENL_A_PORTNAME, arg); } } retval = nl_sock_transact(dp->sock, &request, &reply); ofpbuf_uninit(&request); ofpbuf_delete(reply); return retval; }
/* Tries to decode 'oh', which should be an OpenFlow OFPT_ERROR message. * Returns an OFPERR_* constant on success, 0 on failure. * * If 'payload' is nonnull, on success '*payload' is initialized with a copy of * the error's payload (copying is required because the payload is not properly * aligned). The caller must free the payload (with ofpbuf_uninit()) when it * is no longer needed. On failure, '*payload' is cleared. */ enum ofperr ofperr_decode_msg(const struct ofp_header *oh, struct ofpbuf *payload) { const struct ofp_error_msg *oem; enum ofpraw raw; uint16_t type, code; enum ofperr error; uint32_t vendor; struct ofpbuf b; if (payload) { memset(payload, 0, sizeof *payload); } /* Pull off the error message. */ ofpbuf_use_const(&b, oh, ntohs(oh->length)); error = ofpraw_pull(&raw, &b); if (error) { return 0; } oem = ofpbuf_pull(&b, sizeof *oem); /* Get the error type and code. */ vendor = 0; type = ntohs(oem->type); code = ntohs(oem->code); if (type == NXET_VENDOR && code == NXVC_VENDOR_ERROR) { const struct nx_vendor_error *nve = ofpbuf_try_pull(&b, sizeof *nve); if (!nve) { return 0; } vendor = ntohl(nve->vendor); type = ntohs(nve->type); code = ntohs(nve->code); } else if (type == OFPET12_EXPERIMENTER) { const ovs_be32 *vendorp = ofpbuf_try_pull(&b, sizeof *vendorp); if (!vendorp) { return 0; } vendor = ntohl(*vendorp); type = code; code = 0; } /* Translate the error type and code into an ofperr. */ error = ofperr_decode(oh->version, vendor, type, code); if (error && payload) { ofpbuf_init(payload, b.size); ofpbuf_push(payload, b.data, b.size); } return error; }
/* Starts a Netlink "dump" operation, by sending 'request' to the kernel on a * Netlink socket created with the given 'protocol', and initializes 'dump' to * reflect the state of the operation. * * nlmsg_len in 'msg' will be finalized to match msg->size, and nlmsg_pid will * be set to the Netlink socket's pid, before the message is sent. NLM_F_DUMP * and NLM_F_ACK will be set in nlmsg_flags. * * The design of this Netlink socket library ensures that the dump is reliable. * * This function provides no status indication. An error status for the entire * dump operation is provided when it is completed by calling nl_dump_done(). * * The caller is responsible for destroying 'request'. */ void nl_dump_start(struct nl_dump *dump, int protocol, const struct ofpbuf *request) { ofpbuf_init(&dump->buffer, 4096); dump->status = nl_pool_alloc(protocol, &dump->sock); if (dump->status) { return; } nl_msg_nlmsghdr(request)->nlmsg_flags |= NLM_F_DUMP | NLM_F_ACK; dump->status = nl_sock_send__(dump->sock, request, nl_sock_allocate_seq(dump->sock, 1), true); dump->seq = nl_msg_nlmsghdr(request)->nlmsg_seq; }
struct ofpbuf * flow_mod(enum ofputil_protocol proto) { struct ofputil_flow_mod fm; struct ofpbuf acts; struct ofpact_ipv4 *a_set_field; struct ofpact_goto_table *a_goto; char *error; /* * Taken from neutron OVS-agent, * modified for OF>=1.3. (NXM -> OXM) * NOTE(yamamoto): This needs to be writable. learn_parse() modifies it. */ char learn_args[] = "table=99," "priority=1," "hard_timeout=300," "OXM_OF_VLAN_VID[0..11]," "OXM_OF_ETH_DST[]=OXM_OF_ETH_SRC[]," "load:0->OXM_OF_VLAN_VID[]," "load:OXM_OF_TUNNEL_ID[]->OXM_OF_TUNNEL_ID[]," "output:OXM_OF_IN_PORT[]"; memset(&fm, 0, sizeof(fm)); fm.command = OFPFC_ADD; fm.table_id = 2; fm.new_cookie = htonll(0x123456789abcdef0); fm.cookie_mask = OVS_BE64_MAX; fm.importance = 0x9878; fill_match(&fm.match); ofpbuf_init(&acts, 64); ofpact_put_STRIP_VLAN(&acts); a_set_field = ofpact_put_SET_IPV4_DST(&acts); a_set_field->ipv4 = inet_addr("192.168.2.9"); error = learn_parse(learn_args, &acts); assert(error == NULL); a_goto = ofpact_put_GOTO_TABLE(&acts); a_goto->table_id = 100; fm.ofpacts = acts.data; fm.ofpacts_len = acts.size; return ofputil_encode_flow_mod(&fm, proto); }
int nl_ct_flush_zone(uint16_t flush_zone) { /* Windows can flush a specific zone */ struct ofpbuf buf; int err; ofpbuf_init(&buf, NL_DUMP_BUFSIZE); nl_msg_put_nfgenmsg(&buf, 0, AF_UNSPEC, NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_DELETE, NLM_F_REQUEST); nl_msg_put_be16(&buf, CTA_ZONE, flush_zone); err = nl_transact(NETLINK_NETFILTER, &buf, NULL); ofpbuf_uninit(&buf); return err; }
int nl_ct_flush(void) { struct ofpbuf buf; int err; ofpbuf_init(&buf, NL_DUMP_BUFSIZE); nl_msg_put_nfgenmsg(&buf, 0, AF_UNSPEC, NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_DELETE, NLM_F_REQUEST); err = nl_transact(NETLINK_NETFILTER, &buf, NULL); ofpbuf_uninit(&buf); /* Expectations are flushed automatically, because they do not * have a master connection anymore */ return err; }
/* Initialize a conntrack netlink dump. */ int nl_ct_dump_start(struct nl_ct_dump_state **statep, const uint16_t *zone) { struct nl_ct_dump_state *state; *statep = state = xzalloc(sizeof *state); ofpbuf_init(&state->buf, NL_DUMP_BUFSIZE); if (zone) { state->filter_zone = true; state->zone = *zone; } nl_msg_put_nfgenmsg(&state->buf, 0, AF_UNSPEC, NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_GET, NLM_F_REQUEST); nl_dump_start(&state->dump, NETLINK_NETFILTER, &state->buf); ofpbuf_clear(&state->buf); return 0; }
/* Looks up the Netlink multicast group and datapath index of a datapath * by either the datapath index or name. If 'dp_idx' points to a value * of '-1', then 'dp_name' is used to lookup the datapath. If successful, * stores the multicast group in '*multicast_group' and the index in * '*dp_idx' and returns 0. Otherwise, returns a positive errno value. */ static int query_datapath(int *dp_idx, int *multicast_group, const char *dp_name) { struct nl_sock *sock; struct ofpbuf request, *reply; struct nlattr *attrs[ARRAY_SIZE(openflow_multicast_policy)]; int retval; retval = nl_sock_create(NETLINK_GENERIC, 0, 0, 0, &sock); if (retval) { return retval; } ofpbuf_init(&request, 0); nl_msg_put_genlmsghdr(&request, sock, 0, openflow_family, NLM_F_REQUEST, DP_GENL_C_QUERY_DP, 1); if (*dp_idx != -1) { nl_msg_put_u32(&request, DP_GENL_A_DP_IDX, *dp_idx); } if (dp_name) { nl_msg_put_string(&request, DP_GENL_A_DP_NAME, dp_name); } retval = nl_sock_transact(sock, &request, &reply); ofpbuf_uninit(&request); if (retval) { nl_sock_destroy(sock); return retval; } if (!nl_policy_parse(reply, NLMSG_HDRLEN + GENL_HDRLEN, openflow_multicast_policy, attrs, ARRAY_SIZE(openflow_multicast_policy))) { nl_sock_destroy(sock); ofpbuf_delete(reply); return EPROTO; } *dp_idx = nl_attr_get_u32(attrs[DP_GENL_A_DP_IDX]); *multicast_group = nl_attr_get_u32(attrs[DP_GENL_A_MC_GROUP]); nl_sock_destroy(sock); ofpbuf_delete(reply); return 0; }
static void send_bogus_packet_ins(struct fail_open *fo) { struct ofputil_packet_in pin; uint8_t mac[ETH_ADDR_LEN]; struct ofpbuf b; ofpbuf_init(&b, 128); eth_addr_nicira_random(mac); compose_rarp(&b, mac); memset(&pin, 0, sizeof pin); pin.packet = b.data; pin.packet_len = b.size; pin.reason = OFPR_NO_MATCH; pin.send_len = b.size; pin.fmd.in_port = OFPP_LOCAL; connmgr_send_packet_in(fo->connmgr, &pin); ofpbuf_uninit(&b); }
static void send_bogus_packet_ins(struct fail_open *fo) { struct ofproto_packet_in pin; uint8_t mac[ETH_ADDR_LEN]; struct ofpbuf b; ofpbuf_init(&b, 128); eth_addr_nicira_random(mac); compose_rarp(&b, mac); memset(&pin, 0, sizeof pin); pin.up.packet = ofpbuf_data(&b); pin.up.packet_len = ofpbuf_size(&b); pin.up.reason = OFPR_NO_MATCH; pin.up.fmd.in_port = OFPP_LOCAL; pin.send_len = ofpbuf_size(&b); pin.miss_type = OFPROTO_PACKET_IN_NO_MISS; connmgr_send_packet_in(fo->connmgr, &pin); ofpbuf_uninit(&b); }
/* Starts a Netlink "dump" operation, by sending 'request' to the kernel via * 'sock', and initializes 'dump' to reflect the state of the operation. * * nlmsg_len in 'msg' will be finalized to match msg->size, and nlmsg_pid will * be set to 'sock''s pid, before the message is sent. NLM_F_DUMP and * NLM_F_ACK will be set in nlmsg_flags. * * This Netlink socket library is designed to ensure that the dump is reliable * and that it will not interfere with other operations on 'sock', including * destroying or sending and receiving messages on 'sock'. One corner case is * not handled: * * - If 'sock' has been used to send a request (e.g. with nl_sock_send()) * whose response has not yet been received (e.g. with nl_sock_recv()). * This is unusual: usually nl_sock_transact() is used to send a message * and receive its reply all in one go. * * This function provides no status indication. An error status for the entire * dump operation is provided when it is completed by calling nl_dump_done(). * * The caller is responsible for destroying 'request'. * * The new 'dump' is independent of 'sock'. 'sock' and 'dump' may be destroyed * in either order. */ void nl_dump_start(struct nl_dump *dump, struct nl_sock *sock, const struct ofpbuf *request) { ofpbuf_init(&dump->buffer, 4096); if (sock->dump) { /* 'sock' already has an ongoing dump. Clone the socket because * Netlink only allows one dump at a time. */ dump->status = nl_sock_clone(sock, &dump->sock); if (dump->status) { return; } } else { sock->dump = dump; dump->sock = sock; dump->status = 0; } nl_msg_nlmsghdr(request)->nlmsg_flags |= NLM_F_DUMP | NLM_F_ACK; dump->status = nl_sock_send__(sock, request, nl_sock_allocate_seq(sock, 1), true); dump->seq = nl_msg_nlmsghdr(request)->nlmsg_seq; }
/* Frees memory that 'b' points to and allocates a new ofpbuf */ void ofpbuf_reinit(struct ofpbuf *b, size_t size) { ofpbuf_uninit(b); ofpbuf_init(b, size); }
static int parse_keys(bool wc_keys) { int exit_code = 0; struct ds in; ds_init(&in); vlog_set_levels_from_string_assert("odp_util:console:dbg"); while (!ds_get_test_line(&in, stdin)) { enum odp_key_fitness fitness; struct ofpbuf odp_key; struct ofpbuf odp_mask; struct flow flow; struct ds out; int error; /* Convert string to OVS DP key. */ ofpbuf_init(&odp_key, 0); ofpbuf_init(&odp_mask, 0); error = odp_flow_from_string(ds_cstr(&in), NULL, &odp_key, &odp_mask); if (error) { printf("odp_flow_from_string: error\n"); goto next; } if (!wc_keys) { /* Convert odp_key to flow. */ fitness = odp_flow_key_to_flow(odp_key.data, odp_key.size, &flow); switch (fitness) { case ODP_FIT_PERFECT: break; case ODP_FIT_TOO_LITTLE: printf("ODP_FIT_TOO_LITTLE: "); break; case ODP_FIT_TOO_MUCH: printf("ODP_FIT_TOO_MUCH: "); break; case ODP_FIT_ERROR: printf("odp_flow_key_to_flow: error\n"); goto next; } /* Convert cls_rule back to odp_key. */ ofpbuf_uninit(&odp_key); ofpbuf_init(&odp_key, 0); odp_flow_key_from_flow(&odp_key, &flow, flow.in_port.odp_port); if (odp_key.size > ODPUTIL_FLOW_KEY_BYTES) { printf ("too long: %zu > %d\n", odp_key.size, ODPUTIL_FLOW_KEY_BYTES); exit_code = 1; } } /* Convert odp_key to string. */ ds_init(&out); if (wc_keys) { odp_flow_format(odp_key.data, odp_key.size, odp_mask.data, odp_mask.size, &out); } else { odp_flow_key_format(odp_key.data, odp_key.size, &out); } puts(ds_cstr(&out)); ds_destroy(&out); next: ofpbuf_uninit(&odp_key); } ds_destroy(&in); return exit_code; }
/* Convert 'str_' (as described in the Flow Syntax section of the ovs-ofctl man * page) into 'fm' for sending the specified flow_mod 'command' to a switch. * If 'actions' is specified, an action must be in 'string' and may be expanded * or reallocated. * * To parse syntax for an OFPT_FLOW_MOD (or NXT_FLOW_MOD), use an OFPFC_* * constant for 'command'. To parse syntax for an OFPST_FLOW or * OFPST_AGGREGATE (or NXST_FLOW or NXST_AGGREGATE), use -1 for 'command'. */ void parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_, bool verbose) { enum { F_OUT_PORT = 1 << 0, F_ACTIONS = 1 << 1, F_TIMEOUT = 1 << 3, F_PRIORITY = 1 << 4, F_FLAGS = 1 << 5, } fields; char *string = xstrdup(str_); char *save_ptr = NULL; char *act_str = NULL; char *name; switch (command) { case -1: fields = F_OUT_PORT; break; case OFPFC_ADD: fields = F_ACTIONS | F_TIMEOUT | F_PRIORITY | F_FLAGS; break; case OFPFC_DELETE: fields = F_OUT_PORT; break; case OFPFC_DELETE_STRICT: fields = F_OUT_PORT | F_PRIORITY; break; case OFPFC_MODIFY: fields = F_ACTIONS | F_TIMEOUT | F_PRIORITY | F_FLAGS; break; case OFPFC_MODIFY_STRICT: fields = F_ACTIONS | F_TIMEOUT | F_PRIORITY | F_FLAGS; break; default: NOT_REACHED(); } match_init_catchall(&fm->match); fm->priority = OFP_DEFAULT_PRIORITY; fm->cookie = htonll(0); fm->cookie_mask = htonll(0); if (command == OFPFC_MODIFY || command == OFPFC_MODIFY_STRICT) { /* For modify, by default, don't update the cookie. */ fm->new_cookie = htonll(UINT64_MAX); } else{ fm->new_cookie = htonll(0); } fm->table_id = 0xff; fm->command = command; fm->idle_timeout = OFP_FLOW_PERMANENT; fm->hard_timeout = OFP_FLOW_PERMANENT; fm->buffer_id = UINT32_MAX; fm->out_port = OFPP_ANY; fm->flags = 0; if (fields & F_ACTIONS) { act_str = strstr(string, "action"); if (!act_str) { ofp_fatal(str_, verbose, "must specify an action"); } *act_str = '\0'; act_str = strchr(act_str + 1, '='); if (!act_str) { ofp_fatal(str_, verbose, "must specify an action"); } act_str++; } for (name = strtok_r(string, "=, \t\r\n", &save_ptr); name; name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) { const struct protocol *p; if (parse_protocol(name, &p)) { match_set_dl_type(&fm->match, htons(p->dl_type)); if (p->nw_proto) { match_set_nw_proto(&fm->match, p->nw_proto); } } else if (fields & F_FLAGS && !strcmp(name, "send_flow_rem")) { fm->flags |= OFPFF_SEND_FLOW_REM; } else if (fields & F_FLAGS && !strcmp(name, "check_overlap")) { fm->flags |= OFPFF_CHECK_OVERLAP; } else if (fields & F_FLAGS && !strcmp(name, "reset_counts")) { fm->flags |= OFPFF12_RESET_COUNTS; } else if (fields & F_FLAGS && !strcmp(name, "no_packet_counts")) { fm->flags |= OFPFF13_NO_PKT_COUNTS; } else if (fields & F_FLAGS && !strcmp(name, "no_byte_counts")) { fm->flags |= OFPFF13_NO_BYT_COUNTS; } else { char *value; value = strtok_r(NULL, ", \t\r\n", &save_ptr); if (!value) { ofp_fatal(str_, verbose, "field %s missing value", name); } if (!strcmp(name, "table")) { fm->table_id = str_to_u8(value, name); } else if (!strcmp(name, "out_port")) { if (!ofputil_port_from_string(value, &fm->out_port)) { ofp_fatal(str_, verbose, "%s is not a valid OpenFlow port", name); } } else if (fields & F_PRIORITY && !strcmp(name, "priority")) { fm->priority = str_to_u16(value, name); } else if (fields & F_TIMEOUT && !strcmp(name, "idle_timeout")) { fm->idle_timeout = str_to_u16(value, name); } else if (fields & F_TIMEOUT && !strcmp(name, "hard_timeout")) { fm->hard_timeout = str_to_u16(value, name); } else if (!strcmp(name, "cookie")) { char *mask = strchr(value, '/'); if (mask) { /* A mask means we're searching for a cookie. */ if (command == OFPFC_ADD) { ofp_fatal(str_, verbose, "flow additions cannot use " "a cookie mask"); } *mask = '\0'; fm->cookie = htonll(str_to_u64(value)); fm->cookie_mask = htonll(str_to_u64(mask+1)); } else { /* No mask means that the cookie is being set. */ if (command != OFPFC_ADD && command != OFPFC_MODIFY && command != OFPFC_MODIFY_STRICT) { ofp_fatal(str_, verbose, "cannot set cookie"); } fm->new_cookie = htonll(str_to_u64(value)); } } else if (mf_from_name(name)) { parse_field(mf_from_name(name), value, &fm->match); } else if (!strcmp(name, "duration") || !strcmp(name, "n_packets") || !strcmp(name, "n_bytes") || !strcmp(name, "idle_age") || !strcmp(name, "hard_age")) { /* Ignore these, so that users can feed the output of * "ovs-ofctl dump-flows" back into commands that parse * flows. */ } else { ofp_fatal(str_, verbose, "unknown keyword %s", name); } } } if (!fm->cookie_mask && fm->new_cookie == htonll(UINT64_MAX) && (command == OFPFC_MODIFY || command == OFPFC_MODIFY_STRICT)) { /* On modifies without a mask, we are supposed to add a flow if * one does not exist. If a cookie wasn't been specified, use a * default of zero. */ fm->new_cookie = htonll(0); } if (fields & F_ACTIONS) { struct ofpbuf ofpacts; enum ofperr err; ofpbuf_init(&ofpacts, 32); str_to_inst_ofpacts(act_str, &ofpacts); fm->ofpacts_len = ofpacts.size; fm->ofpacts = ofpbuf_steal_data(&ofpacts); err = ofpacts_check(fm->ofpacts, fm->ofpacts_len, &fm->match.flow, OFPP_MAX, 0); if (err) { exit(EXIT_FAILURE); } } else { fm->ofpacts_len = 0; fm->ofpacts = NULL; } free(string); }
/* Convert 'str_' (as described in the Flow Syntax section of the ovs-ofctl man * page) into 'mm' for sending the specified meter_mod 'command' to a switch. */ void parse_ofp_meter_mod_str(struct ofputil_meter_mod *mm, const char *str_, int command, bool verbose) { enum { F_METER = 1 << 0, F_FLAGS = 1 << 1, F_BANDS = 1 << 2, } fields; char *string = xstrdup(str_); char *save_ptr = NULL; char *band_str = NULL; char *name; switch (command) { case -1: fields = F_METER; break; case OFPMC13_ADD: fields = F_METER | F_FLAGS | F_BANDS; break; case OFPMC13_DELETE: fields = F_METER; break; case OFPMC13_MODIFY: fields = F_METER | F_FLAGS | F_BANDS; break; default: NOT_REACHED(); } mm->command = command; mm->meter.meter_id = 0; mm->meter.flags = 0; if (fields & F_BANDS) { band_str = strstr(string, "band"); if (!band_str) { ofp_fatal(str_, verbose, "must specify bands"); } *band_str = '\0'; band_str = strchr(band_str + 1, '='); if (!band_str) { ofp_fatal(str_, verbose, "must specify bands"); } band_str++; } for (name = strtok_r(string, "=, \t\r\n", &save_ptr); name; name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) { if (fields & F_FLAGS && !strcmp(name, "kbps")) { mm->meter.flags |= OFPMF13_KBPS; } else if (fields & F_FLAGS && !strcmp(name, "pktps")) { mm->meter.flags |= OFPMF13_PKTPS; } else if (fields & F_FLAGS && !strcmp(name, "burst")) { mm->meter.flags |= OFPMF13_BURST; } else if (fields & F_FLAGS && !strcmp(name, "stats")) { mm->meter.flags |= OFPMF13_STATS; } else { char *value; value = strtok_r(NULL, ", \t\r\n", &save_ptr); if (!value) { ofp_fatal(str_, verbose, "field %s missing value", name); } if (!strcmp(name, "meter")) { if (!strcmp(value, "all")) { mm->meter.meter_id = OFPM13_ALL; } else if (!strcmp(value, "controller")) { mm->meter.meter_id = OFPM13_CONTROLLER; } else if (!strcmp(value, "slowpath")) { mm->meter.meter_id = OFPM13_SLOWPATH; } else { mm->meter.meter_id = str_to_u32(value); if (mm->meter.meter_id > OFPM13_MAX) { ofp_fatal(str_, verbose, "invalid value for %s", name); } } } else { ofp_fatal(str_, verbose, "unknown keyword %s", name); } } } if (fields & F_METER && !mm->meter.meter_id) { ofp_fatal(str_, verbose, "must specify 'meter'"); } if (fields & F_FLAGS && !mm->meter.flags) { ofp_fatal(str_, verbose, "meter must specify either 'kbps' or 'pktps'"); } if (fields & F_BANDS) { struct ofpbuf bands; uint16_t n_bands = 0; struct ofputil_meter_band *band = NULL; int i; ofpbuf_init(&bands, 64); for (name = strtok_r(band_str, "=, \t\r\n", &save_ptr); name; name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) { char *value; value = strtok_r(NULL, ", \t\r\n", &save_ptr); if (!value) { ofp_fatal(str_, verbose, "field %s missing value", name); } if (!strcmp(name, "type")) { /* Start a new band */ band = ofpbuf_put_zeros(&bands, sizeof *band); n_bands++; if (!strcmp(value, "drop")) { band->type = OFPMBT13_DROP; } else if (!strcmp(value, "dscp_remark")) { band->type = OFPMBT13_DSCP_REMARK; } else { ofp_fatal(str_, verbose, "field %s unknown value %s", name, value); } } else if (!band || !band->type) { ofp_fatal(str_, verbose, "band must start with the 'type' keyword"); } else if (!strcmp(name, "rate")) { band->rate = str_to_u32(value); } else if (!strcmp(name, "burst_size")) { band->burst_size = str_to_u32(value); } else if (!strcmp(name, "prec_level")) { band->prec_level = str_to_u8(value, name); } else { ofp_fatal(str_, verbose, "unknown keyword %s", name); } } /* validate bands */ if (!n_bands) { ofp_fatal(str_, verbose, "meter must have bands"); } mm->meter.n_bands = n_bands; mm->meter.bands = ofpbuf_steal_data(&bands); for (i = 0; i < n_bands; ++i) { band = &mm->meter.bands[i]; if (!band->type) { ofp_fatal(str_, verbose, "band must have 'type'"); } if (band->type == OFPMBT13_DSCP_REMARK) { if (!band->prec_level) { ofp_fatal(str_, verbose, "'dscp_remark' band must have" " 'prec_level'"); } } else { if (band->prec_level) { ofp_fatal(str_, verbose, "Only 'dscp_remark' band may have" " 'prec_level'"); } } if (!band->rate) { ofp_fatal(str_, verbose, "band must have 'rate'"); } if (mm->meter.flags & OFPMF13_BURST) { if (!band->burst_size) { ofp_fatal(str_, verbose, "band must have 'burst_size' " "when 'burst' flag is set"); } } else { if (band->burst_size) { ofp_fatal(str_, verbose, "band may have 'burst_size' only " "when 'burst' flag is set"); } } } } else { mm->meter.n_bands = 0; mm->meter.bands = NULL; } free(string); }
static int parse_filter(char *filter_parse) { struct ds in; struct flow flow_filter; struct flow_wildcards wc_filter; char *error, *filter = NULL; vlog_set_levels_from_string_assert("odp_util:console:dbg"); if (filter_parse && !strncmp(filter_parse, "filter=", 7)) { filter = xstrdup(filter_parse + 7); memset(&flow_filter, 0, sizeof(flow_filter)); memset(&wc_filter, 0, sizeof(wc_filter)); error = parse_ofp_exact_flow(&flow_filter, &wc_filter, NULL, filter, NULL); if (error) { ovs_fatal(0, "Failed to parse filter (%s)", error); } } else { ovs_fatal(0, "No filter to parse."); } ds_init(&in); while (!ds_get_test_line(&in, stdin)) { struct ofpbuf odp_key; struct ofpbuf odp_mask; struct ds out; /* Convert string to OVS DP key. */ ofpbuf_init(&odp_key, 0); ofpbuf_init(&odp_mask, 0); if (odp_flow_from_string(ds_cstr(&in), NULL, &odp_key, &odp_mask)) { printf("odp_flow_from_string: error\n"); goto next; } if (filter) { struct flow flow; struct flow_wildcards wc; struct match match, match_filter; struct minimatch minimatch; odp_flow_key_to_flow(odp_key.data, odp_key.size, &flow); odp_flow_key_to_mask(odp_mask.data, odp_mask.size, &wc, &flow); match_init(&match, &flow, &wc); match_init(&match_filter, &flow_filter, &wc); match_init(&match_filter, &match_filter.flow, &wc_filter); minimatch_init(&minimatch, &match_filter); if (!minimatch_matches_flow(&minimatch, &match.flow)) { minimatch_destroy(&minimatch); goto next; } minimatch_destroy(&minimatch); } /* Convert odp_key to string. */ ds_init(&out); odp_flow_format(odp_key.data, odp_key.size, odp_mask.data, odp_mask.size, NULL, &out, false); puts(ds_cstr(&out)); ds_destroy(&out); next: ofpbuf_uninit(&odp_key); ofpbuf_uninit(&odp_mask); } ds_destroy(&in); free(filter); return 0; }
int main(void) { int exit_code = 0; struct ds in; ds_init(&in); vlog_set_levels_from_string("odp_util:console:dbg"); while (!ds_get_line(&in, stdin)) { enum odp_key_fitness fitness; struct ofpbuf odp_key; struct flow flow; struct ds out; int error; char *s; /* Delete comments, skip blank lines. */ s = ds_cstr(&in); if (*s == '#') { puts(s); continue; } if (strchr(s, '#')) { *strchr(s, '#') = '\0'; } if (s[strspn(s, " ")] == '\0') { putchar('\n'); continue; } /* Convert string to OVS DP key. */ ofpbuf_init(&odp_key, 0); error = odp_flow_key_from_string(ds_cstr(&in), NULL, &odp_key); if (error) { printf("odp_flow_key_from_string: error\n"); goto next; } /* Convert odp_key to flow. */ fitness = odp_flow_key_to_flow(odp_key.data, odp_key.size, &flow); switch (fitness) { case ODP_FIT_PERFECT: break; case ODP_FIT_TOO_LITTLE: printf("ODP_FIT_TOO_LITTLE: "); break; case ODP_FIT_TOO_MUCH: printf("ODP_FIT_TOO_MUCH: "); break; case ODP_FIT_ERROR: printf("odp_flow_key_to_flow: error\n"); goto next; } /* Convert cls_rule back to odp_key. */ ofpbuf_uninit(&odp_key); ofpbuf_init(&odp_key, 0); odp_flow_key_from_flow(&odp_key, &flow); if (odp_key.size > ODPUTIL_FLOW_KEY_BYTES) { printf ("too long: %zu > %d\n", odp_key.size, ODPUTIL_FLOW_KEY_BYTES); exit_code = 1; } /* Convert odp_key to string. */ ds_init(&out); odp_flow_key_format(odp_key.data, odp_key.size, &out); puts(ds_cstr(&out)); ds_destroy(&out); next: ofpbuf_uninit(&odp_key); } ds_destroy(&in); return exit_code; }
int main(int argc, char *argv[]) { struct unixctl_server *server; enum { MAX_RECV = 1500 }; const char *target; struct ofpbuf buf; bool exiting = false; int error; int sock; int n; proctitle_init(argc, argv); set_program_name(argv[0]); parse_options(argc, argv); if (argc - optind != 1) { ovs_fatal(0, "exactly one non-option argument required " "(use --help for help)"); } target = argv[optind]; sock = inet_open_passive(SOCK_DGRAM, target, 0, NULL, 0); if (sock < 0) { ovs_fatal(0, "%s: failed to open (%s)", argv[1], strerror(-sock)); } daemon_save_fd(STDOUT_FILENO); daemonize_start(); error = unixctl_server_create(NULL, &server); if (error) { ovs_fatal(error, "failed to create unixctl server"); } unixctl_command_register("exit", "", 0, 0, test_netflow_exit, &exiting); daemonize_complete(); ofpbuf_init(&buf, MAX_RECV); n = 0; for (;;) { int retval; unixctl_server_run(server); ofpbuf_clear(&buf); do { retval = read(sock, buf.data, buf.allocated); } while (retval < 0 && errno == EINTR); if (retval > 0) { ofpbuf_put_uninit(&buf, retval); if (n++ > 0) { putchar('\n'); } print_netflow(&buf); fflush(stdout); } if (exiting) { break; } poll_fd_wait(sock, POLLIN); unixctl_server_wait(server); poll_block(); } return 0; }
void learn_execute(const struct nx_action_learn *learn, const struct flow *flow, struct ofputil_flow_mod *fm) { const void *p, *end; struct ofpbuf actions; cls_rule_init_catchall(&fm->cr, ntohs(learn->priority)); fm->cookie = htonll(0); fm->cookie_mask = htonll(0); fm->new_cookie = learn->cookie; fm->table_id = learn->table_id; fm->command = OFPFC_MODIFY_STRICT; fm->idle_timeout = ntohs(learn->idle_timeout); fm->hard_timeout = ntohs(learn->hard_timeout); fm->buffer_id = UINT32_MAX; fm->out_port = OFPP_NONE; fm->flags = ntohs(learn->flags) & OFPFF_SEND_FLOW_REM; fm->actions = NULL; fm->n_actions = 0; ofpbuf_init(&actions, 64); if (learn->fin_idle_timeout || learn->fin_hard_timeout) { struct nx_action_fin_timeout *naft; naft = ofputil_put_NXAST_FIN_TIMEOUT(&actions); naft->fin_idle_timeout = learn->fin_idle_timeout; naft->fin_hard_timeout = learn->fin_hard_timeout; } for (p = learn + 1, end = (char *) learn + ntohs(learn->len); p != end; ) { uint16_t header = ntohs(get_be16(&p)); int n_bits = header & NX_LEARN_N_BITS_MASK; int src_type = header & NX_LEARN_SRC_MASK; int dst_type = header & NX_LEARN_DST_MASK; union mf_subvalue value; struct mf_subfield dst; int chunk, ofs; if (!header) { break; } if (src_type == NX_LEARN_SRC_FIELD) { struct mf_subfield src; get_subfield(n_bits, &p, &src); mf_read_subfield(&src, flow, &value); } else { int p_bytes = 2 * DIV_ROUND_UP(n_bits, 16); memset(&value, 0, sizeof value); bitwise_copy(p, p_bytes, 0, &value, sizeof value, 0, n_bits); p = (const uint8_t *) p + p_bytes; } switch (dst_type) { case NX_LEARN_DST_MATCH: get_subfield(n_bits, &p, &dst); mf_write_subfield(&dst, &value, &fm->cr); break; case NX_LEARN_DST_LOAD: get_subfield(n_bits, &p, &dst); for (ofs = 0; ofs < n_bits; ofs += chunk) { struct nx_action_reg_load *load; chunk = MIN(n_bits - ofs, 64); load = ofputil_put_NXAST_REG_LOAD(&actions); load->ofs_nbits = nxm_encode_ofs_nbits(dst.ofs + ofs, chunk); load->dst = htonl(dst.field->nxm_header); bitwise_copy(&value, sizeof value, ofs, &load->value, sizeof load->value, 0, chunk); } break; case NX_LEARN_DST_OUTPUT: if (n_bits <= 16 || is_all_zeros(value.u8, sizeof value - 2)) { ofputil_put_OFPAT10_OUTPUT(&actions)->port = value.be16[7]; } break; } } fm->actions = ofpbuf_steal_data(&actions); fm->n_actions = actions.size / sizeof(struct ofp_action_header); }
static int parse_keys(bool wc_keys) { int exit_code = 0; struct ds in; ds_init(&in); vlog_set_levels_from_string_assert("odp_util:console:dbg"); while (!ds_get_test_line(&in, stdin)) { enum odp_key_fitness fitness; struct ofpbuf odp_key; struct ofpbuf odp_mask; struct flow flow; struct ds out; int error; /* Convert string to OVS DP key. */ ofpbuf_init(&odp_key, 0); ofpbuf_init(&odp_mask, 0); error = odp_flow_from_string(ds_cstr(&in), NULL, &odp_key, &odp_mask); if (error) { printf("odp_flow_from_string: error\n"); goto next; } if (!wc_keys) { struct odp_flow_key_parms odp_parms = { .flow = &flow, .recirc = true, }; /* Convert odp_key to flow. */ fitness = odp_flow_key_to_flow(odp_key.data, odp_key.size, &flow); switch (fitness) { case ODP_FIT_PERFECT: break; case ODP_FIT_TOO_LITTLE: printf("ODP_FIT_TOO_LITTLE: "); break; case ODP_FIT_TOO_MUCH: printf("ODP_FIT_TOO_MUCH: "); break; case ODP_FIT_ERROR: printf("odp_flow_key_to_flow: error\n"); goto next; } /* Convert cls_rule back to odp_key. */ ofpbuf_uninit(&odp_key); ofpbuf_init(&odp_key, 0); odp_parms.odp_in_port = flow.in_port.odp_port; odp_flow_key_from_flow(&odp_parms, &odp_key); if (odp_key.size > ODPUTIL_FLOW_KEY_BYTES) { printf ("too long: %"PRIu32" > %d\n", odp_key.size, ODPUTIL_FLOW_KEY_BYTES); exit_code = 1; } } /* Convert odp_key to string. */ ds_init(&out); if (wc_keys) { odp_flow_format(odp_key.data, odp_key.size, odp_mask.data, odp_mask.size, NULL, &out, false); } else { odp_flow_key_format(odp_key.data, odp_key.size, &out); } puts(ds_cstr(&out)); ds_destroy(&out); next: ofpbuf_uninit(&odp_key); } ds_destroy(&in); return exit_code; } static int parse_actions(void) { struct ds in; ds_init(&in); vlog_set_levels_from_string_assert("odp_util:console:dbg"); while (!ds_get_test_line(&in, stdin)) { struct ofpbuf odp_actions; struct ds out; int error; /* Convert string to OVS DP actions. */ ofpbuf_init(&odp_actions, 0); error = odp_actions_from_string(ds_cstr(&in), NULL, &odp_actions); if (error) { printf("odp_actions_from_string: error\n"); goto next; } /* Convert odp_actions back to string. */ ds_init(&out); format_odp_actions(&out, odp_actions.data, odp_actions.size); puts(ds_cstr(&out)); ds_destroy(&out); next: ofpbuf_uninit(&odp_actions); } ds_destroy(&in); return 0; } static int parse_filter(char *filter_parse) { struct ds in; struct flow flow_filter; struct flow_wildcards wc_filter; char *error, *filter = NULL; vlog_set_levels_from_string_assert("odp_util:console:dbg"); if (filter_parse && !strncmp(filter_parse, "filter=", 7)) { filter = xstrdup(filter_parse + 7); memset(&flow_filter, 0, sizeof(flow_filter)); memset(&wc_filter, 0, sizeof(wc_filter)); error = parse_ofp_exact_flow(&flow_filter, &wc_filter.masks, filter, NULL); if (error) { ovs_fatal(0, "Failed to parse filter (%s)", error); } } else { ovs_fatal(0, "No filter to parse."); } ds_init(&in); while (!ds_get_test_line(&in, stdin)) { struct ofpbuf odp_key; struct ofpbuf odp_mask; struct ds out; int error; /* Convert string to OVS DP key. */ ofpbuf_init(&odp_key, 0); ofpbuf_init(&odp_mask, 0); error = odp_flow_from_string(ds_cstr(&in), NULL, &odp_key, &odp_mask); if (error) { printf("odp_flow_from_string: error\n"); goto next; } if (filter) { struct flow flow; struct flow_wildcards wc; struct match match, match_filter; struct minimatch minimatch; odp_flow_key_to_flow(odp_key.data, odp_key.size, &flow); odp_flow_key_to_mask(odp_mask.data, odp_mask.size, &wc.masks, &flow); match_init(&match, &flow, &wc); match_init(&match_filter, &flow_filter, &wc); match_init(&match_filter, &match_filter.flow, &wc_filter); minimatch_init(&minimatch, &match_filter); if (!minimatch_matches_flow(&minimatch, &match.flow)) { minimatch_destroy(&minimatch); goto next; } minimatch_destroy(&minimatch); } /* Convert odp_key to string. */ ds_init(&out); odp_flow_format(odp_key.data, odp_key.size, odp_mask.data, odp_mask.size, NULL, &out, false); puts(ds_cstr(&out)); ds_destroy(&out); next: ofpbuf_uninit(&odp_key); ofpbuf_uninit(&odp_mask); } ds_destroy(&in); free(filter); return 0; } static void test_odp_main(int argc, char *argv[]) { int exit_code = 0; set_program_name(argv[0]); if (argc == 2 &&!strcmp(argv[1], "parse-keys")) { exit_code =parse_keys(false); } else if (argc == 2 &&!strcmp(argv[1], "parse-wc-keys")) { exit_code =parse_keys(true); } else if (argc == 2 && !strcmp(argv[1], "parse-actions")) { exit_code = parse_actions(); } else if (argc == 3 && !strcmp(argv[1], "parse-filter")) { exit_code =parse_filter(argv[2]); } else { ovs_fatal(0, "usage: %s parse-keys | parse-wc-keys | parse-actions", argv[0]); } exit(exit_code); }