美文网首页Android开发经验谈Android开发Android技术知识
framework初探之在指定channel上开启softAP

framework初探之在指定channel上开启softAP

作者: ProgZack | 来源:发表于2017-11-30 10:25 被阅读271次

    需求:需要测试wifi模块的5g吞吐量。需要开启5g wifiap

    实现过程:

    1.可行性

    首先看下WifiManager开启ap函数的说明:

        /**
         * Start AccessPoint mode with the specified
         * configuration. If the radio is already running in
         * AP mode, update the new configuration
         * Note that starting in access point mode disables station
         * mode operation
         * @param wifiConfig SSID, security and channel details as
         *        part of WifiConfiguration
         * @return {@code true} if the operation succeeds, {@code false} otherwise
         *
         * @hide Dont open up yet
         */
        public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
            try {
                mService.setWifiApEnabled(wifiConfig, enabled);
                return true;
            } catch (RemoteException e) {
                return false;
            }
        }
    

    参数wifiConfig可指定 SSID, security 和 channel
    然而,WifiConfiguration这个类中不包含channel或者freq这样的属性,那是如何实现配置的呢?

    2. 跟源码

    跟一下framework的实现:
    目前环境是Rockchip3229 android 5.1 ,代码路径与aosp有些差别,不过总体区别不大。
    mService.setWifiApEnabled(wifiConfig, enabled);
    这里通过Binder直接调用到WifiService的同名函数

    2.1. frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiServiceImpl.java

        /**
         * see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)}
         * @param wifiConfig SSID, security and channel details as
         *        part of WifiConfiguration
         * @param enabled true to enable and false to disable
         */
        public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
            ...
            // null wifiConfig is a meaningful input for CMD_SET_AP
            if (wifiConfig == null || wifiConfig.isValid()) {
                mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
            } else {
                Slog.e(TAG, "Invalid WifiConfiguration");
            }
        }
    

    这里可以看到,使用wifiController发送msg去下发指令

    2.2. frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiController.java

    WifiController继承了StateMachine类,上面的obtainMessage函数是StateMachine类的函数,参数列表是(msgWhat, arg1, arg2, obj)
    不清楚有哪些State或各个State下对命令的策略是什么的情况下,直接搜索case CMD_SET_AP,
    发现在ApStaDisabledState下的对该命令的处理为

    class ApStaDisabledState extends State {
                  ...
                  if (msg.arg1 == 1) {
                     mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
                             true);
                     transitionTo(mApEnabledState);
                  }
    

    可以看到他会执行开启softap,并转到ApEnabledState,检查一下这个State的操作

        class ApEnabledState extends State {
            @Override
            public boolean processMessage(Message msg) {
                switch (msg.what) {
                    ...
                    case CMD_SET_AP:
                        if (msg.arg1 == 0) {
                            mWifiStateMachine.setHostApRunning(null, false);
                            transitionTo(mApStaDisabledState);
                        }
                        break;
                    default:
                        return NOT_HANDLED;
                }
                return HANDLED;
            }
        }
    

    可以看到,这个State没有复写enter()函数,只处理了关闭softAP的命令

    2.3 frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java

        public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) {
            if (enable) {
                sendMessage(CMD_START_AP, wifiConfig);
            } else {
                sendMessage(CMD_STOP_AP);
            }
        }
    
    

    WifiStateMachine同样继承StateMachine,所以直接找处理CMD_START_AP指令的代码,搜索case CMD_START_AP:

    class InitialState extends State {
            ...
            @Override
            public boolean processMessage(Message message) {
                    ...
                    case CMD_START_AP:
                        if (mWifiNative.loadDriver()) {
                            setWifiApState(WIFI_AP_STATE_ENABLING);
                            transitionTo(mSoftApStartingState);
                        } else {
                            loge("Failed to load driver for softap");
                        }
                    ...
            }
            ...
    }
    

    这里的执行了加载wifi驱动并转入softapStartingState,没有其他操作,那具体配置softap的操作应该是在该State的enter函数中处理的
    我们来看SoftApStartingState这个类的enter函数

        class SoftApStartingState extends State {
            @Override
            public void enter() {
                final Message message = getCurrentMessage();
                if (message.what == CMD_START_AP) {
                    final WifiConfiguration config = (WifiConfiguration) message.obj;
    
                    if (config == null) {
                        mWifiApConfigChannel.sendMessage(CMD_REQUEST_AP_CONFIG);
                    } else {
                        mWifiApConfigChannel.sendMessage(CMD_SET_AP_CONFIG, config);
                        startSoftApWithConfig(config);
                    }
                } else {
                    throw new RuntimeException("Illegal transition to SoftApStartingState: " + message);
                }
            }
        ...
        }
    

    这里我们传入的config是不为空的,那么核心逻辑应该是在startSoftApWithConfig(config)这个函数中

        private void startSoftApWithConfig(final WifiConfiguration config) {
            // Start hostapd on a separate thread
            new Thread(new Runnable() {
                public void run() {
                    try {
                        mNwService.startAccessPoint(config, mInterfaceName);
                    } catch (Exception e) {
                        loge("Exception in softap start " + e);
                        try {
                            mNwService.stopAccessPoint(mInterfaceName);
                            mNwService.startAccessPoint(config, mInterfaceName);
                        } catch (Exception e1) {
                            loge("Exception in softap re-start " + e1);
                            sendMessage(CMD_START_AP_FAILURE);
                            return;
                        }
                    }
                    ...
                }
            }).start();
        }
    

    这里调用mNwService.startAccessPoint(config, mInterfaceName)并进行了一次出错的重试,这个mInterfaceName是初始化WifiStateMachine时赋值的,我们在WifiServiceImpl类的构造函数中可以看到:

    public WifiServiceImpl(Context context) {
            mContext = context;
            mInterfaceName =  SystemProperties.get("wifi.interface", "wlan0");
            mTrafficPoller = new WifiTrafficPoller(mContext, mInterfaceName);
            ...
        }
    

    这里执行getprop,默认值为wlan0,实际也就是wlan0
    现在我们继续跟到mNwService这个对象,这里也是通过Binder的叫做INetworkManagementService的接口调用的,在frameworks目录find一下文件NetworkManagementService.java

    前方高能!!!!!!!!!!!!!!

    2.4 frameworks/base/services/core/java/com/android/server/NetworkManagementService.java

        @Override
        public void startAccessPoint(
                WifiConfiguration wifiConfig, String wlanIface) {
            mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
            try {
                wifiFirmwareReload(wlanIface, "AP");
                if (wifiConfig == null) {
                    mConnector.execute("softap", "set", wlanIface);
                } else {
                    mConnector.execute("softap", "set", wlanIface, wifiConfig.SSID,
                                       "broadcast", "6", getSecurityType(wifiConfig),
                                       new SensitiveArg(wifiConfig.preSharedKey));
                }
                mConnector.execute("softap", "startap");
            } catch (NativeDaemonConnectorException e) {
                throw e.rethrowAsParcelableException();
            }
        }
    

    这里可以看到,这个execute函数里面,终于用到了我们传入的WifiConfiguration,这个参数经历了长途跋涉,终于被解析了!
    但是,这里只解析了ssid和preSharedKey,也就是wifiap的用户名密码,没有留地方给我们需要的channel或者freq。我们继续往下看这个函数的形参。

    2.5 frameworks/base/services/core/java/com/android/server/NativeDaemonConnector.java

    在这个类中搜到了三个execute()函数,根据上面的类型,只能是下面这个

        /**
         * Issue the given command to the native daemon and return a single expected
         * response. Any arguments must be separated from base command so they can
         * be properly escaped.
         */
        public NativeDaemonEvent execute(String cmd, Object... args)
                throws NativeDaemonConnectorException {
            final NativeDaemonEvent[] events = executeForList(cmd, args);
            if (events.length != 1) {
                throw new NativeDaemonConnectorException(
                        "Expected exactly one response, but received " + events.length);
            }
            return events[0];
        }
    

    函数说明中说道,将指令传给native daemon,参数和基础命令必须分开,继续跟调用

        public NativeDaemonEvent[] executeForList(String cmd, Object... args)
                throws NativeDaemonConnectorException {
                return execute(DEFAULT_TIMEOUT, cmd, args);
        }
    

    最终跟到了boss,

        public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)
                throws NativeDaemonConnectorException {
            ...
            final StringBuilder rawBuilder = new StringBuilder();
            final StringBuilder logBuilder = new StringBuilder();
            final int sequenceNumber = mSequenceNumber.incrementAndGet();
    
            makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
    
            final String rawCmd = rawBuilder.toString();
            final String logCmd = logBuilder.toString();
    
            synchronized (mDaemonLock) {
                if (mOutputStream == null) {
                    throw new NativeDaemonConnectorException("missing output stream");
                } else {
                    try {
                        mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
                    } catch (IOException e) {
                        throw new NativeDaemonConnectorException("problem sending command", e);
                    }
                }
            }
            ...
        }
    

    核心部分是通过这个outputStream将指令写出去,写到哪里呢,我们看下这个流对象如何被赋值的:

    private void listenToSocket() throws IOException {
            LocalSocket socket = null;
            try {
                socket = new LocalSocket();
                LocalSocketAddress address = determineSocketAddress();
                socket.connect(address);
                InputStream inputStream = socket.getInputStream();
                synchronized (mDaemonLock) {
                    mOutputStream = socket.getOutputStream();
                }
    
                ...
            } catch (IOException ex) {
                loge("Communications error: " + ex);
                throw ex;
            } finally {
    
               ...
            }
        }
    
    

    很明显这个是通过determineSocketAddress()这个函数建立的unixSocket,并以客户端的形式连接上这个socket, 看下bind到了哪个地址上:

        private LocalSocketAddress determineSocketAddress() {
            if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
                return new LocalSocketAddress(mSocket);
            } else {
                return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
            }
        }
    

    这个mSocket又是在构造中赋值的,那追构造到NativeDaemonConnector->NetworkManagementService
    发现这个构造是private的,直接本地搜索,发现在

        static NetworkManagementService create(Context context,
                String socket) throws InterruptedException {
            final NetworkManagementService service = new NetworkManagementService(context, socket);
            final CountDownLatch connectedSignal = service.mConnectedSignal;
            if (DBG) Slog.d(TAG, "Creating NetworkManagementService");
            service.mThread.start();
            if (DBG) Slog.d(TAG, "Awaiting socket connection");
            connectedSignal.await();
            if (DBG) Slog.d(TAG, "Connected");
            return service;
        }
    
        public static NetworkManagementService create(Context context) throws InterruptedException {
            return create(context, NETD_SOCKET_NAME);
        }
    
        private static final String NETD_SOCKET_NAME = "netd";
    

    可以看到NETD_SOCKET_NAME就是刚才unixSocket通信的文件名,值为"netd",那connector这边是socket的client端,另一端在哪里呢。
    我们可以看下刚才的代码

    return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
    

    注意第二个参数namespace是RESERVED,这说明这个socket必须是init进程开启的,netd是android的很重要的daemon进程,我们在init.rc中可以找到netd的声明

    service netd /system/bin/netd
        class main
        socket netd stream 0660 root system
        socket dnsproxyd stream 0660 root inet
        socket mdns stream 0660 root system
        socket fwmarkd stream 0660 root inet
    
    

    那么在/system/core/init/init.c中的main函数中可以看到解析init.rc的操作

    int main(int argc, char **argv)
    {
          ...
          restorecon("/dev");
          restorecon("/dev/socket");
          …
          init_parse_config_file("/init.rc");
          …
    }
    

    socket的创建过程是在启动netd service的时候,在函数service_start中实现

    void service_start(struct service *svc, const char *dynamic_args)
    {
            ...
            for (si = svc->sockets; si; si = si->next) {
            int socket_type = (!strcmp(si->type, "stream") ? SOCK_STREAM : (!strcmp(si->type, "dgram") ? SOCK_DGRAM :                     SOCK_SEQPACKET));
                int s = create_socket(si->name, socket_type,
                                      si->perm, si->uid, si->gid);
                if (s >= 0) {
                    publish_socket(si->name, s);
                }
            }
            …
    }
    

    socket的建立是在create_socket函数中

    int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid)
    {
        struct sockaddr_un addr;
        int fd, ret;
    #ifdef HAVE_SELINUX
        char *secon;
    #endif
     
        fd = socket(PF_UNIX, type, 0);
        ...
    }
    

    其实从init启动到socket建立还有很复杂的过程,如要详细说明需要另起篇幅,这里就说到netd的socket建立,然后继续查看netd作为socket的server端,如何处理从NativeDaemonConnector写过去的命令的。
    在netd的main.cpp中,可以看到他如何处理命令

    2.6 system/netd/server/main.cpp

    int main() {
        CommandListener *cl;
        NetlinkManager *nm;
        ...
    
        ALOGI("Netd 1.0 starting");
        remove_pid_file();
        blockSigpipe();
        if (!(nm = NetlinkManager::Instance())) {
            ALOGE("Unable to create NetlinkManager");
            exit(1);
        };
        cl = new CommandListener();
        nm->setBroadcaster((SocketListener *) cl);
        if (nm->start()) {
            ALOGE("Unable to start NetlinkManager (%s)", strerror(errno));
            exit(1);
        }
        ...
    
    }
    

    这里注册了一个command监听器,来处理通过netd socket传进来的指令

    2.7 /system/netd/server/CommandListener.cpp

    CommandListener::CommandListener() :
                     FrameworkListener("netd", true) {
        registerCmd(new InterfaceCmd());
        registerCmd(new IpFwdCmd());
        registerCmd(new TetherCmd());
        registerCmd(new NatCmd());
        registerCmd(new ListTtysCmd());
        registerCmd(new PppdCmd());
        registerCmd(new SoftapCmd());
        registerCmd(new BandwidthControlCmd());
        registerCmd(new IdletimerControlCmd());
        registerCmd(new ResolverCmd());
        registerCmd(new FirewallCmd());
        registerCmd(new ClatdCmd());
        registerCmd(new NetworkCommand());
        if (!sNetCtrl)
            sNetCtrl = new NetworkController();
        if (!sTetherCtrl)
            sTetherCtrl = new TetherController();
        if (!sNatCtrl)
            sNatCtrl = new NatController();
        if (!sPppCtrl)
            sPppCtrl = new PppController();
        if (!sSoftapCtrl)
            sSoftapCtrl = new SoftapController();
        if (!sBandwidthCtrl)
            sBandwidthCtrl = new BandwidthController();
        if (!sIdletimerCtrl)
            sIdletimerCtrl = new IdletimerController();
        if (!sResolverCtrl)
            sResolverCtrl = new ResolverController();
        if (!sFirewallCtrl)
            sFirewallCtrl = new FirewallController();
        if (!sInterfaceCtrl)
            sInterfaceCtrl = new InterfaceController();
        if (!sClatdCtrl)
            sClatdCtrl = new ClatdController(sNetCtrl);
        ...
    
    }
    

    这个commandListener的构造里面,定义了很多针对特定指令的处理器,这里我们发送的是softap类型的command,指令是set和startap,处理softap指令部分的代码如下

    int CommandListener::SoftapCmd::runCommand(SocketClient *cli,
                                            int argc, char **argv) {
        ...
    
        if (argc < 2) {
            cli->sendMsg(ResponseCode::CommandSyntaxError,
                         "Missing argument in a SoftAP command", false);
            return 0;
        }
        if (!strcmp(argv[1], "startap")) {
            rc = sSoftapCtrl->startSoftap();
        } else if (!strcmp(argv[1], "stopap")) {
            rc = sSoftapCtrl->stopSoftap();
        } else if (!strcmp(argv[1], "fwreload")) {
            rc = sSoftapCtrl->fwReloadSoftap(argc, argv);
        } else if (!strcmp(argv[1], "status")) {
            asprintf(&retbuf, "Softap service %s running",
                     (sSoftapCtrl->isSoftapStarted() ? "is" : "is not"));
            cli->sendMsg(rc, retbuf, false);
            free(retbuf);
            return 0;
        } else if (!strcmp(argv[1], "set")) {
            rc = sSoftapCtrl->setSoftap(argc, argv);
        } else {
            cli->sendMsg(ResponseCode::CommandSyntaxError, "Unrecognized SoftAP command", false);
            return 0;
        }
        ...
    
        return 0;
    }
    

    看到调用的是softapController->setSoftap()和softapController->startSoftap()

    2.7 system/netd/server/SoftapController.cpp

    static const char HOSTAPD_CONF_FILE[]    = "/data/misc/wifi/hostapd.conf";
    int SoftapController::setSoftap(int argc, char *argv[]) {
        ...
    
        if (argc > 7) {
            if (!strcmp(argv[6], "wpa-psk")) {
                generatePsk(argv[3], argv[7], psk_str);
                asprintf(&fbuf, "%swpa=3\nwpa_pairwise=TKIP CCMP\nwpa_psk=%s\n", wbuf, psk_str);
            } else if (!strcmp(argv[6], "wpa2-psk")) {
                generatePsk(argv[3], argv[7], psk_str);
                asprintf(&fbuf, "%swpa=2\nrsn_pairwise=CCMP\nwpa_psk=%s\n", wbuf, psk_str);
            } else if (!strcmp(argv[6], "open")) {
                asprintf(&fbuf, "%s", wbuf);
            }
        }
        ...
    
        fd = open(HOSTAPD_CONF_FILE, O_CREAT | O_TRUNC | O_WRONLY | O_NOFOLLOW, 0660);
        ...
    
        if (write(fd, fbuf, strlen(fbuf)) < 0) {
            ALOGE("Cannot write to \"%s\": %s", HOSTAPD_CONF_FILE, strerror(errno));
            ret = ResponseCode::OperationFailed;
        }
        ...
    
        return ret;
    }
    

    可以看到setSoftap()实际就是把参数存入了配置文件/data/misc/wifi/hostapd.conf
    而startSoftap()函数如下:

    static const char HOSTAPD_BIN_FILE[]    = "/system/bin/hostapd";
    int SoftapController::startSoftap() {
        ...
    
        if ((pid = fork()) < 0) {
            ALOGE("fork failed (%s)", strerror(errno));
            return ResponseCode::ServiceStartFailed;
        }
        if (!pid) {
            ensure_entropy_file_exists();
            if (execl(HOSTAPD_BIN_FILE, HOSTAPD_BIN_FILE,
                      "-e", WIFI_ENTROPY_FILE,
                      HOSTAPD_CONF_FILE, (char *) NULL)) {
                ALOGE("execl failed (%s)", strerror(errno));
            }
            ALOGE("SoftAP failed to start");
            return ResponseCode::ServiceStartFailed;
        } else {
            mPid = pid;
            ALOGD("SoftAP started successfully");
            usleep(AP_BSS_START_DELAY);
        }
        return ResponseCode::SoftapStatusResult;
    }
    

    核心就是调用/system/bin/hostapd,然后使用/data/misc/wifi/hostapd.conf中存储的参数,开启ap。接下来就是hostapd去调用驱动的过程了,平台层的代码就分析完了。接下还会出发TetherStateChange,然后通过TetherController去调用dnsmasq去开启DHCP服务,这里就不做详细分析了。

    相关文章

      网友评论

        本文标题:framework初探之在指定channel上开启softAP

        本文链接:https://www.haomeiwen.com/subject/movevxtx.html