%%% Copyright (c) 2011-2023 Michael Santos . All %%% rights reserved. %%% %%% Redistribution and use in source and binary forms, with or without %%% modification, are permitted provided that the following conditions %%% are met: %%% %%% 1. Redistributions of source code must retain the above copyright notice, %%% this list of conditions and the following disclaimer. %%% %%% 2. Redistributions in binary form must reproduce the above copyright %%% notice, this list of conditions and the following disclaimer in the %%% documentation and/or other materials provided with the distribution. %%% %%% 3. Neither the name of the copyright holder nor the names of its %%% contributors may be used to endorse or promote products derived from %%% this software without specific prior written permission. %%% %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS %%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT %%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR %%% A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT %%% HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, %%% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED %%% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR %%% PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF %%% LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING %%% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS %%% SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. %% @doc tunctl behaviour for Linux. %% %% For IPv4 addresses, beam needs to have privileges to configure interfaces. %% %% To add cap_net_admin capabilities: %% %% ``` %% sudo setcap cap_net_admin=ep /path/to/bin/beam.smp %% ''' %% %% To check the privileges: %% %% ``` %% getcap /path/to/bin/beam.smp %% ''' %% %% To remove the privileges: %% %% ``` %% sudo setcap -r cap_net_admin=ep /path/to/bin/beam.smp %% ''' %% %% Currently, IPv6 addresses are configured by calling ifconfig using sudo %% (see below). -module(tunctl_linux). -behaviour(tunctl). -include("tuntap.hrl"). -include_lib("procket/include/ioctl.hrl"). -include_lib("procket/include/procket.hrl"). -export([ create/2, persist/2, owner/2, group/2, up/3, dstaddr/2, broadcast/2, down/1, mtu/3, header/1 ]). -define(SIOCGIFFLAGS, 16#8913). -define(SIOCSIFFLAGS, 16#8914). -define(SIOCSIFADDR, 16#8916). -define(SIOCSIFMTU, 16#8922). -define(SIOCSIFNETMASK, 16#891c). % set remote PA address -define(SIOCSIFDSTADDR, 16#8918). % set broadcast PA address -define(SIOCSIFBRDADDR, 16#891a). -define(SIOGIFINDEX, 16#8933). -define(TUNDEV, "net/tun"). %%-------------------------------------------------------------------- %%% Exports %%-------------------------------------------------------------------- create(<<>>, Opt) -> create(<<0:(15 * 8)>>, Opt); create(Ifname, Opt) when byte_size(Ifname) < ?IFNAMSIZ, is_list(Opt) -> % support namespace DevOpt = case proplists:get_value(namespace, Opt) of undefined -> []; Val -> [{namespace, Val}] end, case procket:dev(?TUNDEV, DevOpt) of {ok, FD} -> create_1(FD, Ifname, Opt); Error -> Error end. create_1(FD, Ifname, Opt) -> Flag = lists:foldl(fun(N, F) -> F bor flag(N) end, 0, Opt), Result = procket:ioctl(FD, ?TUNSETIFF, << Ifname/binary, % ifrn_name[IFNAMSIZ]: interface name 0:((15 * 8) - (byte_size(Ifname) * 8)), 0:8, % ifru_flags Flag:2/native-signed-integer-unit:8, 0:(14 * 8) >>), case Result of {ok, Dev} -> {ok, FD, hd(binary:split(Dev, <<0>>))}; Error -> ok = procket:close(FD), Error end. persist(FD, Status) -> tunctl:ioctl(FD, ?TUNSETPERSIST, Status). %% %% Change the owner/group of the tun device %% owner(FD, Owner) when is_integer(FD), is_integer(Owner) -> tunctl:ioctl(FD, ?TUNSETOWNER, int_to_bin(Owner)). group(FD, Group) when is_integer(FD), is_integer(Group) -> tunctl:ioctl(FD, ?TUNSETGROUP, int_to_bin(Group)). %% %% Configure the interface just like ifconfig except %% with fewer features and no error checking. %% %% Also, we ignore the mask. %% up(Dev, {A, B, C, D}, Mask) when byte_size(Dev) < ?IFNAMSIZ, Mask >= 0, Mask =< 32 -> % struct sockaddr_in % dev[IFNAMSIZ], family:2 bytes, port:2 bytes, ipaddr:4 bytes case procket:socket(inet, dgram, 0) of {ok, Sock} -> Ifr = <>, Res = case ifreq(Dev, Sock, Ifr, ?SIOCSIFADDR, ?IFF_RUNNING bor ?IFF_UP) of ok -> ifreq( Dev, Sock, <>, ?SIOCSIFNETMASK, 0 ); {error, _} = Error -> Error end, ok = procket:close(Sock), Res; {error, _} = Error -> Error end; up(Dev, {A, B, C, D, E, F, G, H}, Mask) when byte_size(Dev) < ?IFNAMSIZ -> case procket:socket(inet6, dgram, 0) of {ok, Sock} -> {ok, IfIdx} = get_ifindex(Sock, Dev), BinAddr = <>, Ifr = <>, Res = ifreq(Dev, Sock, Ifr, ?SIOCSIFADDR, ?IFF_RUNNING bor ?IFF_UP), ok = procket:close(Sock), Res; {error, _} = Error -> Error end. mtu(FD, Ifname, MTU) when byte_size(Ifname) < ?IFNAMSIZ -> case procket:socket(inet, dgram, 0) of {ok, Sock} -> Result = procket:ioctl(Sock, ?SIOCSIFMTU, << Ifname/binary, % ifrn_name[IFNAMSIZ]: interface name 0:((15 * 8) - (byte_size(Ifname) * 8)), 0:8, % ifr_mtu MTU:4/native-signed-integer-unit:8, 0:(12 * 8) >>), ok = procket:close(Sock), case Result of {ok, Dev} -> {ok, FD, hd(binary:split(Dev, <<0>>))}; Error -> Error end; {error, _} = Error -> Error end. dstaddr(Dev, {A, B, C, D}) when byte_size(Dev) < ?IFNAMSIZ -> % struct sockaddr_in % dev[IFNAMSIZ], family:2 bytes, port:2 bytes, ipaddr:4 bytes case procket:socket(inet, dgram, 0) of {ok, Sock} -> Ifr = <>, Res = ifreq(Dev, Sock, Ifr, ?SIOCSIFDSTADDR, ?IFF_POINTOPOINT), ok = procket:close(Sock), Res; {error, _} = Error -> Error end; dstaddr(Dev, {_A, _B, _C, _D, _E, _F, _G, _H}) when byte_size(Dev) < ?IFNAMSIZ -> % See netdevice(7): % Adding a new IPv6 address and deleting an existing IPv6 address % can be done via SIOCSIFADDR and SIOCDIFADDR or via rtnetlink(7). % Retrieving or changing destination IPv6 addresses of a point-to-point % interface is possible only via rtnetlink(7). {error, enodev}. broadcast(Dev, {A, B, C, D}) when byte_size(Dev) < ?IFNAMSIZ -> % struct sockaddr_in % dev[IFNAMSIZ], family:2 bytes, port:2 bytes, ipaddr:4 bytes case procket:socket(inet, dgram, 0) of {ok, Sock} -> Ifr = <>, Res = ifreq(Dev, Sock, Ifr, ?SIOCSIFBRDADDR, 0), ok = procket:close(Sock), Res; {error, _} = Error -> Error end; broadcast(Dev, {_A, _B, _C, _D, _E, _F, _G, _H}) when byte_size(Dev) < ?IFNAMSIZ -> {error, enodev}. ifreq(Dev, Sock, Ifr, Op, Flags) -> try tunctl:ioctl(Sock, Op, Ifr) of ok -> {ok, Flag} = get_flag(Sock, Dev), ok = set_flag(Sock, Dev, Flag bor Flags); {error, eexist} -> ok; {error, _} = Error -> Error catch error:Error -> {error, Error} end. down(Dev) when byte_size(Dev) < ?IFNAMSIZ -> {ok, Socket} = procket:socket(inet, dgram, 0), Res = try {ok, Flags} = get_flag(Socket, Dev), ok = set_flag(Socket, Dev, Flags band bnot (?IFF_UP)) of _ -> ok catch error:Error -> {error, Error} end, ok = procket:close(Socket), Res. header(<>) -> {tun_pi, Flags, Proto, Buf}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- %% %% tun/tap options %% flag(tun) -> ?IFF_TUN; flag(tap) -> ?IFF_TAP; flag(multi_queue) -> ?IFF_MULTI_QUEUE; flag(no_pi) -> ?IFF_NO_PI; flag(one_queue) -> ?IFF_ONE_QUEUE; flag(vnet_hdr) -> ?IFF_VNET_HDR; flag(tun_excl) -> ?IFF_TUN_EXCL; flag(_) -> 0. set_flag(FD, Dev, Flag) -> tunctl:ioctl( FD, ?SIOCSIFFLAGS, <> ). get_flag(FD, Dev) -> {ok, <<_:(16 * 8), Flag:2/native-signed-integer-unit:8, _/binary>>} = procket:ioctl( FD, ?SIOCGIFFLAGS, <> ), {ok, Flag}. get_ifindex(FD, Dev) -> {ok, <<_:(16 * 8), Index:4/native-signed-integer-unit:8, _/binary>>} = procket:ioctl( FD, ?SIOGIFINDEX, <> ), {ok, Index}. int_to_bin(Int) -> <>.