Swift 3 - Got an empty NetService.addresses array after exploring bonjour - ios

I'm pretty new in Swift 3.
I want to get the ip of my NetServices which I explored with bonjour to show them to the user, not to connect with the device. So far I can search for devices with bonjour and get them listed in a listView with this great access code:
https://github.com/ecnepsnai/BonjourSwift
This is my function to scan the bonjour services and put them into a local array of NetServices:
// scanning for services, delete old bonjourServices Array and fill it with new discovered services
var bonjourServices = [NetService]()
private func putServicesToArray(){
let browser: Bonjour = Bonjour()
_ = browser.findService(Bonjour.Services.Line_Printer_Daemon, domain: Bonjour.LocalDomain) { (services) in
self.bonjourServices.removeAll()
for service in browser.services {
if !(self.bonjourServices.contains(service)) {
self.bonjourServices.append(service)
}
}
}
}
I'm using this method to get the ip address, from Swift 3 how to resolve NetService IP?
func netServiceDidResolveAddress(_ sender: NetService) {
print("netServiceDidResolveAddress get called with \(sender).")
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
guard let data = sender.addresses?.first else {
print("guard let data failed")
return
}
do {
try data.withUnsafeBytes { (pointer:UnsafePointer<sockaddr>) -> Void in
guard getnameinfo(pointer, socklen_t(data.count), &hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 else {
throw NSError(domain: "domain", code: 0, userInfo: ["error":"unable to get ip address"])
}
}
} catch {
print(error)
return
}
let address = String(cString:hostname)
print("Adress:", address)
}
And with this IBAction I just want to print the ip address, but my ip is empty and my bonjourServices[0].addresses is empty also the addresses.adress NSData Objects
#IBAction func detectNetwork(_ sender: UIButton) {
print("putServiceToArray: ")
putServicesToArray()
for service in bonjourServices {
print(service)
}
bonjourTableView.reloadData()
if !(bonjourServices.isEmpty){
print(netServiceDidResolveAddress(bonjourServices[0]))
print(bonjourServices[0].addresses)
}
}
Here is my console output:
netServiceDidResolveAddress get called with <NSNetService 0x61800003d6a0> local. _printer._tcp. Brother HL-3152CDW series.
guard let data failed
()
Optional([])
Can you please help me resolve this problem?

I managed to solve my problem with this post: Bonjour Service Browser with Swift does not fetch serviceinfo
I did use Swift Development with Cocoa: Developing for the Mac and IOS App Stores published by O'Reilly for my code from page 299:
https://books.google.de/books?id=pNzSBQAAQBAJ&pg=PA299&lpg=PA299&dq=swift+implement+bonjour+discovery&source=bl&ots=6nhrmuDfEp&sig=eY9kPtzOl7nshavmwdwVYQlpNfM&hl=de&sa=X&ved=0ahUKEwjclvHGncLUAhXBKFAKHZeRByQ4ChDoAQgyMAI#v=onepage&q=swift%20implement%20bonjour%20discovery&f=false
And then I had to make a call like this:
self.bonjourServices.removeAll()
self.browser = NetServiceBrowser()
self.browser.delegate = self
self.browser.searchForServices(ofType:"_raop._tcp", inDomain: "")
Now it's working fine and I got all data of my services.

Related

stopVPNTunnel is not disconnecting the VPN connection in iOS

I have implemented one successful vpn connection. But When i close and Open app while the VPN is connected, then i can't disconnect the VPN.
public func connectVPN() {
//For no known reason the process of saving/loading the VPN configurations fails.On the 2nd time it works
Log.d(message: "connectVPN")
NSLog("ConnectVPN")
self.vpnManager.loadFromPreferences(completionHandler: self.vpnLoadHandler)
NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: nil , queue: nil) {
notification in
let nevpnconn = notification.object as! NEVPNConnection
let status = nevpnconn.status
self.vpnDelegate?.checkNES(status: status)
}
}
public func disconnectVPN() ->Void {
Log.d(message: "Disconnect VPN Called")
Log.d(message: "Log Disconnect VPN Called")
print("vpnManager:disconnectVPN \(self.vpnManager.connection) ") // object available
self.vpnManager.connection.stopVPNTunnel()
}
I could not find out why it is not disconnecting.
Call stopVPNTunnel() inside loadFromPreferences closure.
NEVPNManager.shared().loadFromPreferences { error in
assert(error == nil, "Failed to load preferences: \(error!.localizedDescription)")
NEVPNManager.shared().connection.stopVPNTunnel()
}

