iOS 無線網(wǎng)絡(luò)開發(fā)

前言

最近有一個需求,為了方便用戶操作,優(yōu)化用戶體驗(yàn),我們需要實(shí)現(xiàn)在APP內(nèi)控制WiFi的開關(guān)、連上指定的WiFi,以及給這個WiFi設(shè)置HTTP代理,經(jīng)過一番折騰,發(fā)現(xiàn)在未越獄的手機(jī)下只實(shí)現(xiàn)前兩個功能,要想給指定WiFi設(shè)置系統(tǒng)級的代理目前沒有發(fā)現(xiàn)什么有效的解決方案,在此記錄一下這幾天探索的結(jié)果。

現(xiàn)狀分析

目前只有iOS11以上包含iOS11才可以使用NetworkExtension庫中的NEHotspotConfigurationManagerAPI來操作WiFi,通過該API可以連接指定SSID的WIFI,如果沒有手動打開系統(tǒng)WIFI開關(guān),則還可以通過它打開系統(tǒng)WIFI開關(guān),該庫的有些API是需要像蘋果寫郵件申請權(quán)限的,比如HotspotHelper API權(quán)限,這個HotspotHelper權(quán)限比較難申請,但此次用的API并不需像某些博客中說的需要寫郵件申請HotspotHelper權(quán)限,而此次用到底API只需要在Xcode中開啟Hotspot configuration 普通權(quán)限,并且添加依賴庫NetworkExtension,iOS11之前我們的做法是通過引導(dǎo)用戶手動去操作。通過NEHotspotConfigurationManager來管理熱點(diǎn)還需要有一個熱點(diǎn)配置對象NEHotspotConfiguration,創(chuàng)建這個對象的蘋果提供了一下三種方式。

Network Extension權(quán)限問題 官網(wǎng)介紹
在2016年11月10日之前要使用 NetworkExtension 框架需要向蘋果官方發(fā)郵件提供資質(zhì),申請權(quán)限。
但是在2016年11月10日之后,如果只是使用下面幾種功能的話,已經(jīng)不需要提交郵件申請就可以使用了

  • App Proxy
  • Content Filter
  • Packet Tunnel APIs

但是 Network Extension Hotspot Entitlements(HotSpot) 這個Wifi熱點(diǎn)功能需要郵件申請權(quán)限。
權(quán)限申請地址:https://developer.apple.com/contact/network-extension

WIFI控制實(shí)現(xiàn)方式

在寫實(shí)現(xiàn)方式之前需要先科普一些WiFi的知識:

  1. 目前WIFI常用的加密方式有WEP、WPA/WPA2、TKIP、EAP等。
  2. 無線網(wǎng)絡(luò)的安全性由認(rèn)證和加密來保證:
    1)認(rèn)證允許只有被許可的用戶才能連接到無線網(wǎng)絡(luò);
    2)加密的目的是提供數(shù)據(jù)的保密性和完整性(數(shù)據(jù)在傳輸過程中不會被篡改)。
  3. 常用名字解釋
    Open Wi-Fi Network:開放式WIFI,沒有密碼,但連接WIFI后有可能會彈出portal認(rèn)證網(wǎng)頁要求輸入賬號去認(rèn)證,這個取決于WIFI提供者提供的策略,如果需要portal認(rèn)證界面,在iOS端會有45秒左右的連接延遲,這是由于iOS系統(tǒng)WIFI連接的驗(yàn)證策略導(dǎo)致,但是Android能瞬間彈出portal認(rèn)證界面。
    WEP:一種加密標(biāo)準(zhǔn),該算法已經(jīng)不安全了。
    EAP:全稱Extensible Authentication Protocol,是一種支持多種驗(yàn)證方式的協(xié)議框架,EAP通常運(yùn)行在鏈路層例如PPP和IEEE 802。EAP協(xié)議是IEEE 802.1x認(rèn)證機(jī)制的核心,它將實(shí)現(xiàn)細(xì)節(jié)交由附屬的EAP Method協(xié)議完成,如何選取EAP method由認(rèn)證系統(tǒng)特征決定,這樣實(shí)現(xiàn)了EAP的擴(kuò)展性及靈活性。
    WPA/WPA2 Personal Wi-Fi Network:采用WPA/WPA2加密的個人級WIFI。
    WPA/WPA2 Enterprise Wi-Fi Network:采用WPA/WPA2加密的企業(yè)級WIFI。
    HS2.0 Wi-Fi Network:采用HS2.0協(xié)議的WIFI。

有關(guān)WIFI的認(rèn)證和加密的詳情具體參考文章

