Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gen_tcp cannot connect to IPv6 server through link-local address #4852

Closed
ht-hieu opened this issue May 19, 2021 · 12 comments
Closed

gen_tcp cannot connect to IPv6 server through link-local address #4852

ht-hieu opened this issue May 19, 2021 · 12 comments
Assignees
Labels
bug Issue is reported as a bug stalled waiting for input by the Erlang/OTP team team:PS Assigned to OTP team PS

Comments

@ht-hieu
Copy link

ht-hieu commented May 19, 2021

Describe the bug
gen_tcp get badarg when connecting to IPv6 server through link-local address. However, it can connect to a publish domain through IPv6.

To Reproduce
I have two machines, the first machine opens a TCP v6 server by using netcat.

nc -6 -l 9988

The second machine open erlang shell to connect to the opened server

1> {ok,Addr} = inet_parse:ipv6_address("fe80::2631:aee8:74dd:378d%enp0s31f6").
{ok,{65152,0,0,0,9777,44776,29917,14221}}
2> gen_tcp:connect(Addr, 9988, [binary, inet6]).
** exception exit: badarg
     in function  gen_tcp:connect/4 (gen_tcp.erl, line 167)
3>

Expected behavior
The erlang code can connect to the server and can transmit/receive data.

Affected versions
I tried Erlang 23 on Fedora 34, Erlang 21 on raspberry pi and Erlang 24 build from source.

@ht-hieu ht-hieu added the bug Issue is reported as a bug label May 19, 2021
@RoadRunnr
Copy link
Contributor

One Linux, when using link local IPv6 addresses, one needs to set sin6_scope_id to id of the network interface where that link local address is living.

With Erlang there are two problems with that:

  • gen_tcp does not support setting the scope_id
  • there is no way in Erlang that I know of to translate a interface name to its id

@ht-hieu
Copy link
Author

ht-hieu commented May 19, 2021

How can we establish a link local TCP connection using Erlang?

@RaimoNiskanen
Copy link
Contributor

Use FreeBSD ;-)

I think it actually works there because they have Scope ED 0 for link local. Not entirely sure, though.

Or use IPv4 for link local.

Now and then I have tried to make this work on Linux, with the new socket API, where it is possible to specify an address with Scope ID. But I have still not found a reliable way, since I have not yet found out which Scope ID to use.

