Closed ht-hieu closed 2 years ago
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:
How can we establish a link local TCP connection using Erlang?
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!
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>
Thank you for your suggestion. It works for me on Erlang 23. Sadly, gen_tcp
doesn't support setting scope id.
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
.
@RoadRunnr: That was enlightening. Can this be made to work for the loopback interface?
@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}]}
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.
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.
That bug in net:getaddrinfo/2
will be fixed in the upcoming OTP-24.0.2 and onwards.
A fix/update for this (link-local) has been merged into maint, and will be part of OTP 24.3.
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
.The second machine open erlang shell to connect to the opened server
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.