int ampe_close_peer_link(unsigned char *peer_mac) { struct candidate *cand; assert(peer_mac); if ((cand = find_peer(peer_mac, 0)) == NULL) { sae_debug( AMPE_DEBUG_FSM, "Mesh plink: Attempt to close link with non-existent peer\n"); return -EPERM; } if (!cand->conf) { /* * This can happen if we get a delete event for a station but they * haven't yet advanced to link establishment phase. No need to send * a close then. */ sae_debug( AMPE_DEBUG_FSM, "Mesh plink: not sending close to uninitialized peer " MACSTR "\n", MAC2STR(peer_mac)); return -EPERM; } sae_debug( AMPE_DEBUG_FSM, "Mesh plink: closing link with " MACSTR "\n", MAC2STR(peer_mac)); return plink_frame_tx(cand, PLINK_CLOSE, MESH_LINK_CANCELLED); }
/** * ampe_open_peer_link - attempt to establish a peer link * @peer: MAC address of the candidate peer * @cookie: Opaque cookie that will be returned to the caller along with * frames to be transmitted. * * Returns 0 or a negative error. */ int ampe_open_peer_link(unsigned char *peer_mac, void *cookie) { struct candidate *cand; assert(peer_mac); if ((cand = find_peer(peer_mac, 0)) == NULL) { sae_debug( AMPE_DEBUG_FSM, "Mesh plink: Attempt to peer with " " non-authed peer\n"); return -EPERM; } peer_ampe_init(&e_conf, cand, cookie); set_link_state(cand, PLINK_OPN_SNT); cb->evl->rem_timeout(cand->t2); cand->t2 = cb->evl->add_timeout(SRV_MSEC(cand->timeout), plink_timer, cand); sae_debug( AMPE_DEBUG_FSM, "Mesh plink: starting establishment " "with " MACSTR "\n", MAC2STR(peer_mac)); return plink_frame_tx(cand, PLINK_OPEN, 0); }
int ampe_initialize(struct mesh_node *mesh, struct ampe_cb *callbacks) { int sup_rates_len; if (!check_callbacks(callbacks)) return -1; cb = callbacks; /* TODO: move these to a config file */ ampe_conf.retry_timeout_ms = 1000; ampe_conf.holding_timeout_ms = 10000; ampe_conf.confirm_timeout_ms = 1000; ampe_conf.max_retries = 10; ampe_conf.mesh = mesh; if (mesh->conf->is_secure) { RAND_bytes(mgtk_tx, 16); sae_hexdump(AMPE_DEBUG_KEYS, "mgtk: ", mgtk_tx, sizeof(mgtk_tx)); } if (mesh->conf->pmf) { RAND_bytes(mesh->igtk_tx, 16); mesh->igtk_keyid = 4; memset(mesh->igtk_ipn, 0, sizeof(mesh->igtk_ipn)); sae_hexdump( AMPE_DEBUG_KEYS, "igtk: ", mesh->igtk_tx, sizeof(mesh->igtk_tx)); } /* We can do this because valid supported rates non null and the array is null * terminated */ sup_rates_len = strnlen((char *)mesh->conf->rates, sizeof(mesh->conf->rates)); if (sup_rates_len <= 8) { /* rates fit into a the supported rates IE */ sta_fixed_ies_len = 2 + sup_rates_len; sta_fixed_ies = malloc(sta_fixed_ies_len); *sta_fixed_ies = IEEE80211_EID_SUPPORTED_RATES; *(sta_fixed_ies + 1) = sup_rates_len; memcpy(sta_fixed_ies + 2, mesh->conf->rates, sup_rates_len); } else if (sup_rates_len < sizeof(mesh->conf->rates)) { /* rates overflow onto the extended supported rates IE */ sta_fixed_ies_len = 4 + sup_rates_len; sta_fixed_ies = malloc(sta_fixed_ies_len); *sta_fixed_ies = IEEE80211_EID_SUPPORTED_RATES; *(sta_fixed_ies + 1) = 8; memcpy(sta_fixed_ies + 2, mesh->conf->rates, 8); *(sta_fixed_ies + 10) = IEEE80211_EID_EXTENDED_SUP_RATES; *(sta_fixed_ies + 11) = sup_rates_len - 8; memcpy(sta_fixed_ies + 12, mesh->conf->rates + 8, sup_rates_len - 8); } else { sae_debug(SAE_DEBUG_ERR, "mesh->conf->rates should be null-terminated"); return -1; } sae_hexdump( MESHD_DEBUG, "Fixed Information Elements in this STA", sta_fixed_ies, sta_fixed_ies_len); return 0; }
static void log_reject(struct candidate *cand, const char *reason) { sae_debug( AMPE_DEBUG_FSM, "Mesh plink: rejecting action frame from " MACSTR " due to %s\n", MAC2STR(cand->peer_mac), reason); }
static uint32_t do_estab_peer_link(struct candidate *cand) { uint32_t changed; derive_mtk(cand); cb->estab_peer_link( cand->peer_mac, cand->mtk, sizeof(cand->mtk), cand->mgtk, sizeof(cand->mgtk), cand->mgtk_expiration, (cand->has_igtk) ? cand->igtk : NULL, (cand->has_igtk) ? sizeof(cand->igtk) : 0, cand->igtk_keyid, cand->sup_rates, cand->sup_rates_len, cand->cookie); set_link_state(cand, PLINK_ESTAB); changed = mesh_set_ht_op_mode(cand->conf->mesh); sae_debug( AMPE_DEBUG_FSM, "Mesh plink with " MACSTR " ESTABLISHED\n", MAC2STR(cand->peer_mac)); rekey_verify_peer(cand); return changed; }
/** * fsm_restart - restart a mesh peer link finite state machine * * @cand: mesh peer link to restart * * */ static inline void fsm_restart(struct candidate *cand) { sae_debug( AMPE_DEBUG_FSM, "Deleting peer " MACSTR " to restart FSM\n", MAC2STR(cand->peer_mac)); if (cb->delete_peer) cb->delete_peer(cand->peer_mac); }
static int set_mesh_conf(struct netlink_config_s *nlcfg, struct mesh_node *mesh, uint32_t changed) { struct nl_msg *msg; uint8_t cmd = NL80211_CMD_SET_MESH_CONFIG; int ret = 0; char *pret; sae_debug(MESHD_DEBUG, "%s(%p, %d)\n", __FUNCTION__, nlcfg, changed); msg = nlmsg_alloc(); if (!msg) return -ENOMEM; pret = genlmsg_put(msg, 0, NL_AUTO_SEQ, genl_family_get_id(nlcfg->nl80211), 0, 0, cmd, 0); if (pret == NULL) goto nla_put_failure; struct nlattr *container = nla_nest_start(msg, NL80211_ATTR_MESH_CONFIG); if (!container) goto nla_put_failure; if (changed & MESH_CONF_CHANGED_HT) NLA_PUT_U32(msg, NL80211_MESHCONF_HT_OPMODE, mesh->conf->ht_prot_mode); nla_nest_end(msg, container); NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, nlcfg->ifindex); ret = send_nlmsg(nlcfg->nl_sock, msg); sae_debug(MESHD_DEBUG, "set meshconf (seq num=%d)\n", nlmsg_hdr(msg)->nlmsg_seq); if (ret < 0) sae_debug(MESHD_DEBUG, "set meshconf failed: %d (%s)\n", ret, strerror(-ret)); return ret; nla_put_failure: nlmsg_free(msg); return -ENOBUFS; }
static int tx_frame(struct netlink_config_s *nlcfg, struct mesh_node *mesh, unsigned char *frame, int len) { struct nl_msg *msg; uint8_t cmd = NL80211_CMD_FRAME; int ret = 0; char *pret; sae_debug(MESHD_DEBUG, "%s(%p, %p, %d)\n", __FUNCTION__, nlcfg, frame, len); msg = nlmsg_alloc(); if (!msg) return -ENOMEM; if (!frame || !len) return -EINVAL; pret = genlmsg_put(msg, 0, NL_AUTO_SEQ, genl_family_get_id(nlcfg->nl80211), 0, 0, cmd, 0); if (pret == NULL) goto nla_put_failure; NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, nlcfg->ifindex); NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, mesh->freq); NLA_PUT(msg, NL80211_ATTR_FRAME, len, frame); ret = send_nlmsg(nlcfg->nl_sock, msg); sae_debug(MESHD_DEBUG, "tx frame (seq num=%d)\n", nlmsg_hdr(msg)->nlmsg_seq); if (ret < 0) sae_debug(MESHD_DEBUG, "tx frame failed: %d (%s)\n", ret, strerror(-ret)); else sae_hexdump(MESHD_DEBUG, "tx frame", frame, len); return ret; nla_put_failure: nlmsg_free(msg); return -ENOBUFS; }
static int get_mac_addr(const char * ifname, uint8_t *macaddr) { int fd; struct ifreq ifr; fd = socket(AF_INET, SOCK_DGRAM, 0); ifr.ifr_addr.sa_family = AF_INET; strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); if (ioctl(fd, SIOCGIFHWADDR, &ifr)) { sae_debug(SAE_DEBUG_ERR, "meshd: failed to read MAC address for interface \"%s\": %s\n", ifname, strerror(errno)); return -1; } memcpy(macaddr, ifr.ifr_hwaddr.sa_data, ETH_ALEN); close(fd); return 0; }
/* determine and set the correct ht operation mode for all established peers * according to 802.11mb 9.23.3. Return MESH_CONF_CHANGED_HT bit if a new * operation mode was selected */ static uint32_t mesh_set_ht_op_mode(struct mesh_node *mesh) { struct candidate *peer; uint32_t changed = 0; unsigned int ht_opmode; bool no_ht = false, ht20 = false; if (mesh->conf->channel_type == CHAN_NO_HT) return 0; for_each_peer(peer) { if (peer->link_state != PLINK_ESTAB) continue; switch (peer->ch_type) { case CHAN_NO_HT: no_ht = true; goto out; case CHAN_HT20: ht20 = true; break; default: break; } } out: if (no_ht) ht_opmode = IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED; else if (ht20 && mesh->conf->channel_type > CHAN_HT20) ht_opmode = IEEE80211_HT_OP_MODE_PROTECTION_20MHZ; else ht_opmode = IEEE80211_HT_OP_MODE_PROTECTION_NONE; if (ht_opmode != mesh->conf->ht_prot_mode) { sae_debug(MESHD_DEBUG, "changing ht protection mode to: %d\n", ht_opmode); mesh->conf->ht_prot_mode = ht_opmode; changed = MESH_CONF_CHANGED_HT; } return changed; }
/** * protect_frame - add in-place the MIC and the (encrypted) AMPE ie to a frame * @cand: The candidate this frame is destined for * @mgmt: The frame, populated with all the information elements up to where the MIC information element should go * @mic_start: Pointer to where the mic and AMPE ies are to be written. Should point to the start of the IE, not the IE body. * @len: On input, the total buffer size that contains this frame. On output, the actual lenght of the frame * including the two information elements added by this function. * * Returns: The zero on success, or some error. */ static int protect_frame(struct candidate *cand, struct ieee80211_mgmt_frame *mgmt, unsigned char *mic_start, int *len) { unsigned char *clear_ampe_ie; unsigned char *ie; unsigned short cat_to_mic_len; struct mesh_node *mesh = cand->conf->mesh; size_t ampe_ie_len; u8 ftype = mgmt->action.action_code; le16 igtk_keyid; assert(mic_start && cand && mgmt && len); #define MIC_IE_BODY_SIZE AES_BLOCK_SIZE ampe_ie_len = sizeof(struct ampe_ie); if (ftype != PLINK_CLOSE) { /* MGTK + RSC + Exp */ ampe_ie_len += 16 + 8 + 4; if (mesh->conf->pmf) { /* IGTK KeyId + IPN + IGTK */ ampe_ie_len += 2 + 6 + 16; } } if (mic_start + MIC_IE_BODY_SIZE + 2 + 2 + ampe_ie_len - (unsigned char *) mgmt > *len) { sae_debug(AMPE_DEBUG_KEYS, "protect frame: buffer too small\n"); return -EINVAL; } clear_ampe_ie = malloc(ampe_ie_len + 2); if (!clear_ampe_ie) { sae_debug(AMPE_DEBUG_KEYS, "protect frame: out of memory\n"); return -ENOMEM; } /* IE: AMPE */ ie = clear_ampe_ie; *ie++ = IEEE80211_EID_AMPE; *ie++ = ampe_ie_len; memcpy(ie, pw_suite_selector, 4); ie += 4; memcpy(ie, cand->my_nonce, 32); ie += 32; memcpy(ie, cand->peer_nonce, 32); ie += 32; if (ftype != PLINK_CLOSE) { memcpy(ie, mgtk_tx, 16); ie += 16; memset(ie, 0, 8); /* TODO: Populate Key RSC */ ie += 8; memset(ie, 0xff, 4); /* expire in 13 decades or so */ ie += 4; if (mesh->conf->pmf) { igtk_keyid = htole16(mesh->igtk_keyid); memcpy(ie, &igtk_keyid, 2); ie += 2; memcpy(ie, mesh->igtk_ipn, 6); ie += 6; memcpy(ie, mesh->igtk_tx, 16); ie += 16; } } /* IE: MIC */ ie = mic_start; *ie++ = IEEE80211_EID_MIC; *ie++ = MIC_IE_BODY_SIZE; cat_to_mic_len = mic_start - (unsigned char *) &mgmt->action; siv_encrypt(&cand->sivctx, clear_ampe_ie, ie + MIC_IE_BODY_SIZE, ampe_ie_len + 2, ie, 3, cand->my_mac, ETH_ALEN, cand->peer_mac, ETH_ALEN, &mgmt->action, cat_to_mic_len); *len = mic_start - (unsigned char *) mgmt + ampe_ie_len + 2 + MIC_IE_BODY_SIZE + 2; sae_debug(AMPE_DEBUG_KEYS, "Protecting frame from " MACSTR " to " MACSTR "\n", MAC2STR(cand->my_mac), MAC2STR(cand->peer_mac)); sae_debug(AMPE_DEBUG_KEYS, "Checking tricky lengths of protected frame %d, %d\n", cat_to_mic_len, ampe_ie_len + 2); sae_hexdump(AMPE_DEBUG_KEYS, "SIV- Put AAD[3]: ", (unsigned char *) &mgmt->action, cat_to_mic_len); free(clear_ampe_ie); return 0; }
/** * process_ampe_frame - process an ampe frame * @frame: The full frame * @len: The full frame length * @me: The MAC address of the local interface * @cookie: Opaque cookie that will be returned to the caller along with * frames to be transmitted. * * Returns 0 unless something really horrible happened. In other words, even * the frame could not be processed or it was corrupted, the function still * returns 0. */ int process_ampe_frame( struct ieee80211_mgmt_frame *mgmt, int len, unsigned char *me, void *cookie) { struct info_elems elems; struct info_elems our_elems; unsigned char ftype; struct candidate *cand = NULL; enum plink_event event; unsigned char ie_len = 0; unsigned short plid = 0, llid = 0; unsigned char *ies; unsigned short ies_len; size_t pmkid_len; #define FAKE_LOSS_PROBABILITY 0 #if (FAKE_LOSS_PROBABILITY > 0) do { unsigned short dice; dice = RAND_bytes((unsigned char *)&dice, sizeof(dice)); if ((dice % 100) < FAKE_LOSS_PROBABILITY) { sae_debug(AMPE_DEBUG_FSM, "Frame dropped\n"); return 0; } } while (0); #endif /* management header, category, action code, mesh id and peering mgmt*/ if (len < 24 + 1 + 1 + 2 + 2) return 0; ies = start_of_ies(mgmt, len, &ies_len); parse_ies(ies, ies_len, &elems); if (!elems.mesh_peering) { sae_debug(AMPE_DEBUG_FSM, "Mesh plink: missing necessary peer link ie\n"); return 0; } ftype = mgmt->action.action_code; ie_len = elems.mesh_peering_len; pmkid_len = ampe_conf.mesh->conf->is_secure ? sizeof(cand->pmkid) : 0; if ((ftype == PLINK_OPEN && ie_len != 4 + pmkid_len) || (ftype == PLINK_CONFIRM && ie_len != 6 + pmkid_len) || (ftype == PLINK_CLOSE && ie_len != 6 + pmkid_len && ie_len != 8 + pmkid_len)) { sae_debug( AMPE_DEBUG_FSM, "Mesh plink: incorrect plink ie length %d %d\n", ftype, ie_len); return 0; } if (ftype != PLINK_CLOSE && (!elems.mesh_id || !elems.mesh_config)) { sae_debug( AMPE_DEBUG_FSM, "Mesh plink: missing necessary ie %p %p\n", elems.mesh_id, elems.mesh_config); return 0; } /* Note the lines below are correct, the llid in the frame is the plid * from the point of view of this host. */ memcpy(&plid, PLINK_GET_LLID(elems.mesh_peering), 2); if (ftype == PLINK_CONFIRM || (ftype == PLINK_CLOSE && ie_len == 10)) memcpy(&llid, PLINK_GET_PLID(elems.mesh_peering), 2); /* match BSSBasicRateSet*/ parse_ies(sta_fixed_ies, sta_fixed_ies_len, &our_elems); if (ftype != PLINK_CLOSE && get_basic_rates(&our_elems) != get_basic_rates(&elems)) { sae_debug(AMPE_DEBUG_FSM, "mesh plink: mismatched BSSBasicRateSet!\n"); return 0; } /* require authed peers if secure mesh */ if (ampe_conf.mesh->conf->is_secure) { /* "1" here means only get peers in SAE_ACCEPTED */ if ((cand = find_peer(mgmt->sa, 1)) == NULL) { sae_debug( AMPE_DEBUG_FSM, "Mesh plink: plink open from unauthed peer " MACSTR "\n", MAC2STR(mgmt->sa)); return 0; } } else { /* * In open mesh, there's no auth stage, so we create the station * when the first mgmt frame or beacon is received. Do that now * if we haven't already and this is a plink open frame. */ cand = find_peer(mgmt->sa, 0); if (!cand) { if (ftype != PLINK_OPEN) { sae_debug( AMPE_DEBUG_FSM, "Mesh plink: ignoring non-open frame from neighbor " MACSTR "\n", MAC2STR(mgmt->sa)); return 0; } cand = create_candidate(mgmt->sa, me, 0, cookie); if (!cand) { sae_debug( AMPE_DEBUG_FSM, "Mesh plink: could not create new peer " MACSTR "\n", MAC2STR(mgmt->sa)); return 0; } } } if (cand->my_lid == 0) peer_ampe_init(&e_conf, cand, cookie); ampe_set_peer_ies(cand, &elems); if (!protection_is_valid(cand, mgmt, len, &elems)) return 0; cand->cookie = cookie; if (cand->link_state == PLINK_BLOCKED) { return 0; } /* Now we will figure out the appropriate event... */ event = PLINK_UNDEFINED; switch (ftype) { case PLINK_OPEN: if (!matches_local(ampe_conf.mesh, cand, &elems)) event = OPN_RJCT; else if (!plink_free_count(ampe_conf.mesh)) { log_reject(cand, "no free peer links"); event = REQ_RJCT; } else if (cand->peer_lid && cand->peer_lid != plid) { log_reject(cand, "invalid peer link id"); event = REQ_RJCT; } else { cand->peer_lid = plid; event = OPN_ACPT; } break; case PLINK_CONFIRM: if (!matches_local(ampe_conf.mesh, cand, &elems)) event = CNF_RJCT; else if (!plink_free_count(ampe_conf.mesh)) { log_reject(cand, "no free peer links"); event = REQ_RJCT; } else if (cand->my_lid != llid) { log_reject(cand, "invalid local link id"); event = REQ_RJCT; } else if (cand->peer_lid != plid) { log_reject(cand, "invalid peer link id"); event = REQ_RJCT; } else event = CNF_ACPT; break; case PLINK_CLOSE: if (cand->link_state == PLINK_ESTAB) /* Do not check for llid or plid. This does not * follow the standard but since multiple plinks * per cand are not supported, it is necessary in * order to avoid a livelock when MP A sees an * establish peer link to MP B but MP B does not * see it. This can be caused by a timeout in * B's peer link establishment or B beign * restarted. */ event = CLS_ACPT; else if (cand->peer_lid != plid) event = CLS_IGNR; else if (ie_len == 7 && cand->my_lid != llid) event = CLS_IGNR; else event = CLS_ACPT; break; default: sae_debug(AMPE_DEBUG_FSM, "Mesh plink: unknown frame subtype\n"); return 0; } sae_debug( AMPE_DEBUG_FSM, "Mesh plink peer=" MACSTR " state=%s llid=%d plid=%d event=%s\n", MAC2STR(mgmt->sa), mpl_states[cand->link_state], le16toh(cand->my_lid), le16toh(cand->peer_lid), mpl_events[event]); fsm_step(cand, event); return 0; }
static void plink_timer(void *data) { le16 reason; struct candidate *cand; cand = (struct candidate *)data; assert(cand); sae_debug( AMPE_DEBUG_FSM, "Mesh plink timer for " MACSTR " fired on state %s\n", MAC2STR(cand->peer_mac), mpl_states[(cand->link_state > PLINK_BLOCKED) ? PLINK_UNDEFINED : cand->link_state]); reason = 0; switch (cand->link_state) { case PLINK_OPN_RCVD: case PLINK_OPN_SNT: /* retry timer */ sae_debug( AMPE_DEBUG_FSM, "Mesh plink:retries %d of %d\n", cand->retries, cand->conf->max_retries); if (cand->retries < cand->conf->max_retries) { cand->timeout = cand->conf->retry_timeout_ms; sae_debug( AMPE_DEBUG_FSM, "Mesh plink for " MACSTR " (retry, timeout): %d %d\n", MAC2STR(cand->peer_mac), cand->retries, cand->timeout); ++cand->retries; cb->evl->rem_timeout(cand->t2); cand->t2 = cb->evl->add_timeout(SRV_MSEC(cand->timeout), plink_timer, cand); plink_frame_tx(cand, PLINK_OPEN, 0); break; } reason = MESH_MAX_RETRIES; /* no break / fall through on else */ case PLINK_CNF_RCVD: /* confirm timer */ if (!reason) reason = MESH_CONFIRM_TIMEOUT; set_link_state(cand, PLINK_HOLDING); cb->evl->rem_timeout(cand->t2); cand->t2 = cb->evl->add_timeout( SRV_MSEC(cand->conf->holding_timeout_ms), plink_timer, cand); plink_frame_tx(cand, PLINK_CLOSE, reason); break; case PLINK_HOLDING: /* holding timer */ fsm_restart(cand); break; case PLINK_ESTAB: /* nothing to do */ break; default: sae_debug( AMPE_DEBUG_FSM, "Timeout for peer " MACSTR " in state %d\n", MAC2STR(cand->peer_mac), cand->link_state); break; } }
/** * process_ampe_frame - process an ampe frame * @frame: The full frame * @len: The full frame length * @me: The MAC address of the local interface * @cookie: Opaque cookie that will be returned to the caller along with * frames to be transmitted. * * Returns 0 unless something really horrible happened. In other words, even * the frame could not be processed or it was corrupted, the function still * returns 0. */ int process_ampe_frame(struct ieee80211_mgmt_frame *mgmt, int len, unsigned char *me, void *cookie) { struct info_elems elems; struct info_elems our_elems; unsigned char ftype; struct candidate *cand = NULL; enum plink_event event; unsigned char ie_len = 0; unsigned short plid = 0, llid = 0; unsigned char *ies; unsigned short ies_len; #define FAKE_LOSS_PROBABILITY 0 #if (FAKE_LOSS_PROBABILITY > 0) do { unsigned short dice; dice = RAND_bytes((unsigned char *) &dice, sizeof(dice)); if ((dice % 100) < FAKE_LOSS_PROBABILITY) { sae_debug(AMPE_DEBUG_FSM, "Frame dropped\n"); return 0; } } while (0); #endif /* management header, category, action code, mesh id and peering mgmt*/ if (len < 24 + 1 + 1 + 2 + 2) return 0; //if (is_multicast_ether_addr(mgmt->da)) { // sae_debug(AMPE_DEBUG_FSM, "Mesh plink: ignore frame to multicast address"); // return 0; //} ies = start_of_ies(mgmt, len, &ies_len); parse_ies(ies, ies_len, &elems); if (!elems.mesh_peering) { // || !elems.rsn) { sae_debug(AMPE_DEBUG_FSM, "Mesh plink: missing necessary peer link ie\n"); return 0; } ftype = mgmt->action.action_code; ie_len = elems.mesh_peering_len; if ((ftype == PLINK_OPEN && ie_len != 20) || (ftype == PLINK_CONFIRM && ie_len != 22) || (ftype == PLINK_CLOSE && ie_len != 22 && ie_len != 24)) { sae_debug(AMPE_DEBUG_FSM, "Mesh plink: incorrect plink ie length %d %d\n", ftype, ie_len); return 0; } if (ftype != PLINK_CLOSE && (!elems.mesh_id || !elems.mesh_config)) { sae_debug(AMPE_DEBUG_FSM, "Mesh plink: missing necessary ie %p %p\n", elems.mesh_id, elems.mesh_config); return 0; } /* Note the lines below are correct, the llid in the frame is the plid * from the point of view of this host. */ memcpy(&plid, PLINK_GET_LLID(elems.mesh_peering), 2); if (ftype == PLINK_CONFIRM || (ftype == PLINK_CLOSE && ie_len == 10)) memcpy(&llid, PLINK_GET_PLID(elems.mesh_peering), 2); /* match BSSBasicRateSet*/ parse_ies(sta_fixed_ies, sta_fixed_ies_len, &our_elems); if (get_basic_rates(&our_elems) != get_basic_rates(&elems)) { sae_debug(AMPE_DEBUG_FSM, "mesh plink: mismatched BSSBasicRateSet!\n"); return 0; } /* "1" here means only get peers in SAE_ACCEPTED */ if ((cand = find_peer(mgmt->sa, 1)) == NULL) { sae_debug(AMPE_DEBUG_FSM, "Mesh plink: plink open from unauthed peer "MACSTR"\n", MAC2STR(mgmt->sa)); return 0; } if (cand->my_lid == 0) peer_ampe_init(&e_conf, cand, me, cookie); if (elems.sup_rates) { memcpy(cand->sup_rates, elems.sup_rates, elems.sup_rates_len); cand->sup_rates_len = elems.sup_rates_len; if (elems.ext_rates) { memcpy(cand->sup_rates + elems.sup_rates_len, elems.ext_rates, elems.ext_rates_len); cand->sup_rates_len += elems.ext_rates_len; } } check_frame_protection(cand, mgmt, len, &elems); cand->cookie = cookie; if (cand->link_state == PLINK_BLOCKED) { return 0; } /* Now we will figure out the appropriate event... */ event = PLINK_UNDEFINED; // if (ftype != PLINK_CLOSE && (!mesh_matches_local(&elems, sdata))) { if (ftype != PLINK_CLOSE) { switch (ftype) { case PLINK_OPEN: event = OPN_RJCT; break; case PLINK_CONFIRM: event = CNF_RJCT; break; case PLINK_CLOSE: break; } } switch (ftype) { case PLINK_OPEN: if (!plink_free_count() || (cand->peer_lid && cand->peer_lid != plid)) event = OPN_IGNR; else { cand->peer_lid = plid; event = OPN_ACPT; } break; case PLINK_CONFIRM: if (!plink_free_count() || (cand->my_lid != llid || cand->peer_lid != plid)) event = CNF_IGNR; else event = CNF_ACPT; break; case PLINK_CLOSE: if (cand->link_state == PLINK_ESTAB) /* Do not check for llid or plid. This does not * follow the standard but since multiple plinks * per cand are not supported, it is necessary in * order to avoid a livelock when MP A sees an * establish peer link to MP B but MP B does not * see it. This can be caused by a timeout in * B's peer link establishment or B beign * restarted. */ event = CLS_ACPT; else if (cand->peer_lid != plid) event = CLS_IGNR; else if (ie_len == 7 && cand->my_lid != llid) event = CLS_IGNR; else event = CLS_ACPT; break; default: sae_debug(AMPE_DEBUG_FSM, "Mesh plink: unknown frame subtype\n"); return 0; } sae_debug(AMPE_DEBUG_FSM, "Mesh plink (peer, state, llid, plid, event): " MACSTR " %s %d %d %d\n", MAC2STR(mgmt->sa), mplstates[cand->link_state], le16toh(cand->my_lid), le16toh(cand->peer_lid), event); fsm_step(cand, event); return 0; }
static void fsm_step(struct candidate *cand, enum plink_event event) { struct ampe_config *aconf = cand->conf; unsigned short reason = 0; uint32_t changed = 0; switch (cand->link_state) { case PLINK_LISTEN: switch (event) { case CLS_ACPT: fsm_restart(cand); break; case OPN_ACPT: cand->timeout = aconf->retry_timeout_ms; cand->t2 = srv_add_timeout(srvctx, SRV_MSEC(cand->timeout), plink_timer, cand); plink_frame_tx(cand, PLINK_OPEN, 0); plink_frame_tx(cand, PLINK_CONFIRM, 0); break; default: break; } break; case PLINK_OPN_SNT: switch (event) { case OPN_RJCT: case CNF_RJCT: reason = htole16(MESH_CAPABILITY_POLICY_VIOLATION); case CLS_ACPT: if (!reason) reason = htole16(MESH_CLOSE_RCVD); cand->reason = reason; set_link_state(cand, PLINK_HOLDING); cand->timeout = aconf->holding_timeout_ms; cand->t2 = srv_add_timeout(srvctx, SRV_MSEC(cand->timeout), plink_timer, cand); plink_frame_tx(cand, PLINK_CLOSE, reason); break; case OPN_ACPT: /* retry timer is left untouched */ set_link_state(cand, PLINK_OPN_RCVD); plink_frame_tx(cand, PLINK_CONFIRM, 0); break; case CNF_ACPT: set_link_state(cand, PLINK_CNF_RCVD); cand->timeout = aconf->confirm_timeout_ms; cand->t2 = srv_add_timeout(srvctx, SRV_MSEC(cand->timeout), plink_timer, cand); break; default: break; } break; case PLINK_OPN_RCVD: switch (event) { case OPN_RJCT: case CNF_RJCT: reason = htole16(MESH_CAPABILITY_POLICY_VIOLATION); case CLS_ACPT: if (!reason) reason = htole16(MESH_CLOSE_RCVD); cand->reason = reason; set_link_state(cand, PLINK_HOLDING); cand->timeout = aconf->holding_timeout_ms; cand->t2 = srv_add_timeout(srvctx, SRV_MSEC(cand->timeout), plink_timer, cand); plink_frame_tx(cand, PLINK_CLOSE, reason); break; case OPN_ACPT: plink_frame_tx(cand, PLINK_CONFIRM, 0); break; case CNF_ACPT: //del_timer(&cand->plink_timer); set_link_state(cand, PLINK_ESTAB); //mesh_plink_inc_estab_count(sdata); //ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON); derive_mtk(cand); estab_peer_link(cand->peer_mac, cand->mtk, sizeof(cand->mtk), cand->mgtk, sizeof(cand->mgtk), cand->mgtk_expiration, cand->sup_rates, cand->sup_rates_len, cand->cookie); changed |= mesh_set_ht_op_mode(cand->conf->mesh); sae_debug(AMPE_DEBUG_FSM, "mesh plink with " MACSTR " established\n", MAC2STR(cand->peer_mac)); break; default: break; } break; case PLINK_CNF_RCVD: switch (event) { case OPN_RJCT: case CNF_RJCT: reason = htole16(MESH_CAPABILITY_POLICY_VIOLATION); case CLS_ACPT: if (!reason) reason = htole16(MESH_CLOSE_RCVD); cand->reason = reason; set_link_state(cand, PLINK_HOLDING); cand->timeout = aconf->holding_timeout_ms; cand->t2 = srv_add_timeout(srvctx, SRV_MSEC(cand->timeout), plink_timer, cand); plink_frame_tx(cand, PLINK_CLOSE, reason); break; case OPN_ACPT: set_link_state(cand, PLINK_ESTAB); estab_peer_link(cand->peer_mac, cand->mtk, sizeof(cand->mtk), cand->mgtk, sizeof(cand->mgtk), cand->mgtk_expiration, cand->sup_rates, cand->sup_rates_len, cand->cookie); changed |= mesh_set_ht_op_mode(cand->conf->mesh); //TODO: update the number of available peer "slots" in mesh config //mesh_plink_inc_estab_count(sdata); //ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON); sae_debug(AMPE_DEBUG_FSM, "Mesh plink with " MACSTR " ESTABLISHED\n", MAC2STR(cand->peer_mac)); plink_frame_tx(cand, PLINK_CONFIRM, 0); break; default: break; } break; case PLINK_ESTAB: switch (event) { case CLS_ACPT: reason = htole16(MESH_CLOSE_RCVD); cand->reason = reason; set_link_state(cand, PLINK_HOLDING); cand->timeout = aconf->holding_timeout_ms; cand->t2 = srv_add_timeout(srvctx, SRV_MSEC(cand->timeout), plink_timer, cand); changed |= mesh_set_ht_op_mode(cand->conf->mesh); //TODO: update the number of available peer "slots" in mesh config //if (deactivated) // ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON); plink_frame_tx(cand, PLINK_CLOSE, reason); break; case OPN_ACPT: plink_frame_tx(cand, PLINK_CONFIRM, 0); break; default: break; } break; case PLINK_HOLDING: switch (event) { case CLS_ACPT: //if (del_timer(&cand->plink_timer)) // cand->ignore_plink_timer = 1; fsm_restart(cand); break; case OPN_ACPT: case CNF_ACPT: case OPN_RJCT: case CNF_RJCT: reason = cand->reason; plink_frame_tx(cand, PLINK_CLOSE, reason); break; default: break; } break; default: sae_debug(AMPE_DEBUG_FSM, "Unsupported event transition %d", event); break; } if (changed) meshd_set_mesh_conf(cand->conf->mesh, changed); }
static int plink_frame_tx(struct candidate *cand, enum plink_action_code action, unsigned short reason) { unsigned char *buf; struct ieee80211_mgmt_frame *mgmt; struct mesh_node *mesh = cand->conf->mesh; struct ieee80211_supported_band *sband = &mesh->bands[mesh->band]; struct ht_cap_ie *ht_cap; struct ht_op_ie *ht_op; unsigned char ie_len; int len; unsigned char *ies; unsigned char *pos; u16 peering_proto = htole16(0x0001); /* AMPE */ assert(cand); #define LARGE_FRAME 1500; len = LARGE_FRAME; buf = calloc(1, len); #undef LARGE_FRAME if (!buf) return -1; sae_debug(AMPE_DEBUG_FSM, "Mesh plink: Sending plink action %d\n", action); mgmt = (struct ieee80211_mgmt_frame *) buf; mgmt->frame_control = htole16((IEEE802_11_FC_TYPE_MGMT << 2 | IEEE802_11_FC_STYPE_ACTION << 4)); memcpy(mgmt->da, cand->peer_mac, ETH_ALEN); memcpy(mgmt->sa, cand->my_mac, ETH_ALEN); memcpy(mgmt->bssid, cand->my_mac, ETH_ALEN); mgmt->action.category = IEEE80211_CATEGORY_SELF_PROTECTED; mgmt->action.action_code = action; pos = mgmt->action.u.var8; if (action != PLINK_CLOSE) { /* capability info */ *pos++ = 0x10; /* securitu */ *pos++ = 0; if (action == PLINK_CONFIRM) { /* AID */ memset(pos, 0, 2); pos += 2; } } ies = start_of_ies(mgmt, len, NULL); /* IE: All the static IEs */ memcpy(ies, sta_fixed_ies, sta_fixed_ies_len); ies += sta_fixed_ies_len; /* IE: Mesh ID element */ *ies++ = IEEE80211_EID_MESH_ID; *ies++ = mesh->conf->meshid_len; memcpy((char *) ies, mesh->conf->meshid, mesh->conf->meshid_len); ies += mesh->conf->meshid_len; /* IE: mesh config */ *ies++ = IEEE80211_EID_MESH_CONFIG; *ies++ = 8; /* TODO: IIRC all the defaults are 0. Double check */ memset(ies, 0, 8); ies += 8; ie_len = 4 + 16; /* min. + PMKID */ /* IE: Mesh Peering Management element */ switch (action) { case PLINK_OPEN: break; case PLINK_CONFIRM: ie_len += 2; break; case PLINK_CLOSE: if (&cand->peer_lid) { ie_len += 2; } ie_len += 2; /* reason code */ break; default: free(buf); return -EINVAL; } *ies++ = IEEE80211_EID_MESH_PEERING; *ies++ = ie_len; memcpy(ies, &peering_proto, 2); ies += 2; memcpy(ies, &cand->my_lid, 2); ies += 2; if (cand->peer_lid && (action != PLINK_OPEN)) { memcpy(ies, &cand->peer_lid, 2); ies += 2; } if (action == PLINK_CLOSE) { memcpy(ies, &cand->reason, 2); ies += 2; } memcpy(ies, cand->pmkid, sizeof(cand->pmkid)); ies += sizeof(cand->pmkid); if (mesh->conf->channel_type != NL80211_CHAN_NO_HT && sband->ht_cap.ht_supported) { /* HT IEs */ *ies++ = IEEE80211_EID_HT_CAPABILITY; *ies++ = sizeof(struct ht_cap_ie); ht_cap = (struct ht_cap_ie *) ies; ht_cap->cap_info = htole16(sband->ht_cap.cap); ht_cap->ampdu_params_info = sband->ht_cap.ampdu_factor | (sband->ht_cap.ampdu_density << 2); memcpy(&ht_cap->mcs, &sband->ht_cap.mcs, sizeof(struct mcs_info)); /* mac80211 apparently ignores the rest */ ies += sizeof(*ht_cap); *ies++ = IEEE80211_EID_HT_OPERATION; *ies++ = sizeof(struct ht_op_ie); ht_op = (struct ht_op_ie *) ies; ht_op->primary_chan = mesh->conf->channel; switch (mesh->conf->channel_type) { case NL80211_CHAN_HT40MINUS: ht_op->ht_param = IEEE80211_HT_PARAM_CHA_SEC_BELOW; break; case NL80211_CHAN_HT40PLUS: ht_op->ht_param = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; break; case NL80211_CHAN_HT20: default: ht_op->ht_param = IEEE80211_HT_PARAM_CHA_SEC_NONE; break; } if (sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 && mesh->conf->channel_type > NL80211_CHAN_HT20) ht_op->ht_param |= IEEE80211_HT_PARAM_CHAN_WIDTH_ANY; ht_op->operation_mode = htole16(mesh->conf->ht_prot_mode); memset(ht_op->basic_set, 0, 16); ht_op->basic_set[0] = 0xff; /* mandatory HT phy rates */ ies += sizeof(*ht_op); } /* IE: Add MIC and encrypted AMPE */ if (protect_frame(cand, (struct ieee80211_mgmt_frame *)buf, ies, &len) < 0) sae_debug(SAE_DEBUG_ERR, "Failed to protect frame\n"); if (meshd_write_mgmt((char *)buf, len, cand->cookie) != len) { sae_debug(SAE_DEBUG_ERR, "can't send a peering " "frame to " MACSTR "\n", MAC2STR(cand->peer_mac)); } free(buf); return 0; }
static int check_frame_protection(struct candidate *cand, struct ieee80211_mgmt_frame *mgmt, int len, struct info_elems *elems) { unsigned char *clear_ampe_ie; struct info_elems ies_parsed; unsigned short ampe_ie_len, cat_to_mic_len; int r; unsigned int* key_expiration_p; assert(len && cand && mgmt); clear_ampe_ie = malloc(sizeof(struct ampe_ie) + 2); if (!clear_ampe_ie) { sae_debug(AMPE_DEBUG_KEYS, "Verify frame: out of memory\n"); return -1; } if (!elems->mic || elems->mic_len != MIC_IE_BODY_SIZE) { sae_debug(AMPE_DEBUG_KEYS, "Verify frame: invalid MIC\n"); free(clear_ampe_ie); return -1; } /* * ampe_ie_len is the length of the ciphertext (the encrypted * AMPE IE) and it needs to be inferred from the total frame * size */ ampe_ie_len = len - (elems->mic + elems->mic_len - (unsigned char *)mgmt); /* * cat_to_mic_len is the length of the contents of the frame * from the category (inclusive) to the mic (exclusive) */ cat_to_mic_len = elems->mic - 2 - (unsigned char *) &mgmt->action; r = siv_decrypt(&cand->sivctx, elems->mic + elems->mic_len, clear_ampe_ie, ampe_ie_len, elems->mic, 3, cand->peer_mac, ETH_ALEN, cand->my_mac, ETH_ALEN, &mgmt->action, cat_to_mic_len); sae_debug(AMPE_DEBUG_KEYS, "Checking protection to " MACSTR " from " MACSTR "\n", MAC2STR(cand->my_mac), MAC2STR(cand->peer_mac)); sae_debug(AMPE_DEBUG_KEYS, "Len checking cat-to-mic len:%d ampe ie full length: %d\n", cat_to_mic_len, ampe_ie_len); sae_hexdump(AMPE_DEBUG_KEYS, "SIV- Got AAD[3]: ", (unsigned char *) &mgmt->action, cat_to_mic_len); if (r != 1) { sae_debug(AMPE_DEBUG_KEYS, "Protection check failed\n"); free(clear_ampe_ie); return -1; } sae_hexdump(AMPE_DEBUG_KEYS, "AMPE IE: ", clear_ampe_ie, ampe_ie_len); parse_ies(clear_ampe_ie, ampe_ie_len, &ies_parsed); if (memcmp(ies_parsed.ampe->peer_nonce, null_nonce, 32) != 0 && memcmp(ies_parsed.ampe->peer_nonce, cand->my_nonce, 32) != 0) { sae_hexdump(AMPE_DEBUG_KEYS, "IE peer_nonce ", ies_parsed.ampe->peer_nonce, 32); sae_debug(AMPE_DEBUG_KEYS, "Unexpected nonce\n"); free(clear_ampe_ie); return -1; } memcpy(cand->peer_nonce, ies_parsed.ampe->local_nonce, 32); memcpy(cand->mgtk, ies_parsed.ampe->mgtk, sizeof(cand->mgtk)); sae_hexdump(AMPE_DEBUG_KEYS, "Received mgtk: ", cand->mgtk, sizeof(cand->mgtk)); key_expiration_p = (unsigned int *)ies_parsed.ampe->key_expiration; cand->mgtk_expiration = le32toh(*key_expiration_p); free(clear_ampe_ie); return -1; #undef MIC_IE_BODY_SIZE }
/** * protect_frame - add in-place the MIC and the (encrypted) AMPE ie to a frame * @cand: The candidate this frame is destined for * @mgmt: The frame, populated with all the information elements up to where the MIC information element should go * @mic_start: Pointer to where the mic and AMPE ies are to be written. Should point to the start of the IE, not the IE body. * @len: On input, the total buffer size that contains this frame. On output, the actual lenght of the frame * including the two information elements added by this function. * * Returns: The zero on success, or some error. */ static int protect_frame(struct candidate *cand, struct ieee80211_mgmt_frame *mgmt, unsigned char *mic_start, int *len) { unsigned char *clear_ampe_ie; unsigned char *ie; unsigned short cat_to_mic_len; assert(mic_start && cand && mgmt && len); #define MIC_IE_BODY_SIZE AES_BLOCK_SIZE if (mic_start + MIC_IE_BODY_SIZE + 2 + sizeof(struct ampe_ie) + 2 - (unsigned char *) mgmt > *len) { sae_debug(AMPE_DEBUG_KEYS, "protect frame: buffer too small\n"); return -EINVAL; } clear_ampe_ie = malloc(sizeof(struct ampe_ie) + 2); if (!clear_ampe_ie) { sae_debug(AMPE_DEBUG_KEYS, "protect frame: out of memory\n"); return -ENOMEM; } /* IE: AMPE */ ie = clear_ampe_ie; *ie++ = IEEE80211_EID_AMPE; *ie++ = sizeof(struct ampe_ie); memcpy(ie, pw_suite_selector, 4); ie += 4; memcpy(ie, cand->my_nonce, 32); ie += 32; memcpy(ie, cand->peer_nonce, 32); ie += 32; memcpy(ie, mgtk_tx, 16); ie += 16; memset(ie, 0, 8); /* TODO: Populate Key RSC */ ie += 8; memset(ie, 0xff, 4); /* expire in 13 decades or so */ ie += 4; /* IE: MIC */ ie = mic_start; *ie++ = IEEE80211_EID_MIC; *ie++ = MIC_IE_BODY_SIZE; cat_to_mic_len = mic_start - (unsigned char *) &mgmt->action; siv_encrypt(&cand->sivctx, clear_ampe_ie, ie + MIC_IE_BODY_SIZE, sizeof(struct ampe_ie) + 2, ie, 3, cand->my_mac, ETH_ALEN, cand->peer_mac, ETH_ALEN, &mgmt->action, cat_to_mic_len); *len = mic_start - (unsigned char *) mgmt + sizeof(struct ampe_ie) + 2 + MIC_IE_BODY_SIZE + 2; sae_debug(AMPE_DEBUG_KEYS, "Protecting frame from " MACSTR " to " MACSTR "\n", MAC2STR(cand->my_mac), MAC2STR(cand->peer_mac)); sae_debug(AMPE_DEBUG_KEYS, "Checking tricky lengths of protected frame %d, %d\n", cat_to_mic_len, sizeof(struct ampe_ie) + 2); sae_hexdump(AMPE_DEBUG_KEYS, "SIV- Put AAD[3]: ", (unsigned char *) &mgmt->action, cat_to_mic_len); free(clear_ampe_ie); return 0; }
static void plink_timer(timerid id, void *data) { __le16 reason; struct candidate *cand; cand = (struct candidate *)data; assert(cand); sae_debug(AMPE_DEBUG_FSM, "Mesh plink timer for " MACSTR " fired on state %s\n", MAC2STR(cand->peer_mac), mplstates[(cand->link_state > PLINK_BLOCKED) ? PLINK_UNDEFINED : cand->link_state]); reason = 0; switch (cand->link_state) { case PLINK_OPN_RCVD: case PLINK_OPN_SNT: /* retry timer */ sae_debug(AMPE_DEBUG_FSM, "Mesh plink:retries %d of %d\n", cand->retries, cand->conf->max_retries); if (cand->retries < cand->conf->max_retries) { unsigned int rand; sae_debug(AMPE_DEBUG_FSM, "Mesh plink for " MACSTR " (retry, timeout): %d %d\n", MAC2STR(cand->peer_mac), cand->retries, cand->timeout); RAND_bytes((unsigned char *) &rand, sizeof(rand)); if (!cand->timeout) { cand->timeout = cand->conf->retry_timeout_ms; sae_debug(AMPE_DEBUG_ERR, "WARN: cand " MACSTR " had a timeout of 0ms. Reset to %d\n", MAC2STR(cand->peer_mac),cand->timeout); } cand->timeout += rand % cand->timeout; ++cand->retries; cand->t2 = srv_add_timeout(srvctx, SRV_MSEC(cand->timeout), plink_timer, cand); plink_frame_tx(cand, PLINK_OPEN, 0); break; } reason = htole16(MESH_MAX_RETRIES); /* fall through on else */ case PLINK_CNF_RCVD: /* confirm timer */ if (!reason) reason = htole16(MESH_CONFIRM_TIMEOUT); set_link_state(cand, PLINK_HOLDING); cand->t2 = srv_add_timeout(srvctx, SRV_MSEC(cand->conf->holding_timeout_ms), plink_timer, cand); plink_frame_tx(cand, PLINK_CLOSE, reason); break; case PLINK_HOLDING: /* holding timer */ fsm_restart(cand); break; case PLINK_ESTAB: /* nothing to do */ break; default: sae_debug(AMPE_DEBUG_FSM, "Timeout for peer " MACSTR " in state %d\n", MAC2STR(cand->peer_mac), cand->link_state); break; } }
static int plink_free_count() { sae_debug(AMPE_DEBUG_FSM, "TODO: return available peer link slots\n"); return 99; }
static int check_frame_protection(struct candidate *cand, struct ieee80211_mgmt_frame *mgmt, int len, struct info_elems *elems) { unsigned char *clear_ampe_ie; struct info_elems ies_parsed; struct mesh_node *mesh = cand->conf->mesh; unsigned short ampe_ie_len, cat_to_mic_len; int r; unsigned int* key_expiration_p; u8 ftype = mgmt->action.action_code; u8 *gtkdata, *igtkdata; assert(len && cand && mgmt); if (!elems->mic || elems->mic_len != MIC_IE_BODY_SIZE) { sae_debug(AMPE_DEBUG_KEYS, "Verify frame: invalid MIC\n"); return -1; } /* * ampe_ie_len is the length of the ciphertext (the encrypted * AMPE IE) and it needs to be inferred from the total frame * size */ ampe_ie_len = len - (elems->mic + elems->mic_len - (unsigned char *)mgmt); /* expect at least MGTK + RSC + expiry for open/confirm */ if (ftype != PLINK_CLOSE && ampe_ie_len < 2 + sizeof(struct ampe_ie) + 16 + 8 + 4) { sae_debug(AMPE_DEBUG_KEYS, "Verify frame: AMPE IE too small\n"); return -1; } /* if PMF, then we also need IGTKData */ if (mesh->conf->pmf) { if (ampe_ie_len < 2 + sizeof(struct ampe_ie) + 16 + 8 + 4 + 2 + 6 + 16 /* IGTKData */) { sae_debug(AMPE_DEBUG_KEYS, "Verify frame: AMPE IE missing IGTK\n"); return -1; } } clear_ampe_ie = malloc(ampe_ie_len); if (!clear_ampe_ie) { sae_debug(AMPE_DEBUG_KEYS, "Verify frame: out of memory\n"); return -1; } /* * cat_to_mic_len is the length of the contents of the frame * from the category (inclusive) to the mic (exclusive) */ cat_to_mic_len = elems->mic - 2 - (unsigned char *) &mgmt->action; r = siv_decrypt(&cand->sivctx, elems->mic + elems->mic_len, clear_ampe_ie, ampe_ie_len, elems->mic, 3, cand->peer_mac, ETH_ALEN, cand->my_mac, ETH_ALEN, &mgmt->action, cat_to_mic_len); sae_debug(AMPE_DEBUG_KEYS, "Checking protection to " MACSTR " from " MACSTR "\n", MAC2STR(cand->my_mac), MAC2STR(cand->peer_mac)); sae_debug(AMPE_DEBUG_KEYS, "Len checking cat-to-mic len:%d ampe ie full length: %d\n", cat_to_mic_len, ampe_ie_len); sae_hexdump(AMPE_DEBUG_KEYS, "SIV- Got AAD[3]: ", (unsigned char *) &mgmt->action, cat_to_mic_len); if (r != 1) { sae_debug(AMPE_DEBUG_KEYS, "Protection check failed\n"); free(clear_ampe_ie); return -1; } if (ampe_ie_len != clear_ampe_ie[1] + 2) { sae_debug(AMPE_DEBUG_KEYS, "AMPE -Invalid length (expected %d, got %d)\n", ampe_ie_len, clear_ampe_ie[1] + 2); free(clear_ampe_ie); return -1; } sae_hexdump(AMPE_DEBUG_KEYS, "AMPE IE: ", clear_ampe_ie, ampe_ie_len); parse_ies(clear_ampe_ie, ampe_ie_len, &ies_parsed); if (memcmp(ies_parsed.ampe->peer_nonce, null_nonce, 32) != 0 && memcmp(ies_parsed.ampe->peer_nonce, cand->my_nonce, 32) != 0) { sae_hexdump(AMPE_DEBUG_KEYS, "IE peer_nonce ", ies_parsed.ampe->peer_nonce, 32); sae_debug(AMPE_DEBUG_KEYS, "Unexpected nonce\n"); free(clear_ampe_ie); return -1; } memcpy(cand->peer_nonce, ies_parsed.ampe->local_nonce, 32); gtkdata = ies_parsed.ampe->variable; memcpy(cand->mgtk, gtkdata, sizeof(cand->mgtk)); sae_hexdump(AMPE_DEBUG_KEYS, "Received mgtk: ", cand->mgtk, sizeof(cand->mgtk)); key_expiration_p = (unsigned int *) (gtkdata + 16 + 8); cand->mgtk_expiration = le32toh(*key_expiration_p); igtkdata = gtkdata + 16 + 8 + 4; if (mesh->conf->pmf) { cand->igtk_keyid = le16toh(*(u16 *) igtkdata); igtkdata += 2 + 6; memcpy(cand->igtk, igtkdata, 16); } free(clear_ampe_ie); return -1; #undef MIC_IE_BODY_SIZE }
static int plink_frame_tx( struct candidate *cand, enum plink_action_code action, unsigned short reason) { unsigned char *buf; struct ieee80211_mgmt_frame *mgmt; struct mesh_node *mesh; struct ieee80211_supported_band *sband; struct ht_cap_ie *ht_cap; struct ht_op_ie *ht_op; struct vht_op_ie *vht_op; unsigned char *ie_len_ptr; int len; int ret; unsigned char *ies; unsigned char *pos; u16 peering_proto; u16 close_reason; size_t alloc_len; int peer_count; unsigned char mesh_capa; assert(cand); assert(cand->conf); mesh = cand->conf->mesh; sband = &mesh->bands[mesh->band]; alloc_len = sizeof(struct ieee80211_mgmt_frame) + 2 + /* capability info */ 2 + /* aid */ sta_fixed_ies_len + 2 + mesh->conf->meshid_len + /* mesh id */ 2 + 7 + /* mesh config */ 2 + 8 + sizeof(cand->pmkid) + /* mesh peering management */ 2 + sizeof(struct ht_cap_ie) + /* HT capabilities */ 2 + sizeof(struct ht_op_ie) + /* HT operation */ 2 + 12 + /* VHT capabilities */ 2 + 5 + /* VHT operation */ 2 + 120 + /* AMPE, without Key Replay counter, 16 byte keys */ 2 + MIC_IE_BODY_SIZE; /* MIC */ buf = calloc(1, alloc_len); if (!buf) return -1; sae_debug(AMPE_DEBUG_FSM, "Mesh plink: Sending plink action %d\n", action); mgmt = (struct ieee80211_mgmt_frame *)buf; mgmt->frame_control = htole16((IEEE802_11_FC_TYPE_MGMT << 2 | IEEE802_11_FC_STYPE_ACTION << 4)); memcpy(mgmt->da, cand->peer_mac, ETH_ALEN); memcpy(mgmt->sa, cand->my_mac, ETH_ALEN); memcpy(mgmt->bssid, cand->my_mac, ETH_ALEN); mgmt->action.category = IEEE80211_CATEGORY_SELF_PROTECTED; mgmt->action.action_code = action; pos = mgmt->action.u.var8; if (action != PLINK_CLOSE) { /* capability info */ *pos++ = (mesh->conf->is_secure) ? 0x10 : 0; *pos++ = 0; if (action == PLINK_CONFIRM) { /* AID */ uint16_t *aid = (uint16_t *)pos; *aid = ieee_order(cand->association_id); pos += 2; } } ies = start_of_ies(mgmt, len, NULL); /* IE: All the static IEs */ memcpy(ies, sta_fixed_ies, sta_fixed_ies_len); ies += sta_fixed_ies_len; /* IE: Mesh ID element */ *ies++ = IEEE80211_EID_MESH_ID; *ies++ = mesh->conf->meshid_len; memcpy((char *)ies, mesh->conf->meshid, mesh->conf->meshid_len); ies += mesh->conf->meshid_len; /* IE: mesh config */ *ies++ = IEEE80211_EID_MESH_CONFIG; *ies++ = 7; *ies++ = MESH_CONFIG_PP_HWMP; *ies++ = MESH_CONFIG_PM_ALM; *ies++ = MESH_CONFIG_CC_NONE; *ies++ = MESH_CONFIG_SP_NEIGHBOR_OFFSET; if (mesh->conf->is_secure) { *ies++ = MESH_CONFIG_AUTH_SAE; } else { *ies++ = 0; } /* formation info */ peer_count = plink_estab_count(); *ies++ = MIN(peer_count, 63) << 1; mesh_capa = MESH_CAPA_FORWARDING; if (peer_count < mesh->conf->max_plinks) mesh_capa |= MESH_CAPA_ACCEPT_PEERINGS; *ies++ = mesh_capa; /* IE: Mesh Peering Management element */ *ies++ = IEEE80211_EID_MESH_PEERING; ie_len_ptr = ies; ies++; if (mesh->conf->is_secure) { peering_proto = htole16(1); } else { peering_proto = 0; } memcpy(ies, &peering_proto, 2); ies += 2; memcpy(ies, &cand->my_lid, 2); ies += 2; if (cand->peer_lid && (action != PLINK_OPEN)) { memcpy(ies, &cand->peer_lid, 2); ies += 2; } if (action == PLINK_CLOSE) { close_reason = htole16(reason); memcpy(ies, &close_reason, 2); ies += 2; } if (mesh->conf->is_secure) { memcpy(ies, cand->pmkid, sizeof(cand->pmkid)); ies += sizeof(cand->pmkid); } *ie_len_ptr = ies - ie_len_ptr - 1; if (action != PLINK_CLOSE && mesh->conf->channel_width != CHAN_WIDTH_20_NOHT && sband->ht_cap.ht_supported) { /* HT IEs */ *ies++ = IEEE80211_EID_HT_CAPABILITY; *ies++ = sizeof(struct ht_cap_ie); ht_cap = (struct ht_cap_ie *)ies; ht_cap->cap_info = htole16(sband->ht_cap.cap); ht_cap->ampdu_params_info = sband->ht_cap.ampdu_factor | (sband->ht_cap.ampdu_density << 2); memcpy(&ht_cap->mcs, &sband->ht_cap.mcs, sizeof(struct mcs_info)); /* mac80211 apparently ignores the rest */ ies += sizeof(*ht_cap); *ies++ = IEEE80211_EID_HT_OPERATION; *ies++ = sizeof(struct ht_op_ie); ht_op = (struct ht_op_ie *)ies; ht_op->primary_chan = ieee80211_frequency_to_channel(mesh->conf->control_freq); switch (mesh->conf->channel_width) { case CHAN_WIDTH_40: if (mesh->conf->center_freq1 < mesh->conf->control_freq) ht_op->ht_param = IEEE80211_HT_PARAM_CHA_SEC_BELOW; else ht_op->ht_param = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; break; case CHAN_WIDTH_20: default: ht_op->ht_param = IEEE80211_HT_PARAM_CHA_SEC_NONE; break; } if ((sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) && mesh->conf->channel_width > CHAN_WIDTH_20) ht_op->ht_param |= IEEE80211_HT_PARAM_CHAN_WIDTH_ANY; ht_op->operation_mode = htole16(mesh->conf->ht_prot_mode); memset(ht_op->basic_set, 0, 16); ht_op->basic_set[0] = 0xff; /* mandatory HT phy rates */ ies += sizeof(*ht_op); } if (action != PLINK_CLOSE && mesh->conf->channel_width != CHAN_WIDTH_20_NOHT && sband->vht_cap.vht_supported) { *ies++ = IEEE80211_EID_VHT_CAPABILITY; *ies++ = sizeof(sband->vht_cap.cap) + sizeof(sband->vht_cap.mcs); memcpy(ies, &sband->vht_cap.cap, sizeof(sband->vht_cap.cap)); ies += sizeof(sband->vht_cap.cap); memcpy(ies, &sband->vht_cap.mcs, sizeof(sband->vht_cap.mcs)); ies += sizeof(sband->vht_cap.mcs); *ies++ = IEEE80211_EID_VHT_OPERATION; *ies++ = 5; vht_op = (struct vht_op_ie *)ies; switch (mesh->conf->channel_width) { case CHAN_WIDTH_80: case CHAN_WIDTH_80P80: case CHAN_WIDTH_160: vht_op->width = 1; break; default: vht_op->width = 0; } vht_op->center_chan1 = ieee80211_frequency_to_channel(mesh->conf->center_freq1); vht_op->center_chan2 = ieee80211_frequency_to_channel(mesh->conf->center_freq2); /* TODO allow configuring this for mixed capability STAs; * see 802.11-2016 11.40.7 */ memcpy( &vht_op->basic_set, &sband->vht_cap.mcs.rx_mcs_mask, sizeof(vht_op->basic_set)); ies += sizeof(*vht_op); } if (mesh->conf->is_secure) { /* IE: Add MIC and encrypted AMPE */ len = alloc_len; ret = protect_frame(cand, (struct ieee80211_mgmt_frame *)buf, ies, &len); if (ret) { sae_debug(SAE_DEBUG_ERR, "Failed to protect frame\n"); free(buf); return ret; } } else { len = ies - buf; } if (cb->meshd_write_mgmt((char *)buf, len, cand->cookie) != len) { sae_debug( SAE_DEBUG_ERR, "can't send a peering " "frame to " MACSTR "\n", MAC2STR(cand->peer_mac)); } free(buf); return 0; }
static void fsm_step(struct candidate *cand, enum plink_event event) { struct ampe_config *aconf = cand->conf; unsigned short reason = 0; uint32_t changed = 0; switch (cand->link_state) { case PLINK_LISTEN: switch (event) { case CLS_ACPT: fsm_restart(cand); break; case OPN_ACPT: cand->timeout = aconf->retry_timeout_ms; cb->evl->rem_timeout(cand->t2); cand->t2 = cb->evl->add_timeout(SRV_MSEC(cand->timeout), plink_timer, cand); set_link_state(cand, PLINK_OPN_RCVD); plink_frame_tx(cand, PLINK_OPEN, 0); plink_frame_tx(cand, PLINK_CONFIRM, 0); break; default: break; } break; case PLINK_OPN_SNT: switch (event) { case OPN_RJCT: case CNF_RJCT: case REQ_RJCT: reason = MESH_CAPABILITY_POLICY_VIOLATION; /* no break */ case CLS_ACPT: if (!reason) reason = MESH_CLOSE_RCVD; set_link_state(cand, PLINK_HOLDING); cand->timeout = aconf->holding_timeout_ms; cb->evl->rem_timeout(cand->t2); cand->t2 = cb->evl->add_timeout(SRV_MSEC(cand->timeout), plink_timer, cand); plink_frame_tx(cand, PLINK_CLOSE, reason); break; case OPN_ACPT: /* retry timer is left untouched */ set_link_state(cand, PLINK_OPN_RCVD); plink_frame_tx(cand, PLINK_CONFIRM, 0); break; case CNF_ACPT: set_link_state(cand, PLINK_CNF_RCVD); cand->timeout = aconf->confirm_timeout_ms; cb->evl->rem_timeout(cand->t2); cand->t2 = cb->evl->add_timeout(SRV_MSEC(cand->timeout), plink_timer, cand); break; default: break; } break; case PLINK_OPN_RCVD: switch (event) { case OPN_RJCT: case CNF_RJCT: case REQ_RJCT: reason = MESH_CAPABILITY_POLICY_VIOLATION; /* no break */ case CLS_ACPT: if (!reason) reason = MESH_CLOSE_RCVD; set_link_state(cand, PLINK_HOLDING); cand->timeout = aconf->holding_timeout_ms; cb->evl->rem_timeout(cand->t2); cand->t2 = cb->evl->add_timeout(SRV_MSEC(cand->timeout), plink_timer, cand); plink_frame_tx(cand, PLINK_CLOSE, reason); break; case OPN_ACPT: plink_frame_tx(cand, PLINK_CONFIRM, 0); break; case CNF_ACPT: changed |= do_estab_peer_link(cand); break; default: break; } break; case PLINK_CNF_RCVD: switch (event) { case OPN_RJCT: case CNF_RJCT: case REQ_RJCT: reason = MESH_CAPABILITY_POLICY_VIOLATION; /* no break */ case CLS_ACPT: if (!reason) reason = MESH_CLOSE_RCVD; set_link_state(cand, PLINK_HOLDING); cand->timeout = aconf->holding_timeout_ms; cb->evl->rem_timeout(cand->t2); cand->t2 = cb->evl->add_timeout(SRV_MSEC(cand->timeout), plink_timer, cand); plink_frame_tx(cand, PLINK_CLOSE, reason); break; case OPN_ACPT: changed |= do_estab_peer_link(cand); plink_frame_tx(cand, PLINK_CONFIRM, 0); break; default: break; } break; case PLINK_ESTAB: switch (event) { case OPN_RJCT: case CNF_RJCT: case REQ_RJCT: reason = MESH_CAPABILITY_POLICY_VIOLATION; case CLS_ACPT: if (!reason) reason = MESH_CLOSE_RCVD; set_link_state(cand, PLINK_HOLDING); cand->timeout = aconf->holding_timeout_ms; cb->evl->rem_timeout(cand->t2); cand->t2 = cb->evl->add_timeout(SRV_MSEC(cand->timeout), plink_timer, cand); changed |= mesh_set_ht_op_mode(cand->conf->mesh); plink_frame_tx(cand, PLINK_CLOSE, reason); break; case OPN_ACPT: plink_frame_tx(cand, PLINK_CONFIRM, 0); break; default: break; } break; case PLINK_HOLDING: switch (event) { case CLS_ACPT: fsm_restart(cand); break; case OPN_ACPT: case CNF_ACPT: case OPN_RJCT: case CNF_RJCT: case REQ_RJCT: plink_frame_tx(cand, PLINK_CLOSE, reason); break; default: break; } break; default: sae_debug(AMPE_DEBUG_FSM, "Unsupported event transition %d", event); break; } if (changed) cb->meshd_set_mesh_conf(cand->conf->mesh, changed); }