Unable to attach debugger to Network Extension iOS ("Waiting to Attach")

I'm new to iOS development, and am writing a VPN app, using the OpenVPN framework. I'm making use of the Network Extension target to tunnel network traffic to our OpenVPN server. However, when trying to debug, I can't attach the debugger to the Network Extension - it's stuck at "Waiting to Attach" in the Debug Navigator view.
In this blog post by Alex Grebenyuk he gives some recommendations for troubleshooting when you fail to attach the debugger to the extension. I've meticulously checked all of them, and my app correctly uses everything there: Both the app and the network extension have the Packet Tunnel Network Extension capability and bundle names all work out.
Some more info on the bundle names in case it might be relevant:
Bundle name for the app: com.example.Example-App
Bundle name for the Network Extension: com.example.Example-App.Example-VPN
Group name: group.com.example.Example-App
I'm out of ideas here. Could the problem be in the Network Extension never being started? It doesn't seem to be the case, because the startTunnel() function is called. The code for starting the VPN through the extension:
func configureVPN(serverAddress: String) {
guard let configData = readTestFile() else { return }
// Test file with ovpn configuration seems is read in correctly - contents are shown if printed to console.
self.providerManager?.loadFromPreferences { error in
if error == nil {
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.serverAddress = serverAddress
tunnelProtocol.providerBundleIdentifier = "com.example.Example-App.Example-VPN" // bundle id of the network extension target
tunnelProtocol.providerConfiguration = ["ovpn": configData]
self.providerManager.protocolConfiguration = tunnelProtocol
self.providerManager.localizedDescription = "Example VPN"
self.providerManager.isEnabled = true
self.providerManager.saveToPreferences(completionHandler: { (error) in
if error == nil {
self.providerManager.loadFromPreferences(completionHandler: { (error) in
do {
print("Trying to start connection now") // This line is printed to the Console
try self.providerManager.connection.startVPNTunnel() // start the VPN tunnel.
} catch let error {
print(error.localizedDescription)
}
})
}
})
}
}
}
Not sure if this is relevant information, but I am using the OpenVPNAdapter in the PacketTunnelProvider, following this tutorial:
import NetworkExtension
import OpenVPNAdapter
class PacketTunnelProvider: NEPacketTunnelProvider {
lazy var vpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
return adapter
}()
let vpnReachability = OpenVPNReachability()
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
override func startTunnel(
options: [String : NSObject]?,
completionHandler: #escaping (Error?) -> Void
) {
// There are many ways to provide OpenVPN settings to the tunnel provider. For instance,
// you can use `options` argument of `startTunnel(options:completionHandler:)` method or get
// settings from `protocolConfiguration.providerConfiguration` property of `NEPacketTunnelProvider`
// class. Also you may provide just content of a ovpn file or use key:value pairs
// that may be provided exclusively or in addition to file content.
// In our case we need providerConfiguration dictionary to retrieve content
// of the OpenVPN configuration file. Other options related to the tunnel
// provider also can be stored there.
guard
let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration
else {
fatalError()
}
guard let ovpnFileContent: Data = providerConfiguration["ovpn"] as? Data else {
fatalError()
}
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnFileContent
configuration.settings = [ :
// Additional parameters as key:value pairs may be provided here
]
// Uncomment this line if you want to keep TUN interface active during pauses or reconnections
// configuration.tunPersist = true
// Apply OpenVPN configuration
let evaluation: OpenVPNConfigurationEvaluation
do {
evaluation = try vpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
// Provide credentials if needed
if !evaluation.autologin {
// If your VPN configuration requires user credentials you can provide them by
// `protocolConfiguration.username` and `protocolConfiguration.passwordReference`
// properties. It is recommended to use persistent keychain reference to a keychain
// item containing the password.
guard let username: String = protocolConfiguration.username else {
fatalError()
}
// Retrieve a password from the keychain
let password: String = "Test"
let credentials = OpenVPNCredentials()
credentials.username = username
credentials.password = password
do {
try vpnAdapter.provide(credentials: credentials)
} catch {
completionHandler(error)
return
}
}
// Checking reachability. In some cases after switching from cellular to
// WiFi the adapter still uses cellular data. Changing reachability forces
// reconnection so the adapter will use actual connection.
vpnReachability.startTracking { [weak self] status in
guard status == .reachableViaWiFi else { return }
self?.vpnAdapter.reconnect(afterTimeInterval: 5)
}
// Establish connection and wait for .connected event
startHandler = completionHandler
vpnAdapter.connect(using: packetFlow as! OpenVPNAdapterPacketFlow)
}
override func stopTunnel(
with reason: NEProviderStopReason,
completionHandler: #escaping () -> Void
) {
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
vpnAdapter.disconnect()
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
// protocol if the tunnel is configured without errors. Otherwise send nil.
// `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
// you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
// send `self.packetFlow` to `completionHandler` callback.
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?,
completionHandler: #escaping (Error?) -> Void
) {
// In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
// send empty string to NEDNSSettings.matchDomains
networkSettings?.dnsSettings?.matchDomains = [""]
// Set the network settings for the current tunneling session.
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
}
// Process events returned by the OpenVPN library
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
handleEvent event:
OpenVPNAdapterEvent, message: String?
) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
// Handle only fatal errors
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
fatal == true else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler = startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
}
Any help with this would be hugely appreciated! Thanks.
You need to attache the bundle name and not the bundle identifier and the bundle name should not be an identifier. The PRODUCT_BUNDLE_IDENTIFIER of your network extension would be something like com.example.my-network-extension-id (it cannot be com.example.Example-App.Example-VPN, as a third period is not allowed for network extension IDs) and the PRODUCT_NAME would be something like My Network Extension, and you would then attach to My Network Extension (yes, with spaces and no extension).