It would be possible, in the gen_tcp API, to pass the Scope ID buried in the link local address as FreeBSD does internally in the kernel. This restricts the Scope ID to 16 bit, so it is not a complete solution. `inet:parse_address/1 would then need to be augmented to interpret the %ScopeID suffix, and we still lack a way to translate Interface to ScopeID, and there seems to be a new concept in which the Scope ID 0x16 means link-local, bit I can not find any documentation for this.

So, anybody than can educate me / us on how to handle IPv6 ScopeID:s on Linux, please speak up!

@RaimoNiskanen RaimoNiskanen self-assigned this May 20, 2021
@RaimoNiskanen RaimoNiskanen added the team:PS Assigned to OTP team PS label May 20, 2021
@RoadRunnr
Copy link
Contributor

RoadRunnr commented May 20, 2021

easy, on Linux 5.11, OTP-24, without error handling

-module(test).

-export([get/2]).

get(LL, Port) ->
    [Host, If] = string:lexemes(LL, "%"),
    {ok, Idx} = net:if_name2index(If),
    {ok, IP6} = inet:parse_ipv6_address(Host),
    Addr = #{family => inet6,
	     port => Port,
	     addr => IP6,
	     scope_id => Idx},

    io:format("Host: ~s~nInterface: ~s~nScope: ~p~n",
	      [Host, If, Idx]),

    {ok, Socket} = socket:open(inet6, stream, tcp),
    ok = socket:connect(Socket, Addr, infinity),
    socket:send(Socket, "GET / HTTP/1.0\r\n\r\n"),
    {ok, Data} = socket:recv(Socket),
    io:format("Data:~n~p~n", [Data]),
    ok.
Erlang/OTP 24 [erts-12.0] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit]

Eshell V12.0  (abort with ^G)
1> c(test).                                          
{ok,test}
2> test:get("fe80::fb43:f093:7df5:dfdc%wlan0", 8000).
Host: fe80::fb43:f093:7df5:dfdc
Interface: wlan0
Scope: 5
Data:
<<"HTTP/1.1 404 Not Found\r\nServer: webfs/1.21\r\nConnection: Close\r\nAccept-Ranges: bytes\r\nContent-Type: text/plain\r\nContent-Length: 28\r\nDate: Thu, 20 May 2021 10:28:42 GMT\r\n\r\nFile or directory not found\n">>
ok
3> 

@ht-hieu
Copy link
Author

ht-hieu commented May 20, 2021

Thank you for your suggestion. It works for me on Erlang 23. Sadly, gen_tcp doesn't support setting scope id.

@RaimoNiskanen
Copy link
Contributor

RaimoNiskanen commented May 20, 2021

net:getaddrinfo("fe80::fb43:f093:7df5:dfdc%wlan0") should give a list of addresses, one per protocol. Loop over it to find an item with type := stream and use the family := Family and first item in the protocol := Protocols list for socket:open/3.

@RaimoNiskanen
Copy link
Contributor

@RoadRunnr: That was enlightening. Can this be made to work for the loopback interface?

@RoadRunnr
Copy link
Contributor

@RaimoNiskanen getaddrinfo works nicely. I didn't even knew that function was there

I have no idea how to assign link local address to loopback. However, it works nicely with a dummy interface:

# ip link add dummy0 type dummy
# ip link set up dummy0
# ip -6  addr show dev dummy0
9: dummy0: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
    inet6 fe80::c8f8:70ff:fe2f:ae01/64 scope link 
       valid_lft forever preferred_lft forever
2> net:getaddrinfo("fe80::c8f8:70ff:fe2f:ae01%dummy0").    
{ok,[#{addr =>
           #{addr => {65152,0,0,0,51448,28927,65071,44545},
             family => inet6,flowinfo => 0,port => 0,scope_id => 9},
       family => inet6,protocol => tcp,type => stream},
     #{addr =>
           #{addr => {65152,0,0,0,51448,28927,65071,44545},
             family => inet6,flowinfo => 0,port => 0,scope_id => 9},
       family => inet6,protocol => udp,type => dgram},
     #{addr =>
           #{addr => {65152,0,0,0,51448,28927,65071,44545},
             family => inet6,flowinfo => 0,port => 0,scope_id => 9},
       family => inet6,protocol => ip,type => raw}]}

@RaimoNiskanen
Copy link
Contributor

RaimoNiskanen commented May 20, 2021

I thought the idea with a loopback interface was to have an always present local interface.

20> net:getaddrinfo("::1").                          
{ok,[#{addr =>
           #{addr => {0,0,0,0,0,0,0,1},
             family => inet6,flowinfo => 0,port => 0,scope_id => 0},
       family => inet6,
       protocol => [tcp,'TCP'],
       type => stream},
     #{addr =>
           #{addr => {0,0,0,0,0,0,0,1},
             family => inet6,flowinfo => 0,port => 0,scope_id => 0},
       family => inet6,
       protocol => [udp,'UDP'],
       type => dgram},
     #{addr =>
           #{addr => {0,0,0,0,0,0,0,1},
             family => inet6,flowinfo => 0,port => 0,scope_id => 0},
       family => inet6,
       protocol => [ip,'IP'],
       type => raw}]}
21> net:getaddrinfo("::1%lo").
{error,enoname}

But I can not get it to work. getaddrinfo says scope ID is 0, the interface index is 1, and ifconfig says: scopeid 0x10<host>. I have tried all and fail to connect; I have not figured out how to bind a socket to the ::1 address with a scope ID that I can connect to.

@RaimoNiskanen
Copy link
Contributor

net:getaddrinfo/2 takes a second argument ServiceName, so:

36> net:getaddrinfo("::1", "https"). 
{ok,[#{addr =>
           #{addr => {0,0,0,0,0,0,0,1},
             family => inet6,flowinfo => 0,port => 443,scope_id => 0},
       family => inet6,
       protocol => [tcp,'TCP'],
       type => stream}]}

But it seems we have introduced a bug in OTP-24.0 protocol := P should not be a list!
And the type spec could be more precise... The intent of getaddrinfo is that it should return what you need to open a socket and connect to the service you looked up.

@RaimoNiskanen
Copy link
Contributor

That bug in net:getaddrinfo/2 will be fixed in the upcoming OTP-24.0.2 and onwards.

@IngelaAndin IngelaAndin added the stalled waiting for input by the Erlang/OTP team label Oct 26, 2021
@bmk
Copy link
Contributor

bmk commented Jan 19, 2022

A fix/update for this (link-local) has been merged into maint, and will be part of OTP 24.3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Issue is reported as a bug stalled waiting for input by the Erlang/OTP team team:PS Assigned to OTP team PS
Projects
None yet
Development

No branches or pull requests

5 participants