#ifdef WL_EXT_GENL #include #include #include #include #define AGENL_ERROR(name, arg1, args...) \ do { \ if (android_msg_level & ANDROID_ERROR_LEVEL) { \ printf("[%s] AGENL-ERROR) %s : " arg1, name, __func__, ## args); \ } \ } while (0) #define AGENL_TRACE(name, arg1, args...) \ do { \ if (android_msg_level & ANDROID_TRACE_LEVEL) { \ printf("[%s] AGENL-TRACE) %s : " arg1, name, __func__, ## args); \ } \ } while (0) #define AGENL_INFO(name, arg1, args...) \ do { \ if (android_msg_level & ANDROID_INFO_LEVEL) { \ printf("[%s] AGENL-INFO) %s : " arg1, name, __func__, ## args); \ } \ } while (0) #define htod32(i) i #define htod16(i) i #define dtoh32(i) i #define dtoh16(i) i #ifdef SENDPROB #define MGMT_PROBE_REQ 0x40 #define MGMT_PROBE_RES 0x50 #endif enum { __GENL_CUSTOM_ATTR_INVALID, GENL_CUSTOM_ATTR_MSG, /* message */ __GENL_CUSTOM_ATTR_MAX, }; enum { __GENLL_CUSTOM_COMMAND_INVALID, GENL_CUSTOM_COMMAND_BIND, /* bind */ GENL_CUSTOM_COMMAND_SEND, /* user -> kernel */ GENL_CUSTOM_COMMAND_RECV, /* kernel -> user */ __GENL_CUSTOM_COMMAND_MAX, }; #if defined(ALIBABA_ZEROCONFIG) #define GENL_FAMILY_NAME "WIFI_NL_CUSTOM" #define PROBE_RSP_DST_MAC_OFFSET 4 #define PROBE_RSP_VNDR_ID_OFFSET 55 #else #define GENL_FAMILY_NAME "WLAN_NL_CUSTOM" #define PROBE_RSP_DST_MAC_OFFSET 4 #define PROBE_RSP_VNDR_ID_OFFSET DOT11_MGMT_HDR_LEN #endif #define PROBE_RSP_VNDR_LEN_OFFSET (PROBE_RSP_VNDR_ID_OFFSET+1) #define PROBE_RSP_VNDR_OUI_OFFSET (PROBE_RSP_VNDR_ID_OFFSET+2) #define MAX_CUSTOM_PKT_LENGTH 2048 #define GENL_CUSTOM_ATTR_MAX (__GENL_CUSTOM_ATTR_MAX - 1) #define GENLMSG_UNICAST_RETRY_LIMIT 5 typedef struct genl_params { struct net_device *dev; bool bind; int pm; int bind_pid; int send_retry_cnt; } genl_params_t; struct genl_params *g_zconf = NULL; static int wl_ext_genl_bind(struct sk_buff *skb, struct genl_info *info); static int wl_ext_genl_recv(struct sk_buff *skb, struct genl_info *info); static int wl_ext_genl_send(struct genl_params *zconf, struct net_device *dev, char* buf, int buf_len); static struct nla_policy wl_ext_genl_policy[GENL_CUSTOM_ATTR_MAX + 1] = { [GENL_CUSTOM_ATTR_MSG] = {.type = NLA_NUL_STRING}, }; static struct genl_ops wl_ext_genl_ops[] = { { .cmd = GENL_CUSTOM_COMMAND_BIND, .flags = 0, .policy = wl_ext_genl_policy, .doit = wl_ext_genl_bind, .dumpit = NULL, }, { .cmd = GENL_CUSTOM_COMMAND_SEND, .flags = 0, .policy = wl_ext_genl_policy, .doit = wl_ext_genl_recv, .dumpit = NULL, }, }; static struct genl_family wl_ext_genl_family = { .id = GENL_ID_GENERATE, .hdrsize = 0, .name = GENL_FAMILY_NAME, .version = 1, .maxattr = GENL_CUSTOM_ATTR_MAX, }; #ifdef SENDPROB static int wl_ext_add_del_ie_hex(struct net_device *dev, uint pktflag, char *ie_data, int ie_len, const char* add_del_cmd) { vndr_ie_setbuf_t *vndr_ie = NULL; char iovar_buf[WLC_IOCTL_SMLEN]="\0"; int tot_len = 0, iecount; int err = -1; if (!ie_len) { AGENL_ERROR(dev->name, "wrong ie_len %d\n", ie_len); goto exit; } tot_len = (int)(sizeof(vndr_ie_setbuf_t) + (ie_len)); vndr_ie = (vndr_ie_setbuf_t *) kzalloc(tot_len, GFP_KERNEL); if (!vndr_ie) { AGENL_ERROR(dev->name, "IE memory alloc failed\n"); err = -ENOMEM; goto exit; } /* Copy the vndr_ie SET command ("add"/"del") to the buffer */ strncpy(vndr_ie->cmd, add_del_cmd, VNDR_IE_CMD_LEN - 1); vndr_ie->cmd[VNDR_IE_CMD_LEN - 1] = '\0'; /* Set the IE count - the buffer contains only 1 IE */ iecount = htod32(1); memcpy((void *)&vndr_ie->vndr_ie_buffer.iecount, &iecount, sizeof(s32)); /* Set packet flag to indicate that BEACON's will contain this IE */ pktflag = htod32(pktflag); memcpy((void *)&vndr_ie->vndr_ie_buffer.vndr_ie_list[0].pktflag, &pktflag, sizeof(u32)); /* Set the IE ID */ vndr_ie->vndr_ie_buffer.vndr_ie_list[0].vndr_ie_data.id = (uchar)DOT11_MNG_VS_ID; /* Set the IE LEN */ vndr_ie->vndr_ie_buffer.vndr_ie_list[0].vndr_ie_data.len = ie_len; /* Set the IE OUI and DATA */ memcpy((char *)vndr_ie->vndr_ie_buffer.vndr_ie_list[0].vndr_ie_data.oui, ie_data, ie_len); err = wldev_iovar_setbuf(dev, "vndr_ie", vndr_ie, tot_len, iovar_buf, sizeof(iovar_buf), NULL); if (err != 0) AGENL_ERROR(dev->name, "vndr_ie, ret=%d\n", err); exit: if (vndr_ie) { kfree(vndr_ie); } return err; } static int wl_ext_send_probersp(struct net_device *dev, char* buf, int buf_len) { char addr[ETHER_ADDR_LEN], *pVndrOUI; char iovar_buf[WLC_IOCTL_SMLEN]="\0"; int err = -1, ie_len; if (buf == NULL || buf_len <= 0){ AGENL_ERROR(dev->name, "buf is NULL or buf_len <= 0\n"); return -1; } AGENL_TRACE(dev->name, "Enter\n"); memcpy(addr, (buf+PROBE_RSP_DST_MAC_OFFSET), ETHER_ADDR_LEN); pVndrOUI = (buf+PROBE_RSP_VNDR_OUI_OFFSET); ie_len = *(buf+PROBE_RSP_VNDR_LEN_OFFSET); if (ie_len > (buf_len-PROBE_RSP_VNDR_OUI_OFFSET)) { AGENL_ERROR(dev->name, "wrong vendor ie len %d\n", ie_len); return -1; } err = wl_ext_add_del_ie_hex(dev, VNDR_IE_PRBRSP_FLAG, pVndrOUI, ie_len, "add"); if (err) goto exit; err = wldev_iovar_setbuf(dev, "send_probresp", addr, ETHER_ADDR_LEN, iovar_buf, sizeof(iovar_buf), NULL); if (err != 0) AGENL_ERROR(dev->name, "vndr_ie, ret=%d\n", err); OSL_SLEEP(100); wl_ext_add_del_ie_hex(dev, VNDR_IE_PRBRSP_FLAG, pVndrOUI, ie_len, "del"); exit: return err; } static int wl_ext_set_probreq(struct net_device *dev, bool set) { int bytes_written = 0; char recv_probreq[32]; AGENL_TRACE(dev->name, "Enter\n"); if (set) { sprintf(recv_probreq, "wl recv_probreq 1"); wl_android_ext_priv_cmd(dev, recv_probreq, 0, &bytes_written); } else { sprintf(recv_probreq, "wl recv_probreq 0"); wl_android_ext_priv_cmd(dev, recv_probreq, 0, &bytes_written); } return 0; } void wl_ext_probreq_event(struct net_device *dev, void *argu, const wl_event_msg_t *e, void *data) { struct genl_params *zconf = (struct genl_params *)argu; int i, ret = 0, num_ie = 0, totlen; uint32 event_len = 0; char *buf, *pbuf; uint rem_len, buflen = MAX_CUSTOM_PKT_LENGTH; uint32 event_id[] = {DOT11_MNG_VS_ID}; uint32 datalen = ntoh32(e->datalen); bcm_tlv_t *ie; AGENL_TRACE(dev->name, "Enter\n"); rem_len = buflen; buf = kzalloc(MAX_CUSTOM_PKT_LENGTH, GFP_KERNEL); if (unlikely(!buf)) { AGENL_ERROR(dev->name, "Could not allocate buf\n"); return; } // copy mgmt header pbuf = buf; memcpy(pbuf, data, DOT11_MGMT_HDR_LEN); rem_len -= (DOT11_MGMT_HDR_LEN+1); datalen -= DOT11_MGMT_HDR_LEN; data += DOT11_MGMT_HDR_LEN; // copy IEs pbuf = buf + DOT11_MGMT_HDR_LEN; #if 1 // non-sort by id ie = (bcm_tlv_t*)data; totlen = datalen; while (ie && totlen >= TLV_HDR_LEN) { int ie_id = -1; int ie_len = ie->len + TLV_HDR_LEN; for (i=0; iid == event_id[i]) { ie_id = ie->id; break; } } if ((ie->id == ie_id) && (totlen >= ie_len) && (rem_len >= ie_len)) { memcpy(pbuf, ie, ie_len); pbuf += ie_len; rem_len -= ie_len; num_ie++; } ie = (bcm_tlv_t*)((uint8*)ie + ie_len); totlen -= ie_len; } #else // sort by id for (i = 0; i < sizeof(event_id)/sizeof(event_id[0]); i++) { void *pdata = data; int data_len = datalen; while (rem_len > 0) { ie = bcm_parse_tlvs(pdata, data_len, event_id[i]); if (!ie) break; if (rem_len < (ie->len+TLV_HDR_LEN)) { ANDROID_TRACE(("%s: buffer is not enough\n", __FUNCTION__)); break; } memcpy(pbuf, ie, min(ie->len+TLV_HDR_LEN, rem_len)); pbuf += (ie->len+TLV_HDR_LEN); rem_len -= (ie->len+TLV_HDR_LEN); data_len -= (((void *)ie-pdata) + (ie->len+TLV_HDR_LEN)); pdata = (char *)ie + (ie->len+TLV_HDR_LEN); num_ie++; } } #endif if (num_ie) { event_len = buflen - rem_len; AGENL_INFO(dev->name, "num_ie=%d\n", num_ie); if (android_msg_level & ANDROID_INFO_LEVEL) prhex("buf", buf, event_len); ret = wl_ext_genl_send(zconf, dev, buf, event_len); } if(buf) kfree(buf); return; } #endif static int wl_ext_genl_recv(struct sk_buff *skb, struct genl_info *info) { struct genl_params *zconf = g_zconf; struct net_device *dev; struct nlattr *na; char* pData = NULL; int DataLen = 0; if (info == NULL) { AGENL_ERROR(dev->name, "genl_info is NULL\n"); return -1; } if (zconf == NULL) { AGENL_ERROR("wlan", "g_zconf is NULL\n"); return -1; } dev = zconf->dev; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) AGENL_TRACE(dev->name, "Enter snd_portid=%d\n", info->snd_portid); #else AGENL_TRACE(dev->name, "Enter\n"); #endif na = info->attrs[GENL_CUSTOM_ATTR_MSG]; if (na) { pData = (char*) nla_data(na); DataLen = nla_len(na); AGENL_INFO(dev->name, "nla_len(na) : %d\n", DataLen); if (android_msg_level & ANDROID_INFO_LEVEL) prhex("nla_data(na)", pData, DataLen); } #ifdef SENDPROB if(*pData == MGMT_PROBE_RES) { wl_ext_send_probersp(dev, pData, DataLen); } else if(*pData == MGMT_PROBE_REQ) { AGENL_ERROR(dev->name, "probe req\n"); } else { AGENL_ERROR(dev->name, "Unexpected pkt %d\n", *pData); if (android_msg_level & ANDROID_INFO_LEVEL) prhex("nla_data(na)", pData, DataLen); } #endif return 0; } static int wl_ext_genl_send(struct genl_params *zconf, struct net_device *dev, char* buf, int buf_len) { struct sk_buff *skb = NULL; char* msg_head = NULL; int ret = -1; int bytes_written = 0; char recv_probreq[32]; if (zconf->bind_pid == -1) { AGENL_ERROR(dev->name, "There is no binded process\n"); return -1; } if(buf == NULL || buf_len <= 0) { AGENL_ERROR(dev->name, "buf is NULL or buf_len : %d\n", buf_len); return -1; } skb = genlmsg_new(MAX_CUSTOM_PKT_LENGTH, GFP_KERNEL); if (skb) { msg_head = genlmsg_put(skb, 0, 0, &wl_ext_genl_family, 0, GENL_CUSTOM_COMMAND_RECV); if (msg_head == NULL) { nlmsg_free(skb); AGENL_ERROR(dev->name, "genlmsg_put fail\n"); return -1; } ret = nla_put(skb, GENL_CUSTOM_ATTR_MSG, buf_len, buf); if (ret != 0) { nlmsg_free(skb); AGENL_ERROR(dev->name, "nla_put fail : %d\n", ret); return ret; } genlmsg_end(skb, msg_head); /* sending message */ AGENL_TRACE(dev->name, "send to process %d\n", zconf->bind_pid); ret = genlmsg_unicast(&init_net, skb, zconf->bind_pid); if (ret != 0) { AGENL_ERROR(dev->name, "genlmsg_unicast fail : %d\n", ret); zconf->send_retry_cnt++; if(zconf->send_retry_cnt >= GENLMSG_UNICAST_RETRY_LIMIT) { AGENL_ERROR(dev->name, "Exceeding retry cnt %d, Unbind pid : %d\n", zconf->send_retry_cnt, zconf->bind_pid); zconf->bind_pid = -1; sprintf(recv_probreq, "wl recv_probreq 0"); wl_android_ext_priv_cmd(dev, recv_probreq, 0, &bytes_written); } return ret; } } else { AGENL_ERROR(dev->name, "genlmsg_new fail\n"); return -1; } zconf->send_retry_cnt = 0; return 0; } static int wl_ext_genl_bind(struct sk_buff *skb, struct genl_info *info) { struct genl_params *zconf = g_zconf; struct net_device *dev; struct dhd_pub *dhd; struct nlattr *na; bool bind; char* pData = NULL; int DataLen = 0; if (info == NULL) { AGENL_ERROR("wlan", "genl_info is NULL\n"); return -1; } if (zconf == NULL) { AGENL_ERROR("wlan", "zconf is NULL\n"); return -1; } dev = zconf->dev; dhd = dhd_get_pub(dev); AGENL_TRACE(dev->name, "Enter\n"); na = info->attrs[GENL_CUSTOM_ATTR_MSG]; if (na) { pData = (char*) nla_data(na); DataLen = nla_len(na); AGENL_INFO(dev->name, "nla_len(na) : %d\n", DataLen); if (android_msg_level & ANDROID_INFO_LEVEL) prhex("nla_data(na)", pData, DataLen); } if (strcmp(pData, "BIND") == 0) { bind = TRUE; } else if (strcmp(pData, "UNBIND") == 0) { bind = FALSE; } else { AGENL_ERROR(dev->name, "Unknown cmd %s\n", pData); return -1; } if (bind == zconf->bind) { AGENL_TRACE(dev->name, "Already %s\n", bind?"BIND":"UNBIND"); return 0; } if (bind) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) zconf->bind_pid = info->snd_portid; #endif AGENL_TRACE(dev->name, "BIND pid = %d\n", zconf->bind_pid); #ifdef SENDPROB wl_ext_set_probreq(dev, TRUE); #endif zconf->bind = TRUE; zconf->pm = dhd->conf->pm; dhd->conf->pm = PM_OFF; } else { #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) AGENL_TRACE(dev->name, "UNBIND snd_portid = %d\n", info->snd_portid); #else AGENL_TRACE(dev->name, "UNBIND pid = %d\n", zconf->bind_pid); #endif zconf->bind_pid = -1; #ifdef SENDPROB wl_ext_set_probreq(dev, FALSE); #endif dhd->conf->pm = zconf->pm; zconf->bind = FALSE; } return 0; } int wl_ext_genl_init(struct net_device *net) { struct dhd_pub *dhd = dhd_get_pub(net); struct genl_params *zconf = dhd->zconf; int ret = 0; AGENL_TRACE(net->name, "Enter falimy name: \"%s\"\n", wl_ext_genl_family.name); zconf = kzalloc(sizeof(struct genl_params), GFP_KERNEL); if (unlikely(!zconf)) { AGENL_ERROR(net->name, "Could not allocate zconf\n"); return -ENOMEM; } dhd->zconf = (void *)zconf; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) ret = genl_register_family(&wl_ext_genl_family); //fix me: how to attach wl_ext_genl_ops ret = -1; #elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0) ret = genl_register_family_with_ops(&wl_ext_genl_family, wl_ext_genl_ops); #else ret = genl_register_family_with_ops(&wl_ext_genl_family, wl_ext_genl_ops, ARRAY_SIZE(wl_ext_genl_ops)); #endif if (ret != 0) { AGENL_ERROR(net->name, "GE_NELINK family registration fail\n"); goto err; } zconf->bind_pid = -1; #ifdef SENDPROB ret = wl_ext_event_register(net, dhd, WLC_E_PROBREQ_MSG, wl_ext_probreq_event, zconf, PRIO_EVENT_IAPSTA); if (ret) goto err; #endif zconf->dev = net; g_zconf = zconf; return ret; err: if(zconf) kfree(zconf); return ret; } void wl_ext_genl_deinit(struct net_device *net) { struct dhd_pub *dhd = dhd_get_pub(net); struct genl_params *zconf = dhd->zconf; AGENL_TRACE(net->name, "Enter\n"); #ifdef SENDPROB wl_ext_event_deregister(net, dhd, WLC_E_PROBREQ_MSG, wl_ext_probreq_event); #endif genl_unregister_family(&wl_ext_genl_family); if(zconf != NULL) { kfree(dhd->zconf); dhd->zconf = NULL; } g_zconf = NULL; } #endif