/* $OpenBSD: handle.c,v 1.11 2007/02/08 11:15:55 reyk Exp $ */ /* * Copyright (c) 2005, 2006 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hostapd.h" int hostapd_handle_frame(struct hostapd_apme *, struct hostapd_frame *, u_int8_t *, const u_int); int hostapd_handle_action(struct hostapd_apme *, struct hostapd_frame *, u_int8_t *, u_int8_t *, u_int8_t *, u_int8_t *, const u_int); void hostapd_handle_addr(const u_int32_t, u_int32_t *, u_int8_t *, u_int8_t *, struct hostapd_table *); void hostapd_handle_ref(u_int, u_int, u_int8_t *, u_int8_t *, u_int8_t *, u_int8_t *); int hostapd_handle_radiotap(struct hostapd_radiotap *, u_int8_t *, const u_int); int hostapd_cmp(enum hostapd_op, int, int); int hostapd_handle_input(struct hostapd_apme *apme, u_int8_t *buf, u_int len) { struct hostapd_config *cfg = (struct hostapd_config *)apme->a_cfg; struct hostapd_frame *frame; int ret; TAILQ_FOREACH(frame, &cfg->c_frames, f_entries) { if ((ret = hostapd_handle_frame(apme, frame, buf, len)) != 0) return (ret); } return (0); } void hostapd_handle_addr(const u_int32_t mask, u_int32_t *flags, u_int8_t *addr, u_int8_t *maddr, struct hostapd_table *table) { int ret = 0; if ((*flags & mask) & HOSTAPD_FRAME_TABLE) { if (hostapd_entry_lookup(table, addr) == NULL) ret = 1; } else if (bcmp(addr, maddr, IEEE80211_ADDR_LEN) != 0) ret = 1; if ((ret == 1 && (*flags & mask) & HOSTAPD_FRAME_N) || (ret == 0 && ((*flags & mask) & HOSTAPD_FRAME_N) == 0)) *flags &= ~mask; } void hostapd_handle_ref(u_int flags, u_int shift, u_int8_t *wfrom, u_int8_t *wto, u_int8_t *wbssid, u_int8_t *addr) { if (flags & (HOSTAPD_ACTION_F_REF_FROM << shift)) bcopy(wfrom, addr, IEEE80211_ADDR_LEN); else if (flags & (HOSTAPD_ACTION_F_REF_TO << shift)) bcopy(wto, addr, IEEE80211_ADDR_LEN); else if (flags & (HOSTAPD_ACTION_F_REF_BSSID << shift)) bcopy(wbssid, addr, IEEE80211_ADDR_LEN); else if (flags & (HOSTAPD_ACTION_F_REF_RANDOM << shift)) { hostapd_randval(addr, IEEE80211_ADDR_LEN); /* Avoid multicast/broadcast addresses */ addr[0] &= ~0x1; } } int hostapd_handle_frame(struct hostapd_apme *apme, struct hostapd_frame *frame, u_int8_t *buf, const u_int len) { struct hostapd_config *cfg = (struct hostapd_config *)apme->a_cfg; struct ieee80211_frame *wh; struct hostapd_ieee80211_frame *mh; struct hostapd_radiotap rtap; u_int8_t *wfrom, *wto, *wbssid; struct timeval t_now; u_int32_t flags; int offset, min_rate = 0, val; if ((offset = hostapd_apme_offset(apme, buf, len)) < 0) return (0); wh = (struct ieee80211_frame *)(buf + offset); mh = &frame->f_frame; flags = frame->f_flags; /* Get timestamp */ if (gettimeofday(&t_now, NULL) == -1) hostapd_fatal("gettimeofday"); /* Handle optional limit */ if (frame->f_limit.tv_sec || frame->f_limit.tv_usec) { if (timercmp(&t_now, &frame->f_then, <)) return (0); timeradd(&t_now, &frame->f_limit, &frame->f_then); } switch (wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) { case IEEE80211_FC1_DIR_NODS: wfrom = wh->i_addr2; wto = wh->i_addr1; wbssid = wh->i_addr3; break; case IEEE80211_FC1_DIR_TODS: wfrom = wh->i_addr2; wto = wh->i_addr3; wbssid = wh->i_addr1; break; case IEEE80211_FC1_DIR_FROMDS: wfrom = wh->i_addr3; wto = wh->i_addr1; wbssid = wh->i_addr2; break; default: case IEEE80211_FC1_DIR_DSTODS: return (0); } if (flags & HOSTAPD_FRAME_F_APME_M) { if (frame->f_apme == NULL) return (0); /* Match hostap interface */ if ((flags & HOSTAPD_FRAME_F_APME && apme == frame->f_apme) || (flags & HOSTAPD_FRAME_F_APME_N && apme != frame->f_apme)) flags &= ~HOSTAPD_FRAME_F_APME_M; } if (flags & HOSTAPD_FRAME_F_TYPE) { /* type $type */ if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == (mh->i_fc[0] & IEEE80211_FC0_TYPE_MASK)) flags &= ~HOSTAPD_FRAME_F_TYPE; } else if (flags & HOSTAPD_FRAME_F_TYPE_N) { /* type !$type */ if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) != (mh->i_fc[0] & IEEE80211_FC0_TYPE_MASK)) flags &= ~HOSTAPD_FRAME_F_TYPE_N; } if (flags & HOSTAPD_FRAME_F_SUBTYPE) { /* subtype $subtype */ if ((wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == (mh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK)) flags &= ~HOSTAPD_FRAME_F_SUBTYPE; } else if (flags & HOSTAPD_FRAME_F_SUBTYPE_N) { /* subtype !$subtype */ if ((wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) != (mh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK)) flags &= ~HOSTAPD_FRAME_F_SUBTYPE_N; } if (flags & HOSTAPD_FRAME_F_DIR) { /* dir $dir */ if ((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) == (mh->i_fc[1] & IEEE80211_FC1_DIR_MASK)) flags &= ~HOSTAPD_FRAME_F_DIR; } else if (flags & HOSTAPD_FRAME_F_DIR_N) { /* dir !$dir */ if ((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) != (mh->i_fc[1] & IEEE80211_FC1_DIR_MASK)) flags &= ~HOSTAPD_FRAME_F_DIR_N; } /* from/to/bssid [!]$addr/ */ hostapd_handle_addr(HOSTAPD_FRAME_F_FROM_M, &flags, wfrom, mh->i_from, frame->f_from); hostapd_handle_addr(HOSTAPD_FRAME_F_TO_M, &flags, wto, mh->i_to, frame->f_to); hostapd_handle_addr(HOSTAPD_FRAME_F_BSSID_M, &flags, wbssid, mh->i_bssid, frame->f_bssid); /* parse the optional radiotap header if required */ if (frame->f_radiotap) { if (hostapd_handle_radiotap(&rtap, buf, len) != 0) return (0); else if ((rtap.r_present & frame->f_radiotap) != frame->f_radiotap) { cfg->c_stats.cn_rtap_miss++; return (0); } if (flags & HOSTAPD_FRAME_F_RSSI && rtap.r_max_rssi) { val = ((float)rtap.r_rssi / rtap.r_max_rssi) * 100; if (hostapd_cmp(frame->f_rssi_op, val, frame->f_rssi)) flags &= ~HOSTAPD_FRAME_F_RSSI; } if (flags & HOSTAPD_FRAME_F_RATE) { val = rtap.r_txrate; if (hostapd_cmp(frame->f_txrate_op, val, frame->f_txrate)) flags &= ~HOSTAPD_FRAME_F_RATE; } if (flags & HOSTAPD_FRAME_F_CHANNEL) { val = rtap.r_chan; if (hostapd_cmp(frame->f_chan_op, val, frame->f_chan)) flags &= ~HOSTAPD_FRAME_F_CHANNEL; } } /* Handle if frame matches */ if ((flags & HOSTAPD_FRAME_F_M) != 0) return (0); /* Handle optional minimal rate */ if (frame->f_rate && frame->f_rate_intval) { frame->f_rate_delay = t_now.tv_sec - frame->f_last.tv_sec; if (frame->f_rate_delay < frame->f_rate_intval) { frame->f_rate_cnt++; if (frame->f_rate_cnt < frame->f_rate) min_rate = 1; } else { min_rate = 1; frame->f_rate_cnt = 0; } } /* Update timestamp for the last match of this event */ if (frame->f_rate_cnt == 0 || min_rate == 0) bcopy(&t_now, &frame->f_last, sizeof(struct timeval)); /* Return if the minimal rate is not reached, yet */ if (min_rate) return (0); if (hostapd_handle_action(apme, frame, wfrom, wto, wbssid, buf, len) != 0) return (0); /* Reset minimal rate counter after successfully handled the frame */ frame->f_rate_cnt = 0; return ((frame->f_flags & HOSTAPD_FRAME_F_RET_M) >> HOSTAPD_FRAME_F_RET_S); } int hostapd_handle_action(struct hostapd_apme *apme, struct hostapd_frame *frame, u_int8_t *wfrom, u_int8_t *wto, u_int8_t *wbssid, u_int8_t *buf, const u_int len) { struct hostapd_config *cfg = (struct hostapd_config *)apme->a_cfg; struct hostapd_iapp *iapp = &cfg->c_iapp; struct hostapd_action_data *action = &frame->f_action_data; struct hostapd_node node; u_int8_t *lladdr = NULL; int ret = 0, offset; switch (frame->f_action) { case HOSTAPD_ACTION_RADIOTAP: /* Send IAPP frame with radiotap/pcap payload */ if ((ret = hostapd_iapp_radiotap(apme, buf, len)) != 0) return (ret); if ((frame->f_action_flags & HOSTAPD_ACTION_VERBOSE) == 0) return (0); hostapd_log(HOSTAPD_LOG, "%s: sent IAPP frame HOSTAPD_%s (%u bytes)", iapp->i_iface, cfg->c_apme_dlt == DLT_IEEE802_11_RADIO ? "RADIOTAP" : "PCAP", len); break; case HOSTAPD_ACTION_LOG: /* Log frame to syslog/stderr */ if (frame->f_rate && frame->f_rate_intval) { hostapd_printf("%s: (rate: %ld/%ld sec) ", apme->a_iface, frame->f_rate_cnt, frame->f_rate_delay + 1); } else hostapd_printf("%s: ", apme->a_iface); hostapd_print_ieee80211(cfg->c_apme_dlt, frame->f_action_flags & HOSTAPD_ACTION_VERBOSE, buf, len); /* Flush output buffer */ hostapd_printf(NULL); break; case HOSTAPD_ACTION_DELNODE: case HOSTAPD_ACTION_ADDNODE: bzero(&node, sizeof(node)); if (action->a_flags & HOSTAPD_ACTION_F_REF_FROM) lladdr = wfrom; else if (action->a_flags & HOSTAPD_ACTION_F_REF_TO) lladdr = wto; else if (action->a_flags & HOSTAPD_ACTION_F_REF_BSSID) lladdr = wbssid; else lladdr = action->a_lladdr; bcopy(lladdr, &node.ni_macaddr, IEEE80211_ADDR_LEN); if (frame->f_action == HOSTAPD_ACTION_DELNODE) ret = hostapd_apme_delnode(apme, &node); else ret = hostapd_apme_addnode(apme, &node); if (ret != 0) { hostapd_log(HOSTAPD_LOG_DEBUG, "%s: node add/delete %s failed: %s", apme->a_iface, etheraddr_string(lladdr), strerror(ret)); } break; case HOSTAPD_ACTION_NONE: /* Nothing */ break; case HOSTAPD_ACTION_RESEND: /* Resend received raw IEEE 802.11 frame */ if ((offset = hostapd_apme_offset(apme, buf, len)) < 0) return (EINVAL); if (write(apme->a_raw, buf + offset, len - offset) == -1) ret = errno; break; case HOSTAPD_ACTION_FRAME: if (action->a_flags & HOSTAPD_ACTION_F_REF_M) { hostapd_handle_ref(action->a_flags & HOSTAPD_ACTION_F_REF_FROM_M, HOSTAPD_ACTION_F_REF_FROM_S, wfrom, wto, wbssid, action->a_frame.i_from); hostapd_handle_ref(action->a_flags & HOSTAPD_ACTION_F_REF_TO_M, HOSTAPD_ACTION_F_REF_TO_S, wfrom, wto, wbssid, action->a_frame.i_to); hostapd_handle_ref(action->a_flags & HOSTAPD_ACTION_F_REF_BSSID_M, HOSTAPD_ACTION_F_REF_BSSID_S, wfrom, wto, wbssid, action->a_frame.i_bssid); } /* Send a raw IEEE 802.11 frame */ return (hostapd_apme_output(apme, &action->a_frame)); default: return (0); } return (ret); } int hostapd_handle_radiotap(struct hostapd_radiotap *rtap, u_int8_t *buf, const u_int len) { struct ieee80211_radiotap_header *rh = (struct ieee80211_radiotap_header*)buf; u_int8_t *t, *ptr = NULL; u_int rh_len; const u_int8_t *snapend = buf + len; TCHECK(*rh); rh_len = letoh16(rh->it_len); if (rh->it_version != 0) return (EINVAL); if (len <= rh_len) goto trunc; bzero(rtap, sizeof(struct hostapd_radiotap)); t = (u_int8_t*)buf + sizeof(struct ieee80211_radiotap_header); if ((rtap->r_present = letoh32(rh->it_present)) == 0) return (0); #define RADIOTAP(_x, _len) \ if (rtap->r_present & HOSTAPD_RADIOTAP_F(_x)) { \ TCHECK2(*t, _len); \ ptr = t; \ t += _len; \ } else \ ptr = NULL; /* radiotap doesn't use TLV header fields, ugh */ RADIOTAP(TSFT, 8); RADIOTAP(FLAGS, 1); RADIOTAP(RATE, 1); if (ptr != NULL) { rtap->r_txrate = *(u_int8_t *)ptr; } RADIOTAP(CHANNEL, 4); if (ptr != NULL) { rtap->r_chan = letoh16(*(u_int16_t*)ptr); rtap->r_chan_flags = letoh16(*(u_int16_t*)ptr + 1); } RADIOTAP(FHSS, 2); RADIOTAP(DBM_ANTSIGNAL, 1); RADIOTAP(DBM_ANTNOISE, 1); RADIOTAP(LOCK_QUALITY, 2); RADIOTAP(TX_ATTENUATION, 2); RADIOTAP(DB_TX_ATTENUATION, 2); RADIOTAP(DBM_TX_POWER, 1); RADIOTAP(ANTENNA, 1); RADIOTAP(DB_ANTSIGNAL, 1); RADIOTAP(DB_ANTNOISE, 1); RADIOTAP(FCS, 4); RADIOTAP(RSSI, 2); if (ptr != NULL) { rtap->r_rssi = *(u_int8_t *)ptr; rtap->r_max_rssi = *(u_int8_t *)ptr + 1; } return (0); trunc: return (EINVAL); } int hostapd_cmp(enum hostapd_op op, int val1, int val2) { if ((op == HOSTAPD_OP_EQ && val1 == val2) || (op == HOSTAPD_OP_NE && val1 != val2) || (op == HOSTAPD_OP_LE && val1 <= val2) || (op == HOSTAPD_OP_LT && val1 < val2) || (op == HOSTAPD_OP_GE && val1 >= val2) || (op == HOSTAPD_OP_GT && val1 > val2)) return (1); return (0); }