aiohttp
                                
                                 aiohttp copied to clipboard
                                
                                    aiohttp copied to clipboard
                            
                            
                            
                        support setting client network interface by name
Is your feature request related to a problem?
While setting the target interface's ip should work in most cases, it's possible that multiple devices have the same ip.
Also i'm having trouble using a wireguard vpn when i set Table=off. Table=off disables setting up the routes for the vpn and it justs shows up as a regular device. The connection is fine as pycurl can access it when i set the device but aiohttp fails. probably related but not directly.
Describe the solution you'd like
Users should be able to set the inet device directly.
Describe alternatives you've considered
UnixConnector seems like a good starting place.
loop.create_connection with (socket.SOL_SOCKET, socket.SO_BINDTODEVICE, iface) inspired from here also seems like a good idea
Related component
Client
Additional context
No response
Code of Conduct
- [X] I agree to follow the aio-libs Code of Conduct
Interesting idea, thanks!
Related component
Client
What you're describing sounds like a server-side thing, no?
no?
i'm trying to do request with aiohttp.ClientSession. by definition that's a client side thing, right?
Related component
Client
What you're describing sounds like a server-side thing, no?
Not exactly. You can have multiple interfaces connected to a linux machine all with the same subnet, and DHCP ranges. Adding the abillity to choose exactly what interface to be used for the communication adds a lot of flexbility.
I have created a workaround, but i believe this should be implemented natively. Please note that socket.SO_BINDTODEVICE is only a valid attribute if the OS supports it. Futhermore chosing the source ip and a interface should not be possible if this was to be implemented.
I have not investigated how the workaround works with proxies, but should not be a problem.
async def _wrap_create_connection(
        self,
        *args: Any,
        req: "ClientRequest",
        timeout: "ClientTimeout",
        client_error: Type[Exception] = ClientConnectorError,
        **kwargs: Any,
) -> Tuple[asyncio.Transport, ResponseHandler]:
    try:
        async with aiohttp.helpers.ceil_timeout(timeout.sock_connect):
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, 'en0'.encode())
            # nonblocking as we are async
            s.setblocking(False)
            # socket must be connected otherwise we are thrown an exception
            s.connect_ex((args[-2], args[-1]))
            # pass the socket to asyncio
            kwargs["sock"] = s
            # Remove host and ip from arguments
            args = args[:-2]
            return await self._loop.create_connection(*args, **kwargs)  # type: ignore[return-value]  # noqa
    except cert_errors as exc:
        raise ClientConnectorCertificateError(req.connection_key, exc) from exc
    except ssl_errors as exc:
        raise ClientConnectorSSLError(req.connection_key, exc) from exc
    except OSError as exc:
        if exc.errno is None and isinstance(exc, asyncio.TimeoutError):
            raise
        raise client_error(req.connection_key, exc) from exc
aiohttp.TCPConnector._wrap_create_connection = _wrap_create_connection
I will send a pull request a bit later.
Any chance of getting this implemented? Bug bounty?
@foxx send a PR and we'll consider it.
No can do, but here's a working solution for anyone who doesn't want to have to spend 9000 hours writing a PR:
import httpcore
import socket
class AsyncHttpConnectionPool(httpcore.AsyncConnectionPool):
    """."""
    def __init__(self, *args, **kwargs):
        """."""
        interface_name = kwargs.pop('interface_name', None)
        if interface_name:
            kwargs['socket_options'] = ((socket.SOL_SOCKET, 25, str(interface_name + '\0').encode('ascii')),)
        super().__init__(*args, **kwargs)
    async def get(self, *args, **kwargs):
        """."""
        return await self.request('GET', *args, **kwargs)
    async def post(self, *args, **kwargs):
        """."""
        return await self.request('POST', *args, **kwargs)