了解了上面的名詞,我們來看iOS提供的四種WIFI控制方式,通過這四種方式可以實(shí)現(xiàn)操作不同加密策略的WIFI。

  1. 用于初始化沒有密碼的開方式WIFI的配置對象,注意ssid的的長度是1~32字符。
- (instancetype)initWithSSID:(NSString *)SSID API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(macos, watchos, tvos);
  1. 用于初始化一個采用WEP加密或者WPA/WPA2加密的個人級的WiFi配置對象,如果最后一個參數(shù)是NO,則采用WEP方式加密,否則采用WPA/WPA2加密,無論是那種加密方式都需要提供指定的ssid和passphrase才能連接上WIFI。
    注意passphrase和password是有區(qū)別的
    密碼(password)是一組字母、數(shù)字、符號,表示用戶的身份。密碼一般比較短8/6/6字符,而且密碼的復(fù)雜性有要求,如不能出現(xiàn)常見的字、詞等等。
    passphrase類似于密碼(password),但passphrase并不是直接使用的,而是通過一定的算法將passphrase轉(zhuǎn)換為用于加密的密鑰。Passphrase可以很長,通常為20-30個字符,另外,由于不是直接使用passphrase,所以可以使用容易記憶的句子作為passphrase
/*!
 * @method initWithSSID:passphrase:isWEP
 * @discussion
 *   A designated initializer to instantiate a new NEHotspotConfiguration object.
 *   This initializer is used configure either WEP or WPA/WPA2 Personal Wi-Fi Networks.
 *
 * @param SSID The SSID of the WEP or WPA/WPA2 Personal Wi-Fi Network
 * @param passphrase The passphrase credential.
 *   For WPA/WPA2 Personal networks: between 8 and 63 characters.
 *   For Static WEP(64bit)  : 10 Hex Digits
 *   For Static WEP(128bit) : 26 Hex Digits
 * @param isWEP YES specifies WEP Wi-Fi Network else WPA/WPA2 Personal Wi-Fi Network
 */
- (instancetype)initWithSSID:(NSString *)SSID
                      passphrase:(NSString *)passphrase isWEP:(BOOL)isWEP API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(macos, watchos, tvos);
  1. 用于配置一個企業(yè)級的WiFi配置對象,EAP類型的WIFI,加密方式采用的是WPA/WPA2,連接此種WiFi iOS系統(tǒng)會彈出一個驗(yàn)證窗口,需要輸入用戶名和密碼。
/*!
 * @method initWithSSID:eapSettings
 * @discussion
 *   A designated initializer to instantiate a new NEHotspotConfiguration object.
 *   This initializer is used configure WPA/WPA2 Enterprise Wi-Fi Networks.
 *
 * @param SSID The SSID of WPA/WPA2 Enterprise Wi-Fi Network
 * @param eapSettings EAP configuration
 */
- (instancetype)initWithSSID:(NSString *)SSID
                      eapSettings:(NEHotspotEAPSettings *)eapSettings API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(macos, watchos, tvos);
  1. 具有HS 2.0和EAP設(shè)置的Hotspot 2.0 Wi-Fi網(wǎng)絡(luò)
/*!
 * @method initWithHS20Settings:eapSettings
 * @discussion
 *   A designated initializer to instantiate a new NEHotspotConfiguration object.
 *   This initializer is used configure HS2.0 Wi-Fi Networks.
 *
 * @param hs20Settings Hotspot 2.0 configuration
 * @param eapSettings EAP configuration
 */
- (instancetype)initWithHS20Settings:(NEHotspotHS20Settings *)hs20Settings
                        eapSettings:(NEHotspotEAPSettings *)eapSettings API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(macos, watchos, tvos);

具體代碼實(shí)現(xiàn)

代碼中有詳細(xì)的注釋,需要注意的是NEHotspotConfigurationManager的回調(diào)結(jié)果不能僅憑error參數(shù)來決定,如果連接一個不存在的ssid,errorl也會為空,所以需要結(jié)合當(dāng)前連接的ssid來給出成功與否的判斷。

- (void)openSpecialWIFI {
    
    if (@available(iOS 11.0, *)) { // 用代碼自動切換到指定的WiFi熱點(diǎn)
        MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
        hud.removeFromSuperViewOnHide = YES;
        
        NEHotspotConfiguration *netConfig = nil;
        if (self.enableEAPSwitch.on) {
            // Extensible Authentication Protocol (EAP) settings for configuring WPA and WPA2 enterprise Wi-Fi networks.
            NEHotspotEAPSettings *epSettings = [[NEHotspotEAPSettings alloc] init];
            epSettings.supportedEAPTypes = @[
                                             @(NEHotspotConfigurationEAPTypeEAPPEAP)
                                             ];
            epSettings.ttlsInnerAuthenticationType = NEHotspotConfigurationEAPTTLSInnerAuthenticationMSCHAP;
            epSettings.username = self.username.text;
            epSettings.password = self.pwd.text;
            netConfig = [[NEHotspotConfiguration alloc] initWithSSID:self.wifiName.text eapSettings:epSettings];
            
            //NEHotspotConfiguration *netConfig = [[NEHotspotConfiguration alloc] initWithSSID:self.wifiName.text passphrase:self.wifiPWD.text isWEP:NO];
        } else {
            netConfig = [[NEHotspotConfiguration alloc] initWithSSID:self.wifiName.text];
        }
        NSLog(@"要連接的WIFI信息:\nWiFi名稱:%@\nWiFi密碼:%@\nEAP設(shè)置:\n用戶名:%@\n密碼:%@", self.wifiName.text, self.wifiPWD.text, self.username.text, self.pwd.text);
        
        /*
            NEHotspotConfiguration可以創(chuàng)建兩種形式的WIFI,一種是臨時模式j(luò)oinOnce(默認(rèn)為NO),另一種是永久模式persistent,
         當(dāng)使用臨時模式時:
            1、臨時模式當(dāng)APP在后臺超過15秒就自動斷開指定的WIFI了
            2、設(shè)備睡眠狀態(tài)
            3、本APP crash、退出或者卸載
            4、本APP連接了另一個WIFI
            5、用戶手動連接了另一個WIFI
            具體參考https://developer.apple.com/documentation/networkextension/nehotspotconfiguration/2887518-joinonce
        */
        netConfig.joinOnce = NO;
        // 1、移除網(wǎng)絡(luò)配置,關(guān)閉網(wǎng)絡(luò)
        [self disconnectWIFI];
        // 2、打開網(wǎng)絡(luò),調(diào)用此方法系統(tǒng)會自動彈窗確認(rèn)
        [[NEHotspotConfigurationManager sharedManager] applyConfiguration:netConfig completionHandler:^(NSError * _Nullable error) {

            NSString *currentConnectedWifi = [[self currnentConnectedWIFIInfo] objectForKey:@"SSID"] ? : @"";
            if (error || ![currentConnectedWifi isEqualToString:self.wifiName.text]) {
                NSLog(@"無法連接熱點(diǎn):%@",error);

                NSString *msg = @"連接異常!";
                if (error && error.code != 13 && error.code != 7) {
                    NSLog(@"WIFI連接失敗!");
                    msg = [NSString stringWithFormat:@"%@ - 無法連接熱點(diǎn)%@", self.wifiName.text, error.localizedDescription];
                } else if(error.code ==7) { // 用戶點(diǎn)擊了彈出框的取消按鈕
                    NSLog(@"已取消");
                    msg = @"您取消了WIFI連接按鈕!";
                } else {
                    NSLog(@"連接的不是當(dāng)前WIFI");
                    msg = @"WIFI連接失敗!";
                }

                hud.mode = MBProgressHUDModeText;
                hud.label.text = msg;
                [hud showAnimated:YES];
                [hud hideAnimated:YES afterDelay:1.5f];
            } else {
                //連接wifi成功
                NSLog(@"連接WiFi成功");

                hud.mode = MBProgressHUDModeText;
                hud.label.text = [NSString stringWithFormat:@"%@ 連接成功~",self.wifiName.text];
                [hud showAnimated:YES];
                [hud hideAnimated:YES afterDelay:1.5f];
            }
            // 打印所有已經(jīng)配置過的WIFI
            [self getAllConfiguredWIFIList];
        }];
    } else { // 需要手動去連接WiFi
        UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"提示" message:@"您必須手動連接上指定的WIFI才能使用該功能,請點(diǎn)擊右上角查看具體操作!" preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *sureAction = [UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            [self openWIFISettingPage];
        }];
        [alertVC addAction:sureAction];
        [self presentViewController:alertVC animated:YES completion:^{
        }];
    }
}

斷開指定的已經(jīng)連接的WIFI,此API只是清除了指定WIFI的配置信息,并不會關(guān)閉系統(tǒng)WIFI開關(guān),所有有可能會自動連接上其他已經(jīng)配置的WIFI。

- (void)disconnectWIFI {
    [[NEHotspotConfigurationManager sharedManager] removeConfigurationForSSID:self.wifiName.text];
}

