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

需求:需要测试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)