我有一台具有多个网络接口的机器,每个接口连接到不同的网络 . 我想从Erlang应用程序中找到将用于连接到给定主机的接口 .
例如,我有一台带有接口eth0和eth1的机器 . eth0位于10.x.x.x网络上,eth1位于192.168.0.x网络上 . 我想要一个给ip地址10.0.1.2的函数告诉我eth0并给出ip地址192.168.0.74会告诉我eth1 .
虽然您可以使用 inet:getifaddrs/0 读取标准(如果未记录的)Erlang中的接口及其地址和掩码列表,但您可以设置复杂的路由设置,因为它们通常不转发数据包)您通常只需要默认路由接口地址和掩码,以便弄清楚主机如何为给定地址路由数据包 .
inet:getifaddrs/0
首先,您需要一个接口列表及其路由 . getifaddrs/0 函数通过接口和地址和掩码列表为您提供了一个proplist . 因为接口可以分配多个地址,所以需要进行一些列表解析:
getifaddrs/0
routes() -> {ok, IFData} = inet:getifaddrs(), lists:append([ routes(IF, IFOpts) || {IF, IFOpts} <- IFData ]). routes(IF, Opts) -> {_,Routes} = lists:foldl(fun parse_opts/2, {undefined, []}, Opts), [{IF, Route} || Route <- Routes]. parse_opts({addr, Addr}, {undefined, Routes}) -> {{addr, Addr}, Routes}; parse_opts({netmask, Mask}, {{addr, Addr}, Routes}) when tuple_size(Mask) =:= tuple_size(Addr) -> {undefined, [{Addr, Mask} | Routes]}; parse_opts(_, Acc) -> Acc.
现在你有一个路线信息列表 Routes::[Route::{Interface::string(), {Addr::tuple, Mask::Tuple}}] 你可以找到匹配的路线:
Routes::[Route::{Interface::string(), {Addr::tuple, Mask::Tuple}}]
routes_for(Targ, Routes) -> [ RT || RT = {_IF, {Addr, Mask}} <- Routes, tuple_size(Targ) =:= tuple_size(Addr), match_route(Targ, Addr, Mask) ].
要匹配路由,需要屏蔽目标地址并将其与屏蔽的接口地址进行比较 . 屏蔽地址提供网络地址 .
match_route(Targ, Addr, Mask) when tuple_size(Targ) =:= tuple_size(Addr), tuple_size(Targ) =:= tuple_size(Mask) -> lists:all(fun (A) -> A end, [element(I, Targ) band element(I, Mask) =:= element(I, Addr) band element(I, Mask) || I <- lists:seq(1, tuple_size(Targ)) ]).
然后,您可以通过比较掩码中的高位数来按优先顺序对路径进行排序 . 很简单,Erlang用于表示掩码比较的掩码字节格式直接如下所示:
sort_routes(Routes) -> lists:sort(fun ({_, {_AddrA, MaskA}}, {_, {_AddrB, MaskB}}) -> MaskA > MaskB end, Routes).
现在把它们放在一起:
route(Targ) -> route(Targ, routes()). route(Targ, Routes) -> sort_routes(routes_for(Targ, Routes)). routes_for(Targ, Routes) -> [ RT || RT = {_IF, {Addr, Mask}} <- Routes, tuple_size(Targ) =:= tuple_size(Addr), match_route(Targ, Addr, Mask) ].
在我的机器上,我现在有以下路线:
[{"lo0", {{0,0,0,0,0,0,0,1}, {65535,65535,65535,65535,65535,65535,65535,65535}}}, {"lo0",{{127,0,0,1},{255,0,0,0}}}, {"lo0", {{65152,0,0,0,0,0,0,1},{65535,65535,65535,65535,0,0,0,0}}}, {"en0",{{192,168,1,7},{255,255,255,0}}}, {"en0", {{65152,0,0,0,1548,52991,65242,57142}, {65535,65535,65535,65535,0,0,0,0}}}, {"vmnet1",{{172,16,0,1},{255,255,255,0}}}, {"vmnet8",{{192,168,148,1},{255,255,255,0}}}]
因此,当找到127.0.1.1(127.0.0.0/8网络中的地址)的路由时,我得到: route({127,0,0,1}) -> [{"lo0",{{127,0,0,1},{255,0,0,0}}}] . 不幸的是,我无法获得 {8,8,8,8} 的路由,因为我只有关于直连网络的信息 . 如果我将默认路由添加到混合(通过192.168.1.1),我得到:
route({127,0,0,1}) -> [{"lo0",{{127,0,0,1},{255,0,0,0}}}]
{8,8,8,8}
route({8,8,8,8}, routes() ++ [{{192,168,1,1}, {{0,0,0,0},{0,0,0,0}}}]). [{{192,168,1,1},{{0,0,0,0},{0,0,0,0}}}] route({127,0,0,1}, routes() ++ [{{192,168,1,1}, {{0,0,0,0},{0,0,0,0}}}]). [{"lo0",{{127,0,0,1},{255,0,0,0}}}, {{192,168,1,1},{{0,0,0,0},{0,0,0,0}}}]
从这里你必须a)提出我必须手动添加的额外路由信息(调用os:cmd(“netstat -rn”)可能?)和b)为返回网关的路由实现第二级查找(您需要递归调用路由,直到您返回具有接口名称的路由 - 直接连接的网络,而不是网关地址) .
以上代码可作为要点:interfaces.erl
1 回答
虽然您可以使用
inet:getifaddrs/0
读取标准(如果未记录的)Erlang中的接口及其地址和掩码列表,但您可以设置复杂的路由设置,因为它们通常不转发数据包)您通常只需要默认路由接口地址和掩码,以便弄清楚主机如何为给定地址路由数据包 .首先,您需要一个接口列表及其路由 .
getifaddrs/0
函数通过接口和地址和掩码列表为您提供了一个proplist . 因为接口可以分配多个地址,所以需要进行一些列表解析:现在你有一个路线信息列表
Routes::[Route::{Interface::string(), {Addr::tuple, Mask::Tuple}}]
你可以找到匹配的路线:要匹配路由,需要屏蔽目标地址并将其与屏蔽的接口地址进行比较 . 屏蔽地址提供网络地址 .
然后,您可以通过比较掩码中的高位数来按优先顺序对路径进行排序 . 很简单,Erlang用于表示掩码比较的掩码字节格式直接如下所示:
现在把它们放在一起:
在我的机器上,我现在有以下路线:
因此,当找到127.0.1.1(127.0.0.0/8网络中的地址)的路由时,我得到:
route({127,0,0,1}) -> [{"lo0",{{127,0,0,1},{255,0,0,0}}}]
. 不幸的是,我无法获得{8,8,8,8}
的路由,因为我只有关于直连网络的信息 . 如果我将默认路由添加到混合(通过192.168.1.1),我得到:从这里你必须a)提出我必须手动添加的额外路由信息(调用os:cmd(“netstat -rn”)可能?)和b)为返回网关的路由实现第二级查找(您需要递归调用路由,直到您返回具有接口名称的路由 - 直接连接的网络,而不是网关地址) .
以上代码可作为要点:interfaces.erl