diff --git a/src/arp.c b/src/arp.c index d6532d5..1f321ad 100644 --- a/src/arp.c +++ b/src/arp.c @@ -62,52 +62,7 @@ int arp_probe_max = 2000; // maximum delay until repeated probe (ms) #define RATE_LIMIT_INTERVAL 60000 // delay between successive attempts #define DEFEND_INTERVAL 10000 // minimum interval between defensive ARPs -typedef enum { - AS_NONE = 0, // Nothing to react to wrt ARP - AS_COLLISION_CHECK, // Checking to see if another host has our IP before - // accepting a new lease. - AS_GW_CHECK, // Seeing if the default GW still exists on the local - // segment after the hardware link was lost. - AS_GW_QUERY, // Finding the default GW MAC address. - AS_DEFENSE, // Defending our IP address (RFC5227) - AS_MAX, -} arp_state_t; - -typedef enum { - ASEND_COLLISION_CHECK, - ASEND_GW_PING, - ASEND_ANNOUNCE, - ASEND_MAX, -} arp_send_t; - -struct arp_stats { - long long ts; - int count; -}; - -struct arp_data { - struct dhcpmsg dhcp_packet; // Used only for AS_COLLISION_CHECK - struct arpMsg reply; - struct arp_stats send_stats[ASEND_MAX]; - long long wake_ts[AS_MAX]; - long long last_conflict_ts; // TS of the last conflicting ARP seen. - long long arp_check_start_ts; // TS of when we started the - // AS_COLLISION_CHECK state. - size_t reply_offset; - arp_state_t state; - unsigned int total_conflicts; // Total number of address conflicts on - // the interface. Never decreases. - int gw_check_initpings; // Initial count of ASEND_GW_PING when - // AS_GW_CHECK was entered. - uint16_t probe_wait_time; // Time to wait for a COLLISION_CHECK reply - // (in ms?). - bool using_bpf:1; // Is a BPF installed on the ARP socket? - bool relentless_def:1; // Don't give up defense no matter what. - bool router_replied:1; - bool server_replied:1; -}; - -static struct arp_data garp = { +struct arp_data garp = { .state = AS_NONE, .wake_ts = { -1, -1, -1, -1, -1 }, .send_stats = {{0,0},{0,0},{0,0}}, @@ -237,7 +192,7 @@ void arp_close_fd(struct client_state_t cs[static 1]) garp.wake_ts[i] = -1; } -static void arp_reopen_fd(struct client_state_t cs[static 1]) +void arp_reopen_fd(struct client_state_t cs[static 1]) { arp_state_t prev_state = garp.state; arp_min_close_fd(cs); @@ -406,7 +361,7 @@ static int arp_get_gw_hwaddr(struct client_state_t cs[static 1]) return 0; } -static void arp_failed(struct client_state_t cs[static 1]) +void arp_failed(struct client_state_t cs[static 1]) { log_line("%s: arp: Offered address is in use. Declining.", client_config.interface); @@ -427,7 +382,7 @@ static void arp_failed(struct client_state_t cs[static 1]) 0 : RATE_LIMIT_INTERVAL); } -static void arp_gw_failed(struct client_state_t cs[static 1]) +void arp_gw_failed(struct client_state_t cs[static 1]) { garp.wake_ts[AS_GW_CHECK] = -1; reinit_selecting(cs, 0); @@ -794,31 +749,31 @@ static const arp_state_fn_t arp_states[] = { { arp_do_invalid, 0 }, // AS_MAX }; -void handle_arp_response(struct client_state_t cs[static 1]) +void arp_packet_action(struct client_state_t cs[static 1]) +{ + if (arp_states[garp.state].packet_fn) + arp_states[garp.state].packet_fn(cs); + arp_reply_clear(); +} + +int arp_packet_get(struct client_state_t cs[static 1]) { ssize_t r = 0; if (garp.reply_offset < sizeof garp.reply) { r = safe_read(cs->arpFd, (char *)&garp.reply + garp.reply_offset, sizeof garp.reply - garp.reply_offset); + if (r == 0) + return ARPR_CLOSED; if (r < 0) { log_error("%s: (%s) ARP response read failed: %s", client_config.interface, __func__, strerror(errno)); - switch (garp.state) { - case AS_COLLISION_CHECK: arp_failed(cs); break; - case AS_GW_CHECK: arp_gw_failed(cs); break; - default: arp_reopen_fd(cs); break; - } - } else - garp.reply_offset += (size_t)r; - } - - if (r <= 0) { - handle_arp_timeout(cs, curms()); - return; + return ARPR_ERROR; + } + garp.reply_offset += (size_t)r; } if (garp.reply_offset < ARP_MSG_SIZE) - return; + return ARPR_NONE; // Emulate the BPF filters if they are not in use. if (!garp.using_bpf && @@ -826,12 +781,9 @@ void handle_arp_response(struct client_state_t cs[static 1]) (garp.state == AS_DEFENSE && !arp_validate_bpf_defense(cs, &garp.reply)))) { arp_reply_clear(); - return; + return ARPR_NONE; } - - if (arp_states[garp.state].packet_fn) - arp_states[garp.state].packet_fn(cs); - arp_reply_clear(); + return ARPR_PENDING; } // Perform retransmission if necessary. diff --git a/src/arp.h b/src/arp.h index 38de05a..9e474e9 100644 --- a/src/arp.h +++ b/src/arp.h @@ -58,6 +58,53 @@ extern int arp_probe_num; extern int arp_probe_min; extern int arp_probe_max; +typedef enum { + AS_NONE = 0, // Nothing to react to wrt ARP + AS_COLLISION_CHECK, // Checking to see if another host has our IP before + // accepting a new lease. + AS_GW_CHECK, // Seeing if the default GW still exists on the local + // segment after the hardware link was lost. + AS_GW_QUERY, // Finding the default GW MAC address. + AS_DEFENSE, // Defending our IP address (RFC5227) + AS_MAX, +} arp_state_t; + +typedef enum { + ASEND_COLLISION_CHECK, + ASEND_GW_PING, + ASEND_ANNOUNCE, + ASEND_MAX, +} arp_send_t; + +struct arp_stats { + long long ts; + int count; +}; + +struct arp_data { + struct dhcpmsg dhcp_packet; // Used only for AS_COLLISION_CHECK + struct arpMsg reply; + struct arp_stats send_stats[ASEND_MAX]; + long long wake_ts[AS_MAX]; + long long last_conflict_ts; // TS of the last conflicting ARP seen. + long long arp_check_start_ts; // TS of when we started the + // AS_COLLISION_CHECK state. + size_t reply_offset; + arp_state_t state; + unsigned int total_conflicts; // Total number of address conflicts on + // the interface. Never decreases. + int gw_check_initpings; // Initial count of ASEND_GW_PING when + // AS_GW_CHECK was entered. + uint16_t probe_wait_time; // Time to wait for a COLLISION_CHECK reply + // (in ms?). + bool using_bpf:1; // Is a BPF installed on the ARP socket? + bool relentless_def:1; // Don't give up defense no matter what. + bool router_replied:1; + bool server_replied:1; +}; + +extern struct arp_data garp; + void set_arp_relentless_def(bool v); void arp_reset_send_stats(void); void arp_close_fd(struct client_state_t cs[static 1]); @@ -66,7 +113,19 @@ int arp_check(struct client_state_t cs[static 1], int arp_gw_check(struct client_state_t cs[static 1]); void arp_set_defense_mode(struct client_state_t cs[static 1]); void arp_success(struct client_state_t cs[static 1]); -void handle_arp_response(struct client_state_t cs[static 1]); +void arp_failed(struct client_state_t cs[static 1]); +void arp_gw_failed(struct client_state_t cs[static 1]); +void arp_reopen_fd(struct client_state_t cs[static 1]); + +enum { + ARPR_NONE = 0, + ARPR_ERROR, + ARPR_PENDING, + ARPR_CLOSED, +}; +void arp_packet_action(struct client_state_t cs[static 1]); +int arp_packet_get(struct client_state_t cs[static 1]); + void handle_arp_timeout(struct client_state_t cs[static 1], long long nowts); long long arp_get_wake_ts(void); diff --git a/src/coroutine.h b/src/coroutine.h new file mode 100644 index 0000000..d54a718 --- /dev/null +++ b/src/coroutine.h @@ -0,0 +1,181 @@ +/* coroutine.h + * + * Coroutine mechanics, implemented on top of standard ANSI C. See + * http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html for + * a full discussion of the theory behind this. + * + * To use these macros to define a coroutine, you need to write a + * function that looks something like this. + * + * [Simple version using static variables (scr macros)] + * int ascending (void) { + * static int i; + * + * scrBegin; + * for (i=0; i<10; i++) { + * scrReturn(i); + * } + * scrFinish(-1); + * } + * + * [Re-entrant version using an explicit context structure (ccr macros)] + * int ascending (ccrContParam) { + * ccrBeginContext; + * int i; + * ccrEndContext(foo); + * + * ccrBegin(foo); + * for (foo->i=0; foo->i<10; foo->i++) { + * ccrReturn(foo->i); + * } + * ccrFinish(-1); + * } + * + * In the static version, you need only surround the function body + * with `scrBegin' and `scrFinish', and then you can do `scrReturn' + * within the function and on the next call control will resume + * just after the scrReturn statement. Any local variables you need + * to be persistent across an `scrReturn' must be declared static. + * + * In the re-entrant version, you need to declare your persistent + * variables between `ccrBeginContext' and `ccrEndContext'. These + * will be members of a structure whose name you specify in the + * parameter to `ccrEndContext'. + * + * The re-entrant macros will malloc() the state structure on first + * call, and free() it when `ccrFinish' is reached. If you want to + * abort in the middle, you can use `ccrStop' to free the state + * structure immediately (equivalent to an explicit return() in a + * caller-type routine). + * + * A coroutine returning void type may call `ccrReturnV', + * `ccrFinishV' and `ccrStopV', or `scrReturnV', to avoid having to + * specify an empty parameter to the ordinary return macros. + * + * Ground rules: + * - never put `ccrReturn' or `scrReturn' within an explicit `switch'. + * - never put two `ccrReturn' or `scrReturn' statements on the same + * source line. + * + * The caller of a static coroutine calls it just as if it were an + * ordinary function: + * + * void main(void) { + * int i; + * do { + * i = ascending(); + * printf("got number %d\n", i); + * } while (i != -1); + * } + * + * The caller of a re-entrant coroutine must provide a context + * variable: + * + * void main(void) { + * ccrContext z = 0; + * do { + * printf("got number %d\n", ascending (&z)); + * } while (z); + * } + * + * Note that the context variable is set back to zero when the + * coroutine terminates (by crStop, or by control reaching + * crFinish). This can make the re-entrant coroutines more useful + * than the static ones, because you can tell when they have + * finished. + * + * If you need to dispose of a crContext when it is non-zero (that + * is, if you want to stop calling a coroutine without suffering a + * memory leak), the caller should call `ccrAbort(ctx)' where `ctx' + * is the context variable. + * + * This mechanism could have been better implemented using GNU C + * and its ability to store pointers to labels, but sadly this is + * not part of the ANSI C standard and so the mechanism is done by + * case statements instead. That's why you can't put a crReturn() + * inside a switch() statement. + */ + +/* + * coroutine.h is copyright 1995,2000 Simon Tatham. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id$ + */ + +#ifndef COROUTINE_H +#define COROUTINE_H + +#include + +/* + * `scr' macros for static coroutines. + */ + +#define scrBegin static int scrLine = 0; switch(scrLine) { case 0:; +#define scrFinish(z) } return (z) +#define scrFinishV } return + +#define scrReturn(z) \ + do {\ + scrLine=__LINE__;\ + return (z); case __LINE__:;\ + } while (0) +#define scrReturnV \ + do {\ + scrLine=__LINE__;\ + return; case __LINE__:;\ + } while (0) + +/* + * `ccr' macros for re-entrant coroutines. + */ + +#define ccrContParam void **ccrParam + +#define ccrBeginContext struct ccrContextTag { int ccrLine +#define ccrEndContext(x) } *x = (struct ccrContextTag *)*ccrParam + +#define ccrBegin(x) if(!x) {x= *ccrParam=malloc(sizeof(*x)); x->ccrLine=0;}\ + if (x) switch(x->ccrLine) { case 0:; +#define ccrFinish(z) } free(*ccrParam); *ccrParam=0; return (z) +#define ccrFinishV } free(*ccrParam); *ccrParam=0; return + +#define ccrReturn(z) \ + do {\ + ((struct ccrContextTag *)*ccrParam)->ccrLine=__LINE__;\ + return (z); case __LINE__:;\ + } while (0) +#define ccrReturnV \ + do {\ + ((struct ccrContextTag *)*ccrParam)->ccrLine=__LINE__;\ + return; case __LINE__:;\ + } while (0) + +#define ccrStop(z) do{ free(*ccrParam); *ccrParam=0; return (z); }while(0) +#define ccrStopV do{ free(*ccrParam); *ccrParam=0; return; }while(0) + +#define ccrContext void * +#define ccrAbort(ctx) do { free (ctx); ctx = 0; } while (0) + +#endif /* COROUTINE_H */ diff --git a/src/dhcp.c b/src/dhcp.c index 25a2cfc..908ef91 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -380,13 +380,14 @@ static int validate_dhcp_packet(struct client_state_t cs[static 1], return 1; } -void handle_packet(struct client_state_t cs[static 1]) +int dhcp_packet_get(struct client_state_t cs[static 1], + struct dhcpmsg packet[static 1], + uint8_t msgtype[static 1], + uint32_t srcaddr[static 1]) { if (cs->listenFd < 0) - return; - struct dhcpmsg packet; - uint32_t srcaddr; - ssize_t r = get_raw_packet(cs, &packet, &srcaddr); + return -1; + ssize_t r = get_raw_packet(cs, packet, srcaddr); if (r < 0) { // Not a transient issue handled by packet collection functions. if (r != -2) { @@ -395,12 +396,11 @@ void handle_packet(struct client_state_t cs[static 1]) stop_dhcp_listen(cs); start_dhcp_listen(cs); } - return; + return -1; } - uint8_t msgtype; - if (!validate_dhcp_packet(cs, (size_t)r, &packet, &msgtype)) - return; - packet_action(cs, &packet, msgtype, srcaddr); + if (!validate_dhcp_packet(cs, (size_t)r, packet, msgtype)) + return -1; + return 0; } // Initialize a DHCP client packet that will be sent to a server diff --git a/src/dhcp.h b/src/dhcp.h index bd92e96..15ae7b4 100644 --- a/src/dhcp.h +++ b/src/dhcp.h @@ -83,7 +83,10 @@ struct udp_dhcp_packet { void start_dhcp_listen(struct client_state_t cs[static 1]); void stop_dhcp_listen(struct client_state_t cs[static 1]); -void handle_packet(struct client_state_t cs[static 1]); +int dhcp_packet_get(struct client_state_t cs[static 1], + struct dhcpmsg packet[static 1], + uint8_t msgtype[static 1], + uint32_t srcaddr[static 1]); ssize_t send_discover(struct client_state_t cs[static 1]); ssize_t send_selecting(struct client_state_t cs[static 1]); ssize_t send_renew(struct client_state_t cs[static 1]); diff --git a/src/ndhc.c b/src/ndhc.c index ddc88da..36dce4e 100644 --- a/src/ndhc.c +++ b/src/ndhc.c @@ -182,7 +182,13 @@ static void setup_signals_ndhc(void) epoll_add(cs.epollFd, cs.signalFd); } -static void signal_dispatch(void) +enum { + SIGNAL_NONE = 0, + SIGNAL_RENEW, + SIGNAL_RELEASE +}; + +static int signal_dispatch(void) { struct signalfd_siginfo si; memset(&si, 0, sizeof si); @@ -190,24 +196,22 @@ static void signal_dispatch(void) if (r < 0) { log_error("%s: ndhc: error reading from signalfd: %s", client_config.interface, strerror(errno)); - return; + return SIGNAL_NONE; } if ((size_t)r < sizeof si) { log_error("%s: ndhc: short read from signalfd: %zd < %zu", client_config.interface, r, sizeof si); - return; + return SIGNAL_NONE; } switch (si.ssi_signo) { - case SIGUSR1: force_renew_action(&cs); break; - case SIGUSR2: force_release_action(&cs); break; + case SIGUSR1: return SIGNAL_RENEW; + case SIGUSR2: return SIGNAL_RELEASE; case SIGCHLD: suicide("ndhc-master: Subprocess terminated unexpectedly. Exiting."); - break; case SIGTERM: log_line("Received SIGTERM. Exiting gracefully."); exit(EXIT_SUCCESS); - break; - default: break; + default: return SIGNAL_NONE; } } @@ -261,6 +265,7 @@ static void fail_if_state_dir_dne(void) #define NDHC_NUM_EP_FDS 7 static void do_ndhc_work(void) { + struct dhcpmsg dhcp_packet; struct epoll_event events[NDHC_NUM_EP_FDS]; long long nowts; int timeout; @@ -284,27 +289,53 @@ static void do_ndhc_work(void) goto jumpstart; for (;;) { - int r = epoll_wait(cs.epollFd, events, NDHC_NUM_EP_FDS, timeout); - if (r < 0) { + int maxi = epoll_wait(cs.epollFd, events, NDHC_NUM_EP_FDS, timeout); + if (maxi < 0) { if (errno == EINTR) continue; else suicide("epoll_wait failed"); } - for (int i = 0; i < r; ++i) { + for (int i = 0; i < maxi; ++i) { int fd = events[i].data.fd; if (fd == cs.signalFd) { - if (events[i].events & EPOLLIN) - signal_dispatch(); + if (!(events[i].events & EPOLLIN)) + return; + int sigv = signal_dispatch(); + if (sigv == SIGNAL_RENEW) + force_renew_action(&cs); + else if (sigv == SIGNAL_RELEASE) + force_release_action(&cs); } else if (fd == cs.listenFd) { - if (events[i].events & EPOLLIN) - handle_packet(&cs); + if (!(events[i].events & EPOLLIN)) + return; + uint32_t srcaddr; + uint8_t msgtype; + int r = dhcp_packet_get(&cs, &dhcp_packet, &msgtype, &srcaddr); + if (!r) + packet_action(&cs, &dhcp_packet, msgtype, srcaddr); } else if (fd == cs.arpFd) { - if (events[i].events & EPOLLIN) - handle_arp_response(&cs); + if (!(events[i].events & EPOLLIN)) + return; + int r = arp_packet_get(&cs); + if (r == ARPR_PENDING) { + arp_packet_action(&cs); + } else if (r == ARPR_ERROR) { + if (garp.state == AS_COLLISION_CHECK) + arp_failed(&cs); + else if (garp.state == AS_GW_CHECK) + arp_gw_failed(&cs); + else + arp_reopen_fd(&cs); + handle_arp_timeout(&cs, curms()); + } else if (r == ARPR_CLOSED) + handle_arp_timeout(&cs, curms()); } else if (fd == cs.nlFd) { - if (events[i].events & EPOLLIN) - handle_nl_message(&cs); + if (!(events[i].events & EPOLLIN)) + return; + int nl_event = nl_event_get(&cs); + if (nl_event != IFS_NONE) + nl_event_react(&cs, nl_event); } else if (fd == ifchStream[0]) { if (events[i].events & (EPOLLHUP|EPOLLERR|EPOLLRDHUP)) exit(EXIT_FAILURE); @@ -312,8 +343,30 @@ static void do_ndhc_work(void) if (events[i].events & (EPOLLHUP|EPOLLERR|EPOLLRDHUP)) exit(EXIT_FAILURE); } else if (fd == cs.rfkillFd && client_config.enable_rfkill) { - if (events[i].events & EPOLLIN) - handle_rfkill_notice(&cs, client_config.rfkillIdx); + if (!(events[i].events & EPOLLIN)) + return; + int rfk = rfkill_get(&cs, 1, client_config.rfkillIdx); + if (rfk == RFK_ENABLED) { + cs.rfkill_set = 1; + if (cs.ifsPrevState == IFS_UP) { + log_line("rfkill: radio now blocked; bringing interface down"); + cs.ifsPrevState = IFS_DOWN; + ifnocarrier_action(&cs); + } else + log_line("rfkill: radio now blocked, but interface isn't up"); + } else if (rfk == RFK_DISABLED) { + cs.rfkill_set = 0; + if (cs.ifsPrevState == IFS_DOWN) { + log_line("rfkill: radio now unblocked; bringing interface up"); + cs.ifsPrevState = IFS_UP; + ifup_action(&cs); + } else { + if (cs.ifsPrevState == IFS_SHUT) + log_line("rfkill: radio now unblocked, but interface was shut down by user"); + else + log_line("rfkill: radio now unblocked, but interface is removed"); + } + } } else suicide("epoll_wait: unknown fd"); } @@ -461,7 +514,6 @@ void background(void) static void wait_for_rfkill() { - cs.rfkill_set = 1; struct epoll_event events[2]; cs.rfkillFd = rfkill_open(&client_config.enable_rfkill); if (cs.rfkillFd < 0) @@ -480,13 +532,20 @@ static void wait_for_rfkill() } for (int i = 0; i < r; ++i) { int fd = events[i].data.fd; - if (fd == cs.rfkillFd) { - if (events[i].events & EPOLLIN) { - if (!rfkill_wait_for_end(&cs)) - goto rfkill_gone; - } - } else + if (fd != cs.rfkillFd) suicide("epoll_wait: unknown fd"); + if (events[i].events & EPOLLIN) { + int rfk = rfkill_get(&cs, 0, 0); + if (rfk == RFK_DISABLED) { + switch (perform_ifup()) { + case 1: case 0: goto rfkill_gone; + case -3: + log_line("rfkill: radio immediately blocked again; spurious?"); + break; + default: suicide("failed to set the interface to up state"); + } + } + } } } rfkill_gone: diff --git a/src/netlink.c b/src/netlink.c index 29bd389..8c9ded4 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -43,67 +43,81 @@ #include "nl.h" #include "state.h" -static void nl_process_msgs(const struct nlmsghdr *nlh, void *data) +int nl_event_react(struct client_state_t cs[static 1], int state) { - struct ifinfomsg *ifm = NLMSG_DATA(nlh); - struct client_state_t *cs = data; + if (state == cs->ifsPrevState) + return -1; // If the rfkill switch is set, a lot of netlink state change // commands will fail outright, so just ignore events until // it is gone. if (cs->rfkill_set) - return; + return -1; - switch(nlh->nlmsg_type) { - case RTM_NEWLINK: - if (ifm->ifi_index != client_config.ifindex) - break; - // IFF_UP corresponds to ifconfig down or ifconfig up. - if (ifm->ifi_flags & IFF_UP) { - // IFF_RUNNING is the hardware carrier. - if (ifm->ifi_flags & IFF_RUNNING) { - if (cs->ifsPrevState != IFS_UP) { - cs->ifsPrevState = IFS_UP; - ifup_action(cs); - } - } else if (cs->ifsPrevState != IFS_DOWN) { - // Interface configured, but no hardware carrier. - cs->ifsPrevState = IFS_DOWN; - ifnocarrier_action(cs); - } - } else if (cs->ifsPrevState != IFS_SHUT) { - // User shut down the interface. - cs->ifsPrevState = IFS_SHUT; - ifdown_action(cs); - } - break; - case RTM_DELLINK: - if (ifm->ifi_index != client_config.ifindex) - break; - if (cs->ifsPrevState != IFS_REMOVED) { - cs->ifsPrevState = IFS_REMOVED; - log_line("Interface removed. Exiting."); - exit(EXIT_SUCCESS); - } - break; - default: - break; + switch (state) { + case IFS_UP: + cs->ifsPrevState = IFS_UP; + ifup_action(cs); + break; + case IFS_DOWN: + // Interface configured, but no hardware carrier. + cs->ifsPrevState = IFS_DOWN; + ifnocarrier_action(cs); + break; + case IFS_SHUT: + // User shut down the interface. + cs->ifsPrevState = IFS_SHUT; + ifdown_action(cs); + break; + case IFS_REMOVED: + cs->ifsPrevState = IFS_REMOVED; + log_line("Interface removed. Exiting."); + exit(EXIT_SUCCESS); + break; + default: break; } + return 0; } -void handle_nl_message(struct client_state_t cs[static 1]) +static int nl_process_msgs_return; +static void nl_process_msgs(const struct nlmsghdr *nlh, void *data) +{ + (void)data; + struct ifinfomsg *ifm = NLMSG_DATA(nlh); + + if (ifm->ifi_index != client_config.ifindex) + return; + + if (nlh->nlmsg_type == RTM_NEWLINK) { + // IFF_UP corresponds to ifconfig down or ifconfig up. + // IFF_RUNNING is the hardware carrier. + if (ifm->ifi_flags & IFF_UP) { + if (ifm->ifi_flags & IFF_RUNNING) + nl_process_msgs_return = IFS_UP; + else + nl_process_msgs_return = IFS_DOWN; + } else { + nl_process_msgs_return = IFS_SHUT; + } + } else if (nlh->nlmsg_type == RTM_DELLINK) + nl_process_msgs_return = IFS_REMOVED; +} + +int nl_event_get(struct client_state_t cs[static 1]) { char nlbuf[8192]; ssize_t ret; assert(cs->nlFd != -1); + nl_process_msgs_return = IFS_NONE; do { ret = nl_recv_buf(cs->nlFd, nlbuf, sizeof nlbuf); if (ret < 0) break; - if (nl_foreach_nlmsg(nlbuf, ret, 0, cs->nlPortId, nl_process_msgs, cs) + if (nl_foreach_nlmsg(nlbuf, ret, 0, cs->nlPortId, nl_process_msgs, 0) < 0) break; } while (ret > 0); + return nl_process_msgs_return; } static int get_if_index_and_mac(const struct nlmsghdr *nlh, diff --git a/src/netlink.h b/src/netlink.h index 368cb53..5812140 100644 --- a/src/netlink.h +++ b/src/netlink.h @@ -40,7 +40,8 @@ enum { IFS_REMOVED }; -void handle_nl_message(struct client_state_t cs[static 1]); +int nl_event_react(struct client_state_t cs[static 1], int state); +int nl_event_get(struct client_state_t cs[static 1]); int nl_getifdata(void); #endif /* NK_NETLINK_H_ */ diff --git a/src/rfkill.c b/src/rfkill.c index c2cec6e..b12cebf 100644 --- a/src/rfkill.c +++ b/src/rfkill.c @@ -26,7 +26,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include #include #include #include @@ -36,8 +35,6 @@ #include "nk/log.h" #include "nk/io.h" #include "ndhc.h" -#include "netlink.h" -#include "ifset.h" #include "rfkill.h" int rfkill_open(char enable_rfkill[static 1]) @@ -53,90 +50,31 @@ int rfkill_open(char enable_rfkill[static 1]) return r; } -static int rfkill_check(struct client_state_t cs[static 1], - int (*rfenable)(struct client_state_t[static 1]), - int (*rfdisable)(struct client_state_t[static 1]), - bool check_idx, uint32_t rfkidx) +// check_idx: Does rfkidx have any meaning? +// rfkidx: Pay attention only to this radio kill switch number. +int rfkill_get(struct client_state_t cs[static 1], + int check_idx, uint32_t rfkidx) { struct rfkill_event event; ssize_t len = safe_read(cs->rfkillFd, (char *)&event, sizeof event); if (len < 0) { log_error("rfkill: safe_read failed: %s", strerror(errno)); - return -1; + return RFK_FAIL; } if (len != RFKILL_EVENT_SIZE_V1) { log_error("rfkill: event has unexpected size: %d", len); - return -1; + return RFK_FAIL; } log_line("rfkill: idx[%u] type[%u] op[%u] soft[%u] hard[%u]", event.idx, event.type, event.op, event.soft, event.hard); if (check_idx && event.idx != rfkidx) - return 0; + return RFK_NONE; if (event.op != RFKILL_OP_CHANGE && event.op != RFKILL_OP_CHANGE_ALL) - return 0; + return RFK_NONE; if (event.soft || event.hard) { - return rfenable(cs); + return RFK_ENABLED; } else { - return rfdisable(cs); + return RFK_DISABLED; } } -static int handle_rfkill_notice_enable(struct client_state_t cs[static 1]) -{ - cs->rfkill_set = 1; - if (cs->ifsPrevState == IFS_UP) { - log_line("rfkill: radio now blocked; bringing interface down"); - cs->ifsPrevState = IFS_DOWN; - ifnocarrier_action(cs); - } else - log_line("rfkill: radio now blocked, but interface isn't up"); - return 0; -} - -static int handle_rfkill_notice_disable(struct client_state_t cs[static 1]) -{ - cs->rfkill_set = 0; - if (cs->ifsPrevState == IFS_DOWN) { - log_line("rfkill: radio now unblocked; bringing interface up"); - cs->ifsPrevState = IFS_UP; - ifup_action(cs); - } else { - if (cs->ifsPrevState == IFS_SHUT) - log_line("rfkill: radio now unblocked, but interface was shut down by user"); - else - log_line("rfkill: radio now unblocked, but interface is removed"); - } - return 0; -} - -static int rfkill_wait_for_end_enable(struct client_state_t cs[static 1]) -{ - (void)cs; - return -1; -} - -static int rfkill_wait_for_end_disable(struct client_state_t cs[static 1]) -{ - switch (perform_ifup()) { - case 1: case 0: - cs->rfkill_set = 0; - return 0; - case -3: - log_line("rfkill: radio immediately blocked again; spurious?"); - return -1; - default: suicide("failed to set the interface to up state"); - } -} - -int handle_rfkill_notice(struct client_state_t cs[static 1], uint32_t rfkidx) -{ - return rfkill_check(cs, handle_rfkill_notice_enable, - handle_rfkill_notice_disable, true, rfkidx); -} - -int rfkill_wait_for_end(struct client_state_t cs[static 1]) -{ - return rfkill_check(cs, rfkill_wait_for_end_enable, - rfkill_wait_for_end_disable, false, 0); -} - diff --git a/src/rfkill.h b/src/rfkill.h index 6fb05a8..29fdfac 100644 --- a/src/rfkill.h +++ b/src/rfkill.h @@ -28,9 +28,16 @@ * POSSIBILITY OF SUCH DAMAGE. */ +enum { + RFK_NONE = 0, + RFK_FAIL, + RFK_ENABLED, + RFK_DISABLED, +}; + int rfkill_open(char enable_rfkill[static 1]); -int handle_rfkill_notice(struct client_state_t cs[static 1], uint32_t rfkidx); -int rfkill_wait_for_end(struct client_state_t cs[static 1]); +int rfkill_get(struct client_state_t cs[static 1], + int check_idx, uint32_t rfkidx); #endif