aiocoap
aiocoap copied to clipboard
macOS not fully supported
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?
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?
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
Also just tried on macOS 10.11.6, same result, can't find IPV6_RECVPKTINFO.
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?
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
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.
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:
Is there any update on this issue? I get the same issue, running on OS X v10.10.5
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.
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))
I've forwarded this to the Python issue tracker as issue35569.
@chrysn - Is this now resolved?
Does it require an updated python version? I am currently running on Python 3.7.4
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).
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)
(Note to self: run tests using https://github.com/kholia/OSX-KVM/blob/master/boot-macOS-NG.sh ?)
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
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)
@bboynton97 What version are you on? Updating to the latest version helped me with the problem.
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.
Has any fallback been found for macOS versions without IPV6_RECVPKTINFO?
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.
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
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).
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?