/* * Copyright (c) 2016 Cisco and/or its affiliates. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @file * @brief Local TCP/IP stack punt infrastructure. * * Provides a set of VPP nodes together with the relevant APIs and CLI * commands in order to adjust and dispatch packets from the VPP data plane * to the local TCP/IP stack */ #include #include #include #include #include #include #include #include #include #include #include punt_main_t punt_main; char * vnet_punt_get_server_pathname (void) { punt_main_t *pm = &punt_main; return pm->sun_path; } static void punt_client_l4_db_add (ip_address_family_t af, u16 port, u32 index) { punt_main_t *pm = &punt_main; pm->db.clients_by_l4_port = hash_set (pm->db.clients_by_l4_port, punt_client_l4_mk_key (af, port), index); } static u32 punt_client_l4_db_remove (ip_address_family_t af, u16 port) { punt_main_t *pm = &punt_main; u32 key, index = ~0; uword *p; key = punt_client_l4_mk_key (af, port); p = hash_get (pm->db.clients_by_l4_port, key); if (p) index = p[0]; hash_unset (pm->db.clients_by_l4_port, key); return (index); } static void punt_client_ip_proto_db_add (ip_address_family_t af, ip_protocol_t proto, u32 index) { punt_main_t *pm = &punt_main; pm->db.clients_by_ip_proto = hash_set (pm->db.clients_by_ip_proto, punt_client_ip_proto_mk_key (af, proto), index); } static u32 punt_client_ip_proto_db_remove (ip_address_family_t af, ip_protocol_t proto) { punt_main_t *pm = &punt_main; u32 key, index = ~0; uword *p; key = punt_client_ip_proto_mk_key (af, proto); p = hash_get (pm->db.clients_by_ip_proto, key); if (p) index = p[0]; hash_unset (pm->db.clients_by_ip_proto, key); return (index); } static void punt_client_exception_db_add (vlib_punt_reason_t reason, u32 pci) { punt_main_t *pm = &punt_main; vec_validate_init_empty (pm->db.clients_by_exception, reason, ~0); pm->db.clients_by_exception[reason] = pci; } static u32 punt_client_exception_db_remove (vlib_punt_reason_t reason) { punt_main_t *pm = &punt_main; u32 pci = ~0; if (punt_client_exception_get (reason)) { pci = pm->db.clients_by_exception[reason]; pm->db.clients_by_exception[reason] = ~0; } return pci; } static clib_error_t * punt_socket_read_ready (clib_file_t * uf) { vlib_main_t *vm = vlib_get_main (); punt_main_t *pm = &punt_main; /** Schedule the rx node */ vlib_node_set_interrupt_pending (vm, punt_socket_rx_node.index); vec_add1 (pm->ready_fds, uf->file_descriptor); return 0; } static clib_error_t * punt_socket_register_l4 (vlib_main_t * vm, ip_address_family_t af, u8 protocol, u16 port, char *client_pathname) { punt_main_t *pm = &punt_main; punt_client_t *c; if (port == (u16) ~ 0) return clib_error_return (0, "Port number required"); u32 node_index; switch (protocol) { case IP_PROTOCOL_UDP: node_index = (af == AF_IP4 ? udp4_punt_socket_node.index : udp6_punt_socket_node.index); udp_register_dst_port (vm, port, node_index, af == AF_IP4); break; case IP_PROTOCOL_ICMP6: if (af != AF_IP6) return clib_error_return ( 0, "only UDP or ICMP6 protocol (%d, %d) is supported, got %d", IP_PROTOCOL_UDP, IP_PROTOCOL_ICMP6, protocol); node_index = icmp6_punt_socket_node.index; icmp6_register_type (vm, port, node_index); break; default: return clib_error_return ( 0, "only UDP or ICMP6 protocol (%d) is supported, got %d", IP_PROTOCOL_UDP, protocol); } c = punt_client_l4_get (af, port); if (NULL == c) { pool_get_zero (pm->punt_client_pool, c); punt_client_l4_db_add (af, port, c - pm->punt_client_pool); } snprintf (c->caddr.sun_path, sizeof (c->caddr.sun_path), "%s", client_pathname); c->caddr.sun_family = AF_UNIX; c->reg.type = PUNT_TYPE_L4; c->reg.punt.l4.port = port; c->reg.punt.l4.protocol = protocol; c->reg.punt.l4.af = af; return (NULL); } static clib_error_t * punt_socket_register_ip_proto (vlib_main_t * vm, ip_address_family_t af, ip_protocol_t proto, char *client_pathname) { punt_main_t *pm = &punt_main; punt_client_t *c; c = punt_client_ip_proto_get (af, proto); if (NULL == c) { pool_get_zero (pm->punt_client_pool, c); punt_client_ip_proto_db_add (af, proto, c - pm->punt_client_pool); } snprintf (c->caddr.sun_path, sizeof (c->caddr.sun_path), "%s", client_pathname); c->caddr.sun_family = AF_UNIX; c->reg.type = PUNT_TYPE_IP_PROTO; c->reg.punt.ip_proto.protocol = proto; c->reg.punt.ip_proto.af = af; if (af == AF_IP4) ip4_register_protocol (proto, ip4_proto_punt_socket_node.index); else ip6_register_protocol (proto, ip6_proto_punt_socket_node.index); return (NULL); } static clib_error_t * punt_socket_register_exception (vlib_main_t * vm, vlib_punt_reason_t reason, char *client_pathname) { punt_main_t *pm = &punt_main; punt_client_t *pc; pc = punt_client_exception_get (reason); if (NULL == pc) { pool_get_zero (pm->punt_client_pool, pc); punt_client_exception_db_add (reason, pc - pm->punt_client_pool); } snprintf (pc->caddr.sun_path, sizeof (pc->caddr.sun_path), "%s", client_pathname); pc->caddr.sun_family = AF_UNIX; pc->reg.type = PUNT_TYPE_EXCEPTION; pc->reg.punt.exception.reason = reason; vlib_punt_register (pm->hdl, pc->reg.punt.exception.reason, "exception-punt-socket"); return (NULL); } static clib_error_t * punt_socket_unregister_l4 (ip_address_family_t af, ip_protocol_t protocol, u16 port) { u32 pci; udp_unregister_dst_port (vlib_get_main (), port, af == AF_IP4); pci = punt_client_l4_db_remove (af, port); if (~0 != pci) pool_put_index (punt_main.punt_client_pool, pci); return (NULL); } static clib_error_t * punt_socket_unregister_ip_proto (ip_address_family_t af, ip_protocol_t proto) { u32 pci; if (af == AF_IP4) ip4_unregister_protocol (proto); else ip6_unregister_protocol (proto); pci = punt_client_ip_proto_db_remove (af, proto); if (~0 != pci) pool_put_index (punt_main.punt_client_pool, pci); return (NULL); } static clib_error_t * punt_socket_unregister_exception (vlib_punt_reason_t reason) { u32 pci; pci = punt_client_exception_db_remove (reason); if (~0 != pci) pool_put_index (punt_main.punt_client_pool, pci); return (NULL); } clib_error_t * vnet_punt_socket_add (vlib_main_t * vm, u32 header_version, const punt_reg_t * pr, char *client_pathname) { punt_main_t *pm = &punt_main; if (!pm->is_configured) return clib_error_return (0, "socket is not configured"); if (header_version != PUNT_PACKETDESC_VERSION) return clib_error_return (0, "Invalid packet descriptor version"); if (strncmp (client_pathname, vnet_punt_get_server_pathname (), UNIX_PATH_MAX) == 0) return clib_error_return (0, "Punt socket: Invalid client path: %s", client_pathname); /* Register client */ switch (pr->type) { case PUNT_TYPE_L4: return (punt_socket_register_l4 (vm, pr->punt.l4.af, pr->punt.l4.protocol, pr->punt.l4.port, client_pathname)); case PUNT_TYPE_IP_PROTO: return (punt_socket_register_ip_proto (vm, pr->punt.ip_proto.af, pr->punt.ip_proto.protocol, client_pathname)); case PUNT_TYPE_EXCEPTION: return (punt_socket_register_exception (vm, pr->punt.exception.reason, client_pathname)); } return 0; } clib_error_t * vnet_punt_socket_del (vlib_main_t * vm, const punt_reg_t * pr) { punt_main_t *pm = &punt_main; if (!pm->is_configured) return clib_error_return (0, "socket is not configured"); switch (pr->type) { case PUNT_TYPE_L4: return (punt_socket_unregister_l4 (pr->punt.l4.af, pr->punt.l4.protocol, pr->punt.l4.port)); case PUNT_TYPE_IP_PROTO: return (punt_socket_unregister_ip_proto (pr->punt.ip_proto.af, pr->punt.ip_proto.protocol)); case PUNT_TYPE_EXCEPTION: return (punt_socket_unregister_exception (pr->punt.exception.reason)); } return 0; } /** * @brief Request IP L4 traffic punt to the local TCP/IP stack. * * @em Note * - UDP is the only protocol supported in the current implementation * * @param vm vlib_main_t corresponding to the current thread * @param af IP address family. * @param protocol 8-bits L4 protocol value * UDP is 17 * TCP is 1 * @param port 16-bits L4 (TCP/IP) port number when applicable (UDP only) * * @returns 0 on success, non-zero value otherwise */ static clib_error_t * punt_l4_add_del (vlib_main_t * vm, ip_address_family_t af, ip_protocol_t protocol, u16 port, bool is_add) { int is_ip4 = af == AF_IP4; /* For now we only support TCP and UDP punt */ if (protocol != IP_PROTOCOL_UDP && protocol != IP_PROTOCOL_TCP) return clib_error_return (0, "only UDP (%d) and TCP (%d) protocols are supported, got %d", IP_PROTOCOL_UDP, IP_PROTOCOL_TCP, protocol); if (port == (u16) ~ 0) { if (protocol == IP_PROTOCOL_UDP) udp_punt_unknown (vm, is_ip4, is_add); else if (protocol == IP_PROTOCOL_TCP) tcp_punt_unknown (vm, is_ip4, is_add); return 0; } else if (is_add) { const vlib_node_registration_t *punt_node = is_ip4 ? &udp4_punt_node : &udp6_punt_node; if (protocol == IP_PROTOCOL_TCP) return clib_error_return (0, "punt TCP ports is not supported yet"); udp_register_dst_port (vm, port, punt_node->index, is_ip4); return 0; } else { if (protocol == IP_PROTOCOL_TCP) return clib_error_return (0, "punt TCP ports is not supported yet"); udp_unregister_dst_port (vm, port, is_ip4); return 0; } } /** * @brief Request exception traffic punt. * * @param reason Punting reason * * @returns 0 on success, non-zero value otherwise */ static clib_error_t * punt_exception_add_del (vlib_punt_reason_t reason, bool is_add) { punt_main_t *pm = &punt_main; int rv = 0; vnet_punt_reason_flag_t flag = vlib_punt_reason_get_flags (reason); const char *node_name = vnet_punt_reason_flag_is_IP6_PACKET (flag) ? "ip6-punt" : "ip4-punt"; if (is_add) rv = vlib_punt_register (pm->hdl, reason, node_name); else rv = vlib_punt_unregister (pm->hdl, reason, node_name); if (!rv) return 0; else return clib_error_return (0, is_add ? "Existing punting registration..." : "Punting registration not found..."); } clib_error_t * vnet_punt_add_del (vlib_main_t * vm, const punt_reg_t * pr, bool is_add) { switch (pr->type) { case PUNT_TYPE_L4: return (punt_l4_add_del (vm, pr->punt.l4.af, pr->punt.l4.protocol, pr->punt.l4.port, is_add)); case PUNT_TYPE_EXCEPTION: return punt_exception_add_del (pr->punt.exception.reason, is_add); case PUNT_TYPE_IP_PROTO: break; } return (clib_error_return (0, "Unsupported punt type: %d", pr->type)); } static clib_error_t * punt_cli (vlib_main_t * vm, unformat_input_t * input__, vlib_cli_command_t * cmd) { unformat_input_t line_input, *input = &line_input; clib_error_t *error = NULL; bool is_add = true; punt_reg_t pr = { .punt = { .l4 = { .af = AF_IP4, .port = ~0, .protocol = IP_PROTOCOL_UDP, }, }, .type = PUNT_TYPE_L4, }; u32 port; if (!unformat_user (input__, unformat_line_input, input)) return 0; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { if (unformat (input, "del")) is_add = false; else if (unformat (input, "reason %U", unformat_punt_reason, &pr.punt.exception.reason)) pr.type = PUNT_TYPE_EXCEPTION; else if (unformat (input, "ipv4")) pr.punt.l4.af = AF_IP4; else if (unformat (input, "ipv6")) pr.punt.l4.af = AF_IP6; else if (unformat (input, "ip6")) pr.punt.l4.af = AF_IP6; else if (unformat (input, "%d", &port)) pr.punt.l4.port = port; else if (unformat (input, "all")) pr.punt.l4.port = ~0; else if (unformat (input, "udp")) pr.punt.l4.protocol = IP_PROTOCOL_UDP; else if (unformat (input, "tcp")) pr.punt.l4.protocol = IP_PROTOCOL_TCP; else { error = clib_error_return (0, "parse error: '%U'", format_unformat_error, input); goto done; } } /* punt both IPv6 and IPv4 when used in CLI */ error = vnet_punt_add_del (vm, &pr, is_add); if (error) { clib_error_report (error); } done: unformat_free (input); return error; } /*? * The set of 'set punt' commands allows specific IP traffic to * be punted to the host TCP/IP stack * * @em Note * - UDP is the only protocol supported in the current implementation * - All TCP traffic is currently punted to the host by default * * @cliexpar * @parblock * Example of how to request NTP traffic to be punted * @cliexcmd{set punt udp 125} * * Example of how to request all 'unknown' UDP traffic to be punted * @cliexcmd{set punt udp all} * * Example of how to stop all 'unknown' UDP traffic to be punted * @cliexcmd{set punt udp del all} * @endparblock ?*/ VLIB_CLI_COMMAND (punt_command, static) = { .path = "set punt", .short_help = "set punt [IPV4|ip6|ipv6] [UDP|tcp] [del] [ALL|]", .function = punt_cli, }; static clib_error_t * punt_socket_register_cmd (vlib_main_t * vm, unformat_input_t * input__, vlib_cli_command_t * cmd) { unformat_input_t line_input, *input = &line_input; u8 *socket_name = 0; clib_error_t *error = NULL; punt_reg_t pr = { .punt = { .l4 = { .af = AF_IP4, .port = ~0, .protocol = IP_PROTOCOL_UDP, }, }, .type = PUNT_TYPE_L4, }; if (!unformat_user (input__, unformat_line_input, input)) return 0; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { if (unformat (input, "ipv4")) pr.punt.l4.af = AF_IP4; else if (unformat (input, "ipv6")) pr.punt.l4.af = AF_IP6; else if (unformat (input, "udp")) pr.punt.l4.protocol = IP_PROTOCOL_UDP; else if (unformat (input, "tcp")) pr.punt.l4.protocol = IP_PROTOCOL_TCP; else if (unformat (input, "%d", &pr.punt.l4.port)) ; else if (unformat (input, "all")) pr.punt.l4.port = ~0; else if (unformat (input, "socket %s", &socket_name)) ; else if (unformat (input, "reason %U", unformat_punt_reason, &pr.punt.exception.reason)) pr.type = PUNT_TYPE_EXCEPTION; else { error = clib_error_return (0, "parse error: '%U'", format_unformat_error, input); goto done; } } if (!socket_name) error = clib_error_return (0, "socket name not specified"); else error = vnet_punt_socket_add (vm, 1, &pr, (char *) socket_name); done: unformat_free (input); return error; } /*? * * @cliexpar * @cliexcmd{punt socket register socket punt_l4_foo.sock} ?*/ VLIB_CLI_COMMAND (punt_socket_register_command, static) = { .path = "punt socket register", .function = punt_socket_register_cmd, .short_help = "punt socket register [IPV4|ipv6] [UDP|tcp] [ALL|] socket ", .is_mp_safe = 1, }; static clib_error_t * punt_socket_deregister_cmd (vlib_main_t * vm, unformat_input_t * input__, vlib_cli_command_t * cmd) { unformat_input_t line_input, *input = &line_input; clib_error_t *error = NULL; punt_reg_t pr = { .punt = { .l4 = { .af = AF_IP4, .port = ~0, .protocol = IP_PROTOCOL_UDP, }, }, .type = PUNT_TYPE_L4, }; if (!unformat_user (input__, unformat_line_input, input)) return 0; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { if (unformat (input, "ipv4")) pr.punt.l4.af = AF_IP4; else if (unformat (input, "ipv6")) pr.punt.l4.af = AF_IP6; else if (unformat (input, "udp")) pr.punt.l4.protocol = IP_PROTOCOL_UDP; else if (unformat (input, "tcp")) pr.punt.l4.protocol = IP_PROTOCOL_TCP; else if (unformat (input, "%d", &pr.punt.l4.port)) ; else if (unformat (input, "all")) pr.punt.l4.port = ~0; else if (unformat (input, "reason %U", unformat_punt_reason, &pr.punt.exception.reason)) pr.type = PUNT_TYPE_EXCEPTION; else { error = clib_error_return (0, "parse error: '%U'", format_unformat_error, input); goto done; } } error = vnet_punt_socket_del (vm, &pr); done: unformat_free (input); return error; } /*? * * @cliexpar * @cliexcmd{punt socket register} ?*/ VLIB_CLI_COMMAND (punt_socket_deregister_command, static) = { .path = "punt socket deregister", .function = punt_socket_deregister_cmd, .short_help = "punt socket deregister [IPV4|ipv6] [UDP|tcp] [ALL|]", .is_mp_safe = 1, }; void punt_client_walk (punt_type_t pt, punt_client_walk_cb_t cb, void *ctx) { punt_main_t *pm = &punt_main; switch (pt) { case PUNT_TYPE_L4: { u32 pci, key; hash_foreach(key, pci, pm->db.clients_by_l4_port, ({ cb (pool_elt_at_index(pm->punt_client_pool, pci), ctx); })); break; } case PUNT_TYPE_IP_PROTO: { u32 pci, key; hash_foreach(key, pci, pm->db.clients_by_ip_proto, ({ cb (pool_elt_at_index(pm->punt_client_pool, pci), ctx); })); break; } case PUNT_TYPE_EXCEPTION: { u32 *pci; vec_foreach (pci, pm->db.clients_by_exception) { if (~0 != *pci) cb (pool_elt_at_index (pm->punt_client_pool, *pci), ctx); } break; } } } static u8 * format_punt_client (u8 * s, va_list * args) { punt_client_t *pc = va_arg (*args, punt_client_t *); s = format (s, " punt "); switch (pc->reg.type) { case PUNT_TYPE_L4: s = format (s, "%U %U port %d", format_ip_address_family, pc->reg.punt.l4.af, format_ip_protocol, pc->reg.punt.l4.protocol, pc->reg.punt.l4.port); break; case PUNT_TYPE_IP_PROTO: s = format (s, "%U %U", format_ip_address_family, pc->reg.punt.ip_proto.af, format_ip_protocol, pc->reg.punt.ip_proto.protocol); break; case PUNT_TYPE_EXCEPTION: s = format (s, " %U", format_vlib_punt_reason, pc->reg.punt.exception.reason); break; } s = format (s, " to socket %s \n", pc->caddr.sun_path); return (s); } static walk_rc_t punt_client_show_one (const punt_client_t * pc, void *ctx) { vlib_cli_output (ctx, "%U", format_punt_client, pc); return (WALK_CONTINUE); } static clib_error_t * punt_socket_show_cmd (vlib_main_t * vm, unformat_input_t * input__, vlib_cli_command_t * cmd) { unformat_input_t line_input, *input = &line_input; clib_error_t *error = NULL; punt_type_t pt; pt = PUNT_TYPE_L4; if (!unformat_user (input__, unformat_line_input, input)) return 0; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { if (unformat (input, "exception")) pt = PUNT_TYPE_EXCEPTION; else if (unformat (input, "l4")) pt = PUNT_TYPE_L4; else if (unformat (input, "ip")) pt = PUNT_TYPE_IP_PROTO; else { error = clib_error_return (0, "parse error: '%U'", format_unformat_error, input); goto done; } } punt_client_walk (pt, punt_client_show_one, vm); done: unformat_free (input); return (error); } /*? * * @cliexpar * @cliexcmd{show punt socket ipv4} ?*/ VLIB_CLI_COMMAND (show_punt_socket_registration_command, static) = { .path = "show punt socket registrations", .function = punt_socket_show_cmd, .short_help = "show punt socket registrations [l4|exception]", .is_mp_safe = 1, }; clib_error_t * ip_punt_init (vlib_main_t * vm) { clib_error_t *error = NULL; punt_main_t *pm = &punt_main; vlib_thread_main_t *tm = vlib_get_thread_main (); pm->is_configured = false; pm->interface_output_node = vlib_get_node_by_name (vm, (u8 *) "interface-output"); if ((error = vlib_call_init_function (vm, punt_init))) return error; pm->hdl = vlib_punt_client_register ("ip-punt"); vec_validate_aligned (pm->thread_data, tm->n_vlib_mains, CLIB_CACHE_LINE_BYTES); return (error); } u8 * format_vnet_punt_reason_flags (u8 *s, va_list *args) { vnet_punt_reason_flag_t flag = va_arg (*args, int); #define _(pos, len, value, name, str) \ if (vnet_punt_reason_flag_is_##name (flag)) \ s = format (s, "%s ", str); foreach_vnet_punt_reason_flag #undef _ return (s); } VLIB_INIT_FUNCTION (ip_punt_init); static clib_error_t * punt_config (vlib_main_t * vm, unformat_input_t * input) { punt_main_t *pm = &punt_main; char *socket_path = 0; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { if (unformat (input, "socket %s", &socket_path)) strncpy (pm->sun_path, socket_path, UNIX_PATH_MAX - 1); else return clib_error_return (0, "unknown input `%U'", format_unformat_error, input); } if (socket_path == 0) return 0; /* UNIX domain socket */ struct sockaddr_un addr; if ((pm->socket_fd = socket (AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)) == -1) { return clib_error_return (0, "socket error"); } clib_memset (&addr, 0, sizeof (addr)); addr.sun_family = AF_UNIX; if (*socket_path == '\0') { *addr.sun_path = '\0'; strncpy (addr.sun_path + 1, socket_path + 1, sizeof (addr.sun_path) - 2); } else { strncpy (addr.sun_path, socket_path, sizeof (addr.sun_path) - 1); unlink (socket_path); } if (bind (pm->socket_fd, (struct sockaddr *) &addr, sizeof (addr)) == -1) { return clib_error_return (0, "bind error"); } int n_bytes = 0x10000; if (setsockopt (pm->socket_fd, SOL_SOCKET, SO_SNDBUF, &n_bytes, sizeof (n_bytes)) == -1) { return clib_error_return (0, "setsockopt error"); } /* Register socket */ clib_file_main_t *fm = &file_main; clib_file_t template = { 0 }; template.read_function = punt_socket_read_ready; template.file_descriptor = pm->socket_fd; template.description = format (0, "punt socket %s", socket_path); pm->clib_file_index = clib_file_add (fm, &template); pm->is_configured = true; return 0; } VLIB_CONFIG_FUNCTION (punt_config, "punt"); /* * fd.io coding-style-patch-verification: ON * * Local Variables: * eval: (c-set-style "gnu") * End: */