Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #79917 > unrolled thread

Send UDP packet to link-local IPv6 address?

Started byGrant Edwards <invalid@invalid.invalid>
First post2014-10-29 15:29 +0000
Last post2014-10-29 17:36 +0000
Articles 3 — 2 participants

Back to article view | Back to comp.lang.python


Contents

  Send UDP packet to link-local IPv6 address? Grant Edwards <invalid@invalid.invalid> - 2014-10-29 15:29 +0000
    Re: Send UDP packet to link-local IPv6 address? Alain Ketterlin <alain@dpt-info.u-strasbg.fr> - 2014-10-29 17:50 +0100
      Re: Send UDP packet to link-local IPv6 address? Grant Edwards <invalid@invalid.invalid> - 2014-10-29 17:36 +0000

#79917 — Send UDP packet to link-local IPv6 address?

FromGrant Edwards <invalid@invalid.invalid>
Date2014-10-29 15:29 +0000
SubjectSend UDP packet to link-local IPv6 address?
Message-ID<m2r149$ej8$1@reader1.panix.com>
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            

[toc] | [next] | [standalone]


#79922

FromAlain Ketterlin <alain@dpt-info.u-strasbg.fr>
Date2014-10-29 17:50 +0100
Message-ID<87zjce252h.fsf@dpt-info.u-strasbg.fr>
In reply to#79917
Grant Edwards <invalid@invalid.invalid> writes:

[...]
> 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 is to use socket.getaddrinfo(), which does
accept "%net1" after the IPv6 address, and returns scope_id (actually a
complete sockaddr). I can't access my files now, by I think

    socket.getaddrinfo("fe80::2c0:4eff:fe40:5f%net1",port,...)

should work for you (family, socktype etc should be passed also to avoid
searching the results of getaddrinfo()).

Hope this helps,

-- Alain.

[toc] | [prev] | [next] | [standalone]


#79926

FromGrant Edwards <invalid@invalid.invalid>
Date2014-10-29 17:36 +0000
Message-ID<m2r8iu$r3k$1@reader1.panix.com>
In reply to#79922
On 2014-10-29, Alain Ketterlin <alain@dpt-info.u-strasbg.fr> wrote:
> Grant Edwards <invalid@invalid.invalid> writes:
>
> [...]
>> 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 is to use socket.getaddrinfo(), which does
> accept "%net1" after the IPv6 address, and returns scope_id (actually a
> complete sockaddr). I can't access my files now, by I think
>
>     socket.getaddrinfo("fe80::2c0:4eff:fe40:5f%net1",port,...)
>
> should work for you (family, socktype etc should be passed also to avoid
> searching the results of getaddrinfo()).

Doh!, of course that works.  Using getaddrinfo() is how you're always
supposed to do things these days.  Creating an address tuple by hand
is a bad habit left over from too many years spent doing network stuff
in the old days before getaddrinfo et al.  I looked at the
documentation for getaddrinfo(), and I would have sworn I had tried it
and couldn't get it to work.  But it does.

> Hope this helps,

Definitely!

Thanks!

-- 
Grant Edwards               grant.b.edwards        Yow! FOOLED you!  Absorb
                                  at               EGO SHATTERING impulse
                              gmail.com            rays, polyester poltroon!!

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web