예제 #1
0
파일: ampe.c 프로젝트: cozybit/authsae
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);
}
예제 #2
0
파일: ampe.c 프로젝트: cozybit/authsae
/**
 * 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(&ampe_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);
}
예제 #3
0
파일: ampe.c 프로젝트: cozybit/authsae
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;
}
예제 #4
0
파일: ampe.c 프로젝트: cozybit/authsae
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);
}
예제 #5
0
파일: ampe.c 프로젝트: cozybit/authsae
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;
}
예제 #6
0
파일: ampe.c 프로젝트: cozybit/authsae
/**
 * 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);
}
예제 #7
0
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;
}
예제 #8
0
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;
}
예제 #9
0
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;
}
예제 #10
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;
}
예제 #11
0
/**
 * 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;
}
예제 #12
0
파일: ampe.c 프로젝트: cozybit/authsae
/**
 * 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(&ampe_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;
}
예제 #13
0
파일: ampe.c 프로젝트: cozybit/authsae
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;
  }
}
예제 #14
0
파일: ampe.c 프로젝트: cococorp/authsae
/**
 * 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(&ampe_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;
}
예제 #15
0
파일: ampe.c 프로젝트: cococorp/authsae
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);
}
예제 #16
0
파일: ampe.c 프로젝트: cococorp/authsae
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;
}
예제 #17
0
파일: ampe.c 프로젝트: cococorp/authsae
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
}
예제 #18
0
파일: ampe.c 프로젝트: cococorp/authsae
/**
 * 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;
}
예제 #19
0
파일: ampe.c 프로젝트: cococorp/authsae
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;
	}
}
예제 #20
0
파일: ampe.c 프로젝트: cococorp/authsae
static int plink_free_count() {
    sae_debug(AMPE_DEBUG_FSM, "TODO: return available peer link slots\n");
    return 99;
}
예제 #21
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;
    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
}
예제 #22
0
파일: ampe.c 프로젝트: cozybit/authsae
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;
}
예제 #23
0
파일: ampe.c 프로젝트: cozybit/authsae
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);
}