Using Hotspot Helper Extension

  • Tutorial
In the modern world, the presence of public Wi-Fi in public institutions is taken for granted. When visiting cafes, shopping centers, hotels, airports, leisure parks and many other places, we immediately look for the coveted signal without a password. And this is not easy, because, firstly, there may be several points in the list, and secondly, free Wi-Fi can be password protected, so the only way out is to catch an employee who can point to the correct network and name the password. But even after that, it happens that nothing works. The user must guess that he needs to open a browser (and there’s another question, which page should be downloaded) and take additional actions (log in, view ads, confirm the user agreement) before he is given full access to the network.

However, now many popular institutions offer applications that facilitate connection to free points. I am sure that each of us will be able to easily recall a couple of such examples, so I can do without names and advertising. Moreover, we will discuss below another solution to this problem - we will write our own Network Helper! With this approach, you no longer have to guess which grid to connect to. Even additional actions to gain access to the network can be performed in a convenient native UI and much faster than in a browser.

Everything is simple. It is enough to use the NEHotspotHelper technology, which has become available to developers since the release of iOS 9. The main task of this tool is the classification of Wi-Fi networks and user authorization in them. NEHotspotHelper is part of the NetworkExtension framework. Below you will find a diagram of the tools included in it at the time of the release of iOS 11:

Network Extention Framework

The main documentation is here:  Hotspot Network Subsystem Programming Guide . In addition, no information was found on the network, and therefore I am writing this article. I hope my material will help fill in the gaps in the documentation and explain how to implement your own Hotspot Helper. An example of the use of this technology can be found on GitHub .

Principle of operation


Hotspot Helper is based on a Wi-Fi connection state machine and commands sent by the Helper system, the processing of which transfers the machine from one state to another. Below is an approximate state diagram that Apple itself cites in its documentation:

State machine

At first, such a complex picture is scary, but do not worry - in practice, everything is quite simple.

It is enough to understand the following:

  1. When connecting to the network for the first time after rebooting the device, Helper is selected to serve it ( Evaluate ).
  2. Once the choice is made, authorization on the network is started using the selected Hotspot Helper ( Authenticating ).
  3. If during the authorization process it is necessary to display the UI, Hotspot Helper explicitly requests it ( PresentingUI ). If there is no such need, Helper in the background takes the necessary steps to authorize the user on the network ( Authenticated ).
  4. Periodically, the system wakes up the selected Hotspot Helper to support the session, if necessary ( Maintain ).
  5. During session support, Helper may do nothing, or may request re-authorization or provoke a reconnection to the network.

The only non-obvious point: after the first connection to the network, the system caches the Hotspot Helper selected for it, so the next time the machine is connected, the machine immediately switches to the Maintain state, bypassing all the previous ones.

