aiocoap icon indicating copy to clipboard operation
aiocoap copied to clipboard

macOS not fully supported

Open mattdodge opened this issue 8 years ago • 24 comments

I doubt this is an issue with this particular library but I am unable to get the server/client up and running because of it so I thought I'd log it here. Basically, when running ./server.py I see the following stack trace:

ERROR:asyncio:Task exception was never retrieved
future: <Task finished coro=<Context.create_server_context() done, defined at /Users/matt/code/coap/aiocoap/aiocoap/protocol.py:530> exception=AttributeError("module 'socket' has no attribute 'IPV6_RECVPKTINFO'",)>
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/tasks.py", line 239, in _step
    result = coro.send(None)
  File "/Users/matt/code/coap/aiocoap/aiocoap/protocol.py", line 546, in create_server_context
    self.transport_endpoints.append((yield from TransportEndpointUDP6.create_server_transport_endpoint(new_message_callback=self._dispatch_message, new_error_callback=self._dispatch_error, log=self.log, loop=loop, dump_to=dump_to, bind=bind)))
  File "/Users/matt/code/coap/aiocoap/aiocoap/transports/udp6.py", line 126, in create_server_transport_endpoint
    return (yield from cls._create_transport_endpoint(new_message_callback, new_error_callback, log, loop, dump_to, bind))
  File "/Users/matt/code/coap/aiocoap/aiocoap/transports/udp6.py", line 100, in _create_transport_endpoint
    sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_RECVPKTINFO, 1)
AttributeError: module 'socket' has no attribute 'IPV6_RECVPKTINFO'

Sure enough, I'm unable to see that socket option on any of my Python 3 installs (3.4, 3.5, 3.6). On a different Ubuntu machine the option is there. I just updated the Mac today and I'm wondering if possibly they got rid of some setting from the C socket implementation.

Currently running macOS 10.12.4. Any ideas of how to get that option back or a workaround?

mattdodge avatar Apr 11 '17 02:04 mattdodge

On Mon, Apr 10, 2017 at 07:10:19PM -0700, Matt Dodge wrote:

Currently running macOS 10.12.4. Any ideas of how to get that option back or a workaround?

Has this worked with a previous macOS version?

chrysn avatar Apr 11 '17 05:04 chrysn

Confirmed on two coworker's machines, one running 10.12.3 and the other on 10.12.4, that it fails on both. I am able to get it to work on Linux machines though. Has anyone tested macOS support?

Looks like it's in the Apple source though unless I'm looking at the wrong spot? https://opensource.apple.com/source/xnu/xnu-1699.24.8/bsd/netinet6/in6.h

mattdodge avatar Apr 11 '17 18:04 mattdodge

Also just tried on macOS 10.11.6, same result, can't find IPV6_RECVPKTINFO.

mattdodge avatar Apr 11 '17 19:04 mattdodge

On Tue, Apr 11, 2017 at 12:05:21PM -0700, Matt Dodge wrote:

Also just tried on macOS 10.11.6, same result, can't find IPV6_RECVPKTINFO.

Thanks, sounds like I need to play IPV6_RECVPKTINFO through aiocoap/util/socknumbers.py. Could you, for a quick check, replace the constant with a numeric value of 61 (from the files you mentioned) and see whether the test suite runs through?

chrysn avatar Apr 11 '17 19:04 chrysn

Replaced socket.IPV6_RECVPKTINFO with 61 in aiocoap/transports/udp6.py. Now server won't start and tests fail with an invalid argument error to setsockopt with IPV6_RECVERR. Looks like macOS doesn't like setting option 25, which is what socknumbers.IPV6_RECVERR is, to 1.

