##dhcp-probe-02-keep-pcap.patch - add option to keep pcap open all the time --- src/dhcp_probe.c.01 2009-08-16 11:52:26.000000000 +0300 +++ src/dhcp_probe.c 2009-08-16 12:31:22.000000000 +0300 @@ -49,6 +49,7 @@ */ int snaplen = CAPTURE_BUFSIZE; int socket_receive_timeout_feature = 0; +int keep_pcap = 0; char *prog = NULL; char *logfile_name = NULL; @@ -75,6 +76,89 @@ int use_8021q = 0; int vlan_id = 0; +int need_promiscuous(void) +{ + /* If we're going to claim a chaddr different than my_eaddr, some of the responses + may come back to chaddr (as opposed to my_eaddr or broadcast), so we'll need to + listen promiscuously. + If we're going to claim an ether_src different than my_eaddr, in theory that should + make no difference; bootp/dhcp servers should rely on chaddr, not ether_src. Still, + it's possible there's a server out there that does it wrong, and might therefore mistakenly + send responses to ether_src. So lets also listen promiscuously if ether_src != my_eaddr. + */ + int promiscuous = 0; + if (bcmp(GetChaddr(), &my_eaddr, sizeof(struct ether_addr)) || + bcmp(GetEther_src(), &my_eaddr, sizeof(struct ether_addr))) + promiscuous = 1; + return promiscuous; +} + +int init_pcap(int promiscuous, bpf_u_int32 netmask) +{ + /* open packet capture descriptor */ + /* XXX On Solaris 7, sometimes pcap_open_live() fails with a message like: + pcap_open_live qfe0: recv_ack: info unexpected primitive ack 0x8 + It's not clear what causes this, or what the 0x8 code indicates. + The error appears to be transient; retrying sometimes will work, so I've wrapped the call in a retry loop. + I've also added a delay after each failure; perhaps the failure has something to do with the fact that + we call pcap_open_live() so soon after pcap_close() (for the second and succeeding packets in each cycle); + adding a delay might help in that case. + */ + struct bpf_program bpf_code; + char pcap_errbuf[PCAP_ERRBUF_SIZE]; + int linktype; + int pcap_open_retries = PCAP_OPEN_LIVE_RETRY_MAX; + + do { + pcap_errbuf[0] = '\0'; /* so we can tell if a warning was produced on success */ + if ((pd = pcap_open_live(ifname, snaplen, promiscuous, GetResponse_wait_time(), pcap_errbuf)) != NULL) { + break; /* success */ + } else { /* failure */ + if (pcap_open_retries == 0) { + report(LOG_DEBUG, "pcap_open_live(%s): %s; retry count (%d) exceeded, giving up", ifname, pcap_errbuf, PCAP_OPEN_LIVE_RETRY_MAX); + my_exit(1, 1, 1); + } else { + if (debug > 1) + report(LOG_DEBUG, "pcap_open_live(%s): %s; will retry", ifname, pcap_errbuf); + sleep(PCAP_OPEN_LIVE_RETRY_DELAY); /* before next retry */ + } + } /* failure */ + } while (pcap_open_retries--); + + + if (pcap_errbuf[0] != '\0') + /* even on success, a warning may be produced */ + report(LOG_WARNING, "pcap_open_live(%s): succeeded but with warning: %s", ifname, pcap_errbuf); + + /* make sure this interface is ethernet */ + linktype = pcap_datalink(pd); + if (linktype != DLT_EN10MB) { + report(LOG_ERR, "interface %s link layer type %d not ethernet", ifname, linktype); + my_exit(1, 1, 1); + } + /* compile bpf filter to select just udp/ip traffic to udp port bootpc */ + if (pcap_compile(pd, &bpf_code, "udp dst port bootpc", 1, netmask) < 0) { + report(LOG_ERR, "pcap_compile: %s", pcap_geterr(pd)); + my_exit(1, 1, 1); + } + /* install compiled filter */ + if (pcap_setfilter(pd, &bpf_code) < 0) { + report(LOG_ERR, "pcap_setfilter: %s", pcap_geterr(pd)); + my_exit(1, 1, 1); + } + if (socket_receive_timeout_feature) + set_pcap_timeout(pd); + + return 0; +} + +void +reset_pcap() +{ + /* close packet capture descriptor */ + pcap_close(pd); +} + /* capture packets from pcap for timeout seconds */ int loop_for_packets(int timeout) @@ -115,8 +199,6 @@ /* for libpcap */ bpf_u_int32 netnumber, netmask; - struct bpf_program bpf_code; - int linktype; char pcap_errbuf[PCAP_ERRBUF_SIZE], pcap_errbuf2[PCAP_ERRBUF_SIZE]; /* get progname = last component of argv[0] */ @@ -126,7 +208,7 @@ else prog = argv[0]; - while ((c = getopt(argc, argv, "c:d:fhl:o:p:Q:s:Tvw:")) != EOF) { + while ((c = getopt(argc, argv, "c:d:fhkl:o:p:Q:s:Tvw:")) != EOF) { switch (c) { case 'c': if (optarg[0] != '/') { @@ -151,6 +233,9 @@ case 'h': usage(); my_exit(0, 0, 0); + case 'k': + keep_pcap = 1; + break; case 'l': if (optarg[0] != '/') { fprintf(stderr, "%s: invalid log file '%s', must be an absolute pathname\n", prog, optarg); @@ -447,8 +532,10 @@ } } + if (keep_pcap) + init_pcap(need_promiscuous(), netmask); + while (1) { /* MAIN EVENT LOOP */ - int promiscuous; libnet_t *l; /* to iterate through libnet context queue */ /* struct pcap_stat ps; */ /* to hold pcap stats */ @@ -489,26 +576,9 @@ interface in promiscuous mode as little as possible, since that can affect the host's performance. */ - /* If we're going to claim a chaddr different than my_eaddr, some of the responses - may come back to chaddr (as opposed to my_eaddr or broadcast), so we'll need to - listen promiscuously. - If we're going to claim an ether_src different than my_eaddr, in theory that should - make no difference; bootp/dhcp servers should rely on chaddr, not ether_src. Still, - it's possible there's a server out there that does it wrong, and might therefore mistakenly - send responses to ether_src. So lets also listen promiscuously if ether_src != my_eaddr. - */ - if (bcmp(GetChaddr(), &my_eaddr, sizeof(struct ether_addr)) || - bcmp(GetEther_src(), &my_eaddr, sizeof(struct ether_addr))) - promiscuous = 1; - else - promiscuous = 0; - - for (l = libnet_cq_head(); libnet_cq_last(); l = libnet_cq_next()) { /* write one flavor packet and listen for answers */ int packets_recv; - int pcap_open_retries; - /* We set up for packet capture BEFORE writing our packet, to minimize the delay between our writing and when we are able to start capturing. (I cannot tell from @@ -518,54 +588,9 @@ we wanted! */ - /* open packet capture descriptor */ - /* XXX On Solaris 7, sometimes pcap_open_live() fails with a message like: - pcap_open_live qfe0: recv_ack: info unexpected primitive ack 0x8 - It's not clear what causes this, or what the 0x8 code indicates. - The error appears to be transient; retrying sometimes will work, so I've wrapped the call in a retry loop. - I've also added a delay after each failure; perhaps the failure has something to do with the fact that - we call pcap_open_live() so soon after pcap_close() (for the second and succeeding packets in each cycle); - adding a delay might help in that case. - */ - pcap_open_retries = PCAP_OPEN_LIVE_RETRY_MAX; - while (pcap_open_retries--) { - pcap_errbuf[0] = '\0'; /* so we can tell if a warning was produced on success */ - if ((pd = pcap_open_live(ifname, snaplen, promiscuous, GetResponse_wait_time(), pcap_errbuf)) != NULL) { - break; /* success */ - } else { /* failure */ - if (pcap_open_retries == 0) { - report(LOG_DEBUG, "pcap_open_live(%s): %s; retry count (%d) exceeded, giving up", ifname, pcap_errbuf, PCAP_OPEN_LIVE_RETRY_MAX); - my_exit(1, 1, 1); - } else { - if (debug > 1) - report(LOG_DEBUG, "pcap_open_live(%s): %s; will retry", ifname, pcap_errbuf); - sleep(PCAP_OPEN_LIVE_RETRY_DELAY); /* before next retry */ - } - } /* failure */ - } - if (pcap_errbuf[0] != '\0') - /* even on success, a warning may be produced */ - report(LOG_WARNING, "pcap_open_live(%s): succeeded but with warning: %s", ifname, pcap_errbuf); - - /* make sure this interface is ethernet */ - linktype = pcap_datalink(pd); - if (linktype != DLT_EN10MB) { - report(LOG_ERR, "interface %s link layer type %d not ethernet", ifname, linktype); - my_exit(1, 1, 1); - } - /* compile bpf filter to select just udp/ip traffic to udp port bootpc */ - if (pcap_compile(pd, &bpf_code, "udp dst port bootpc", 1, netmask) < 0) { - report(LOG_ERR, "pcap_compile: %s", pcap_geterr(pd)); - my_exit(1, 1, 1); - } - /* install compiled filter */ - if (pcap_setfilter(pd, &bpf_code) < 0) { - report(LOG_ERR, "pcap_setfilter: %s", pcap_geterr(pd)); - my_exit(1, 1, 1); - } - if (socket_receive_timeout_feature) - set_pcap_timeout(pd); - + if (! keep_pcap) + init_pcap(need_promiscuous(), netmask); + /* write one packet */ if (debug > 10) @@ -621,7 +646,8 @@ */ /* close packet capture descriptor */ - pcap_close(pd); + if (! keep_pcap) + reset_pcap(); /* check for 'quit' request after each packet, since waiting until end of probe cycle would impose a substantial delay. */ @@ -669,7 +695,7 @@ reconfigure(write_packet_len); reread_config_file = 0; } - + /* We allow must signals that come in during our sleep() to interrupt us. E.g. we want to cut short our sleep when we're signalled to exit. But we must block SIGCHLD during our sleep. That's because if we forked an alert_program or alert_program2 child above, its termination will likely happen while we're sleeping; @@ -684,7 +710,19 @@ sigaddset(&new_sigset, SIGCHLD); sigprocmask(SIG_BLOCK, &new_sigset, &old_sigset); /* block SIGCHLD */ - sleep(time_to_sleep); + if (keep_pcap) { + /* If we're going to keep the packet capture running, + we might as well read off all the packets received while + waiting. We shouldn't get any since we don't send any requests + but this should prevent any buffers from accidentally filling + with unhandled packets. */ + int packets_recv = loop_for_packets(time_to_sleep); + + if (packets_recv && debug > 10) + report(LOG_DEBUG, "captured %d packets while sleeping", packets_recv); + } else { + sleep(time_to_sleep); + } sigprocmask(SIG_SETMASK, &old_sigset, NULL); /* unblock SIGCHLD */ @@ -692,8 +730,10 @@ } /* MAIN EVENT LOOP */ - /* we only reach here after receiving a signal requesting we quit */ + + if (keep_pcap) + reset_pcap(); if (pd_template) /* only used if a capture file requested */ pcap_close(pd_template); @@ -1142,6 +1182,7 @@ fprintf(stderr, " -d debuglevel enable debugging at specified level\n"); fprintf(stderr, " -f don't fork (only use for debugging)\n"); fprintf(stderr, " -h display this help message then exit\n"); + fprintf(stderr, " -k keep pcap open constantly (don't recreate on each cycle)\n"); fprintf(stderr, " -l log_file log to file instead of syslog\n"); fprintf(stderr, " -o capture_file enable capturing of unexpected answers\n"); fprintf(stderr, " -p pid_file override default pid file [%s]\n", PID_FILE);