獲取當(dāng)前連接WIFI的信息,注意iOS12包含iOS12以后如果要獲取當(dāng)前已連接WiFi的信息需要在Xcode開啟only need open Access WIFI information on capacities

- (NSDictionary *)currnentConnectedWIFIInfo {
    NSArray *ifs = (__bridge_transfer id)CNCopySupportedInterfaces();
    NSLog(@"interfaces:%@",ifs);
    NSDictionary *info = nil;
    for (NSString *ifname in ifs) {
        info = (__bridge_transfer NSDictionary *)CNCopyCurrentNetworkInfo((__bridge CFStringRef)ifname);
        NSLog(@"%@ => %@", ifname, info);
        if (info && [info count]) break;
    }
    return info;
}

獲取已經(jīng)配置過(也就是已經(jīng)被保存過的WIFI)的wifi名稱。如果你設(shè)置joinOnce為YES,此WIFI在這里就不會被獲取。

//獲取已經(jīng)配置過(也就是已經(jīng)被保存過的WIFI)的wifi名稱。如果你設(shè)置joinOnce為YES,這里就不會有了
-(void)getAllConfiguredWIFIList {
    [[NEHotspotConfigurationManager sharedManager] getConfiguredSSIDsWithCompletionHandler:^(NSArray<NSString *> * _Nonnull list) {
        NSLog(@"所有已經(jīng)配置過的WIFI列表~~~ %@", list);
    }];
}

WIFI HotspotHelper

iOS9 蘋果提供了獲取系統(tǒng)WiFi列表,注意需要手動開啟WiFi,從系統(tǒng)settings進(jìn)入WiFi列表后,回調(diào)才起作用,才能獲取到WIFI列表,另外此操作需要向Apple申請權(quán)限

-(void)getWifiList {
    // iOS9 之前無法獲取WiFi列表
    if (!([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0)) return;
    
    // iOS9之后可以獲取WiFi列表,對WiFi進(jìn)行一些操作,比如給指定的WiFi設(shè)置密碼
    NSMutableDictionary* options = [[NSMutableDictionary alloc] init];
    [options setObject:@"點(diǎn)我上網(wǎng)" forKey:kNEHotspotHelperOptionDisplayName];
    
    dispatch_queue_t queue = dispatch_queue_create("com.pronetwayXY", NULL);
    BOOL returnType = [NEHotspotHelper registerWithOptions:options queue:queue handler: ^(NEHotspotHelperCommand * cmd) {
        NEHotspotNetwork* network;
        NSLog(@"COMMAND TYPE:   %ld", (long)cmd.commandType);
        [cmd createResponse:kNEHotspotHelperResultAuthenticationRequired];
        if (cmd.commandType == kNEHotspotHelperCommandTypeEvaluate || cmd.commandType ==kNEHotspotHelperCommandTypeFilterScanList) {
            NSLog(@"WIFILIST:   %@", cmd.networkList);
            for (network  in cmd.networkList) {
                // NSLog(@"COMMAND TYPE After:   %ld", (long)cmd.commandType);
                if ([network.SSID isEqualToString:@"ssid"] || [network.SSID isEqualToString:@"proict_test"]) {
                    
                    NSString* wifiInfoString = [[NSString alloc] initWithFormat: @"---------------------------\nSSID: %@\nMac地址: %@\n信號強(qiáng)度: %f\nCommandType:%ld\n---------------------------\n\n", network.SSID, network.BSSID, network.signalStrength, (long)cmd.commandType];
                    NSLog(@"%@", wifiInfoString);
                   
                    [network setConfidence:kNEHotspotHelperConfidenceHigh];
                    [network setPassword:@"password"];
                    
                    NEHotspotHelperResponse *response = [cmd createResponse:kNEHotspotHelperResultSuccess];
                    NSLog(@"Response CMD %@", response);
                    
                    [response setNetworkList:@[network]];
                    [response setNetwork:network];
                    
                    [response deliver];
                }
            }
        }
    }];
    NSLog(@"result :%d", returnType);
    NSArray *array = [NEHotspotHelper supportedNetworkInterfaces];
    NSLog(@"wifiArray:%@", array);
    NEHotspotNetwork *connectedNetwork = [array lastObject];
    NSLog(@"supported Network Interface: %@", connectedNetwork);
}

總結(jié):

經(jīng)過一番折騰發(fā)現(xiàn),想要在iOS上實(shí)現(xiàn)系統(tǒng)級別的操作幾乎是不可能,iOS的很多操作都是僅限于沙盒級別的操作,如果非想要一些特殊的功能,那只能去探索越獄了。

參考文獻(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容