>       sock.setsockopt(socket.IPPROTO_IPV6, socknumbers.IPV6_RECVERR, 1)
E       OSError: [Errno 22] Invalid argument
ERROR:asyncio:Task exception was never retrieved
future: <Task finished coro=<Context.create_server_context() done, defined at /Users/matt/code/coap/aiocoap/aiocoap/protocol.py:530> exception=OSError(22, 'Invalid argument')>
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/tasks.py", line 239, in _step
    result = coro.send(None)
  File "/Users/matt/code/coap/aiocoap/aiocoap/protocol.py", line 546, in create_server_context
    self.transport_endpoints.append((yield from TransportEndpointUDP6.create_server_transport_endpoint(new_message_callback=self._dispatch_message, new_error_callback=self._dispatch_error, log=self.log, loop=loop, dump_to=dump_to, bind=bind)))
  File "/Users/matt/code/coap/aiocoap/aiocoap/transports/udp6.py", line 129, in create_server_transport_endpoint
    return (yield from cls._create_transport_endpoint(new_message_callback, new_error_callback, log, loop, dump_to, bind))
  File "/Users/matt/code/coap/aiocoap/aiocoap/transports/udp6.py", line 104, in _create_transport_endpoint
    sock.setsockopt(socket.IPPROTO_IPV6, socknumbers.IPV6_RECVERR, 1)
OSError: [Errno 22] Invalid argument

mattdodge avatar Apr 11 '17 19:04 mattdodge

If you just comment out the RECVERR line, do things get better? (And possibly also other setsockopt lines).

That loses reception of ICMP error messages, so requests to unavailable hosts or closed ports take longer to timeout, but that'd be fine for initial (it seems you are the first to try it) support on OSX.

chrysn avatar Apr 11 '17 19:04 chrysn

Not really able to make it work reliably unfortunately. I can get the server.py to start successfully by commenting out the setsockopts for IPPROTO (v6 and non-v6). Upon sending messages though it starts spitting out additional errors, this time around socket.MSG_ERRQUEUE. Replacing that value with 0x2000 (got it from linux machine) doesn't seem to work either. I get repeated stack traces like this:

INFO:coap-server:Received unexpected ancillary data to recvmsg errqueue: level 41, type 46, data b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00'
--- Logging error ---
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/logging/__init__.py", line 985, in emit
    msg = self.format(record)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/logging/__init__.py", line 831, in format
    return fmt.format(record)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/logging/__init__.py", line 568, in format
    record.message = record.getMessage()
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/logging/__init__.py", line 331, in getMessage
    msg = msg % self.args
TypeError: %d format: a number is required, not NoneType
Call stack:
  File "./server.py", line 133, in <module>
    main()
  File "./server.py", line 130, in main
    asyncio.get_event_loop().run_forever()
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 345, in run_forever
    self._run_once()
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 1333, in _run_once
    handle._run()
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/events.py", line 125, in _run
    self._callback(*self._args)
  File "/Users/matt/code/coap/aiocoap/aiocoap/util/asyncio.py", line 46, in _read_ready
    self._protocol.datagram_errqueue_received(data, ancdata, flags, addr)
  File "/Users/matt/code/coap/aiocoap/aiocoap/transports/udp6.py", line 220, in datagram_errqueue_received
    self.new_error_callback(errno, remote)
  File "/Users/matt/code/coap/aiocoap/aiocoap/protocol.py", line 198, in _dispatch_error
    self.log.debug("Incoming error %d from %r", errno, remote)
Message: 'Incoming error %d from %r'
Arguments: (None, <UDP6EndpointAddress [::1]:49725>)

For reference, here's the diff of what I changed

diff --git a/aiocoap/transports/udp6.py b/aiocoap/transports/udp6.py
index 8f4ea10..10aa045 100644
--- a/aiocoap/transports/udp6.py
+++ b/aiocoap/transports/udp6.py
@@ -97,11 +97,12 @@ class TransportEndpointUDP6(RecvmsgDatagramProtocol, interfaces.TransportEndpoin
         sock = transport._sock

         sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
-        sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_RECVPKTINFO, 1)
-        sock.setsockopt(socket.IPPROTO_IPV6, socknumbers.IPV6_RECVERR, 1)
+        # sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_RECVPKTINFO, 1)
+        sock.setsockopt(socket.IPPROTO_IPV6, 61, 1)
+        # sock.setsockopt(socket.IPPROTO_IPV6, socknumbers.IPV6_RECVERR, 1)
         # i'm curious why this is required; didn't IPV6_V6ONLY=0 already make
         # it clear that i don't care about the ip version as long as everything looks the same?
