/* * Copyright (c) 2010, The Linux Foundation. All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of The Linux Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "QWiFiSoftApCfg.h" #define UPDATE_ERROR_CODE(msg, code) \ { \ int rc; \ rc = snprintf(resp, sizeof(resp), "failure %s:%s",msg, code); \ if ( rc == sizeof(resp)) resp[sizeof(resp)-1] = 0; \ ALOGE("%s",resp); \ } static struct sockaddr_nl rtnl_local; static int rtnl_fd = -1; static char evt_buf[MAX_EVT_BUF_SIZE]; static int evt_len; static void softap_handle_custom_event(char * buf, int len) { if (strncmp(buf, "AUTO-SHUT.indication ", strlen("AUTO-SHUT.indication ")) == 0) { ALOGD("EVENT: Custom Event\n"); snprintf(evt_buf, sizeof(evt_buf), "105 AP Shutdown"); } } static void softap_handle_associated_event(char *mac_addr) { snprintf(evt_buf, sizeof(evt_buf), "102 Station " HWA_FORM " Associated", HWA_ARG(mac_addr)); } static void softap_handle_disassociated_event(char *mac_addr) { snprintf(evt_buf, sizeof(evt_buf), "103 Station " HWA_FORM " Disassociated", HWA_ARG(mac_addr)); } static void softap_handle_wireless_event(char *atr, int atrlen) { int len = 0; struct iw_event iwe; char *buffer = atr + RTA_ALIGN(RTATTRLEN); atrlen -= RTA_ALIGN(RTATTRLEN); while ((len + (int)IW_EV_LCP_LEN) < atrlen) { memcpy((char *)&iwe, buffer + len, sizeof(struct iw_event)); if (iwe.len <= IW_EV_LCP_LEN) break; ALOGD("Received Wireless Event: cmd=0x%x len=%d", iwe.cmd, iwe.len); switch (iwe.cmd) { case IWEVEXPIRED: ALOGD("EVENT: IWEVEXPIRED\n"); softap_handle_disassociated_event(iwe.u.addr.sa_data); break; case IWEVREGISTERED: ALOGD("EVENT: IWEVREGISTERED\n"); softap_handle_associated_event(iwe.u.addr.sa_data); break; case IWEVCUSTOM: ALOGD("EVENT: Custom Event\n"); softap_handle_custom_event(buffer + len + IW_EV_POINT_LEN, iwe.u.data.length); break; default: break; } len += iwe.len; } return; } void softap_handle_rtm_link_event(struct nlmsghdr *hdr) { char *ptr = (char *)NLMSG_DATA(hdr); struct rtattr *atr; int atr_len; if ((hdr->nlmsg_len - MSGHDRLEN) < IFINFOLEN) { ALOGD("Message Length Problem1"); return; } if ((atr_len = hdr->nlmsg_len - NLMSG_ALIGN(IFINFOLEN)) < 0) { ALOGD("Message Length Problem2"); return; } ptr += NLMSG_ALIGN(IFINFOLEN); atr = (struct rtattr *)ptr; while (RTA_OK(atr, atr_len)) { switch (atr->rta_type) { case IFLA_WIRELESS: softap_handle_wireless_event((char *)atr, atr->rta_len); break; default: break; } atr = RTA_NEXT(atr, atr_len); } return; } static void softap_handle_iface_event(void) { int cnt, mlen = 0; char *ptr, buffer[MAX_RECV_BUF_SIZE]; socklen_t slen; struct nlmsghdr * hdr; while (1) { cnt = recvfrom(rtnl_fd, buffer, sizeof(buffer), MSG_DONTWAIT, (struct sockaddr *)&rtnl_local, &slen); if (cnt <= 0) { buffer[0] = '\0'; ALOGD("recvfrom failed"); return; } ptr = buffer; while (cnt >= MSGHDRLEN) { hdr = (struct nlmsghdr *)ptr; mlen = hdr->nlmsg_len; if ((mlen > cnt) || ((mlen - MSGHDRLEN) < 0)) { break; } switch (hdr->nlmsg_type) { case RTM_NEWLINK: case RTM_DELLINK: softap_handle_rtm_link_event(hdr); break; } mlen = NLMSG_ALIGN(hdr->nlmsg_len); cnt -= mlen; ptr += mlen; } } return; } static inline int softap_rtnl_wait(void) { fd_set fds; int oldfd, ret; if (rtnl_fd < 0) { ALOGD("Netlink Socket Not Available"); return -1; } /* Initialize fds */ FD_ZERO(&fds); FD_SET(rtnl_fd, &fds); oldfd = rtnl_fd; /* Wait for some trigger event */ ret = select(oldfd + 1, &fds, NULL, NULL, NULL); if (ret < 0) { /* Error Occurred */ ALOGD("Select on Netlink Socket Failed"); return ret; } else if (!ret) { ALOGD("Select on Netlink Socket Timed Out"); /* Timeout Occurred */ return -1; } /* Check if any event is available for us */ if (FD_ISSET(rtnl_fd, &fds)) { softap_handle_iface_event(); } return 0; } static void softap_rtnl_close(void) { close(rtnl_fd); } static int softap_rtnl_open(void) { int addr_len; rtnl_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (rtnl_fd < 0) { ALOGE("open netlink socket failed"); return -1; } memset(&rtnl_local, 0, sizeof(rtnl_local)); rtnl_local.nl_family = AF_NETLINK; rtnl_local.nl_groups = RTMGRP_LINK; if (bind(rtnl_fd, (struct sockaddr*)&rtnl_local, sizeof(rtnl_local)) < 0) { ALOGE("bind netlink socket failed"); return -1; } addr_len = sizeof(rtnl_local); if (getsockname(rtnl_fd, (struct sockaddr*)&rtnl_local, (socklen_t *) &addr_len) < 0) { ALOGE("getsockname failed"); return -1; } if (addr_len != sizeof(rtnl_local)) { ALOGE("Wrong address length %d\n", addr_len); return -1; } if (rtnl_local.nl_family != AF_NETLINK) { ALOGE("Wrong address family %d\n", rtnl_local.nl_family); return -1; } return 0; } JNIEXPORT void JNICALL Java_com_qualcomm_wifi_softap_QWiFiSoftApCfg_SapCloseNetlink (JNIEnv *env, jobject obj) { softap_rtnl_close(); return; } JNIEXPORT jstring JNICALL Java_com_qualcomm_wifi_softap_QWiFiSoftApCfg_SapWaitForEvent (JNIEnv *env, jobject obj) { int ret; do { evt_len = 0; memset(evt_buf, 0, sizeof(evt_buf)); ret = softap_rtnl_wait(); } while (!strlen(evt_buf)); return (*env)->NewStringUTF(env, evt_buf); } JNIEXPORT jboolean JNICALL Java_com_qualcomm_wifi_softap_QWiFiSoftApCfg_SapOpenNetlink (JNIEnv *env, jobject obj) { if (softap_rtnl_open() != 0) { ALOGD("Netlink Open Fail"); return JNI_FALSE; } return JNI_TRUE; } JNIEXPORT jstring JNICALL Java_com_qualcomm_wifi_softap_QWiFiSoftApCfg_SapSendCommand (JNIEnv *env, jobject obj, jstring jcmd) { const char *pcmd; char cmd[MAX_CMD_SIZE]; char resp[MAX_RESP_SIZE]; int sock = -1; int rc; int done = 0; char code[32] = {0}; int connect_retry; strlcpy(cmd, "softap qccmd ", sizeof(cmd)); pcmd = (char *) ((*env)->GetStringUTFChars(env, jcmd, NULL)); if ( pcmd == NULL ) { UPDATE_ERROR_CODE("Command not handled",""); goto end; } ALOGD("Received Command: %s\n", pcmd); if ((strlen(cmd) + strlen(pcmd)) >= sizeof(cmd)) { UPDATE_ERROR_CODE("Command length is larger than MAX_CMD_SIZE", ""); goto end; } strlcat(cmd, pcmd, sizeof(cmd)); connect_retry = 0; while ( 1 ) { if ((sock = socket_local_client("netd", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM)) < 0) { if (connect_retry > 3) { UPDATE_ERROR_CODE("Error connecting", strerror(errno)); goto end; } ALOGW("Unable to connect to netd, retrying ...\n"); sleep(1); } else { break; } connect_retry++; } if (write(sock, cmd, strlen(cmd) + 1) < 0) { UPDATE_ERROR_CODE("Error Writing to socket", strerror(errno)); goto end; } while (!done) { int i; if ((rc = read(sock, resp, sizeof(resp))) <= 0) { if (rc == 0) { UPDATE_ERROR_CODE("Lost connection to Netd", strerror(errno)); } else { UPDATE_ERROR_CODE("Error reading data", strerror(errno)); } done = 1; } else { /* skip broadcase messages */ i = 0; while(resp[i] && (i<(int)(sizeof(code)-1)) && (resp[i] != ' ') && (resp[i] != '\t')) { code[i] = resp[i]; i++; } code[i] = '\0'; if ( (!strcmp(code, "success")) || (!strcmp(code, "failure")) ) { done=1; } else { ALOGW("Code(%s)\n", code); ALOGW("Ignore messages : %s\n", resp); } } } end: (*env)->ReleaseStringUTFChars(env, jcmd, pcmd); if( sock >= 0 ){ close(sock); sock = -1; } return (*env)->NewStringUTF(env, resp); }