python-trio / python-trio/trio
raw sockets don't accept output of getaddrinfo (at least under linux)
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 7.3k
- Forks
- 431
- Avg merge
- 2d 17h
- Merged PRs (30d)
- 6
Description
I've just been playing with creating an async ICMP ping utility and noticed most socket methods seem broken with type=SOCK_RAW sockets raising:
Traceback (most recent call last):
File "test_raw_socket.py", line 9, in <module>
trio.run(main)
File "/home/smason/work/trio/trio/_core/_run.py", line 1783, in run
raise runner.main_task_outcome.error
File "test_raw_socket.py", line 7, in main
await sock.sendto(b'x' * 56, ('127.0.0.1', 0))
File "/home/smason/work/trio/trio/_socket.py", line 743, in sendto
args[-1] = await self._resolve_remote_address(args[-1])
File "/home/smason/work/trio/trio/_socket.py", line 554, in _resolve_remote_address
return await self._resolve_address(address, 0)
File "/home/smason/work/trio/trio/_socket.py", line 529, in _resolve_address
host, port, self._sock.family, self.type, self._sock.proto, flags
File "/home/smason/work/trio/trio/_socket.py", line 159, in getaddrinfo
host, port, family, type, proto, flags | _NUMERIC_ONLY
File "/usr/lib64/python3.7/socket.py", line 748, in getaddrinfo
for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
socket.gaierror: [Errno -8] Servname not supported for ai_socktype
when run with the following code:
import trio
from trio import socket
async def main():
with socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) as sock:
await sock.sendto(b'x' * 56, ('127.0.0.1', 0))
trio.run(main)
note that this needs to be run with super-user privileges (RAW sockets are generally sensitive).
this is obviously an invalid ICMP packet, but it doesn't get far enough for this to actually matter.
the above is the code I wanted to write, but it also raises a similar exception for sock.connect(('127.0.0.1', 0)) due to both using socket._resolve_address. the address comes is this form from getaddrinfo, e.g:
socket.getaddrinfo('localhost', '', family=socket.AF_INET, proto=socket.IPPROTO_ICMP)
gives me [(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_RAW: 3>, 1, '', ('127.0.0.1', 0))] and is accepted by the native/non-async socket.sendto, or can be worked around with:
await sock._nonblocking_helper(
socket._stdlib_socket.socket.sendto,
(b'x' * 56, ('127.0.0.1', 0)), {},
trio.hazmat.wait_writable
)
note that using await sock.sendto(b'x' * 56, ('127.0.0.1', '')) works, i.e. an empty string for port.
it seems reasonable for _resolve_address to special case SOCK_RAW and always pass '' for the getaddrinfo service, maybe raising ValueError if it's truthy?
I can submit a patch doing this, but am not sure if my understanding of raw sockets is off/there are other preferences. also not sure what to do about tests, OS level privilege checks make this awkward.
some version numbers for reference: am using trio's current master branch, under Python 3.7.3 and Linux 5.1.15
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in trio/_socket.py at _resolve_address and the sendto/connect paths, then run the test_raw_socket.py reproducer under Linux with the required privileges. Check how raw-socket addresses from getaddrinfo are handled, including service 0 and an empty service; done means async sendto and connect no longer raise the reported getaddrinfo error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- networking
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100