-        sock.setsockopt(socket.IPPROTO_IP, socknumbers.IP_RECVERR, 1)
+        # sock.setsockopt(socket.IPPROTO_IP, socknumbers.IP_RECVERR, 1)

         if bind is not None:
             # FIXME: SO_REUSEPORT should be safer when available (no port hijacking), and the test suite should work with it just as well (even without). why doesn't it?
@@ -198,7 +199,7 @@ class TransportEndpointUDP6(RecvmsgDatagramProtocol, interfaces.TransportEndpoin
         self.new_message_callback(message)

     def datagram_errqueue_received(self, data, ancdata, flags, address):
-        assert flags == socket.MSG_ERRQUEUE
+        assert flags == 0x2000
         pktinfo = None
         errno = None
         for cmsg_level, cmsg_type, cmsg_data in ancdata:
diff --git a/aiocoap/util/asyncio.py b/aiocoap/util/asyncio.py
index 3da0ce9..e85fef4 100644
--- a/aiocoap/util/asyncio.py
+++ b/aiocoap/util/asyncio.py
@@ -35,7 +35,7 @@ class RecvmsgSelectorDatagramTransport(_SelectorDatagramTransport):

     def _read_ready(self):
         try:
-            data, ancdata, flags, addr = self._sock.recvmsg(self.max_size, 1024, socket.MSG_ERRQUEUE)
+            data, ancdata, flags, addr = self._sock.recvmsg(self.max_size, 1024, 0x2000)
         except (BlockingIOError, InterruptedError):
             pass
         except OSError as exc:

mattdodge avatar Apr 11 '17 20:04 mattdodge

Is there any update on this issue? I get the same issue, running on OS X v10.10.5

Singhal-Vaibhav avatar Jul 14 '17 07:07 Singhal-Vaibhav

The simple6 transport should give at least a working client implementation.

Could you try running AIOCOAP_CLIENT_TRANSPORT=simple6 ./aiocoap-client coap://coap.me/ or similar with current master? Server is not implemented there, but I think that clients using the simple6 transport should work now on OSX too, but can't test it myself.

chrysn avatar Sep 12 '17 08:09 chrysn

For me running the client (commit 43255ab9e488a04039a4aa4499ee59f1500b2318) works without the AIOCOAP_CLIENT_TRANSPORT option on macOS High Sierra 10.13.3

:: /tmp/aiocoap » ./aiocoap-client coap://coap.me/
</test>;rt="test";ct=0,</validate>;rt="validate";ct=0,</hello>;rt="Type1";ct=0;if="If1",</bl%C3%A5b%C3%A6rsyltet%C3%B8y>;rt="blåbærsyltetøy";ct=0,</sink>;rt="sink";ct=0,</separate>;rt="separate";ct=0,</large>;rt="Type1 Type2";ct=0;sz=1700;if="If2",</secret>;rt="secret";ct=0,</broken>;rt="Type2 Type1";ct=0;if="If2 If1",</weird33>;rt="weird33";ct=0,</weird44>;rt="weird44";ct=0,</weird55>;rt="weird55";ct=0,</weird333>;rt="weird333";ct=0,</weird3333>;rt="weird3333";ct=0,</weird33333>;rt="weird33333";ct=0,</123412341234123412341234>;rt="123412341234123412341234";ct=0,</location-query>;rt="location-query";ct=0,</create1>;rt="create1";ct=0,</large-update>;rt="large-update";ct=0,</large-create>;rt="large-create";ct=0,</query>;rt="query";ct=0,</seg1>;rt="seg1";ct=40,</path>;rt="path";ct=40,</location1>;rt="location1";ct=40,</multi-format>;rt="multi-format";ct=0,</3>;rt="3";ct=50,</4>;rt="4";ct=50,</5>;rt="5";ct=50
(No newline at end of message)

Interestingly enough running the server from the Usage Examples results in the above error regarding the apparently missing socket option. Running the server.py from the repository (which - skimming the code - looks very similar) does give a different error (as described in #101):

:: /tmp/aiocoap » ./server.py                     
ERROR:asyncio:Task exception was never retrieved
future: <Task finished coro=<Context.create_server_context() done, defined at /private/tmp/aiocoap/aiocoap/protocol.py:545> exception=ValueError('The transport can not be bound to any-address.',)>
Traceback (most recent call last):
  File "/private/tmp/aiocoap/aiocoap/protocol.py", line 576, in create_server_context
    self.transport_endpoints.append(await TransportEndpointSimpleServer.create_server(bind, self, log=self.log, loop=loop))
  File "/private/tmp/aiocoap/aiocoap/transports/simplesocketserver.py", line 113, in create_server
    self._pool = await _DatagramServerSocketSimple.create(server_address, log, self._loop, self._received_datagram, self._received_exception)
  File "/private/tmp/aiocoap/aiocoap/transports/simplesocketserver.py", line 59, in create
    raise ValueError("The transport can not be bound to any-address.")
ValueError: The transport can not be bound to any-address.

Changing the file so that it explicitly binds to an address results in the error of this issue though:

aiocoap.Context.create_server_context(root, bind=('10.0.0.68', 5683))

TilBlechschmidt avatar Mar 04 '18 11:03 TilBlechschmidt

I've forwarded this to the Python issue tracker as issue35569.

chrysn avatar Dec 23 '18 12:12 chrysn

@chrysn - Is this now resolved?

Does it require an updated python version? I am currently running on Python 3.7.4

DanAbbruzzese avatar Mar 24 '20 12:03 DanAbbruzzese

Does it require an updated python version? I am currently running on Python 3.7.4

No, but the default for macOS has not been switched yet, as my limited experience with that platform means I can't test it very well.

With the current master branch, you can run your application prefixed by AIOCOAP_SERVER_TRANSPORT=udp6 AIOCOAP_CLIENT_TRANSPORT=udp6. For servers, that should work just fine. For clients, that'd allow multicast (with the very limited support there is right now), but will wait for timeouts (rather than acting immediately on "port closed" ICMP messages) when a server is absent.

If you could give this a try with your application(s), and can confirm that behavior, I'd switch the AIOCOAP_SERVER_TRANSPORT default for macOS over to udp6.

For whether this is fully resolved, I'm waiting a bit for feedback from the lightweight implementers' group; if my suspicion that there is no standardized API for all that CoAP can do cross-platform, I'll close this (and hope to do more when macOS grows the API that makes thigns run so smoothly on Linux).

chrysn avatar Mar 24 '20 12:03 chrysn

Updated to Python 3.8.2 and still getting the error message.

Setting the env vars as @chrysn suggested didn't help at all:

$ python3 --version
Python 3.8.2

$ AIOCOAP_SERVER_TRANSPORT=udp6 AIOCOAP_CLIENT_TRANSPORT=udp6 python3 -m live
ERROR:asyncio:Task exception was never retrieved
future: <Task finished name='Task-1' coro=<Context.create_server_context() done, defined at /usr/local/lib/python3.8/site-packages/aiocoap/protocol.py:515> exception=AttributeError("module 'socket' has no attribute 'IPV6_RECVPKTINFO'")>
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/aiocoap/protocol.py", line 531, in create_server_context
    self.transport_endpoints.append((yield from TransportEndpointUDP6.create_server_transport_endpoint(new_message_callback=self._dispatch_message, new_error_callback=self._dispatch_error, log=self.log, loop=loop, dump_to=dump_to, bind=bind)))
  File "/usr/local/lib/python3.8/site-packages/aiocoap/transports/udp6.py", line 113, in create_server_transport_endpoint
    return (yield from cls._create_transport_endpoint(new_message_callback, new_error_callback, log, loop, dump_to, bind))
  File "/usr/local/lib/python3.8/site-packages/aiocoap/transports/udp6.py", line 87, in _create_transport_endpoint
    sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_RECVPKTINFO, 1)
AttributeError: module 'socket' has no attribute 'IPV6_RECVPKTINFO'

Is there any way to make this work or do I have to use different library/language to work with CoAP? :/

I got the same error run aiocoap on python(3.8.1)

yance-dev avatar May 09 '20 07:05 yance-dev

(Note to self: run tests using https://github.com/kholia/OSX-KVM/blob/master/boot-macOS-NG.sh ?)

chrysn avatar May 18 '20 18:05 chrysn

Has this issue still not been resolved? I'm experiencing it on both mac and windows. No luck at all. I suppose I'll have to use a different CoAP library

bboynton97 avatar Aug 09 '20 14:08 bboynton97

I've received conflicting reports on that, so it'd be great to see which concrete errors you see (along with the output of python3 -m aiocoap.cli.defaults or the used aiocoap version if the former does not work)

chrysn avatar Aug 09 '20 15:08 chrysn

@bboynton97 What version are you on? Updating to the latest version helped me with the problem.

marianhlavac avatar Aug 10 '20 05:08 marianhlavac

Greetings, I am having this error too. I am on Windows 10 using the latest Visual Studio Code (if that matters at all). Python version is 3.8.4.

iskep11 avatar Oct 14 '20 08:10 iskep11

Has any fallback been found for macOS versions without IPV6_RECVPKTINFO?

barracuda156 avatar May 28 '23 10:05 barracuda156

I'll have to explain summarize the status of this more elaborately at some point, but briefly from on the road:

MacOS should work fine, it will just not use the optimal backend (udp6) but the fallbacks (simple6 & co), which are suboptimal when working thousands of peers simultaneously.

chrysn avatar May 28 '23 11:05 chrysn

I'll have to explain summarize the status of this more elaborately at some point, but briefly from on the road: MacOS should work fine, it will just not use the optimal backend (udp6) but the fallbacks (simple6 & co), which are suboptimal when working thousands of peers simultaneously.

@chrysn Are those portable? I bumped into this problem with ngtcp2: https://github.com/ngtcp2/ngtcp2/issues/804

barracuda156 avatar May 28 '23 11:05 barracuda156

The simple6 and simplesocketserver are portable, but they do without even PKTINFO -- they simply spawn individual sockets. (Actually, aiocoap by now has the infrastructure to use PKTINFO even on OSX by virtue of hardcoded numbers to fill in what has been missing up to 3.9, but it's not only PKTINFO that's missing -- see longer upcoming explanation).

chrysn avatar May 29 '23 15:05 chrysn

The current status of OSX and Windows support, as far as I can tell it without access to either system, is as follows:

  • All of the following only applies to the UDP transport (coaps and coaps); CoAP over TCP and WebSockets works the same everywhere.
  • There are two implementations of CoAP-over-UDP: udp6 and the simplesocket-simplesocketserver pair.
  • udp6 is the high quality implementation that should not have any limitations in terms of how many peers the program can interact with, but it has two caveats:
    • It requires PKTINFO (eg. socket.IPV6_RECVPKTINFO). This is (by now) available on OSX, but not on Windows (because it even lacks recvmsg). Without PKTINFO, the udp6 transport would violate the CoAP protocol without any local means of debugging, thus it fails hard when attempted to use it. (It would send acknowledgements to messages from a different IP address, which is an error).
    • It works best with RECVERR (socket.IPV6_RECVERR / socket.MSG_ERRQUEUE), which is a feature of Linux. Without RECVERR, ICMP errors are not propagated to the socket, so instead of getting an error about an unreachable peer immediately, aiocoap would be retransmitting until timeout. This is bad practice.
  • Consequently, on all platforms but Linux, the udp6 transport is disabled by default, and instead, the simple6/simplesocketserver pair is used. That transport has the downside of opening a socket per connection (as a client), still not getting ICMP errors (as a server), requiring a specific IP address to be bound to (as a server) and not sending requests from the same port as the server is running on (when using both roles) -- but it's portable.

I'd encourage users (especially on the server side) on non-Linux UNIX systems, to try out setting AIOCOAP_SERVER_TRANSPORT=oscore:tinydtls:tcpserver:tcpclient:tlsserver:tlsclient:ws:udp6 (or fewer of these depending on which you actually use, but with udp6 instead of the simple* ones) and report experiences here.

Apart from that, I'd encourage developers familiar with Windows to contribute to https://github.com/python/cpython/issues/80398. To developers familiar with the network stacks of non-Linux systems: Do these platforms provide any equivalent of the RECVERR mechanism of Linux?

chrysn avatar May 29 '23 15:05 chrysn