frameworkプローブ指定channelでソフトウェアAPを開く

20223 ワード

需要:wifiモジュールの5 gスループットをテストする必要があります。5 g 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の実装について説明します:現在の環境はRockchip 3229 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,arg 1,arg 2,obj)どのStateまたは各Stateでコマンドに対するポリシーが何であるかが不明な場合、case CMD_を直接検索するSET_APは、ApStaDisabledStateでのこのコマンドに対する処理が
class ApStaDisabledState extends State {
              ...
              if (msg.arg1 == 1) {
                 mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
                         true);
                 transitionTo(mApEnabledState);
              }

彼がソフトウェアを起動し、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()関数を複写せず,ソフトウェアを閉じるコマンドのみを処理していることがわかる.

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ドライバのロードとソフトウェアStartingStateへの移行を実行していますが、他の操作はありません.ソフトウェアを具体的に構成する操作は、この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();
    }

ここでmNwServicesを呼び出します.startAccessPoint(config,mInterfaceName)では、WifiStateMachineの初期化時に付与されたエラーの再試行が行われました.WifiServiceImplクラスのコンストラクション関数では、次のことがわかります.
public WifiServiceImpl(Context context) {
        mContext = context;
        mInterfaceName =  SystemProperties.get("wifi.interface", "wlan0");
        mTrafficPoller = new WifiTrafficPoller(mContext, mInterfaceName);
        ...
    }

ここでgetpropを実行します.デフォルト値はwlan 0です.実際にはwlan 0はmNwServiceというオブジェクトに続きます.ここでもBinderのINetworkManagementServiceというインタフェースで呼び出し、frameworksディレクトリfindでファイルNetworkManagementServicesをダウンロードします.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


このクラスで3つの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);
    }

最終的にボスについてきて、
    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のクライアント側で、反対側はどこですか.さっきのコードを見てみましょう
return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);

2番目のパラメータ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_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);
            }
        }
        …
}

ソケットの構築はcreate_ソケット関数
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);
    }
    ...

}

ここには、netd socketを介して送信された命令を処理するcommandリスナーが登録されています.

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の構造には,特定の命令に対するプロセッサが多く定義されているが,ここではソフトウェアタイプのcommandを送信し,命令はsetとstartapであり,ソフトウェア命令部分を処理するコードは以下の通りである.
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;
}

呼び出されたのはソフトウェアコントローラ->setSoftap()とソフトウェアコントローラ->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
wpa_pairwise=TKIP CCMP
wpa_psk=%s
", wbuf, psk_str); } else if (!strcmp(argv[6], "wpa2-psk")) { generatePsk(argv[3], argv[7], psk_str); asprintf(&fbuf, "%swpa=2
rsn_pairwise=CCMP
wpa_psk=%s
", 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サービスを開くので、ここでは詳しく分析しません.