Reputation: 14254
I have a multicast system with multiple network interfaces. Packets transmitted using the socket I created are being sent out the correct interface, but the packet's source address is incorrect. The source address should match the interface that was used to transmit the packet over an isolated network, but it's using the source address from the interface on the private network (which ultimately connects to the internet).
What do I need to change about my socket configuration, so that transmitted packets are sent with the isolated network interface's IP address as the source address?
import socket
src_ip = '172.17.0.1' # isolated network interface IP address which should be source address but isn't
dst_ip = '225.17.0.18' # destination IP address
port = 30000
msg = bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x88, 0xAA, 0xBB, 0xCC, 0xDD ])
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.connect((dst_ip, port))
sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(src_ip))
sock.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(dst_ip) + socket.inet_aton(src_ip))
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.sendall(msg)
The packet gets transmitted as expected, except for the source address. Any thoughts? I'm using Python 3.6. I know it's old but it's what they're using here.
Upvotes: 1
Views: 118
Reputation: 178031
The level specified with option IP_MULTICAST_IF and IP_ADD_MEMBERSHIP should be IPPROTO_IP, not SOL_IP. Also, UDP is connectionless. bind
is used to receive, but connect
is not needed.
Receiver using my system to listen on both my network interface and the localhost interface:
import socket
mgrp = '224.3.29.71'
server = '', 5000
interfaces = '127.0.0.1', '192.168.1.3'
with socket.socket(type=socket.SOCK_DGRAM) as s:
s.bind(server)
for interface in interfaces:
mreq = socket.inet_aton(mgrp) + socket.inet_aton(interface)
s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
while True:
data, addr = s.recvfrom(1024)
print(f'{addr}: {data}')
Sender using IP_ADD_MEMBERSHIP to send on each interface:
import socket
msg = b'hello, world!'
mgrp = ('224.3.29.71', 5000)
interfaces = '192.168.1.3', '127.0.0.1'
with socket.socket(type=socket.SOCK_DGRAM) as s:
ttl = 1
s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, bytes([ttl]))
for interface in interfaces:
s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(interface))
s.sendto(msg, mgrp)
Run the receiver and then the sender a few times:
('192.168.1.3', 63854): b'hello, world!'
('127.0.0.1', 63854): b'hello, world!'
('192.168.1.3', 63855): b'hello, world!'
('127.0.0.1', 63855): b'hello, world!'
('192.168.1.3', 63856): b'hello, world!'
('127.0.0.1', 63856): b'hello, world!'
Upvotes: 0
Reputation: 782166
If you don't bind the socket to a specific address, the network stack chooses the source address arbitrarily, and it doesn't have to be the same as the address of the interface it sends from. If you want a specific source address, call sock.bind()
.
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.bind((src_ip, 0))
sock.connect((dst_ip, port))
sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(src_ip))
sock.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(dst_ip) + socket.inet_aton(src_ip))
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
Specifying port 0 in bind()
allows the OS to select an unused source port.
Upvotes: 2