Getting a list of access points and their signal strength

I'm currently trying to build a proof-of-concept iOS app to check if we are able to implement some sort of indoor positioning capability without deploying beacons or any other hardware.
What we have
There is a database containing all registered access points in our building including their X- and Y-coordinates. The coordinates are mapped to a custom-built grid that spans the whole building.
The app will be released using our Enterprise distribution, so there are no constraints concerning any Apple Store requirements. The app will be running exclusively on devices that automatically connect to the proper WiFi using a certificate.
What we'd like to build
In order to improve the usability of the app, we'd like to show the user his current position. Using Apples native CLLocation services is not accurate enough because we are operating inside a building. The basic idea is to fetch all nearby access points including their BSSID and signal strength and calculate a more or less accurate position using both signal strength and the location database for our access points (see above).
What i've tried so far
Using SystemConfiguration.CaptiveNetwork to get the BSSID
import SystemConfiguration.CaptiveNetwork
func getCurrentBSSID() -> String {
guard let currentInterfaces = CNCopySupportedInterfaces() as? [String] else { return "" }
for interface in currentInterfaces {
print("Looking up BSSID info for \(interface)") // en0
let SSIDDict = CNCopyCurrentNetworkInfo(interface as CFString) as! [String : AnyObject]
return SSIDDict[kCNNetworkInfoKeyBSSID as String] as! String
}
return ""
}
This solution works (after setting the proper entitlements), but i'm only able to read the BSSID of the CURRENTLY CONNECTED access point.
Using UIStatusBarDataNetworkItemView to read signal strength
private func wifiStrength() -> Int? {
let app = UIApplication.shared
var rssi: Int?
guard let statusBar = app.value(forKey: "statusBar") as? UIView, let foregroundView = statusBar.value(forKey: "foregroundView") as? UIView else {
return rssi
}
for view in foregroundView.subviews {
if let statusBarDataNetworkItemView = NSClassFromString("UIStatusBarDataNetworkItemView"), view .isKind(of: statusBarDataNetworkItemView) {
if let val = view.value(forKey: "wifiStrengthRaw") as? Int {
rssi = val
break
}
}
}
return rssi
}
This one is kind of obvious, it only reads the signal strength for the connected WiFi network, not the access point specific one.
QUESTION
Is there any way to read a list of available access points (not WiFi networks) including their BSSID and signal strength? We cannot jailbreak the devices since they are under device management.
Maybe there is some way to do it using MobileWiFi.framework (see this link), but i couldn't wrap my head around doing it in Swift (kind of a beginner when it comes to iOS development).
I am afraid it is not possible to implement this on not jailbroken device.
I found some code for this, but it was outdated. I don't think that you will use it on iOS 3/4 devices.
NEHotspotHelper works only when Settings->Wifi page is active. You can get signal strength there, but I unsure how it will work.
MobileWiFi.framework requires entitlement, which can't be set without jailbreak.
Useful links:
Technical Q&A QA1942
Probably iBeacons or QR (AR) is the only options.
Although many resources say that while using Apple "official" frameworks, you can only get network's SSID that your iPhone is at the moment connected to. Here are workaround:
You can use NEHotspotConfigurationManager class but at first you must to enable the Hotspot Configuration Entitlement (property list key) in Xcode.
You can also use NEHotspotHelper class (although it requires Apple's permission). For this you need to apply for the Network Extension entitlement and then modify your Provisioning Profile plus some additional actions. Look at this SO post for further details.
Here's a code snippet how to use NEHotspotConfigurationManager:
import NetworkExtension
class ViewController: UIViewController {
let SSID = ""
#IBAction func connectAction(_ sender: Any) {
let hotspotConfig = NEHotspotConfiguration(ssid: SSID, passphrase: "", isWEP: false)
NEHotspotConfigurationManager.shared.apply(hotspotConfig) {[unowned self] (error) in
if let error = error {
self.showError(error: error)
} else {
self.showSuccess()
}
}
}
#IBAction func disconnectAction(_ sender: Any) {
NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: SSID)
}
private func showError(error: Error) {
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
let action = UIAlertAction(title: "Darn", style: .default, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
private func showSuccess() {
let alert = UIAlertController(title: "", message: "Connected", preferredStyle: .alert)
let action = UIAlertAction(title: "Cool", style: .default, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
}
Here's a code snippet how to use NEHotspotHelper:
import NetworkExtension
import SystemConfiguration.CaptiveNetwork
func getSSID() -> String {
if #available(iOS 11.0, *) {
let networkInterfaces = NEHotspotHelper.supportedNetworkInterfaces()
let wiFi = NEHotspotNetwork()
let st = "SSID:\(wiFi.SSID), BSSID:\(wiFi.BSSID)"
return st
for hotspotNetwork in NEHotspotHelper.supportedNetworkInterfaces() {
let signalStrength = hotspotNetwork.signalStrength
print(signalStrength)
}
} else {
let interfaces = CNCopySupportedInterfaces()
guard interfaces != nil else {
return ""
}
let if0: UnsafePointer<Void>? = CFArrayGetValueAtIndex(interfaces, 0)
guard if0 != nil else {
return ""
}
let interfaceName: CFStringRef = unsafeBitCast(if0!, CFStringRef.self)
let dictionary = CNCopyCurrentNetworkInfo(interfaceName) as NSDictionary?
guard dictionary != nil else {
return ""
}
return String(dictionary![String(kCNNetworkInfoKeySSID)])
}
}
You can use transportable Differential GPS reference station inside your building and improve accuracy to about 1-3 cm and then rely on mobile phone built-in GPS.

How can I call asynchronously a method only when other async method finishes working in Swift?

I'm implementing socket.io library for swift.
There is a method related to connecting to the server that looks as follows:
func connectToServerWithNickname(nickname: String) {
socket.emit("connectUser", nickname)
}
The socket.emit calls:
private func _emit(data: [AnyObject], ack: Int? = nil) {
dispatch_async(emitQueue) {
guard self.status == .Connected else {
self.handleEvent("error", data: ["Tried emitting when not connected"], isInternalMessage: true)
return
}
let packet = SocketPacket.packetFromEmit(data, id: ack ?? -1, nsp: self.nsp, ack: false)
let str = packet.packetString
DefaultSocketLogger.Logger.log("Emitting: %#", type: self.logType, args: str)
self.engine?.send(str, withData: packet.binary)
}
}
as you can see it's all packed in dispatch_async. I would like to post an NSNotification as soon as this method is done and my app connects to the server, sth like:
NSNotificationCenter.defaultCenter().postNotificationName(setConnectionStatus, object: self)
So my question is - how should I modify my connectToServerWithNickname method so that it calls my nsnotificationcenter when everything goes correct?
After you stated you are using socket.IO
When you make your instance of the socket before you connect you will have
var socket: SocketIOClient = SocketIOClient(socketURL: NSURL(string: "http://192.168.1.XXX:3000")!)
then after this put
socket.on("connect") { (data, ack) -> Void in
// This will be fired once your socket connects.
}
EDIT:
For individual emits, you would keep a pointer to your socket and use
let json = ["things_i_need_to_send" : stuff]
socket.emitWithAck(Place_i_need_to_send_to, json)(timeoutAfter: 3) { data in
//This is called on completion
}
So using your above example.
socket.emitWithAck(connectUser, nickname)(timeoutAfter: 3) { data in
//This is called on completion
}

iOS swift application using GCDAsyncUdpSocket

I'm very new to building iOS apps.
I'm trying to make a class to receive udp multicast messages, but I can't seam to get it working at all...
class Discovery : GCDAsyncUdpSocketDelegate{
var udpSocket:GCDAsyncUdpSocket!;
init() {
udpSocket = GCDAsyncUdpSocket(delegate: self, delegateQueue: dispatch_get_main_queue())
var e:NSErrorPointer = nil;
//Binding to port
if(!udpSocket.bindToPort(2025, error: e)){
println(e);
return;
}
//Joining multicast group
if(!udpSocket.joinMulticastGroup("239.5.6.7", error: e)){
println(e);
return;
}
//Begin recieve
if(!udpSocket.beginReceiving(e)){
println(e);
return;
}
println("UDP socket was opened!")
}
func udpSocket(sock: GCDAsyncUdpSocket!, didReceiveData data: NSData!, fromAddress address: NSData!, withFilterContext filterContext: AnyObject!) {
println("Got data!");
}
}
Can anyone see where I'm making a mistake? I'm getting the UDP socket was opened msg, but not receiving any packages. I know they are being sent as I'm capturing them with wireshark.
The discovery is called from my view controller
class ViewController: UIViewController, CLLocationManagerDelegate {
let peer = Peer(id: NSUUID.new())
let uuid = NSUUID.new()
let discovery:Discovery = Discovery()
let locationManager = CLLocationManager()
let region = CLBeaconRegion(proximityUUID: NSUUID(UUIDString: "8DEEFBB9-F738-4297-8040-96668BB44281"), identifier: "Roximity")
let com = OconCom()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self;
if (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.AuthorizedAlways) {
locationManager.requestAlwaysAuthorization()
}
locationManager.startMonitoringForRegion(region) //Background
locationManager.startRangingBeaconsInRegion(region) //Foreground
}
Any suggestion will be helpful.
Is your receiver on the same network as the broadcaster. Generally multicast has a low TTL and can't make it too far unless the routers your packets traverse are configured to allow it.
I was running into the same issue in my custom class. Simply add #obj public to your delegate functions. Now the delegate will be called properly.

Resources