How do you send a UDP frame to an IPv6 link-local address? Initially, I'm most concerned with Linux.
I'm using CPython 2.7.7, but so far all behavior seems to be identical under 3.3.5, and I need to keep my code compatible with both 2.7 and 3.3/3.4. I'm particularly interested in sending to the all-nodes multicast address (ff02::1), but all the experimenting I've done seems to show that multicast and unicast behave the same. With link-local addresses you also need to specify which interface to use. The normal way of doing this on Linux with command-line utilities is append %<ifname> to the address/hostname (e.g. ping6 ff02::1%net1). That doesn't work: s.sendto(data, ("ff02::1%net1",port)) s.sendto(data, ("fe80::2c0:4eff:fe40:5f%net1",port)) The "%net1" appears to be ignored, and the packet will go out _some_ interface, but I can't figure out how it decides which one (it's not always the same one). The only way I've found to send to link-local IPv6 addresses is to use the extended length destination address tuple of (address, port, flow_info, scope_id) like so: s.sendto(data, ("ff02::1",port,0,scope_id)) s.sendto(data, ("fe80::2c0:4eff:fe40:5f",port,0,scope_id)) Through trial and error, I've determined that the valid scope_id values for my system are 0,2,3,4, and I've found that 4 corresponds to interface "net1". After re-reading the Python 2.7 socket module documentation, I can't find any way to map an interface name to a scope id. So, after browsing around /proc, I found the file /proc/net/if_inet6 which contains: $ cat if_inet6 fe80000000000000922b34fffe5e7edc 03 40 20 80 net0 00000000000000000000000000000001 01 80 10 80 lo fdfedcba987600100000000000000001 04 40 00 80 net1 fe80000000000000021b21fffeb1d1e9 04 40 20 80 net1 fdfedcba987600080000000000000004 03 40 00 80 net0 fe80000000000000922b34fffe5e7ede 02 40 20 80 net2 The first column is obviously the IPv6 address and the last column the interface name. It appears that second column is the scope id, and some further reading has lead me to believe that the fourth column is the scope (global vs. link-local vs. internal). So I've done the following: ip6scopeid = {} for line in open("/proc/net/if_inet6"): addr, id, _, scope, _, ifacename = line.split() ip6scopeid[ifacename] = int(id) This is highly non-portable, but now I can at least send to link-local addresses like this: s.sendto(data, ("ff02::1",port,0,ip6scopeid["net1"])) s.sendto(data, ("fe80::2c0:4eff:fe40:5f",port,0,ip6scopeid["net1"])) So, my program works (but only on Linux). What's the pythonic, portable way to do this? The only other thing I can think of is to make the user specify the IPv6 address of the interface they want to send from, bind a socket to that address, then ask for that socket's address: s.bind(("fdfe:dcba:9876:10::1",65432)) print s.getsockname() The above prints this: ('fdfe:dcba:9876:10::1', 5678, 0, 0) so, it unfortunately appears that getsockname() doesn't return the scope id if you bind to the global address assigned to an interface. Trying to bind to the link-local address fails unless you already know the scope id and pass it to bind: This fails: s.bind(("fe80::21b:21ff:feb1:d1e9",5678)) This works: s.bind(("fe80::21b:21ff:feb1:d1e9",5678,0,4)) But I'm trying to _find_ the scope id, so that isn't helpful. Any suggestions? Ideally, I'd like a solution that works on Windows and BSD as well... -- Grant Edwards grant.b.edwards Yow! Am I SHOPLIFTING? at gmail.com -- https://mail.python.org/mailman/listinfo/python-list