From 41989072fdfa766916489088aa7eae48a7d89961 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Fri, 12 Jul 2013 15:42:33 +0200 Subject: Implement emulator netns support for TCP and UDP --- lib/kernel/src/inet_int.hrl | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/kernel/src/inet_int.hrl b/lib/kernel/src/inet_int.hrl index 67a99913a1..18a4a61b2f 100644 --- a/lib/kernel/src/inet_int.hrl +++ b/lib/kernel/src/inet_int.hrl @@ -143,6 +143,7 @@ -define(INET_LOPT_TCP_SEND_TIMEOUT_CLOSE, 35). -define(INET_LOPT_MSGQ_HIWTRMRK, 36). -define(INET_LOPT_MSGQ_LOWTRMRK, 37). +-define(INET_LOPT_NETNS, 38). % Specific SCTP options: separate range: -define(SCTP_OPT_RTOINFO, 100). -define(SCTP_OPT_ASSOCINFO, 101). -- cgit v1.2.3 From a9f92f3d024feffe23303af141dc4b13c7c17aa5 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Tue, 16 Jul 2013 09:34:54 +0200 Subject: Implement netns option for TCP and UDP --- lib/kernel/src/inet.erl | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index 3ea530a366..0ee3234b05 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -634,6 +634,13 @@ con_opt([Opt | Opts], R, As) -> {tcp_module,_} -> con_opt(Opts, R, As); inet -> con_opt(Opts, R, As); inet6 -> con_opt(Opts, R, As); + {netns,NS} -> + case prim_inet:is_sockopt_val(netns, NS) of + true -> + con_opt(Opts, R#connect_opts { fd = [Opt] }, As); + false -> + {error, badarg} + end; {Name,Val} when is_atom(Name) -> con_add(Name, Val, R, Opts, As); _ -> {error, badarg} end; @@ -692,6 +699,13 @@ list_opt([Opt | Opts], R, As) -> {tcp_module,_} -> list_opt(Opts, R, As); inet -> list_opt(Opts, R, As); inet6 -> list_opt(Opts, R, As); + {netns,NS} -> + case prim_inet:is_sockopt_val(netns, NS) of + true -> + list_opt(Opts, R#listen_opts { fd = [Opt] }, As); + false -> + {error, badarg} + end; {Name,Val} when is_atom(Name) -> list_add(Name, Val, R, Opts, As); _ -> {error, badarg} end; @@ -738,6 +752,13 @@ udp_opt([Opt | Opts], R, As) -> {udp_module,_} -> udp_opt(Opts, R, As); inet -> udp_opt(Opts, R, As); inet6 -> udp_opt(Opts, R, As); + {netns,NS} -> + case prim_inet:is_sockopt_val(netns, NS) of + true -> + list_opt(Opts, R#udp_opts { fd = [Opt] }, As); + false -> + {error, badarg} + end; {Name,Val} when is_atom(Name) -> udp_add(Name, Val, R, Opts, As); _ -> {error, badarg} end; @@ -1063,7 +1084,7 @@ gethostbyaddr_tm_native(Addr, Timer, Opts) -> Result -> Result end. --spec open(Fd :: integer(), +-spec open(Fd_or_OpenOpts :: integer() | list(), Addr :: ip_address(), Port :: port_number(), Opts :: [socket_setopt()], @@ -1073,8 +1094,14 @@ gethostbyaddr_tm_native(Addr, Timer, Opts) -> Module :: atom()) -> {'ok', socket()} | {'error', posix()}. -open(Fd, Addr, Port, Opts, Protocol, Family, Type, Module) when Fd < 0 -> - case prim_inet:open(Protocol, Family, Type) of +open(FdO, Addr, Port, Opts, Protocol, Family, Type, Module) + when is_integer(FdO), FdO < 0; + is_list(FdO) -> + OpenOpts = + if is_list(FdO) -> FdO; + true -> [] + end, + case prim_inet:open(Protocol, Family, Type, OpenOpts) of {ok,S} -> case prim_inet:setopts(S, Opts) of ok -> @@ -1097,7 +1124,8 @@ open(Fd, Addr, Port, Opts, Protocol, Family, Type, Module) when Fd < 0 -> Error -> Error end; -open(Fd, _Addr, _Port, Opts, Protocol, Family, Type, Module) -> +open(Fd, _Addr, _Port, Opts, Protocol, Family, Type, Module) + when is_integer(Fd) -> fdopen(Fd, Opts, Protocol, Family, Type, Module). bindx(S, [Addr], Port0) -> -- cgit v1.2.3 From 08ff3673e25fdd184ff92d45d4609cd423fd1e34 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Tue, 16 Jul 2013 15:14:50 +0200 Subject: Implement netns for SCTP + bugfixes --- lib/kernel/src/inet.erl | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'lib') diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index 0ee3234b05..e118382bfe 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -828,6 +828,13 @@ sctp_opt([Opt|Opts], Mod, R, As) -> {sctp_module,_} -> sctp_opt (Opts, Mod, R, As); % Done with inet -> sctp_opt (Opts, Mod, R, As); % Done with inet6 -> sctp_opt (Opts, Mod, R, As); % Done with + {netns,NS} -> + case prim_inet:is_sockopt_val(netns, NS) of + true -> + sctp_opt(Opts, Mod, R#sctp_opts { fd = [Opt] }, As); + false -> + {error, badarg} + end; {Name,Val} -> sctp_opt (Opts, Mod, R, As, Name, Val); _ -> {error,badarg} end; -- cgit v1.2.3 From 4b4e9b8edb7c2340dbcdfd18e68981c028ede787 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Wed, 17 Jul 2013 11:14:36 +0200 Subject: Make netns option value a string --- lib/kernel/src/inet.erl | 78 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index e118382bfe..d2137065b6 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -200,7 +200,14 @@ send(Socket, Packet) -> Options :: [socket_setopt()]. setopts(Socket, Opts) -> - prim_inet:setopts(Socket, Opts). + SocketOpts = + [case Opt of + {netns,NS} -> + {netns,filename2binary(NS)}; + _ -> + Opt + end || Opt <- Opts], + prim_inet:setopts(Socket, SocketOpts). -spec getopts(Socket, Options) -> {'ok', OptionValues} | {'error', posix()} when @@ -209,7 +216,18 @@ setopts(Socket, Opts) -> OptionValues :: [socket_setopt()]. getopts(Socket, Opts) -> - prim_inet:getopts(Socket, Opts). + case prim_inet:getopts(Socket, Opts) of + {ok,OptionValues} -> + {ok, + [case OptionValue of + {netns,Bin} -> + {netns,binary2filename(Bin)}; + _ -> + OptionValue + end || OptionValue <- OptionValues]}; + Other -> + Other + end. -spec getifaddrs(Socket :: socket()) -> {'ok', [string()]} | {'error', posix()}. @@ -635,9 +653,10 @@ con_opt([Opt | Opts], R, As) -> inet -> con_opt(Opts, R, As); inet6 -> con_opt(Opts, R, As); {netns,NS} -> - case prim_inet:is_sockopt_val(netns, NS) of + BinNS = filename2binary(NS), + case prim_inet:is_sockopt_val(netns, BinNS) of true -> - con_opt(Opts, R#connect_opts { fd = [Opt] }, As); + con_opt(Opts, R#connect_opts { fd = [{netns,BinNS}] }, As); false -> {error, badarg} end; @@ -700,9 +719,10 @@ list_opt([Opt | Opts], R, As) -> inet -> list_opt(Opts, R, As); inet6 -> list_opt(Opts, R, As); {netns,NS} -> - case prim_inet:is_sockopt_val(netns, NS) of + BinNS = filename2binary(NS), + case prim_inet:is_sockopt_val(netns, BinNS) of true -> - list_opt(Opts, R#listen_opts { fd = [Opt] }, As); + list_opt(Opts, R#listen_opts { fd = [{netns,BinNS}] }, As); false -> {error, badarg} end; @@ -753,9 +773,10 @@ udp_opt([Opt | Opts], R, As) -> inet -> udp_opt(Opts, R, As); inet6 -> udp_opt(Opts, R, As); {netns,NS} -> - case prim_inet:is_sockopt_val(netns, NS) of + BinNS = filename2binary(NS), + case prim_inet:is_sockopt_val(netns, BinNS) of true -> - list_opt(Opts, R#udp_opts { fd = [Opt] }, As); + list_opt(Opts, R#udp_opts { fd = [{netns,BinNS}] }, As); false -> {error, badarg} end; @@ -829,9 +850,13 @@ sctp_opt([Opt|Opts], Mod, R, As) -> inet -> sctp_opt (Opts, Mod, R, As); % Done with inet6 -> sctp_opt (Opts, Mod, R, As); % Done with {netns,NS} -> - case prim_inet:is_sockopt_val(netns, NS) of + BinNS = filename2binary(NS), + case prim_inet:is_sockopt_val(netns, BinNS) of true -> - sctp_opt(Opts, Mod, R#sctp_opts { fd = [Opt] }, As); + sctp_opt( + Opts, Mod, + R#sctp_opts { fd = [{netns,BinNS}] }, + As); false -> {error, badarg} end; @@ -879,6 +904,39 @@ add_opt(Name, Val, Opts, As) -> end. +%% Passthrough all unknown - catch type errors later +filename2binary(List) when is_list(List) -> + OutEncoding = file:native_name_encoding(), + try unicode:characters_to_binary(List, unicode, OutEncoding) of + Bin when is_binary(Bin) -> + Bin; + _ -> + List + catch + error:badarg -> + List + end; +filename2binary(Bin) -> + Bin. + +binary2filename(Bin) -> + InEncoding = file:native_name_encoding(), + case unicode:characters_to_list(Bin, InEncoding) of + Filename when is_list(Filename) -> + Filename; + _ -> + %% For getopt/setopt of netns this should only happen if + %% a binary with wrong encoding was used when setting the + %% option, hence the user shall eat his/her own medicine. + %% + %% I.e passthrough here too for now. + %% Future usecases will most probably not want this, + %% rather Unicode error or warning + %% depending on emulator flag instead. + Bin + end. + + translate_ip(any, inet) -> {0,0,0,0}; translate_ip(loopback, inet) -> {127,0,0,1}; translate_ip(any, inet6) -> {0,0,0,0,0,0,0,0}; -- cgit v1.2.3 From 184e421b08feeae294cd4e629853a45ca640937f Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Wed, 17 Jul 2013 16:54:43 +0200 Subject: Rudimentary test --- lib/kernel/test/inet_SUITE.erl | 99 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/kernel/test/inet_SUITE.erl b/lib/kernel/test/inet_SUITE.erl index 62ba95e1a3..427dbbc129 100644 --- a/lib/kernel/test/inet_SUITE.erl +++ b/lib/kernel/test/inet_SUITE.erl @@ -38,10 +38,10 @@ gethostnative_debug_level/0, gethostnative_debug_level/1, getif/1, getif_ifr_name_overflow/1,getservbyname_overflow/1, getifaddrs/1, - parse_strict_address/1]). + parse_strict_address/1, simple_netns/1]). -export([get_hosts/1, get_ipv6_hosts/1, parse_hosts/1, parse_address/1, - kill_gethost/0, parallell_gethost/0]). + kill_gethost/0, parallell_gethost/0, test_netns/0]). -export([init_per_testcase/2, end_per_testcase/2]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -53,7 +53,7 @@ all() -> t_gethostnative, gethostnative_parallell, cname_loop, gethostnative_debug_level, gethostnative_soft_restart, getif, getif_ifr_name_overflow, getservbyname_overflow, - getifaddrs, parse_strict_address]. + getifaddrs, parse_strict_address, simple_netns]. groups() -> [{parse, [], [parse_hosts, parse_address]}]. @@ -1099,3 +1099,96 @@ toupper([C|Cs]) when is_integer(C) -> end; toupper([]) -> []. + + +simple_netns(Config) when is_list(Config) -> + {ok,U} = gen_udp:open(0), + case inet:setopts(U, [{netns,""}]) of + ok -> + jog_netns_opt(U), + ok = gen_udp:close(U), + %% + {ok,L} = gen_tcp:listen(0, []), + jog_netns_opt(L), + ok = gen_tcp:close(L), + %% + {ok,S} = gen_sctp:open(), + jog_netns_opt(S), + ok = gen_sctp:close(S); + {error,einval} -> + {skip,"setns() not supported"} + end. + +jog_netns_opt(S) -> + %% This is just jogging the option mechanics + ok = inet:setopts(S, [{netns,""}]), + {ok,[{netns,""}]} = inet:getopts(S, [netns]), + ok = inet:setopts(S, [{netns,"/proc/self/ns/net"}]), + {ok,[{netns,"/proc/self/ns/net"}]} = inet:getopts(S, [netns]), + ok. + + +%% Manual test to be run outside test_server in an emulator +%% started by root, in a machine with setns() support... +test_netns() -> + DefaultIF = v1, + DefaultIP = {192,168,1,17}, + Namespace = "test", + NamespaceIF = v2, + NamespaceIP = {192,168,1,18}, + %% + DefaultIPString = inet_parse:ntoa(DefaultIP), + NamespaceIPString = inet_parse:ntoa(NamespaceIP), + cmd("ip netns add ~s", + [Namespace]), + cmd("ip link add name ~w type veth peer name ~w netns ~s", + [DefaultIF,NamespaceIF,Namespace]), + cmd("ip netns exec ~s ip addr add ~s/30 dev ~w", + [Namespace,NamespaceIPString,NamespaceIF]), + cmd("ip netns exec ~s ip link set ~w up", + [Namespace,NamespaceIF]), + cmd("ip addr add ~s/30 dev ~w", + [DefaultIPString,DefaultIF]), + cmd("ip link set ~w up", + [DefaultIF]), + try test_netns( + {DefaultIF,DefaultIP}, + filename:join("/var/run/netns/", Namespace), + {NamespaceIF,NamespaceIP}) of + Result -> + io:put_chars(["#### Test done",io_lib:nl()]), + Result + after + cmd("ip link delete ~w type veth", + [DefaultIF]), + cmd("ip netns delete ~s", + [Namespace]) + end. + +test_netns({DefaultIF,DefaultIP}, Namespace, {NamespaceIF,NamespaceIP}) -> + {ok,ListenSocket} = gen_tcp:listen(0, [{active,false}]), + {ok,[{addr,DefaultIP}]} = inet:ifget(ListenSocket, DefaultIF, [addr]), + {ok,ListenPort} = inet:port(ListenSocket), + {ok,ConnectSocket} = + gen_tcp:connect( + DefaultIP, ListenPort, [{active,false},{netns,Namespace}], 3000), + {ok,[{addr,NamespaceIP}]} = inet:ifget(ConnectSocket, NamespaceIF, [addr]), + {ok,ConnectPort} = inet:port(ConnectSocket), + {ok,AcceptSocket} = gen_tcp:accept(ListenSocket, 0), + {ok,AcceptPort} = inet:port(AcceptSocket), + {ok,{NamespaceIP,ConnectPort}} = inet:peername(AcceptSocket), + {ok,{DefaultIP,AcceptPort}} = inet:peername(ConnectSocket), + ok = gen_tcp:send(ConnectSocket, "data"), + ok = gen_tcp:close(ConnectSocket), + {ok,"data"} = gen_tcp:recv(AcceptSocket, 4, 1000), + {error,closed} = gen_tcp:recv(AcceptSocket, 1, 1000), + ok = gen_tcp:close(AcceptSocket), + ok = gen_tcp:close(ListenSocket). + +cmd(Cmd, Args) -> + cmd(io_lib:format(Cmd, Args)). +%% +cmd(CmdString) -> + io:put_chars(["# ",CmdString,io_lib:nl()]), + io:put_chars([os:cmd(CmdString++" ; echo ' =>' $?")]), + ok. -- cgit v1.2.3 From af112cb10613d422080785621a274a18d96567c0 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Fri, 19 Jul 2013 15:18:42 +0200 Subject: Document socket option 'netns' --- lib/kernel/doc/src/inet.xml | 53 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'lib') diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml index 7cd98914d1..650ab41c3e 100644 --- a/lib/kernel/doc/src/inet.xml +++ b/lib/kernel/doc/src/inet.xml @@ -715,6 +715,59 @@ fe80::204:acff:fe17:bf38

Received Packet is delivered as defined by Mode.

+ {netns, Namespace :: file:filename_all()} + +

Set a network namespace for the socket. The Namespace + parameter is a filename defining the namespace for example + "/var/run/netns/example" typically created by the command + ip netns add example. This option must be used in a + function call that creates a socket i.e + + gen_tcp:connect/3,4, + + gen_tcp:listen/2, + + gen_udp:open/1,2 or + + gen_sctp:open/0-2. +

+

This option uses the Linux specific syscall + setns() such as in Linux kernel 3.0 or later + and therefore only exists when the runtime system + has been compiled for such an operating system. +

+

+ The virtual machine also needs elevated privileges either + running as superuser or (for Linux) having the capability + CAP_SYS_ADMIN according to the documentation for setns(2). + However, during testing also CAP_SYS_PTRACE + and CAP_DAC_READ_SEARCH has proven to be necessary. + Example: +setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp + + Note also that the filesystem containing the virtual machine + executable (beam.smp in the example above) has to be local, + mounted without the nosetuid flag, + support extended attributes and that + the kernel has to support file capabilities. + All this runs out of the box on at least Ubuntu 12.04 LTS, + except that SCTP sockets appears to not support + network namespaces. +

+

The Namespace is a file name and is encoded + and decoded as discussed in + file + except that the emulator flag +fnu is ignored and + getopts/2 + for this option will return a binary for the filename + if the stored filename can not be decoded, + which should only happen if you set the option using a binary + that can not be decoded with the emulator's filename encoding: + + file:native_name_encoding/0. +

+
+ list

Received Packet is delivered as a list.

-- cgit v1.2.3