framework 탐색자가 지정한 채널에서 softAP를 시작합니다

20246 단어
수요: 와이파이 모듈의 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, 보안, 채널을 지정할 수 있습니다. 그러나 WifiConfiguration 클래스에는 채널이나 freq 같은 속성이 포함되어 있지 않습니다. 어떻게 설정을 실현합니까?
2. 원본과
프레임워크의 실현에 따라: 현재 환경은 Rockchip3229android5.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을 열고 ApEnabled State로 이동해서 이 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");
                    }
                ...
        }
        ...
}

여기에서 와이파이 드라이브를 불러오고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는 비어 있지 않습니다. 핵심 논리는 start Soft ApWith Config (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();
    }

여기에서 mNw Service를 호출합니다.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입니다. 현재 우리는 mNw 서비스라는 대상을 계속 따라갑니다. 여기도 Binder의INetwork Management 서비스라는 인터페이스를 통해 호출됩니다. 프레임워크 디렉터리find에서 파일Network Management 서비스를 호출합니다.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, 즉 와이파이ap의 사용자 이름 비밀번호만 해석되어 우리에게 필요한 채널이나 freq를 남기지 않았습니다.우리는 계속해서 이 함수의 형삼을 내려다보았다.
2.5 frameworks/base/services/core/java/com/android/server/NativeDaemonConnector.java
이 클래스에서 세 개의excute () 함수를 찾았습니다. 위의 유형에 따라 아래의 이것만 찾을 수 있습니다.
    /**
     * 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];
    }

함수 설명에서 지령을 네이티브 데몬에게 전달하려면 매개 변수와 기초 명령을 분리하고 계속 호출해야 한다
    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 {

           ...
        }
    }


이것은 determine Socket Address () 함수를 통해 만들어진 unix Socket입니다. 클라이언트 형식으로 이 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은 구조에서 값을 부여한 것이다. Native Daemon Connector->Network Management 서비스에서 이 구조가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의 중요한 데몬 프로세스입니다. 우리는 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");
      …
}

cket의 생성 과정은 넷드 서비스를 시작할 때 함수 서비스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의 설립은createsocket 함수 중
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 구축에 이르기까지는 아직 복잡한 과정이 있다. 만약에 다른 편폭이 필요하다면 여기서 넷의 socket 구축을 말하고 넷이 socket의 서버로서 Native Daemon Connector에서 쓴 명령을 어떻게 처리하는지 계속 살펴보자.넷에 있는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);
    ...

}

이command Listener의 구조에서 특정 명령에 대한 프로세서를 많이 정의했습니다. 여기서 우리가 보낸 것은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
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; }

set Softap () 는 실제적으로 설정 파일/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가 구동을 호출하는 과정이고 플랫폼 층의 코드는 분석되었다.다음에Tether State Change를 출발하고Tether Controller를 통해dnsmasq를 호출하여 DHCP 서비스를 시작합니다. 여기서는 상세한 분석을 하지 않겠습니다.

좋은 웹페이지 즐겨찾기