Feature #1428
closedTCP/UDP face with link-local IPv6 address
100%
Description
FaceUri parser should recognize link-local IPv6 address in the form of tcp6://[fe80::d6ae:52ff:fecd:260c%eth1]:6363
.
%eth1
part is required to specify the NIC being used.
TCP and UDP factories should allow creating faces using link-local IPv6 address with explicitly specified NIC name.
Updated by Junxiao Shi over 10 years ago
- Tracker changed from Bug to Feature
- Subject changed from NFD: support link-local IPv6 address to TCP/UDP face with link-local IPv6 address
- Description updated (diff)
- Category set to Management
- Priority changed from Normal to Low
- Target version set to Unsupported
- Start date deleted (
03/31/2014) - Estimated time set to 3.00 h
Updated by Junxiao Shi over 10 years ago
- Related to Bug #1421: Failed to create tcp6 and udp6 faces added
Updated by Junxiao Shi almost 10 years ago
- Has duplicate Bug #2522: tcp6://[fe80::1%lo0] face is not considered "local" added
Updated by Davide Pesavento about 9 years ago
There's a minor problem here. The percent sign ("%") is always treated as an escape character in URI syntax, therefore it must be percent-encoded.
Quoting from RFC 6874:
[...] any occurrences of literal "%" symbols in a URI MUST
be percent-encoded and represented in the form "%25". Thus, the
scoped address fe80::a%en1 would appear in a URI as
http://[fe80::a%25en1].
Updated by Alex Afanasyev about 9 years ago
- Target version changed from Unsupported to v0.5
Updated by Junxiao Shi about 8 years ago
- Description updated (diff)
- Target version changed from v0.5 to v0.6
Updated by Yanbiao Li over 7 years ago
- Category changed from Management to Faces
- Status changed from New to Code review
- Assignee set to Yanbiao Li
- % Done changed from 0 to 50
Updated by Junxiao Shi over 7 years ago
- Status changed from Code review to In Progress
https://gerrit.named-data.net/4021 only contains FaceUri
parsing. This issue is far from finish: it needs changes in TcpFactory
and UdpFactory
as well. CodeReview state should not be set until the last Change is uploaded.
Updated by Junxiao Shi over 7 years ago
I think putting local interface name into RemoteUri, despite being the syntax used in OS, is not the correct design. The local interface name should be reflected as part of LocalUri field. For example:
- RemoteUri:
tcp6://[fe80::d6ae:52ff:fecd:260c]:6363
- LocalUri:
tcp6+dev://eth0
Updated by Davide Pesavento over 7 years ago
Junxiao Shi wrote:
- RemoteUri:
tcp6://[fe80::d6ae:52ff:fecd:260c]:6363
- LocalUri:
tcp6+dev://eth0
This looks like a NIC-associated face. For a regular face, the LocalUri needs to be an IP address (i.e. tcp6
or udp6
scheme). In the case of link-local addresses, the LocalUri will contain a link-local address with a zone ID (ifname). Therefore FaceUri must still recognize scoped IPv6 addresses.
Updated by Alex Afanasyev over 7 years ago
Junxiao Shi wrote:
I think putting local interface name into RemoteUri, despite being the syntax used in OS, is not the correct design. The local interface name should be reflected as part of LocalUri field. For example:
- RemoteUri:
tcp6://[fe80::d6ae:52ff:fecd:260c]:6363
- LocalUri:
tcp6+dev://eth0
fe80::d6ae:52ff:fecd:260c%en1
is a valid link-local IPv4 address on Linux platform, which can be used directly to do whatever operations in boost. While the localUri may be used in some cases, we have to support addresses that are returned by the system.
Updated by Alex Afanasyev over 7 years ago
Davide Pesavento wrote:
Junxiao Shi wrote:
- RemoteUri:
tcp6://[fe80::d6ae:52ff:fecd:260c]:6363
- LocalUri:
tcp6+dev://eth0
This looks like a NIC-associated face. For a regular face, the LocalUri needs to be an IP address (i.e.
tcp6
orudp6
scheme). In the case of link-local addresses, the LocalUri will contain a link-local address with a zone ID (ifname). Therefore FaceUri must still recognize scoped IPv6 addresses.
+1 to this comment.
Updated by Davide Pesavento over 7 years ago
- % Done changed from 50 to 10
A scoped IPv6 address would also be needed in a ChannelUri, if we ever allow creating channels on local addresses different from "0.0.0.0" and "::".
Updated by Junxiao Shi over 7 years ago
fe80::d6ae:52ff:fecd:260c%en1
is a valid link-local IPv4 address on Linux platform, which can be used directly to do whatever operations in boost. While the localUri may be used in some cases, we have to support addresses that are returned by the system.
Agreed. But should LocalUri gain some indication that it's link-local as well? Can you show an example of a pair of RemoteUri-LocalUri for a UDP/TCP face on link-local?
Updated by Davide Pesavento over 7 years ago
Junxiao Shi wrote:
Agreed. But should LocalUri gain some indication that it's link-local as well? Can you show an example of a pair of RemoteUri-LocalUri for a UDP/TCP face on link-local?
I don't understand the question. An ipv4 link-local address is in the block 169.254.0.0/16
; an ipv6 link-local address is in the block fe80::/64
. You don't need a zone ID (%eth0
) to recognize a link-local address.
Updated by Yanbiao Li over 7 years ago
some issues to discuss:
1/ To use a link-local IPv6 address, an network interface identifier is specified (e.g. eth0) on which corresponding messages will be sent out. Shall we check whether the specified network interface exists and supports IPv6 (i.e. is configured with a valid IPv6 address) during canonization.
2/ I think it would be better to define the URI of the corresponding channel as a link-local address in the format defined in RFC 4862.
3/ For test purpose, I may need to enumerate all network interfaces in the unit-tests. Shall we move some helper functions of NetworkInterface from NFD/core to ndn-cxx
Updated by Alex Afanasyev over 7 years ago
1/ To use a link-local IPv6 address, an network interface identifier is specified (e.g. eth0) on which corresponding messages will be sent out. Shall we check whether the specified network interface exists and supports IPv6 (i.e. is configured with a valid IPv6 address) during canonization.
No. Canoical URI doesn't mean a valid URI for a specific NFD. If the form is canonical, nothing else needs to be done
2/ I think it would be better to define the URI of the corresponding channel as a link-local address in the format defined in RFC 4862.
Don't understand this
3/ For test purpose, I may need to enumerate all network interfaces in the unit-tests. Shall we move some helper functions of NetworkInterface from NFD/core to ndn-cxx
We have ndn-cxx support for that and we have a mock version of interface enumeration facility (in review?). All of these in util as part of networkmonitor.
Updated by Davide Pesavento over 7 years ago
Alex Afanasyev wrote:
1/ To use a link-local IPv6 address, an network interface identifier is specified (e.g. eth0) on which corresponding messages will be sent out. Shall we check whether the specified network interface exists and supports IPv6 (i.e. is configured with a valid IPv6 address) during canonization.
No. Canoical URI doesn't mean a valid URI for a specific NFD. If the form is canonical, nothing else needs to be done
Agree with Alex.
3/ For test purpose, I may need to enumerate all network interfaces in the unit-tests. Shall we move some helper functions of NetworkInterface from NFD/core to ndn-cxx
We have ndn-cxx support for that and we have a mock version of interface enumeration facility (in review?). All of these in util as part of networkmonitor.
NetworkMonitorStub
has already been merged into ndn-cxx.
Updated by Yanbiao Li over 7 years ago
Alex Afanasyev wrote:
1/ To use a link-local IPv6 address, an network interface identifier is specified (e.g. eth0) on which corresponding messages will be sent out. Shall we check whether the specified network interface exists and supports IPv6 (i.e. is configured with a valid IPv6 address) during canonization.
No. Canoical URI doesn't mean a valid URI for a specific NFD. If the form is canonical, nothing else needs to be done
2/ I think it would be better to define the URI of the corresponding channel as a link-local address in the format defined in RFC 4862.
Don't understand this
To find a proper channel for creating a face, we currently scan the channel list and adopt the first whose address version is matched.
we now only have two v6 channels (for tcp and udp respectively) that accept ANY.
What I think about is:
1/ make enablement of creating link-local v6 faces (faces that toward link-local v6 addresses) configurable.
2/ if it is enabled, we enumerate all network interfaces support IPv6 and create a channel for each. So that each of these channels corresponds to one scope id.
3/ when being requested to create such a face, we just find out the corresponding channel via the specified scope id and create the face on it.
Though I do not see any mechanical benefits (because I actually did not get a clear picture about the use of localUri) of this design, I just think it may make the use of link-local v6 addresses clearer.
Updated by Davide Pesavento over 7 years ago
Yanbiao Li wrote:
To find a proper channel for creating a face, we currently scan the channel list and adopt the first whose address version is matched.
we now only have two v6 channels (for tcp and udp respectively) that accept ANY.What I think about is:
1/ make enablement of creating link-local v6 faces (faces that toward link-local v6 addresses) configurable.
2/ if it is enabled, we enumerate all network interfaces support IPv6 and create a channel for each. So that each of these channels corresponds to one scope id.
3/ when being requested to create such a face, we just find out the corresponding channel via the specified scope id and create the face on it.
Are you sure this is really needed?
Alex previously said that boost supports IPv6 addresses with a scope-id. However, it's not clear to me how link-local addresses are used with sockets (bind, connect)... I know that the sin6_scope_id
field in the sockaddr_in6
structure must be the interface index, but can you connect()
to a link-local address without a prior bind()
? does the OS choose the right interface for you?
Before talking about channels, we must have a clear understanding of the underlying socket behavior...
Eventually, we do want to have one channel per interface or local IP address (I think we discussed about it in another ticket), but unless necessary to implement the link-local feature, it's outside the scope of this issue.
Updated by Yanbiao Li over 7 years ago
Davide Pesavento wrote:
Are you sure this is really needed?
Alex previously said that boost supports IPv6 addresses with a scope-id.
yes, the boost library does support that.
but, it's different from what is expected naturally (at least for me).
Actually we can never use address_v6::from_string() to get an IPv6 address with a zone id from something like fe80::1:2:3:4%eth0. Because it invokes ::inet_pton to parse the host string, but that function can not recognize an address with the zone id associated.
As a result, IpHostCanonizeProvider::isCanonical() will always return false.
But, such a link-local address can still be canonized by using resolver, from which we can get the correct IpAddress with the zone id. Then we can construct an endpoint and thus can perform required network operations later (i.e., connect, or bind and then listen).
To this point, boost does support link-local Ipv6 addresses.
However, in our case, after get the IpAddress from the dns query via the resolver we go back to the URI again (construct a canonicalUri from the endpoint) and pass it through for later operations. Then we will fail wherever the isCanonical() check is performed.
To resolve the above issue, I guess we have at least 2 options now: 1) modify cxx code to pass the endpoint through for later network operations in the link-local case, or 2) write another v6_address::from_string() helper function that uses getaddrinfo to parse host strings (returned from FaceUri::getHost()).
But I'm not so clear about how many code changes are needed in each case.
However, it's not clear to me how link-local addresses are used with sockets (bind, connect)... I know that the
sin6_scope_id
field in thesockaddr_in6
structure must be the interface index, but can youconnect()
to a link-local address without a priorbind()
? does the OS choose the right interface for you?
Before talking about channels, we must have a clear understanding of the underlying socket behavior...Eventually, we do want to have one channel per interface or local IP address (I think we discussed about it in another ticket), but unless necessary to implement the link-local feature, it's outside the scope of this issue.
Updated by Yanbiao Li over 7 years ago
I just performed some tests and am sure that I can address the issue mentioned in note #21 by a few lines of code.
But here comes another issue if taking consistency into account.
I will write a helper function from_string to convert a string into boost::asio::ip::address, and use it to replace boost::ip::address::from_string everywhere such a conversion is required.
If I do, many files will be changed in the corresponding commit.
Can I just do it?
Or, anyone can provide a better solution?
Updated by Alex Afanasyev over 7 years ago
I will write a helper function from_string to convert a string into boost::asio::ip::address, and use it to replace boost::ip::address::from_string everywhere such a conversion is required.
If I do, many files will be changed in the corresponding commit.
Yanbiao and I talked a bit about this. There are not that many places that need a change, so I see no issues to make the change. None of the code in existing test cases needs a change.
Updated by Yanbiao Li over 7 years ago
- Status changed from In Progress to Code review
- % Done changed from 10 to 80
Updated by Davide Pesavento over 7 years ago
Yanbiao Li wrote:
Actually we can never use address_v6::from_string() to get an IPv6 address with a zone id from something like fe80::1:2:3:4%eth0. Because it invokes ::inet_pton to parse the host string, but that function can not recognize an address with the zone id associated.
This is not true. asio::ip::address_v6::from_string()
supports link-local addresses with a scope ID just fine. It does use inet_pton()
internally, but it parses the "%eth0" suffix separately and assigns it as scope_id
to the returned address_v6
object.
So, as far as I can see, we can use asio functions directly.
Updated by Yanbiao Li over 7 years ago
int result = error_wrapper(::inet_pton(af, src_ptr, dest), ec);
if (result <= 0 && !ec)
ec = boost::asio::error::invalid_argument;
if (result > 0 && is_v6 && scope_id)
{
using namespace std; // For strchr and atoi.
scope_id = 0;
if (if_name != 0)
{
in6_addr_type ipv6_address = static_cast(dest);
bool is_link_local = ((ipv6_address->s6_addr[0] == 0xfe)
&& ((ipv6_address->s6_addr[1] & 0xc0) == 0x80));
bool is_multicast_link_local = ((ipv6_address->s6_addr[0] == 0xff)
&& ((ipv6_address->s6_addr[1] & 0x0f) == 0x02));
if (is_link_local || is_multicast_link_local)
*scope_id = if_nametoindex(if_name + 1);
if (*scope_id == 0)
*scope_id = atoi(if_name + 1);
}
}
return result;
I think you just saw above code but did not test it yourself.
"::inet_pton(af, src_ptr, dest)" will return error if the address contains scope id
Updated by Yanbiao Li over 7 years ago
I tested it with boost 1.54 and boost 1.55 which I can get by ubuntu sudo apt-get
but I just noticed that since 1.58 this piece of code has been upgraded.
So when boost 1.58 is ready in apt-get, we can turn to use it.
Updated by Davide Pesavento over 7 years ago
Yanbiao Li wrote:
I think you just saw above code but did not test it yourself.
I did test it, and as I said, it works. What version of boost are you using?
"::inet_pton(af, src_ptr, dest)" will return error if the address contains scope id
I know that, but boost handles the scope id separately, it's not passed to inet_pton()
.
Updated by Davide Pesavento over 7 years ago
Yanbiao Li wrote:
I tested it with boost 1.54 and boost 1.55 which I can get by ubuntu sudo apt-get
but I just noticed that since 1.58 this piece of code has been upgraded.
Yes, it seems to have been fixed with this commit https://github.com/boostorg/asio/commit/b261738d790351f3472ee64ebd0d18feb271d1c5
So when boost 1.58 is ready in apt-get, we can turn to use it.
Use #if
/#endif
to select between the asio implementation and our fallback, depending on the version of boost.
Updated by Yanbiao Li over 7 years ago
yes, I'm working on this. It's better to keep compatible with not too old boost versions
Updated by Davide Pesavento about 7 years ago
- Has duplicate deleted (Bug #2522: tcp6://[fe80::1%lo0] face is not considered "local")
Updated by Alex Afanasyev about 7 years ago
I have tried to test https://gerrit.named-data.net/#/c/4082/ commit, but failing so far.
I have succeeded in creating a UDP face towards link-local address, but there is no reaction on the other side. Is there anything special we need to do to listen on link-local address? With UDP6 face, I see that host is receiving packets, but I don't see NFD actually receiving them. Similarly, for tcp6:// link-local face, it failed to connect.
Updated by Yanbiao Li about 7 years ago
Alex Afanasyev wrote:
I have tried to test https://gerrit.named-data.net/#/c/4082/ commit, but failing so far.
I have succeeded in creating a UDP face towards link-local address, but there is no reaction on the other side. Is there anything special we need to do to listen on link-local address? With UDP6 face, I see that host is receiving packets, but I don't see NFD actually receiving them. Similarly, for tcp6:// link-local face, it failed to connect.
Sorry for the delay....
Redmine notifications kept going to my Junk dir...
Actually, I do not use any special configurations. I tried (and tested again just now) in the following way, please correct me if there is something wrong or trick:
I set two vagrant hosts in the public-network mode, bridging on my laptop's wifi interface.
vagrant A: eth1(fe80::a00:27ff:fe1c:ad7f), run ndn-cxx/examples/producer
vagrant B: enp0s8(fe80::a00:27ff:fe36:c44e), create an udp face toward A's eth1, and register the the prefix
"face-created id=262 local=udp6://[fe80::a00:27ff:fe36:c44e%enp0s8]:6363 remote=udp6://[fe80::a00:27ff:fe1c:ad7f%enp0s8]:6363 persistency=persistent reliability=off"
"route-add-accepted prefix=/example/testApp nexthop=262 origin=static cost=0 flags=child-inherit expires=never"
vagrant B: run ndn-cxx/examples/consumer, can see the data is back. While on vagrant A, I can observer the following:
"1507451429.609215 INFO: [UnicastUdpTransport] [id=0,local=udp6://[fe80::a00:27ff:fe1c:ad7f%eth1]:6363,remote=udp6://[fe80::a00:27ff:fe36:c44e%eth1]:6363] Creating transport"
"1507451429.609265 INFO: [FaceTable] Added face id=266 remote=udp6://[fe80::a00:27ff:fe36:c44e%eth1]:6363 local=udp6://[fe80::a00:27ff:fe1c:ad7f%eth1]:6363"
Updated by Alex Afanasyev about 7 years ago
My attempt was host-macOS to bridged VirtualBox'ed Linux. Will try again later with a different combination.
Updated by Davide Pesavento about 7 years ago
All transport tests seem to work with IPv6 link-local on both Linux and macOS (https://gerrit.named-data.net/4265).
@Alex, can you retry your test?
Updated by Davide Pesavento about 7 years ago
- Status changed from Code review to Closed
- % Done changed from 80 to 100
Updated by Davide Pesavento almost 7 years ago
- Related to Bug #4474: FaceUri throws "Malformed URI" for IPv6 UDP multicast address added