From: Alan T. DeKok Date: Mon, 4 May 2020 18:41:02 +0000 (-0400) Subject: first draft of renewed "proto_arp" X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=eb247c59570a6b6e6da3c231e408885e36601b8a;p=thirdparty%2Ffreeradius-server.git first draft of renewed "proto_arp" It still needs proto_arp_ethernet to open the socket. and proto_arp_process to process the packets --- diff --git a/src/modules/proto_arp/proto_arp.c b/src/modules/proto_arp/proto_arp.c index 097a9de14e3..fdea33bb851 100644 --- a/src/modules/proto_arp/proto_arp.c +++ b/src/modules/proto_arp/proto_arp.c @@ -1,8 +1,4 @@ /* - * arp.c ARP processing. - * - * Version: $Id$ - * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -16,323 +12,390 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA - * - * @copyright 2013 Network RADIUS SARL (legal@networkradius.com) */ +/** + * $Id$ + * @file proto_arp.c + * @brief RADIUS master protocol handler. + * + * @copyright 2017 Arran Cudbard-Bell (a.cudbardb@freeradius.org) + * @copyright 2016 Alan DeKok (aland@freeradius.org) + */ #include -#include +#include +#include #include #include #include -#include -#include -typedef struct { - listen_socket_t lsock; - uint64_t counter; - RADCLIENT client; -} arp_socket_t; +extern fr_app_t proto_arp; -/* - * ARP for ethernet && IPv4. - */ typedef struct { - uint16_t htype; //!< Format of hardware address. - uint16_t ptype; //!< Format of protocol address. - uint8_t hlen; //!< Length of hardware address. - uint8_t plen; //!< Length of protocol address. - uint8_t op; //!< 1 - Request, 2 - Reply. - uint8_t sha[ETHER_ADDR_LEN]; //!< sender hardware address. - uint8_t spa[4]; //!< Sender protocol address. - uint8_t tha[ETHER_ADDR_LEN]; //!< Target hardware address. - uint8_t tpa[4]; //!< Target protocol address. -} arp_over_ether_t; + CONF_SECTION *server_cs; //!< server CS for this listener + CONF_SECTION *cs; //!< my configuration -static fr_dict_t const *dict_arp; + dl_module_inst_t *io_submodule; //!< As provided by the transport_parse + ///< callback. Broken out into the + ///< app_io_* fields below for convenience. -extern fr_dict_autoload_t proto_arp_dict[]; -fr_dict_autoload_t proto_arp_dict[] = { - { .out = &dict_arp, .proto = "arp" }, - { NULL } -}; + CONF_SECTION *app_io_conf; //!< for the APP IO + fr_app_io_t const *app_io; //!< Easy access to the app_io handle. + void *app_io_instance; //!< Easy access to the app_io instance. -static int request_receive(UNUSED TALLOC_CTX *ctx, UNUSED rad_listen_t *listener, UNUSED RADIUS_PACKET *packet, - UNUSED RADCLIENT *client, UNUSED RAD_REQUEST_FUNP fun) -{ - return 0; -} + dl_module_inst_t *app_process; //!< app_process pointer + void *process_instance; //!< app_process instance -static rlm_rcode_t arp_process(REQUEST *request) -{ - CONF_SECTION *unlang; + fr_dict_t *dict; //!< root dictionary - request->server_cs = request->listener->server_cs; - unlang = cf_section_find(request->server_cs, "arp", NULL); + char const *interface; //!< interface name + uint32_t num_messages; //!< for message ring buffer + uint32_t priority; //!< for packet processing, larger == higher - request->component = "arp"; + fr_schedule_t *sc; //!< the scheduler, where we insert new readers - return unlang_interpret_section(request, unlang, RLM_MODULE_NOOP); -} + fr_listen_t *listen; //!< The listener structure which describes + //!< the I/O path. +} proto_arp_t; -/* - * Check if an incoming request is "ok" +/** How to parse an ARP listen section * - * It takes packets, not requests. It sees if the packet looks - * OK. If so, it does a number of sanity checks on it. */ -static int arp_socket_recv(rad_listen_t *listener) -{ - int ret; - arp_socket_t *sock = listener->data; - pcap_t *handle = sock->lsock.pcap->handle; +static CONF_PARSER const proto_arp_config[] = { + { FR_CONF_OFFSET("interface", FR_TYPE_STRING | FR_TYPE_NOT_EMPTY, proto_arp_t, + interface), .dflt = "eth0" }, - const uint8_t *data; - struct pcap_pkthdr *header; - ssize_t link_len; + /* + * @todo - allow for pcap filter + */ - arp_over_ether_t const *arp; - RADIUS_PACKET *packet; + { FR_CONF_OFFSET("num_messages", FR_TYPE_UINT32, proto_arp_t, num_messages) } , - ret = pcap_next_ex(handle, &header, &data); - if (ret == 0) { - DEBUG("No packet retrieved from pcap."); - return 0; /* no packet */ - } + CONF_PARSER_TERMINATOR +}; - if (ret < 0) { - ERROR("Error requesting next packet, got (%i): %s", ret, pcap_geterr(handle)); - return 0; - } +static fr_dict_t const *dict_arp; - link_len = fr_pcap_link_layer_offset(data, header->caplen, sock->lsock.pcap->link_layer); - if (link_len < 0) { - PERROR("Failed determining link layer header offset"); - return 0; - } +extern fr_dict_autoload_t proto_arp_dict[]; +fr_dict_autoload_t proto_arp_dict[] = { + { .out = &dict_arp, .proto = "arp" }, + { NULL } +}; - /* - * Silently ignore it if it's too small to be ARP. - * - * This can happen when pcap gets overloaded and starts truncating packets. - */ - if (header->caplen < (link_len + sizeof(*arp))) { - ERROR("Packet too small, we require at least %zu bytes, got %i bytes", - link_len + sizeof(*arp), header->caplen); - return 0; - } +#if 0 +static fr_dict_attr_t const *attr_packet_type; - data += link_len; - arp = (arp_over_ether_t const *) data; +extern fr_dict_attr_autoload_t proto_arp_dict_attr[]; +fr_dict_attr_autoload_t proto_arp_dict_attr[] = { + { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_arp}, + { NULL } +}; +#endif - if (ntohs(arp->htype) != ARPHRD_ETHER) return 0; +/** Decode the packet + * + */ +static int mod_decode(UNUSED void const *instance, REQUEST *request, uint8_t *const data, size_t data_len) +{ +// proto_arp_t const *inst = talloc_get_type_abort_const(instance, proto_arp_t); + fr_arp_packet_t const *arp; - if (ntohs(arp->ptype) != 0x0800) return 0; + /* + * Set the request dictionary so that we can do + * generic->protocol attribute conversions as + * the request runs through the server. + */ + request->dict = dict_arp; - if (arp->hlen != ETHER_ADDR_LEN) return 0; /* FIXME: malformed error */ + if (fr_arp_decode(request->packet, data, data_len, &request->packet->vps) < 0) { + RPEDEBUG("Failed decoding packet"); + return -1; + } - if (arp->plen != 4) return 0; /* FIXME: malformed packet error */ + arp = (fr_arp_packet_t const *) data; + request->packet->code = arp->op; - packet = talloc_zero(NULL, RADIUS_PACKET); - if (!packet) return 0; + request->packet->data = talloc_memdup(request->packet, data, data_len); + request->packet->data_len = data_len; - packet->dst_port = 1; /* so it's not a "fake" request */ - packet->data_len = header->caplen - link_len; - packet->data = talloc_memdup(packet, arp, packet->data_len); - talloc_set_type(packet->data, uint8_t); + request->config = main_config; + REQUEST_VERIFY(request); - DEBUG("ARP received on interface %s", sock->lsock.interface); + if (RDEBUG_ENABLED) { + RDEBUG("Received %d via socket %s", + request->packet->code, + request->async->listen->name); - if (!request_receive(NULL, listener, packet, &sock->client, arp_process)) { - fr_radius_packet_free(&packet); - return 0; + log_request_pair_list(L_DBG_LVL_1, request, request->packet->vps, ""); } - return 1; -} - -static int arp_socket_send(UNUSED rad_listen_t *listener, UNUSED REQUEST *request) -{ return 0; } - -static int arp_socket_encode(UNUSED rad_listen_t *listener, UNUSED REQUEST *request) +static ssize_t mod_encode(UNUSED void const *instance, REQUEST *request, uint8_t *buffer, size_t buffer_len) { - return 0; -} - + ssize_t slen; +// proto_arp_t const *inst = talloc_get_type_abort_const(instance, proto_arp_t); -typedef struct { - char const *name; - size_t len; -} arp_decode_t; - -static const arp_decode_t header_names[] = { - { "ARP-Hardware-Format", 2 }, - { "ARP-Protocol-Format", 2 }, - { "ARP-Hardware-Address-Length", 1 }, - { "ARP-Protocol-Address-Length", 1 }, - { "ARP-Operation", 2 }, - { "ARP-Sender-Hardware-Address", 6 }, - { "ARP-Sender-Protocol-Address", 4 }, - { "ARP-Target-Hardware-Address", 6 }, - { "ARP-Target-Protocol-Address", 4 }, - - { NULL, 0 } -}; + /* + * The packet timed out. Tell the network side that the packet is dead. + */ + if (buffer_len == 1) { + *buffer = true; + return 1; + } -static int arp_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request) -{ - int i; - uint8_t const *p = request->packet->data, *end = p + request->packet->data_len; - fr_cursor_t cursor; + /* + * "Do not respond" + */ + if ((request->reply->code == FR_CODE_DO_NOT_RESPOND) || + (request->reply->code == 0) || (request->reply->code >= FR_ARP_MAX_PACKET_CODE)) { + *buffer = false; + return 1; + } - fr_cursor_init(&cursor, &request->packet->vps); + slen = fr_arp_encode(buffer, buffer_len, request->reply->vps); + if (slen <= 0) { + RPEDEBUG("Failed encoding reply"); + return -1; + } - for (i = 0; header_names[i].name != NULL; i++) { - ssize_t ret; - size_t len; - fr_dict_attr_t const *da; - VALUE_PAIR *vp = NULL; - len = header_names[i].len; + if (RDEBUG_ENABLED) { + RDEBUG("Sending %d via socket %s", + request->reply->code, + request->async->listen->name); - if (!fr_cond_assert((size_t)(end - p) < len)) return -1; /* Should have been detected in socket_recv */ + log_request_pair_list(L_DBG_LVL_1, request, request->reply->vps, ""); + } - da = fr_dict_attr_by_name(dict_arp, header_names[i].name); - if (!da) return 0; + return slen; +} - MEM(vp = fr_pair_afrom_da(request->packet, da)); - ret = fr_value_box_from_network(vp, &vp->data, da->type, da, p, len, true); - if (ret <= 0) { - fr_pair_to_unknown(vp); - fr_pair_value_memcpy(vp, p, len, true); - } +static void mod_entry_point_set(void const *instance, REQUEST *request) +{ + proto_arp_t const *inst = talloc_get_type_abort_const(instance, proto_arp_t); + fr_app_worker_t const *app_process; - DEBUG2("&%pP", vp); - fr_cursor_insert(&cursor, vp); - } + /* + * Only one "process" function: proto_arp_process. + */ + app_process = (fr_app_worker_t const *) inst->app_process->module->common; - return 0; + request->server_cs = inst->server_cs; + request->async->process = app_process->entry_point; + request->async->process_inst = inst->process_instance; } -/** Build PCAP filter string to pass to libpcap - * Will be called by init_pcap. - * - * @param this listen section (not used) - * @return PCAP filter string + +/* + * @todo - do actual priority setting */ -static const char * arp_pcap_filter_builder(UNUSED rad_listen_t *this) +static int mod_priority_set(UNUSED void const *instance, UNUSED uint8_t const *buffer, UNUSED size_t buflen) { - return "arp"; + return PRIORITY_NORMAL; } -static int arp_socket_parse(CONF_SECTION *cs, rad_listen_t *this) +/** Open listen sockets/connect to external event source + * + * @param[in] instance Ctx data for this application. + * @param[in] sc to add our file descriptor to. + * @param[in] conf Listen section parsed to give us instance. + * @return + * - 0 on success. + * - -1 on failure. + */ +static int mod_open(void *instance, fr_schedule_t *sc, UNUSED CONF_SECTION *conf) { - int rcode; - arp_socket_t *sock = this->data; - RADCLIENT *client; - CONF_PAIR *cp = NULL; + fr_listen_t *li; + proto_arp_t *inst = talloc_get_type_abort(instance, proto_arp_t); - sock->lsock.pcap_filter_builder = arp_pcap_filter_builder; - sock->lsock.pcap_type = PCAP_INTERFACE_IN; + /* + * Build the #fr_listen_t. This describes the complete + * path, data takes from the socket to the decoder and + * back again. + */ + li = talloc_zero(inst, fr_listen_t); + talloc_set_destructor(li, fr_io_listen_free); - this->nodup = true; /* don't check for duplicates */ + li->app = &proto_arp; + li->app_instance = instance; + li->server_cs = inst->server_cs; - /* Add ipaddress to conf section as it is not required by ARP config */ - cp = cf_pair_alloc(cs, "ipv4addr", "0.0.0.0", T_OP_SET, T_BARE_WORD, T_BARE_WORD); - cf_pair_add(cs, cp); + /* + * Set configurable parameters for message ring buffer. + */ + li->default_message_size = FR_ARP_PACKET_SIZE; + li->num_messages = inst->num_messages; - rcode = common_socket_parse(cs, this); - if (rcode != 0) return rcode; + li->app_io = inst->app_io; + li->app_io_instance = inst->app_io_instance; + li->thread_instance = talloc_zero_array(NULL, uint8_t, li->app_io->thread_inst_size); + talloc_set_name(li->thread_instance, "proto_%s_thread_t", inst->app_io->name); - if (!sock->lsock.interface) { - cf_log_err(cs, "'interface' is required for arp"); + /* + * Open the raw socket. + */ + if (inst->app_io->open(li) < 0) { + talloc_free(li); return -1; } + li->name = talloc_asprintf(li, "arp on interface %s", inst->interface); + /* - * The server core is still RADIUS, and needs a client. - * So we fake one here. + * Watch the directory for changes. */ - client = &sock->client; - memset(client, 0, sizeof(*client)); - client->ipaddr.af = AF_INET; - client->ipaddr.addr.v4.s_addr = INADDR_NONE; - client->ipaddr.prefix = 0; - client->longname = client->shortname = sock->lsock.interface; - client->secret = client->shortname; - client->nas_type = talloc_typed_strdup(sock, "none"); + if (!fr_schedule_listen_add(sc, li)) { + talloc_free(li); + return -1; + } + + inst->listen = li; /* Probably won't need it, but doesn't hurt */ + inst->sc = sc; return 0; } -static int arp_socket_print(const rad_listen_t *this, char *buffer, size_t bufsize) +/** Instantiate the application + * + * Instantiate I/O and type submodules. + * + * @param[in] instance Ctx data for this application. + * @param[in] conf Listen section parsed to give us instance. + * @return + * - 0 on success. + * - -1 on failure. + */ +static int mod_instantiate(void *instance, UNUSED CONF_SECTION *conf) { - arp_socket_t *sock = this->data; + proto_arp_t *inst = talloc_get_type_abort(instance, proto_arp_t); + fr_app_worker_t const *app_process; - snprintf(buffer, bufsize, "arp interface %s", sock->lsock.interface); + /* + * Instantiate the I/O module. But DON'T instantiate the + * work submodule. We leave that until later. + */ + if (inst->app_io->instantiate && + (inst->app_io->instantiate(inst->app_io_instance, + inst->app_io_conf) < 0)) { + cf_log_err(conf, "Instantiation failed for \"%s\"", inst->app_io->name); + return -1; + } - return 1; -} + if (!inst->num_messages) inst->num_messages = 256; -/* - * If there's no "arp" section, we can't bootstrap anything. - */ -static int arp_socket_bootstrap(CONF_SECTION *server_cs, UNUSED CONF_SECTION *listen_cs) -{ - CONF_SECTION *cs; + FR_INTEGER_BOUND_CHECK("num_messages", inst->num_messages, >=, 32); + FR_INTEGER_BOUND_CHECK("num_messages", inst->num_messages, <=, 65535); - cs = cf_section_find(server_cs, "arp", NULL); - if (!cs) { - cf_log_err(server_cs, "No 'arp' sub-section found"); + /* + * Instantiate the ARP processor + */ + app_process = (fr_app_worker_t const *)inst->app_process->module->common; + if (app_process->instantiate && (app_process->instantiate(inst->app_process->data, + inst->app_process->conf) < 0)) { + cf_log_err(conf, "Instantiation failed for \"%s\"", app_process->name); return -1; } - return 0; + return -1; } -/* - * Ensure that the "arp" section is compiled. + +/** Bootstrap the application + * + * Bootstrap I/O and type submodules. + * + * @param[in] instance Ctx data for this application. + * @param[in] conf Listen section parsed to give us instance. + * @return + * - 0 on success. + * - -1 on failure. */ -static int arp_socket_compile(CONF_SECTION *server_cs, UNUSED CONF_SECTION *listen_cs) +static int mod_bootstrap(void *instance, CONF_SECTION *conf) { - CONF_SECTION *cs; + proto_arp_t *inst = talloc_get_type_abort(instance, proto_arp_t); + dl_module_inst_t *parent_inst; + fr_app_worker_t const *app_process; + + /* + * Ensure that the server CONF_SECTION is always set. + */ + inst->server_cs = cf_item_to_section(cf_parent(conf)); + + parent_inst = cf_data_value(cf_data_find(inst->cs, dl_module_inst_t, "proto_arp")); + fr_assert(parent_inst); + + if (dl_module_instance(inst->cs, &inst->io_submodule, inst->cs, + parent_inst, "ethernet", DL_MODULE_TYPE_SUBMODULE) < 0) { + cf_log_perr(inst->cs, "Failed to load proto_arp_ethernet"); + return -1; + } - cs = cf_section_find(server_cs, "arp", NULL); - if (!cs) { - cf_log_err(server_cs, "No 'arp' sub-section found"); + /* + * Parent dl_module_inst_t added in virtual_servers.c (listen_parse) + */ + if (dl_module_instance(inst->cs, &inst->app_process, inst->cs, + parent_inst, "process", DL_MODULE_TYPE_SUBMODULE) < 0) { + cf_log_perr(inst->cs, "Failed to load proto_arp_process"); return -1; } - cf_log_debug(cs, "Loading arp {...}"); + /* + * Bootstrap the I/O module + */ + inst->app_io = (fr_app_io_t const *) inst->io_submodule->module->common; + inst->app_io_instance = inst->io_submodule->data; + inst->app_io_conf = conf; + + if (inst->app_io->bootstrap && (inst->app_io->bootstrap(inst->app_io_instance, + inst->app_io_conf) < 0)) { + cf_log_err(inst->app_io_conf, "Bootstrap failed for \"%s\"", inst->app_io->name); + return -1; + } - if (unlang_compile(cs, MOD_POST_AUTH, NULL, NULL) < 0) { - cf_log_err(cs, "Failed compiling 'arp' section"); + /* + * Bootstrap the ARP processor + */ + app_process = (fr_app_worker_t const *)(inst->app_process->module->common); + if (app_process->bootstrap && (app_process->bootstrap(inst->app_process->data, + inst->app_process->conf) < 0)) { + cf_log_err(conf, "Bootstrap failed for \"%s\"", app_process->name); return -1; } return 0; } -extern rad_protocol_t proto_arp; -rad_protocol_t proto_arp = { - .magic = RLM_MODULE_INIT, - .name = "arp", - .inst_size = sizeof(arp_socket_t), - .transports = 0, - .tls = false, - - .bootstrap = arp_socket_bootstrap, - .compile = arp_socket_compile, - .parse = arp_socket_parse, - .open = common_socket_open, - .recv = arp_socket_recv, - .send = arp_socket_send, - .print = arp_socket_print, - .debug = common_packet_debug, - .encode = arp_socket_encode, - .decode = arp_socket_decode +static int mod_load(void) +{ + if (fr_arp_init() < 0) { + PERROR("Failed initialising protocol library"); + return -1; + } + return 0; +} + +static void mod_unload(void) +{ + fr_arp_free(); +} + +fr_app_t proto_arp = { + .magic = RLM_MODULE_INIT, + .name = "arp", + .config = proto_arp_config, + .inst_size = sizeof(proto_arp_t), + .dict = &dict_arp, + + .onload = mod_load, + .unload = mod_unload, + .bootstrap = mod_bootstrap, + .instantiate = mod_instantiate, + .open = mod_open, + .decode = mod_decode, + .encode = mod_encode, + .entry_point_set = mod_entry_point_set, + .priority = mod_priority_set };