/** Set the event list for a new IO instance * * @param[in] li the listener * @param[in] el the event list * @param[in] nr context from the network side */ static void mod_event_list_set(fr_listen_t *li, fr_event_list_t *el, UNUSED void *nr) { proto_detail_file_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_detail_file_t); proto_detail_file_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_detail_file_thread_t); struct timeval when; thread->el = el; if (inst->immediate) { work_init(thread); return; } /* * Delay for a bit, before reading the detail files. * This gives the server time to call * rad_suid_down_permanent(), and for /proc/PID to * therefore change permissions, so that libkqueue can * read it. */ gettimeofday(&when, NULL); when.tv_sec +=1; if (fr_event_timer_insert(thread, thread->el, &thread->ev, &when, work_retry_timer, thread) < 0) { ERROR("Failed inserting poll timer for %s", thread->filename_work); } }
static int mod_close(fr_listen_t *li) { proto_detail_file_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_detail_file_t); proto_detail_file_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_detail_file_thread_t); if (thread->nr) (void) fr_network_socket_delete(thread->nr, inst->parent->listen); /* * @todo - have our OWN event loop for timers, and a * "copy timer from -> to, which means we only have to * delete our child event loop from the parent on close. */ close(thread->fd); if (thread->vnode_fd >= 0) { if (thread->nr) { (void) fr_network_socket_delete(thread->nr, inst->parent->listen); } else { if (fr_event_fd_delete(thread->el, thread->vnode_fd, FR_EVENT_FILTER_VNODE) < 0) { PERROR("Failed removing DELETE callback on detach"); } } close(thread->vnode_fd); thread->vnode_fd = -1; pthread_mutex_destroy(&thread->worker_mutex); } return 0; }
/** Open a detail listener * */ static int mod_open(fr_listen_t *li) { proto_detail_file_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_detail_file_t); proto_detail_file_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_detail_file_thread_t); if (inst->poll_interval == 0) { int oflag; #ifdef O_EVTONLY oflag = O_EVTONLY; #else oflag = O_RDONLY; #endif li->fd = thread->fd = open(inst->directory, oflag); } else { li->fd = thread->fd = open("/dev/null", O_RDONLY); } if (thread->fd < 0) { cf_log_err(inst->cs, "Failed opening %s: %s", inst->directory, fr_syserror(errno)); return -1; } thread->inst = inst; thread->name = talloc_typed_asprintf(thread, "detail polling for files matching %s", inst->filename); thread->vnode_fd = -1; pthread_mutex_init(&thread->worker_mutex, NULL); DEBUG("Listening on %s bound to virtual server %s", thread->name, cf_section_name2(inst->parent->server_cs)); return 0; }
/** Continue after unlang_resumable() * */ static rlm_rcode_t mod_radius_resume(REQUEST *request, void *instance, void *thread, void *ctx) { rlm_radius_t const *inst = talloc_get_type_abort_const(instance, rlm_radius_t); rlm_radius_thread_t *t = talloc_get_type_abort(thread, rlm_radius_thread_t); return inst->io->resume(request, inst->io_instance, t->thread_io_ctx, ctx); }
static xlat_action_t redis_remap_xlat(TALLOC_CTX *ctx, fr_cursor_t *out, REQUEST *request, void const *xlat_inst, UNUSED void *xlat_thread_inst, fr_value_box_t **in) { rlm_redis_t const *inst = talloc_get_type_abort_const(*((void const * const *)xlat_inst), rlm_redis_t); fr_socket_addr_t node_addr; fr_pool_t *pool; fr_redis_conn_t *conn; fr_redis_cluster_rcode_t rcode; fr_value_box_t *vb; if (!in) { REDEBUG("Missing key"); return XLAT_ACTION_FAIL; } if (fr_value_box_list_concat(ctx, *in, in, FR_TYPE_STRING, true) < 0) { RPEDEBUG("Failed concatenating input"); return XLAT_ACTION_FAIL; } if (fr_inet_pton_port(&node_addr.ipaddr, &node_addr.port, (*in)->vb_strvalue, (*in)->vb_length, AF_UNSPEC, true, true) < 0) { RPEDEBUG("Failed parsing node address"); return XLAT_ACTION_FAIL; } if (fr_redis_cluster_pool_by_node_addr(&pool, inst->cluster, &node_addr, true) < 0) { RPEDEBUG("Failed locating cluster node"); return XLAT_ACTION_FAIL; } conn = fr_pool_connection_get(pool, request); if (!conn) { REDEBUG("No connections available for cluster node"); return XLAT_ACTION_FAIL; } rcode = fr_redis_cluster_remap(request, inst->cluster, conn); fr_pool_connection_release(pool, request, conn); MEM(vb = fr_value_box_alloc_null(ctx)); fr_value_box_strdup(vb, vb, NULL, fr_int2str(fr_redis_cluster_rcodes_table, rcode, "<INVALID>"), false); fr_cursor_append(out, vb); return XLAT_ACTION_DONE; }
/** Set the file descriptor for this socket. * */ static int mod_fd_set(fr_listen_t *li, int fd) { proto_dhcpv4_udp_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_dhcpv4_udp_t); proto_dhcpv4_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_dhcpv4_udp_thread_t); thread->sockfd = fd; thread->name = fr_app_io_socket_name(thread, &proto_dhcpv4_udp, &thread->connection->src_ipaddr, thread->connection->src_port, &inst->ipaddr, inst->port, inst->interface); return 0; }
/** Signal the network to read from a listener * * @param nr the network * @param listen the listener to read from */ void fr_network_listen_read(fr_network_t *nr, fr_listen_t const *listen) { fr_network_socket_t my_socket, *s; (void) talloc_get_type_abort(nr, fr_network_t); (void) talloc_get_type_abort_const(listen, fr_listen_t); my_socket.listen = listen; s = rbtree_finddata(nr->sockets, &my_socket); if (!s) return; /* * Go read the socket. */ fr_network_read(nr->el, s->fd, 0, s); }
/** Return the node that is currently servicing a particular key * * */ static xlat_action_t redis_node_xlat(TALLOC_CTX *ctx, fr_cursor_t *out, REQUEST *request, void const *xlat_inst, UNUSED void *xlat_thread_inst, fr_value_box_t **in) { rlm_redis_t const *inst = talloc_get_type_abort_const(*((void const * const *)xlat_inst), rlm_redis_t); fr_redis_cluster_key_slot_t const *key_slot; fr_redis_cluster_node_t const *node; fr_ipaddr_t ipaddr; uint16_t port; char const *p; char *q; char const *key; size_t key_len; unsigned long idx = 0; fr_value_box_t *vb; if (!in) { REDEBUG("Missing key"); return XLAT_ACTION_FAIL; } if (fr_value_box_list_concat(ctx, *in, in, FR_TYPE_STRING, true) < 0) { RPEDEBUG("Failed concatenating input"); return XLAT_ACTION_FAIL; } key = p = (*in)->vb_strvalue; p = strchr(p, ' '); /* Look for index */ if (p) { key_len = p - key; idx = strtoul(p, &q, 10); if (q == p) { REDEBUG("Tailing garbage after node index"); return XLAT_ACTION_FAIL; } } else { key_len = (*in)->vb_length; } key_slot = fr_redis_cluster_slot_by_key(inst->cluster, request, (uint8_t const *)key, key_len); if (idx == 0) { node = fr_redis_cluster_master(inst->cluster, key_slot); } else { node = fr_redis_cluster_slave(inst->cluster, key_slot, idx - 1); } if (!node) { RDEBUG2("No node available for this key slot"); return XLAT_ACTION_DONE; } if ((fr_redis_cluster_ipaddr(&ipaddr, node) < 0) || (fr_redis_cluster_port(&port, node) < 0)) { REDEBUG("Failed retrieving node information"); return XLAT_ACTION_FAIL; } MEM(vb = fr_value_box_alloc_null(ctx)); fr_value_box_asprintf(vb, vb, NULL, false, "%pV:%u", fr_box_ipaddr(ipaddr), port); fr_cursor_append(out, vb); return XLAT_ACTION_DONE; }
static int test_fd(void const *ctx) { fr_listen_test_t const *io_ctx = talloc_get_type_abort_const(ctx, fr_listen_test_t); return io_ctx->sockfd; }
/** Open a UDP listener for DHCPV4 * */ static int mod_open(fr_listen_t *li) { proto_dhcpv4_udp_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_dhcpv4_udp_t); proto_dhcpv4_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_dhcpv4_udp_thread_t); int sockfd, rcode; uint16_t port = inst->port; CONF_SECTION *server_cs; CONF_ITEM *ci; li->fd = sockfd = fr_socket_server_udp(&inst->ipaddr, &port, inst->port_name, true); if (sockfd < 0) { PERROR("Failed opening UDP socket"); error: return -1; } /* * Set SO_REUSEPORT before bind, so that all packets can * listen on the same destination IP address. */ if (1) { int on = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0) { ERROR("Failed to set socket 'reuseport': %s", fr_syserror(errno)); close(sockfd); return -1; } } if (inst->broadcast) { int on = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0) { ERROR("Failed to set broadcast option: %s", fr_syserror(errno)); close(sockfd); return -1; } } /* * SUID up is really only needed if interface is set, OR port <1024. */ rad_suid_up(); rcode = fr_socket_bind(sockfd, &inst->ipaddr, &port, inst->interface); rad_suid_down(); if (rcode < 0) { close(sockfd); PERROR("Failed binding socket"); goto error; } thread->sockfd = sockfd; ci = cf_parent(inst->cs); /* listen { ... } */ rad_assert(ci != NULL); ci = cf_parent(ci); rad_assert(ci != NULL); server_cs = cf_item_to_section(ci); thread->name = fr_app_io_socket_name(thread, &proto_dhcpv4_udp, NULL, 0, &inst->ipaddr, inst->port, inst->interface); DEBUG("Listening on dhcpv4 address %s bound to virtual server %s", thread->name, cf_section_name2(server_cs)); return 0; }
static ssize_t mod_write(fr_listen_t *li, void *packet_ctx, UNUSED fr_time_t request_time, uint8_t *buffer, size_t buffer_len, UNUSED size_t written) { proto_dhcpv4_udp_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_dhcpv4_udp_t); proto_dhcpv4_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_dhcpv4_udp_thread_t); fr_io_track_t *track = talloc_get_type_abort(packet_ctx, fr_io_track_t); fr_io_address_t address; int flags; ssize_t data_size; /* * @todo - share a stats interface with the parent? or * put the stats in the listener, so that proto_dhcpv4 * can update them, too.. <sigh> */ thread->stats.total_responses++; flags = UDP_FLAGS_CONNECTED * (thread->connection != NULL); rad_assert(track->reply_len == 0); /* * Swap src/dst IP/port */ address.src_ipaddr = track->address->dst_ipaddr; address.src_port = track->address->dst_port; address.dst_ipaddr = track->address->src_ipaddr; address.dst_port = track->address->src_port; address.if_index = track->address->if_index; /* * Figure out which kind of packet we're sending. */ if (!thread->connection) { uint8_t const *code, *sid; uint32_t ipaddr; dhcp_packet_t *packet = (dhcp_packet_t *) buffer; dhcp_packet_t *request = (dhcp_packet_t *) track->packet; /* only 20 bytes tho! */ #ifdef WITH_IFINDEX_IPADDR_RESOLUTION fr_ipaddr_t primary; #endif /* * This isn't available in the packet header. */ code = fr_dhcpv4_packet_get_option(packet, buffer_len, attr_message_type); if (!code || (code[1] < 1) || (code[2] == 0) || (code[2] >= FR_DHCP_INFORM)) { DEBUG("WARNING - silently discarding reply due to invalid or missing message type"); return 0; } /* * Set the source IP of the packet. * * - if src_ipaddr is unicast, use that * - else if socket wasn't bound to *, then use that * - else if we have if_index, get main IP from that interface and use that. * - else for offer/ack, look at option 54, for Server Identification and use that * - else leave source IP as whatever is already in "address.src_ipaddr". */ if (inst->src_ipaddr.addr.v4.s_addr != INADDR_ANY) { address.src_ipaddr = inst->src_ipaddr; } else if (inst->ipaddr.addr.v4.s_addr != INADDR_ANY) { address.src_ipaddr = inst->ipaddr; #ifdef WITH_IFINDEX_IPADDR_RESOLUTION } else if ((address->if_index > 0) && (fr_ipaddr_from_ifindex(&primary, thread->sockfd, &address.dst_ipaddr.af, &address.if_index) == 0)) { address.src_ipaddr = primary; #endif } else if (((code[2] == FR_DHCP_OFFER) || (code[2] == FR_DHCP_ACK)) && ((sid = fr_dhcpv4_packet_get_option(packet, buffer_len, attr_dhcp_server_identifier)) != NULL) && (sid[1] == 4)) { memcpy(&address.src_ipaddr.addr.v4.s_addr, sid + 2, 4); } /* * We have GIADDR in the packet, so send it * there. The packet is FROM our IP address and * port, TO the destination IP address, at the * same (i.e. server) port. */ memcpy(&ipaddr, &packet->giaddr, 4); if (ipaddr != INADDR_ANY) { DEBUG("Reply will be sent to giaddr."); address.dst_ipaddr.addr.v4.s_addr = ipaddr; address.dst_port = inst->port; address.src_port = inst->port; /* * Increase the hop count for client * packets sent to the next gateway. */ if ((code[2] == FR_DHCP_DISCOVER) || (code[2] == FR_DHCP_REQUEST)) { packet->opcode = 1; /* client message */ packet->hops = request->hops + 1; } else { packet->opcode = 2; /* server message */ } goto send_reply; } /* * If there's no GIADDR, we don't know where to * send client packets. */ if ((code[2] == FR_DHCP_DISCOVER) || (code[2] == FR_DHCP_REQUEST)) { DEBUG("WARNING - silently discarding client reply, as there is no GIADDR to send it to."); return 0; } packet->opcode = 2; /* server message */ /* * The original packet requested a broadcast * reply, and CIADDR is empty, go broadcast the * reply. RFC 2131 page 23. */ if (((request->flags & FR_DHCP_FLAGS_VALUE_BROADCAST) != 0) && (request->ciaddr == INADDR_ANY)) { DEBUG("Reply will be broadcast due to client request."); address.dst_ipaddr.addr.v4.s_addr = INADDR_BROADCAST; goto send_reply; } /* * The original packet has CIADDR, so we unicast * the reply there. RFC 2131 page 23. */ if (request->ciaddr != INADDR_ANY) { DEBUG("Reply will be unicast to CIADDR from original packet."); memcpy(&address.dst_ipaddr.addr.v4.s_addr, &request->ciaddr, 4); goto send_reply; } /* * The original packet was unicast to us, such as * via a relay. We have a unicast destination * address, so we just use that. */ if ((packet->yiaddr == htonl(INADDR_ANY)) && (address.dst_ipaddr.addr.v4.s_addr != htonl(INADDR_BROADCAST))) { DEBUG("Reply will be unicast to source IP from original packet."); goto send_reply; } switch (code[2]) { /* * Offers are sent to YIADDR if we * received a unicast packet from YIADDR. * Otherwise, they are unicast to YIADDR * (if we can update ARP), otherwise they * are broadcast. */ case FR_DHCP_OFFER: /* * If the packet was unicast from the * client, unicast it back. */ if (memcmp(&address.dst_ipaddr.addr.v4.s_addr, &packet->yiaddr, 4) == 0) { DEBUG("Reply will be unicast to YIADDR."); #ifdef SIOCSARP } else if (inst->broadcast && inst->interface) { if (fr_dhcpv4_udp_add_arp_entry(thread->sockfd, inst->interface, &packet->yiaddr, &packet->chaddr) < 0) { DEBUG("Failed adding ARP entry. Reply will be broadcast."); address.dst_ipaddr.addr.v4.s_addr = INADDR_BROADCAST; } else { DEBUG("Reply will be unicast to YIADDR after ARP table updates."); } #endif } else { DEBUG("Reply will be broadcast due to OFFER."); address.dst_ipaddr.addr.v4.s_addr = INADDR_BROADCAST; } break; /* * ACKs are unicast to YIADDR */ case FR_DHCP_ACK: DEBUG("Reply will be unicast to YIADDR."); memcpy(&address.dst_ipaddr.addr.v4.s_addr, &packet->yiaddr, 4); break; /* * NAKs are broadcast. */ case FR_DHCP_NAK: DEBUG("Reply will be broadcast due to NAK."); address.dst_ipaddr.addr.v4.s_addr = INADDR_BROADCAST; break; default: DEBUG("WARNING - silently discarding reply due to invalid message type %d", code[2]); return 0; } } send_reply: /* * proto_dhcpv4 takes care of suppressing do-not-respond, etc. */ data_size = udp_send(thread->sockfd, buffer, buffer_len, flags, &address.src_ipaddr, address.src_port, address.if_index, &address.dst_ipaddr, address.dst_port); /* * This socket is dead. That's an error... */ if (data_size <= 0) return data_size; return data_size; }
/* * All of the decoding is done by proto_detail and proto_detail_work */ static int mod_decode(void const *instance, REQUEST *request, uint8_t *const data, size_t data_len) { proto_detail_file_t const *inst = talloc_get_type_abort_const(instance, proto_detail_file_t); return inst->parent->work_io->decode(inst->parent->work_io_instance, request, data, data_len); }
/** Allow the admin to set packet contents for Status-Server ping checks * * @param[in] ctx to allocate data in (instance of proto_radius). * @param[out] out Where to write our parsed data * @param[in] parent Base structure address. * @param[in] ci #CONF_SECTION specifying the things to update * @param[in] rule unused. * @return * - 0 on success. * - -1 on failure. */ static int status_check_update_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, UNUSED CONF_PARSER const *rule) { int rcode; CONF_SECTION *cs; char const *name2; vp_map_t *head = NULL; rad_assert(cf_item_is_section(ci)); cs = cf_item_to_section(ci); name2 = cf_section_name2(cs); if (!name2 || (strcmp(name2, "request") != 0)) { cf_log_err(cs, "You must specify 'request' as the destination list"); return -1; } /* * Compile the "update" section. */ { vp_tmpl_rules_t parse_rules = { .allow_foreign = true /* Because we don't know where we'll be called */ }; rcode = map_afrom_cs(ctx, &head, cs, &parse_rules, &parse_rules, unlang_fixup_update, NULL, 128); if (rcode < 0) return -1; /* message already printed */ if (!head) { cf_log_err(cs, "'update' sections cannot be empty"); return -1; } } /* * Rely on "bootstrap" to do sanity checks between 'type * = Access-Request', and 'update' containing passwords. */ memcpy(out, &head, sizeof(head)); return 0; } static void mod_radius_signal(REQUEST *request, void *instance, void *thread, void *ctx, fr_state_signal_t action) { rlm_radius_t const *inst = talloc_get_type_abort_const(instance, rlm_radius_t); rlm_radius_thread_t *t = talloc_get_type_abort(thread, rlm_radius_thread_t); /* * We've been told we're done. Clean up. * * Note that the caller doesn't necessarily need to send * us the signal, as he can just talloc_free(request). * But it is more polite to send a signal, and it allows * the IO modules to do additional debugging if * necessary. */ if (action == FR_SIGNAL_CANCEL) { talloc_free(ctx); return; } /* * We received a duplicate packet, but we're not doing * synchronous proxying. Ignore the dup, and rely on the * IO submodule to time it's own retransmissions. */ if ((action == FR_SIGNAL_DUP) && !inst->synchronous) return; if (!inst->io->signal) return; inst->io->signal(request, inst->io_instance, t->thread_io_ctx, ctx, action); }
/** Open a UDP listener for RADIUS * */ static int mod_open(fr_listen_t *li) { proto_radius_udp_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_radius_udp_t); proto_radius_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_radius_udp_thread_t); int sockfd; uint16_t port = inst->port; CONF_SECTION *server_cs; CONF_ITEM *ci; li->fd = sockfd = fr_socket_server_udp(&inst->ipaddr, &port, inst->port_name, true); if (sockfd < 0) { PERROR("Failed opening UDP socket"); error: return -1; } /* * Set SO_REUSEPORT before bind, so that all packets can * listen on the same destination IP address. */ if (1) { int on = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0) { ERROR("Failed to set socket 'reuseport': %s", fr_syserror(errno)); return -1; } } #ifdef SO_RCVBUF if (inst->recv_buff_is_set) { int opt; opt = inst->recv_buff; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(int)) < 0) { WARN("Failed setting 'recv_buf': %s", fr_syserror(errno)); } } #endif #ifdef SO_SNDBUF if (inst->send_buff_is_set) { int opt; opt = inst->send_buff; if (setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(int)) < 0) { WARN("Failed setting 'send_buf': %s", fr_syserror(errno)); } } #endif if (fr_socket_bind(sockfd, &inst->ipaddr, &port, inst->interface) < 0) { close(sockfd); PERROR("Failed binding socket"); goto error; } thread->sockfd = sockfd; ci = cf_parent(inst->cs); /* listen { ... } */ rad_assert(ci != NULL); ci = cf_parent(ci); rad_assert(ci != NULL); server_cs = cf_item_to_section(ci); thread->name = fr_app_io_socket_name(thread, &proto_radius_udp, NULL, 0, &inst->ipaddr, inst->port, inst->interface); // @todo - also print out auth / acct / coa, etc. DEBUG("Listening on radius address %s bound to virtual server %s", thread->name, cf_section_name2(server_cs)); return 0; }
static ssize_t mod_read(fr_listen_t *li, void **packet_ctx, fr_time_t **recv_time, uint8_t *buffer, size_t buffer_len, size_t *leftover, UNUSED uint32_t *priority, UNUSED bool *is_dup) { proto_radius_udp_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_radius_udp_t); proto_radius_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_radius_udp_thread_t); fr_io_address_t *address, **address_p; int flags; ssize_t data_size; size_t packet_len; struct timeval timestamp; decode_fail_t reason; fr_time_t *recv_time_p; *leftover = 0; /* always for UDP */ /* * Where the addresses should go. This is a special case * for proto_radius. */ address_p = (fr_io_address_t **) packet_ctx; address = *address_p; recv_time_p = *recv_time; /* * Tell udp_recv if we're connected or not. */ flags = UDP_FLAGS_CONNECTED * (thread->connection != NULL); data_size = udp_recv(thread->sockfd, buffer, buffer_len, flags, &address->src_ipaddr, &address->src_port, &address->dst_ipaddr, &address->dst_port, &address->if_index, ×tamp); if (data_size < 0) { DEBUG2("proto_radius_udp got read error: %s", fr_strerror()); return data_size; } if (!data_size) { DEBUG2("proto_radius_udp got no data: ignoring"); return 0; } packet_len = data_size; if (data_size < 20) { DEBUG2("proto_radius_udp got 'too short' packet size %zd", data_size); thread->stats.total_malformed_requests++; return 0; } if (packet_len > inst->max_packet_size) { DEBUG2("proto_radius_udp got 'too long' packet size %zd > %u", data_size, inst->max_packet_size); thread->stats.total_malformed_requests++; return 0; } if ((buffer[0] == 0) || (buffer[0] > FR_MAX_PACKET_CODE)) { DEBUG("proto_radius_udp got invalid packet code %d", buffer[0]); thread->stats.total_unknown_types++; return 0; } /* * If it's not a RADIUS packet, ignore it. */ if (!fr_radius_ok(buffer, &packet_len, inst->max_attributes, false, &reason)) { /* * @todo - check for F5 load balancer packets. <sigh> */ DEBUG2("proto_radius_udp got a packet which isn't RADIUS"); thread->stats.total_malformed_requests++; return 0; } // @todo - maybe convert timestamp? *recv_time_p = fr_time(); /* * proto_radius sets the priority */ /* * Print out what we received. */ DEBUG2("proto_radius_udp - Received %s ID %d length %d %s", fr_packet_codes[buffer[0]], buffer[1], (int) packet_len, thread->name); return packet_len; }