int run_udp_server(fko_srv_options_t *opts) { int s_sock, sfd_flags, selval, pkt_len; int is_err, s_timeout, rv=1, chk_rm_all=0; int rules_chk_threshold; fd_set sfd_set; struct sockaddr_in saddr, caddr; struct timeval tv; char sipbuf[MAX_IPV4_STR_LEN] = {0}; char dgram_msg[MAX_SPA_PACKET_LEN+1] = {0}; unsigned short port; socklen_t clen; port = strtol_wrapper(opts->config[CONF_UDPSERV_PORT], 1, MAX_PORT, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { log_msg(LOG_ERR, "[*] Invalid max UDPSERV_PORT value."); return -1; } s_timeout = strtol_wrapper(opts->config[CONF_UDPSERV_SELECT_TIMEOUT], 1, RCHK_MAX_UDPSERV_SELECT_TIMEOUT, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { log_msg(LOG_ERR, "[*] Invalid max UDPSERV_SELECT_TIMEOUT value."); return -1; } rules_chk_threshold = strtol_wrapper(opts->config[CONF_RULES_CHECK_THRESHOLD], 0, RCHK_MAX_RULES_CHECK_THRESHOLD, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { log_msg(LOG_ERR, "[*] invalid RULES_CHECK_THRESHOLD"); clean_exit(opts, FW_CLEANUP, EXIT_FAILURE); } log_msg(LOG_INFO, "Kicking off UDP server to listen on port %i.", port); /* Now, let's make a UDP server */ if ((s_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { log_msg(LOG_ERR, "run_udp_server: socket() failed: %s", strerror(errno)); return -1; } /* Make our main socket non-blocking so we don't have to be stuck on * listening for incoming datagrams. */ if((sfd_flags = fcntl(s_sock, F_GETFL, 0)) < 0) { log_msg(LOG_ERR, "run_udp_server: fcntl F_GETFL error: %s", strerror(errno)); close(s_sock); return -1; } sfd_flags |= O_NONBLOCK; if(fcntl(s_sock, F_SETFL, sfd_flags) < 0) { log_msg(LOG_ERR, "run_udp_server: fcntl F_SETFL error setting O_NONBLOCK: %s", strerror(errno)); close(s_sock); return -1; } /* Construct local address structure */ memset(&saddr, 0x0, sizeof(saddr)); saddr.sin_family = AF_INET; /* Internet address family */ saddr.sin_addr.s_addr = htonl(INADDR_ANY); /* Any incoming interface */ saddr.sin_port = htons(port); /* Local port */ /* Bind to the local address */ if (bind(s_sock, (struct sockaddr *) &saddr, sizeof(saddr)) < 0) { log_msg(LOG_ERR, "run_udp_server: bind() failed: %s", strerror(errno)); close(s_sock); return -1; } /* Initialize our signal handlers. You can check the return value for * the number of signals that were *not* set. Those that were not set * will be listed in the log/stderr output. */ if(set_sig_handlers() > 0) log_msg(LOG_ERR, "Errors encountered when setting signal handlers."); FD_ZERO(&sfd_set); /* Now loop and receive SPA packets */ while(1) { if(sig_do_stop()) { if(opts->verbose) log_msg(LOG_INFO, "udp_server: terminating signal received, will stop."); break; } /* Check for any expired firewall rules and deal with them. */ if(!opts->test) { if(rules_chk_threshold > 0) { opts->check_rules_ctr++; if ((opts->check_rules_ctr % rules_chk_threshold) == 0) { chk_rm_all = 1; opts->check_rules_ctr = 0; } } check_firewall_rules(opts, chk_rm_all); chk_rm_all = 0; } /* Initialize and setup the socket for select. */ FD_SET(s_sock, &sfd_set); /* Set our select timeout to (500ms by default). */ tv.tv_sec = 0; tv.tv_usec = s_timeout; selval = select(s_sock+1, &sfd_set, NULL, NULL, &tv); if(selval == -1) { if(errno == EINTR) { /* restart loop but only after we check for a terminating * signal above in sig_do_stop() */ continue; } else { log_msg(LOG_ERR, "run_udp_server: select error socket: %s", strerror(errno)); rv = -1; break; } } if(selval == 0) continue; if(! FD_ISSET(s_sock, &sfd_set)) continue; /* If we make it here then there is a datagram to process */ clen = sizeof(caddr); pkt_len = recvfrom(s_sock, dgram_msg, MAX_SPA_PACKET_LEN, 0, (struct sockaddr *)&caddr, &clen); dgram_msg[pkt_len] = 0x0; if(opts->verbose) { memset(sipbuf, 0x0, MAX_IPV4_STR_LEN); inet_ntop(AF_INET, &(caddr.sin_addr.s_addr), sipbuf, MAX_IPV4_STR_LEN); log_msg(LOG_INFO, "udp_server: Got UDP datagram (%d bytes) from: %s", pkt_len, sipbuf); } /* Expect the data to not be too large */ if(pkt_len <= MAX_SPA_PACKET_LEN) { /* Copy the packet for SPA processing */ strlcpy((char *)opts->spa_pkt.packet_data, dgram_msg, pkt_len+1); opts->spa_pkt.packet_data_len = pkt_len; opts->spa_pkt.packet_proto = IPPROTO_UDP; opts->spa_pkt.packet_src_ip = caddr.sin_addr.s_addr; opts->spa_pkt.packet_dst_ip = saddr.sin_addr.s_addr; opts->spa_pkt.packet_src_port = ntohs(caddr.sin_port); opts->spa_pkt.packet_dst_port = ntohs(saddr.sin_port); incoming_spa(opts); } memset(dgram_msg, 0x0, sizeof(dgram_msg)); opts->packet_ctr += 1; if(opts->foreground == 1 && opts->verbose > 2) log_msg(LOG_DEBUG, "run_udp_server() processed: %d packets", opts->packet_ctr); if (opts->packet_ctr_limit && opts->packet_ctr >= opts->packet_ctr_limit) { log_msg(LOG_WARNING, "* Incoming packet count limit of %i reached", opts->packet_ctr_limit ); break; } } /* infinite while loop */ close(s_sock); return rv; }
/* The pcap capture routine. */ int pcap_capture(fko_srv_options_t *opts) { pcap_t *pcap; char errstr[PCAP_ERRBUF_SIZE] = {0}; struct bpf_program fp; int res; int pcap_errcnt = 0; int pending_break = 0; int promisc = 0; int set_direction = 1; int pcap_file_mode = 0; int status; int chk_rm_all = 0; pid_t child_pid; #if FIREWALL_IPFW time_t now; #endif /* Set promiscuous mode if ENABLE_PCAP_PROMISC is set to 'Y'. */ if(strncasecmp(opts->config[CONF_ENABLE_PCAP_PROMISC], "Y", 1) == 0) promisc = 1; if(opts->config[CONF_PCAP_FILE] != NULL && opts->config[CONF_PCAP_FILE][0] != '\0') pcap_file_mode = 1; if(pcap_file_mode == 1) { log_msg(LOG_INFO, "Reading pcap file: %s", opts->config[CONF_PCAP_FILE]); pcap = pcap_open_offline(opts->config[CONF_PCAP_FILE], errstr); if(pcap == NULL) { log_msg(LOG_ERR, "[*] pcap_open_offline() error: %s", errstr); clean_exit(opts, FW_CLEANUP, EXIT_FAILURE); } } else { log_msg(LOG_INFO, "Sniffing interface: %s", opts->config[CONF_PCAP_INTF]); pcap = pcap_open_live(opts->config[CONF_PCAP_INTF], opts->max_sniff_bytes, promisc, 100, errstr ); if(pcap == NULL) { log_msg(LOG_ERR, "[*] pcap_open_live() error: %s", errstr); clean_exit(opts, FW_CLEANUP, EXIT_FAILURE); } } /* Set pcap filters, if any. */ if (opts->config[CONF_PCAP_FILTER][0] != '\0') { if(pcap_compile(pcap, &fp, opts->config[CONF_PCAP_FILTER], 1, 0) == -1) { log_msg(LOG_ERR, "[*] Error compiling pcap filter: %s", pcap_geterr(pcap) ); clean_exit(opts, FW_CLEANUP, EXIT_FAILURE); } if(pcap_setfilter(pcap, &fp) == -1) { log_msg(LOG_ERR, "[*] Error setting pcap filter: %s", pcap_geterr(pcap) ); clean_exit(opts, FW_CLEANUP, EXIT_FAILURE); } log_msg(LOG_INFO, "PCAP filter is: '%s'", opts->config[CONF_PCAP_FILTER]); pcap_freecode(&fp); } /* Determine and set the data link encapsulation offset. */ switch(pcap_datalink(pcap)) { case DLT_EN10MB: opts->data_link_offset = 14; break; #if defined(__linux__) case DLT_LINUX_SLL: opts->data_link_offset = 16; break; #elif defined(__OpenBSD__) case DLT_LOOP: set_direction = 0; opts->data_link_offset = 4; break; #endif case DLT_NULL: set_direction = 0; opts->data_link_offset = 4; break; default: opts->data_link_offset = 0; break; } /* We are only interested on seeing packets coming into the interface. */ if ((opts->pcap_any_direction == 0) && (set_direction == 1) && (pcap_file_mode == 0) && (pcap_setdirection(pcap, PCAP_D_IN) < 0)) if(opts->verbose) log_msg(LOG_WARNING, "[*] Warning: pcap error on setdirection: %s.", pcap_geterr(pcap)); /* Set our pcap handle nonblocking mode. * * NOTE: This is simply set to 0 for now until we find a need * to actually use this mode (which when set on a FreeBSD * system, it silently breaks the packet capture). */ if((pcap_file_mode == 0) && (pcap_setnonblock(pcap, DEF_PCAP_NONBLOCK, errstr)) == -1) { log_msg(LOG_ERR, "[*] Error setting pcap nonblocking to %i: %s", 0, errstr ); clean_exit(opts, FW_CLEANUP, EXIT_FAILURE); } /* Initialize our signal handlers. You can check the return value for * the number of signals that were *not* set. Those that were not set * will be listed in the log/stderr output. */ if(set_sig_handlers() > 0) log_msg(LOG_ERR, "Errors encountered when setting signal handlers."); log_msg(LOG_INFO, "Starting fwknopd main event loop."); /* Jump into our home-grown packet cature loop. */ while(1) { /* If we got a SIGCHLD and it was the tcp server, then handle it here. */ if(got_sigchld) { if(opts->tcp_server_pid > 0) { child_pid = waitpid(0, &status, WNOHANG); if(child_pid == opts->tcp_server_pid) { if(WIFSIGNALED(status)) log_msg(LOG_WARNING, "TCP server got signal: %i", WTERMSIG(status)); log_msg(LOG_WARNING, "TCP server exited with status of %i. Attempting restart.", WEXITSTATUS(status) ); opts->tcp_server_pid = 0; /* Attempt to restart tcp server ? */ usleep(1000000); run_tcp_server(opts); } } got_sigchld = 0; } if(sig_do_stop()) { pcap_breakloop(pcap); pending_break = 1; } res = pcap_dispatch(pcap, opts->pcap_dispatch_count, (pcap_handler)&process_packet, (unsigned char *)opts); /* Count processed packets */ if(res > 0) { if(opts->foreground == 1 && opts->verbose > 2) log_msg(LOG_DEBUG, "pcap_dispatch() processed: %d packets", res); /* Count the set of processed packets (pcap_dispatch() return * value) - we use this as a comparison for --packet-limit regardless * of SPA packet validity at this point. */ opts->packet_ctr += res; if (opts->packet_ctr_limit && opts->packet_ctr >= opts->packet_ctr_limit) { log_msg(LOG_WARNING, "* Incoming packet count limit of %i reached", opts->packet_ctr_limit ); pcap_breakloop(pcap); pending_break = 1; } } /* If there was an error, complain and go on (to an extent before * giving up). */ else if(res == -1) { if((strncasecmp(opts->config[CONF_EXIT_AT_INTF_DOWN], "Y", 1) == 0) && errno == ENETDOWN) { log_msg(LOG_ERR, "[*] Fatal error from pcap_dispatch: %s", pcap_geterr(pcap) ); clean_exit(opts, FW_CLEANUP, EXIT_FAILURE); } else { log_msg(LOG_ERR, "[*] Error from pcap_dispatch: %s", pcap_geterr(pcap) ); } if(pcap_errcnt++ > MAX_PCAP_ERRORS_BEFORE_BAIL) { log_msg(LOG_ERR, "[*] %i consecutive pcap errors. Giving up", pcap_errcnt ); clean_exit(opts, FW_CLEANUP, EXIT_FAILURE); } } else if(pending_break == 1 || res == -2) { /* pcap_breakloop was called, so we bail. */ log_msg(LOG_INFO, "Gracefully leaving the fwknopd event loop."); break; } else pcap_errcnt = 0; if(!opts->test) { if(opts->enable_fw) { /* Check for any expired firewall rules and deal with them. */ if(opts->rules_chk_threshold > 0) { opts->check_rules_ctr++; if ((opts->check_rules_ctr % opts->rules_chk_threshold) == 0) { chk_rm_all = 1; opts->check_rules_ctr = 0; } } check_firewall_rules(opts, chk_rm_all); chk_rm_all = 0; } /* See if any CMD_CYCLE_CLOSE commands need to be executed. */ cmd_cycle_close(opts); } #if FIREWALL_IPFW /* Purge expired rules that no longer have any corresponding * dynamic rules. */ if(opts->fw_config->total_rules > 0) { time(&now); if(opts->fw_config->last_purge < (now - opts->fw_config->purge_interval)) { ipfw_purge_expired_rules(opts); opts->fw_config->last_purge = now; } } #endif usleep(opts->pcap_loop_sleep); } pcap_close(pcap); return(0); }