Michal Charemza
Michal Charemza

Reputation: 27012

Send UDP to local server, but not via loopback interface

I'm ultimately trying to test a UDP client, and want to make sure that it works when sending data not through the loopback interface, to avoid any subtle issues this introduces, such as differences in checksum validation (Bad UDP checksum has no effect: why?).

However, even when sending data to the result of socket.gethostbyname(socket.gethostname()), which is not 127.0.0.1, then according to Wireshark, the data seems to go via the loopback interface.

The below program sends and receives b'somedata' successfully, and has the below capture from Wireshark.

import asyncio
import socket

async def server():
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
        sock.setblocking(False)
        sock.bind(('', 4567))
        data = await loop.sock_recv(sock, 512)
        print('Received', data)

async def main():
    local_ip = socket.gethostbyname(socket.gethostname())
    print('Local IP', local_ip)  # Outputs 192.168.0.34

    asyncio.ensure_future(server())
    await asyncio.sleep(0)

    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
        sock.setblocking(False)
        sock.connect((local_ip, 4567))
        await loop.sock_sendall(sock, b'somedata')
        await asyncio.sleep(1)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

enter image description here

How can I send data from a client running locally, to a server running locally, but avoiding the loopback interface and actually sending data out into the network?

Ideally answers would be applicable to both Linux and macOS.

Upvotes: 3

Views: 3154

Answers (2)

VPfB
VPfB

Reputation: 17322

To 'convince' the networking stack to physically transmit the frame using the Ethernet (or WiFi) card rather than the loopback, use a broadcast address.

I have successfully sent and received an UDP packet this way on my Linux system. I verified it with tcpdump. It shows one packet on Ethernet interface and no activity on loopback.

I have used literal broadcast address. The socket module documentation mentions also the string '<broadcast>' as a special case address. I did not tried it.

with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
    sock.setblocking(False)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
    sock.connect(('192.168.0.255', 4567))
    await loop.sock_sendall(sock, b'somedata')
    await asyncio.sleep(1)

Notes:

  1. other hosts on the same network will receive the UDP packet as well.
  2. make sure the firewall/packet filter (e.g. Linux iptables/nftables) will not block the packet.
  3. regarding the setsockopt: Python socket.error: [Errno 13] Permission denied

Upvotes: 1

andreihondrari
andreihondrari

Reputation: 5833

That's probably because your hostname is pointing to the loopback address hence socket.gethostbyname(socket.gethostname()) will yield 127.0.0.1

What you need to do is cancel that pointing from the hostname to the loopback address:

  • in Linux edit the /etc/hosts and comment out the line 127.0.0.1 YOUR_HOSTNAME
  • in Windows you should have c:\windows\system32\drivers\etc\hosts which looks similar to the Linux one

After this if you call the socket.gethostbyname(socket.gethostname()) it will yield your DHCP assigned IP.

Though even in this case, calling from yourip to yourip might result in the network driver to route the package through the loopback interface. Alternative would be to use the public IP, outside your network router. You could use an external service as described in this answer

Upvotes: 0

Related Questions