2011-09-27 44 views
2

我有一台机器有多个网络接口,每个接口都连接到不同的网络。我想从一个Erlang应用程序中找出将用于连接到给定主机的接口。在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。

回答

7

虽然您可以在inet:getifaddrs/0的标准(如果未公开)Erlang中读取接口列表及其地址和掩码,但无法获取提供获取地址路由所需其他信息的路由列表。在大多数主机上(那些没有复杂路由设置的主机,因为它们通常不转发数据包)通常只需要默认路由以及接口地址和掩码,以便确定主机如何路由数据包一个给定的地址。

首先,您需要一个接口及其路由列表。 getifaddrs/0函数通过接口为您提供一个proplist,以及地址和掩码列表。由于接口可以分配有多个地址,你需要列表分析一点点:

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_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)) ]). 

然后,您可以通过比较掩码中高位一位的数量,按照优先顺序排列路由。得心应手,字节-的面罩,作为元组格式二郎用来表示口罩比较如下直接:

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({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}}}] 

从这里你不得不一)拿出额外的路由信息​​,我不得不手动添加(调用OS :cmd(“netstat -rn”)也许?)和b)为返回网关的路由实现第二级查找(您需要递归调用路由,直到找回带有接口名称的路由 - 直接连接的网络,不是网关地址)。

上面的代码可以作为一个要点:interfaces.erl

+0

以上回答需要二郎R14相信。我们设法解决了我们的家长问题,而无需找到路由接口。我会接受答案,因为它确实有效(不适合我们)。 –

+0

Yup - tuple_size是R14的具体我想。你可以用大小来替换它,它应该可以在我认为的大多数版本中工作。 – archaelus