Hotspot Helper is involved in user authorization in a Wi-Fi network at all stages - from displaying a list of networks and connecting to the selected one to support authorization and self-logout. At the same time, to establish a connection, Hotspot Helper processes all the required commands, so the system ensures its launch in any situation (even if the user forcibly shuts down the application (which would ignore silent-pushnotifications). After all, the operation of the entire device depends on this. It should be understood that for the user the whole process is transparent. Thus, the most common scenario is to run the application in the background.

So, again: to establish a Wi-Fi connection, the Hotspot Helper must process all the required commands. In other words, the device will not consider itself connected to the network until StateMachine enters the Authenticated state  . And this is despite the fact that Hotspot Helper will start to see the connection when it receives the Evaluate command  . This moment is perfectly tracked by Reachability tools, which we will talk about below.

I must say that NEHotspotHelper is not a separate target, as is often the case with other extensions, but the main application registered as Hotspot Helper. This means that as soon as he needs to process any command, the main application will be launched with all the ensuing consequences. That is, the application will be able to execute any code, but when launched in the background, it can deploy full-blown actions, as if initiated by the user himself. However, such activity will only mean wasting resources, so what is happening in the background is worth watching .

Preliminary preparation


To register an application as Hotspot Helper, you need permission from Apple. To do this, Team Agent must follow the  link  and fill out the questionnaire.
At the time of writing, it looks like this:
Questionnaire_1Questionnaire_2

If everything goes well, then when creating a provisioning profile on https://developer.apple.com it will be possible to choose entitlement for it with the key com.apple.developer.networking.HotspotHelper , which gives the right to use all the goodies.

Provisioning
In addition, you need to enable the Background Mode Capability in the project   and write the  line  network-authentication in the  Info.plist  in the UIBackgroundModes section  . After that, you can proceed to the most interesting - coding.

check in


In order for the application to become Hotspot Helper, it must be registered in the system. To do this, call the following method:

classNEHotspotHelper 
    class func register(options: [String : NSObject]? = nil, queue: DispatchQueue, 
                        handler: @escapingNetworkExtension.NEHotspotHelperHandler) -> Bool
 
The method takes three parameters:

  • Optional parameter dictionary. Now the only parameter is supported:  kNEHotspotHelperOptionDisplayName - the name of the Hotspot Helper, which will be displayed in the list of available Wi-Fi networks next to those networks that HH supports (more on that below). You can change this name only after restarting the application by passing the new name as a parameter. According to Apple , the name should be as short as possible.

  • The queue on which the commands received from the system will be processed. It can be used not only to process commands in the background, but also to synchronize the processing of commands with other application code.

  • Block is a command handler. This is a key element of the design. His signature is simple:

    typealias NEHotspotHelperHandler = (NEHotspotHelperCommand) -> Void
    

    It takes a system command as the only parameter and returns nothing. The block will be called on the queue passed as the second parameter.

Register Helper exactly once at each start. Recalling in the same run gives nothing and returns  false . It is important to note that until the re-registration is completed, the application will not receive a command for which it was launched by the system.

You can’t cancel the registration in any way. You can only stop registering the block, then the application will not process the connection in any way, but it will still start up -   more info here .

In addition, unlike many other functions of the system (such as a photo gallery, calendar, and even notifications), processing a Wi-Fi connection using Hotspot Helper does not require any permissions from the user and is transparent to him (he simply does not encounter such concepts) .

Command processing


A command is an object of the NEHotspotHelperCommand class that contains the type and data set specific to each command (a network or a list of networks, if the command implies).

After processing each of the commands, create NEHotspotHelperResponse with the result of the execution and a data set that also depends on the particular command.
The NEHotspotHelperResponse object is created by calling this method on the received command:

func createResponse(_ result: NEHotspotHelperResult) -> NEHotspotHelperResponse

In addition, using the command object allows you to establish a  TCP  or  UDP connection based on the network to which the command belongs, by calling the appropriate methods:
 
 func createTCPConnection(_ endpoint: NWEndpoint) -> NWTCPConnection
 func createUDPSession(_ endpoint: NWEndpoint) -> NWUDPSession
 
For higher level communication with the server, you can create NSURLRequest. Attaching a command to it, Hotspot Helper gets the opportunity to interact with the server in conditions when the device does not see a Wi-Fi connection. The connection established in this way can be used for authorization “in one's own way”. IYKWIM
 
func bind(to command: NEHotspotHelperCommand)

Below, we consider each command that Hotspot Helper can receive in the order corresponding to the basic scenario of connecting to the network. Most of the commands are named similarly to the State Machine states in which they are called.

Officially, no more than 45 seconds are given for each team  (however, if you look at the available operating time in the background, you can see the figure of 60 seconds). After that, the team is considered unprocessed, and the work of Hotspot Helper is suspended. This restriction is necessary to eliminate unnecessary delays when connecting to the network, because until the command is processed, the user will not see the coveted Wi-Fi icon in the Status Bar. It should be understood that if there are several Hotspot Helper in the system that process the same network, the fastest one will be selected (more on that below).

NEHotspotHelperCommandType. filterScanList


This is a special command, which, unlike the others, is not tied to any of the StateMachine states and can be called at any time. The command is called on all Hotspot Helper known to the system  every 5 seconds . This happens all the time while the user is on the list of Wi-Fi networks in the system Settings.app.

The team serves the sole purpose of demonstrating to the user which networks Hotspot Helper is processing. To do this, the command contains a list of available networks in the corresponding field:

var networkList: [NEHotspotNetwork]? { get }

This is the same list that the user will see. Moreover, the list of networks may change with each new call to the team.

It is assumed that Hotspot Helper should analyze the list of networks and return those that it is ready to serve in response to the command. An empty array in the answer will mean that there are no networks available for servicing by this Helper. Hide for the network user from the list does not work.

Those networks in the list that Hotspot Helper returned in response to this command will have a signature. It will correspond to the value that was transferred at the last registration of Hotspot Helper as an option kNEHotspotHelperOptionDisplayName. Signature value can be only one. It is set during registration and cannot be changed until the next registration (and this happens after the application is restarted), so signing networks in different ways, alas, will not work.

It is important to note that in addition to transmitting the network itself in response, it needs to set a password if it is protected by it. Without this, the signature will not appear. If the password is set, then in addition to the signature, the user does not need to enter the password himself. I must say, this is the only moment when you can set a password for the network. If you do not ask it now, then the user will have to do it.

As a result, the command should be processed as follows:
 
let network = <A network from command.networkList>
network.setPassword("PASSWORD")
let response = command.createResponse(.success)
response.setNetworkList([network])
response.deliver()
 
As a result, the user will see something similar:
Wi-Fi settings_1Wi-Fi settings_2

It should be understood that the filterScanList command is used to demonstrate the list of networks to the user and does not affect the rest of the processing of the network connection. If, in response to a Hotspot Helper command, no network is returned, he will still be offered to process the connection to any of them using the commands described below.

An interesting fact:  if you uninstall the application, the signatures in the list of networks will remain until the device is restarted.

NEHotspotHelperCommandType. evaluate


This command is called upon the first connection to the network on all Hotspot Helper known to the system.
Note:  on subsequent connections, evaluate will not be called, immediately followed by maintain on the Hotspot Helper that was selected in the evaluate process.
 
The purpose of this team is to first identify the most suitable Hotspot Helper network for processing the connection to the selected network. To do this, together with the Hotspot Helper team, it also receives data over the network to which you are connecting:
 
var network: NEHotspotNetwork? { get }

Network contains a number of properties, however, in the process of processing the evaluate command, only the following values ​​have values:
 
// Идентифицируют сетьvar ssid: String { get }
var bssid: String { get }
 
// Отражают силу сигнала по шкале от 0.0 до 1.0 (дБ, увы, не предоставляются) var signalStrength: Double { get }
 
// Признак необходимости ввода пароля для подключения к сетиvar isSecure: Bool { get }
 
// Признак, показывающий, было ли подключение выполнено автоматически // или пользователь явно выбрал эту сеть в настройкахvar didAutoJoin: Bool { get }

Having received this information, Hotspot Helper must analyze the network in any way (starting from the local table and ending with the request to the server) and set the network to the appropriate level of  confidence :
 
// Helper уверен, что не обрабатывает эту сеть.case none
 
// Helper предполагает, что сможет обработать подключение к этой сети, но не уверен полностью*.case low
 
// Helper уверен, что может полноценно обработать подключение к этой сети.case high

* In this case, they can provide him with the opportunity to authorize the user, but if in the Helper process he realizes that he is not compatible with this network, he will be able to refuse to work with it, and StateMachine will again enter the evaluate state, and Helper will be added to the exclusion list by this network.

It is important to note:  Apple strongly makes it clear that the level of confidence must be chosen carefully and should not be thoughtlessly set to all networks high (and even low), as this directly affects the UX of the entire system.
 
As a result, the command should be processed as follows:

let network = command.network
 
// Оценить сеть и определить уровень confidence...
 
network.setConfidence(<Appropriate level of confidence>)
 
let response = command.createResponse(.success)
response.setNetwork(network)
response.deliver()
 
It takes 45 seconds to process the command  , but it makes sense to try to do it as quickly as possible. Because, as soon as the system receives the first response with high confidence, the processing of the command is terminated. Then, the responding Hotspot Helper is selected for further processing of the network connection, and all the others stop their work and go into a suspended state.

NEHotspotHelperCommandType. authenticate


The authenticate command is called on the most appropriate Hotspot Helper based on the results of the evaluate command.

The purpose of this command is to perform all the steps necessary to provide full user access to the selected Wi-Fi network. To do this, together with the Hotspot Helper team, it also receives data over the network to which you are connecting:

var network: NEHotspotNetwork? { get }
 
The core of the Hotspot Helper is at the core of this team. At this stage, only the Hotspot Helper is the barrier between the user and network access, and he must decide in any way imaginable by the developer whether to grant the user access or not.
It takes 45 seconds to process the command  , after which it is necessary to return a response with one of the following results:
 
.success - authorization successfully completed. Network access is fully open. StateMachine enters authenticated state.
 
.unsupportedNetwork- this network is not supported by Helper: the network was incorrectly analyzed at the evaluate stage. StateMachine returns to the evaluate phase, and Helper is added to the exceptions for this network. This can happen, for example, if Helper returned low confidence for this network at the evaluate stage, and now has made sure that it can’t cope.
 
.uiRequired - requires user interaction. This result is returned if any additional data is required for authorization. However, not everything is so simple: the UI will not seem by itself when this result is returned .

It works like this: Hotspot Helper is generated by UILocalNotification, through which it informs the user about the need for additional interaction. If the user ignores it, nothing else will happen. Having received the processing result, StateMachine enters the presentingUI state and remains in it until authorization or disconnection from the network is completed. 
 
.temporaryFailure is a fixable error. For example, a network error occurred during authorization. StateMachine goes into failure state, the device disconnects from the selected network.
 
.failure(or any other result, as well as its absence) - an fatal error. Something went completely wrong, and you cannot handle the connection: for example, the authorization protocol on the server side has changed, and the client is not ready for this. StateMachine goes into failure state, the device disconnects from the selected network. Additionally, unlike temporaryFailure, the auto-join function is disabled for such a network
 
As a result, the command should be processed as follows:
 
let network = command.network
// Авторизовать пользователя необходимым образом и сформировать результат обработки команды// Вывести UILocalNotification в случае необходимости дополнительного взаимодействия (.uiRequired)
command.createResponse(<Commandresult>).deliver()

NEHotspotHelperCommandType. presentUI


This command is called on the selected Hotspot Helper if it returned the uiRequired result during the processing of the authenticate command.

This is the only command that does not wake the application in the background and has unlimited execution time. It arrives only after the user starts the application, for example, having received UILocalNotification about the need for additional interaction in the process of processing the authenticate command. 

Just at this stage, you should ask the user to enter his domain loans if the network is corporate, or show, for example, advertising if the network is commercial. In short, the options are limited only by the imagination and common sense of the developer.

In the end, you need to return response with one of the following results:
 
.success- authorization completed successfully. Network access is fully open. StateMachine enters authenticated state.
 
.unsupportedNetwork - this network is not supported by Helper: the network was incorrectly analyzed at the evaluate stage. StateMachine returns to the evaluate phase, and Helper is added to the exceptions for this network. This can happen, for example, if Helper returned low confidence for this network at the evaluate stage, and now has made sure that it can’t cope.
 
.temporaryFailure is a fixable error. Say a network error occurred during authorization. StateMachine goes into failure state, the device disconnects from the selected network.
 
.failure(or any other result, as well as its absence) - an fatal error. Something went wrong and the connection cannot be processed: for example, the authorization protocol on the server side has changed, and the client is not ready for this. StateMachine goes into failure state, the device disconnects from the selected network. Additionally, unlike temporaryFailure, the auto-join function is disabled for such a network

As a result, the command should be processed as follows:

let network = command.network
// Произвести любые необходимые действия для авторизации пользователя любыми средствами// за неограниченные период времени и сформировать результат выполнения команды
command.createResponse(<Commandresult>).deliver()

NEHotspotHelperCommandType. maintain


The maintain command, as you might guess from its name, is designed to maintain a user authorization session in the current network. It is called on the Hotspot Helper selected for the network in the evaluate process in two cases:

  1. Every 300 seconds (five minutes) during the entire time the device is connected to a Wi-Fi network.
  2. When establishing a network connection, instead of the evaluate command, for processing which the current Hotspot Helper was selected earlier.


It is assumed that in both cases, Hotspot Helper will analyze the current state of the authorization session and perform one of the following actions:

  • instantly provide (continue to provide) the user access to the network, causing a response with the result  .success ;
  • will require re-authorization by calling response with the result  .authenticationRequired (in this case, StateMachine enters the Authenticating state and sends the authenticate command to the Hotspot Helper).
  • will report the impossibility to continue the session by completing the command with an error code ( .temporaryFailure / .failure or any other than those listed above). In this case, StateMachine enters the Evaluating state to select the Hotspot Helper that can handle the connection.

To distinguish the first case when the maintain command is called from the second in the array of data over the network transmitted with the command, a special flag is provided - didJustJoin .

As a result, the command should be processed as follows:

let network = command.network
if network.didJustJoin {
     // Новое подключение к сети, для обработки которой выбран данный Helper
}
else {
     // Поддержка сессии в сети, в которой была произведена авторизация (раз в 300 сек.)
}
 
// Обеспечить авторизацию пользователя в сети любым способом // и сформировать результат обработки команды
command.createResponse(<Commandresult>).deliver()

It should be noted that during re-authorization, the connection will be unavailable to the device until it returns to Authenticated state.

NEHotspotHelperCommandType. logoff


The logoff command, as you might expect, is not sent when disconnected from the network . It is not possible to track the disconnection from the network using Hotspot Helper.

This command is intended to end the internal authorization session of the selected Hotspot Helper and is sent to it in response to a call to the static method NEHotspotHelper:

class func logoff(_ networkNEHotspotNetwork) -> Bool

This method can only be successfully called with the current network as a parameter, only with the Hotspot Helper that authorized it, and only when the application is active.  Otherwise, the method will return false and the command will not be called.

As a result, StateMachine enters the LoggingOff state, and the Hotspot Helper receives the coveted command and  45 seconds  to execute it.

You can get the network that you want to pass to the method as follows:
 
let network = NEHotspotHelper.supportedNetworkInterfaces().first 

The main use-case: logging in from the application UI, which is relevant for the authorization script using the presentUI command.
 
As soon as the Hotspot Helper completes the command (or the time runs out), StateMachine enters the inactive state and the device disconnects from the Wi-Fi network. 

As a result, the command should be processed as follows:
 
let network = command.network
 
// Произвести logoff и сбросить внутренние данные сессии авторизации
 
let response = command.createResponse(.success).deliver()

It should be noted that before receiving this command, the application should not try to perform any actions to disconnect from the network, as this will negatively affect the UX of the entire system (in fact, the user will see a connection, but the data will not be transmitted).

What else is good to know


The principle of operation and implementation details of Hotspot Helper are described above. However, there are several more features that you should be aware of when starting development, namely:

  • Hotspot Helper cannot be registered on the simulator - a real device will be required for development and debugging.

  • An application registered as Hotspot Helper will be launched by the system in the background in any situation, since the operation of the entire system depends on it. Even if the user unloaded the application, turned off background fetch, turned on low power mode, etc.

  • Hotspot Helper does not provide any ability to monitor network outages (let the logoff command be misleading: this is the processing of the end of the authorization session by Helper himself). If you need to monitor the shutdown, you should use notifications from reachability (by itself, the application should be active - in the background, the system will not raise you).

  • The network to which the device is currently connected can be found out as follows:

    let network = NEHotspotHelper.supportedNetworkInterfaces().first

    It should be noted that, despite the swift signature of this method (which indicates a non-optional array as the result) and the expected behavior (the absence of a network is represented by an empty array), in the absence of a connection, you can get a network object with empty strings as the SSID and BSSID and signal strength 0.0. Sometimes, even worse, the output can be nil (and with it crash). The example shows code to avoid these situations. 

    Note:  With the release of iOS 11, this problem has been  fixed .

  • The network connection for the entire device appears only after StateMachine enters the Authenticated state. This is clearly seen using reachbility, which will not see the connection until the Hotspot Helper processes all the necessary commands.

    It should be noted that there is only one situation in which reachability already sees Wi-Fi, and the Hotspot Helper does not receive any commands. This happens when the user can see the following state in the settings:

    Wi-fi settings

    What causes this situation is difficult to understand. If you have any ideas, please share them.

  • Several Helper in the system.
    There are no restrictions on the number of Hotspot Helper simultaneously registered in the system. This means that a situation is possible in which several applications will conflict for processing the same network.

    The conflict is resolved at the processing stage of the evaluate command: the Hotspot Helper is selected, which returned high-confidence for the network faster than others. All further processing for this network occurs using only this Helper. The selected Helper can then refuse to process this network by returning the corresponding result code during the processing of the next command. But if he does not, for the rest of the Hotspot Helper there is no way to participate in processing the connection. 

    The situation is further aggravated by the fact that the user knows nothing and cannot know about the existence of any Helper. All this happens imperceptibly for him: this functionality is not indicated anywhere, no permissions are requested from him. For this reason, one of the  requirements of Apple  is the need to provide the user in the application UI the ability to disable the processing of all networks or a specific network (by SSID).

    It should be noted that there is no reliable way to determine the presence of another Hotspot Helper in the system. The only thing that can be done is to check whether Hotspot Helper is currently selected as the main one for the active network. This can be done like this:

    let network = NEHotspotHelper.supportedNetworkInterfaces().first
    if !network.isChosenHelper {
         // Hotspot Helper не обрабатывает активную сеть
    }

    Note that  false  in this flag may mean that another Helper has been selected for the network, or even that no one has been selected yet (for example, in the evaluate process). In addition, the network may already appear, but connection processing has not yet begun. This situation is described above.

  • In the process of processing a command, it is possible to send both low-level and high-level requests. To do this, the team has the following methods:

    func createTCPConnection(_ endpoint: NWEndpoint) -> NWTCPConnectionfunc createUDPSession(_ endpoint: NWEndpoint) -> NWUDPSession

    As well as an extension to NSMutableURLRequest:

    func bind(to command: NEHotspotHelperCommand)

  • With the release of iOS10.3, obviously in order to save resources, the system began to make unauthorized disconnections from the network when the device fell asleep and then reconnected to the same network. The disconnect-connection intervals are not predictable and depend on the current state of the system, device and applications installed on it. Intervals can be very different: from fractions of seconds to several hours. Moreover, it is not possible to find any way to distinguish automatic reconnection to the network from the one performed by the user himself - for the device they both occur the same way.

    If you have any ideas how to distinguish between such situations or at least predict them, please share them.

  • With the release of iOS 11, NEHotspotHelper functionality will complement the new NEHotspotConfigurationManager class   , with which you can connect to Wi-Fi directly from the application. You no longer have to force the user to go to settings alien to him and select a network from a huge list with confused names. This is useful in two situations:

    • for applications providing public services (cafes, business centers, etc.);
    • for applications that configure devices over their own Wi-Fi network (for example, camcorders). To do this, you can temporarily connect to a given grid until you exit the application.

Conclusion


NEHotspotHelper technology, which appeared several years ago, has not lost its relevance to this day. This tool allows you to significantly improve and facilitate the process of using network services. Here I examined the basic principles of work, methods of application and all the steps that should be taken to use it effectively. In addition, he talked about some features of Helper, about which the documentation is tactfully silent.

I hope you now have a complete understanding of why and how to use this thing in your project. However, if you have any questions, write, I am ready to answer them.

useful links


  1. GitHub implementation example
  2. Hotspot Network Subsystem Programming Guide
  3. Network extension
  4. NEhotspotHelper reference
  5. WWDC'15 What's New in Network Extension and VPN and its synopsis
  6. WWDC'17 Advances in Networking, Part 1
  7. Forum: How to cancel register an app as a Hotspot Helper (NEHotspotHelper)?
  8. Forum: How to get entitlements via mail or through a direct link to the request form
  9. Forum: How to show U I
  10. Short article about HotspotHelper

Also popular now: