Swift NWConnection - Receive failed with error "No message available on STREAM" - swift

I'm currently working on an iOS-App and i need to connect to another device. The NWConnection is successfully established using TCP and I can receive one (and only one) message from the remote device and get an error immediately. The connection gets cancelled (caused by the error I guess).
The console log shows:
Hello World.
[connection] nw_flow_add_read_request [C1 <ip and port of remote device> ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, ipv6, dns)] already delivered final read, cannot accept read requests
[connection] nw_read_request_report [C1] Receive failed with error "No message available on STREAM"
error
My code looks like this:
private func setupReceive() {
NWCon!.receive(minimumIncompleteLength: 1, maximumLength: 65536) { (data, _, isComplete, error) in
if let data = data, !data.isEmpty {
let message = String(data: data, encoding: .utf8)
if(message!.contains("Hello World")) {
print("Hello World.")
} else if(message == "Closing connection!") {
//...
}
}
if error != nil { print("error: \(error)") }
else { self.setupReceive() }
}
}
The remote device only sends one package and to receive further packages, the method gets called again.
EDIT: I printed the error message and got
error: Optional(POSIXErrorCode: No message available on STREAM)
which seems to refer to POSIXError ENODATA.

Related

macOS network extension - activate vpn provider failed on startVPNTunnel

I'm trying to start the vpn provider from a container application
After I successfully activated the extension, and started the process by sending xpc request to the network extension, I've tried to load the provider and start getting notifications about handleNewFlow
I use the following callback to signal process has started, in order to start the configuration phase.
extension NetworkExtensionManager: OSSystemExtensionRequestDelegate {
...
func request(_ request: OSSystemExtensionRequest,
didFinishWithResult result: OSSystemExtensionRequest.Result) {
...
After loadAllPreferences, I've set the manager from which I'd like to load the provider:
NEAppProxyProviderManager.loadAllFromPreferences { [self] (managers, error) in
assert(Thread.isMainThread)
if let error = error {
print(error.localizedDescription)
} else {
let manager = managers?.first ?? NEAppProxyProviderManager()
let proto = (manager.protocolConfiguration as? NETunnelProviderProtocol) ?? NETunnelProviderProtocol()
proto.serverAddress = "myHost"
proto.providerBundleIdentifier = "myExtensionBundle"
proto.providerConfiguration = ["key": "val"]
manager.localizedDescription = "myApp"
manager.protocolConfiguration = proto
manager.isEnabled = true
Then I extract the connection out of the manager and use it to start the tunnel provider (hopefully this operation will create a new item under System Preferences --> Network)
let session = manager.connection as! NETunnelProviderSession
do {
os_log(" manager.connection status: \(session.status.rawValue)")
try session.startVPNTunnel(options: nil)
}
catch {
os_log("failed to initProviderManager app proxy provider, \(error.localizedDescription)")
print(error)
}
However, I've got the following error message from the exception thrown out of this line session.startVPNTunnel :
failed to initProviderManager app proxy provider, The operation couldn’t be completed. (NEVPNErrorDomain error 1.)
Looking for the error name I found this NEVPNErrorConfigurationInvalid
Perhaps you can help me figure out where I go wrong in my attempt to start the tunnel provider ?
Thanks !

Not retrieving .notConnectedToInternet URLSession

So I'm trying to get URLSession URLError.code .notConnectedToInternet but all I'm getting is Code(rawValue: -1020) when using URLSession when disconnecting from the internet.
URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error {
print("error.code \((error as! URLError).code)")
//prints - error.code Code(rawValue: -1020)
if (error as! URLError).code == .notConnectedToInternet {
print("no internet")
//doesn't print
return
}
}
error code -1020 means dataNotAllowed. It means the connection failed because data use isn’t currently allowed on the device.
What you are trying to catch notConnectedToInternet error code -1009 means connection failed because the device isn’t connected to the internet.
So notConnectedToInternet is not same as dataNotAllowed. In this case check your device settings.

Getting the error code and description from a session.dataTask call

I am calling some APIs from my app. I'd like to get and process the error code. There seems to be a lot of information on how to retrieve and process the response codes, but very little on the error code. I'd like to access the error code in case, say the user has no internet connection so the call to the API fails. I am using this code:
session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) in
if let error = error {
print("Tide Station API call failed with error \(error)")
return
}
if let response = response as? HTTPURLResponse {
print("Tide Station API call response is \(response.statusCode)")
}
if let data = data
{
do {
let result = try JSONDecoder().decode(TideStations.self, from: data)
self.tideStations = result.stations
print("\(result.stations.count) Tide Stations successfully loaded")
self.showTideStationsOnMap()
} catch {
print("Error while stations parsing: \(error)")
}
}
}).resume()
Which generates the following output in the debug area when my device is is airplane mode, i.e. I am forcing one sure way of the API call failing: (note I have bolded the area from my error handling print statement)
2020-02-26 17:57:58.235368+0000 APITest[5464:2590011] Task <006E7876-214F-41E3-8BDE-26960AF1F554>.<1> finished with error [-1009] Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo={NSUnderlyingError=0x283a918f0 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://api.sunrise-sunset.org/json?lat=57.5081&lng=-1.7841&date=2020-06-21&formatted=0, NSErrorFailingURLKey=https://api.sunrise-sunset.org/json?lat=57.5081&lng=-1.7841&date=2020-06-21&formatted=0, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=50, NSLocalizedDescription=The Internet connection appears to be offline.}
Astronomical Times API call failed with error Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo={NSUnderlyingError=0x283a918f0 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://api.sunrise-sunset.org/json?lat=57.5081&lng=-1.7841&date=2020-06-21&formatted=0, NSErrorFailingURLKey=https://api.sunrise-sunset.org/json?lat=57.5081&lng=-1.7841&date=2020-06-21&formatted=0, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=50, NSLocalizedDescription=The Internet connection appears to be offline.}
In this example, I want to extract from this the fact that the internet connection appears down and advise the user. I would have thought that there would be similar support for error code extraction as there is for result code extraction. Would it be more normal to just say an error occurred (easy to detect) and handle elsewhere (e.g. detect no internet before calling the API)? If so, what shout I be guarding for?
The Error class in swift does not contain the error code and the error domain in it. However, if you print the error you can definitely see the error code and domain in the logs.
You can access those by simply casting the Swift's Error to the old Objective-C NSError. This classes are toll-free bridgeable, so there is no need to check if the cast fails or forcefully perform the cast with as!. Simply do:
let nsError = error as NSError
print("Error code is: \(nsError.code)")

Alamofire: Network error vs invalid status code?

Using Alamofire 4/Swift 3 how can you differentiate between a request that fails due to:
Network connectivity (host down, can't reach host) vs
Invalid server HTTP response code (ie: 499) which causes the Alamofire request to fail due to calling validate()?
Code:
sessionManager.request(url, method: .post, parameters:dict, encoding: JSONEncoding.default)
.validate() //Validate status code
.responseData { response in
if response.result.isFailure {
//??NETWORK ERROR OR INVALID SERVER RESPONSE??
}
}
We want to handle each case differently. In the latter case we want to interrogate the response. (In the former we don't as there is no response).
Here's our current working solution:
sessionManager.request(url, method: .post, parameters:dict, encoding: JSONEncoding.default)
.validate() //Validate status code
.responseData { response in
if response.result.isFailure {
if let error = response.result.error as? AFError, error.responseCode == 499 {
//INVALID SESSION RESPONSE
} else {
//NETWORK FAILURE
}
}
}
If result.error it is of type AFError you can use responseCode. From the AFError source comments:
/// Returns whether the `AFError` is a response validation error. When `true`, the `acceptableContentTypes`,
/// `responseContentType`, and `responseCode` properties will contain the associated values.
public var isResponseValidationError: Bool {
if case .responseValidationFailed = self { return true }
return false
}
Maybe there is a better way (?) but that seems to work...
Alamofire can tell you about the status of the request,
this code works just fine for me:
if let error = response.result.error as? NSError {
print(error)//print the error description
if (error.code == -1009){
print(error.code) // this will print -1009,somehow it means , there is no internet connection
self.errorCode = error.code
}
//check for other error.code
}else{
//there is no problem
}
the error.code will tell you what is the problem
Automatic validation should consider status code within 200...299 (success codes) range so when you get an invalid server HTTP response code 5xx (499 means Client Closed Request) you are sure it's not depend by validation.
About the statusCode, my advice is to follow the correct new rules to get it. If you have some problem to retrieve it look this SO answer.
Speaking about network reachability you could write:
let manager = NetworkReachabilityManager(host: "www.apple.com")
manager?.listener = { status in
print("Network Status Changed: \(status)")
}
manager?.startListening()
There are some important things to remember when using network reachability to determine what to do next.
Do NOT use Reachability to determine if a network request should be
sent. You should ALWAYS send it.
When Reachability is restored, use the event to retry failed network
requests. Even though the network requests may still fail, this is a
good moment to retry them.
The network reachability status can be useful for determining why a
network request may have failed. If a network request fails, it is
more useful to tell the user that the network request failed due to
being offline rather than a more technical error, such as "request
timed out."
You can find these details also in the official Alamofire 4 GitHUB page

golang unix socket error. dial: resource temporarily unavailable

So I'm trying to use unix sockets with fluentd for a logging task and find that randomly, once in a while the error
dial: {socket_name} resource temporarily unavailable
Any ideas as to why this might be occurring?
I tried adding "retry" logic, to reduce the error, but it still occurs at times.
Also, for fluntd we are using the default config for unix sockets communication
func connect() {
var connection net.Conn
var err error
for i := 0; i < retry_count; i++ {
connection, err = net.Dial("unix", path_to_socket)
if err == nil {
break
}
time.Sleep(time.Duration(math.Exp2(float64(retry_count))) * time.Millisecond)
}
if err != nil {
fmt.Println(err)
} else {
connection.Write(data_to_send_socket)
}
defer connection.Close()
}
Go creates its sockets in non-blocking mode, which means that certain system calls that would usually block instead. In most cases it transparently handles the EAGAIN error (what is indicated by the "resource temporarily unavailable" message) by waiting until the socket is ready to read/write. It doesn't seem to have this logic for the connect call in Dial though.
It is possible for connect to return EAGAIN when connecting to a UNIX domain socket if its listen queue has filled up. This will happen if clients are connecting to it faster than it is accepting them. Go should probably wait on the socket until it becomes connectable in this case and retry similar to what it does for Read/Write, but it doesn't seem to have that logic.
So your best bet would be to handle the error by waiting and retrying the Dial call. That, or work out why your server isn't accepting connections in a timely manner.
For the exponential backoff you can use this library: github.com/cenkalti/backoff. I think the way you have it now it always sleeps for the same amount of time.
For the network error you need to check if it's a temporary error or not. If it is then retry:
type TemporaryError interface {
Temporary() bool
}
func dial() (conn net.Conn, err error) {
backoff.Retry(func() error {
conn, err = net.Dial("unix", "/tmp/ex.socket")
if err != nil {
// if this is a temporary error, then retry
if terr, ok := err.(TemporaryError); ok && terr.Temporary() {
return err
}
}
// if we were successful, or there was a non-temporary error, fail
return nil
}, backoff.NewExponentialBackOff())
return
}