【问题标题】:Finding routing interface in Erlang在 Erlang 中查找路由接口
【发布时间】:2011-09-27 21:29:25
【问题描述】:

我有一台带有多个网络接口的机器,每个接口都连接到不同的网络。我想从 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。

【问题讨论】:

    标签: routing erlang


    【解决方案1】:

    虽然您可以使用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)) ]).
    

    然后,您可以通过比较掩码中高一位的数量来按优先顺序对路由进行排序。方便的是,Erlang 用来表示掩码的 bytes-of-mask-as-tuple 格式直接比较如下:

    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}) -&gt; [{"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}}}]
    

    从这里你必须 a) 想出我必须手动添加的额外路由信息(调用 os:cmd("netstat -rn") 可能吗?)和 b) 实现第二级路由查找返回网关(您需要递归调用路由,直到您返回具有接口名称的路由 - 直接连接的网络,而不是网关地址)。

    以上代码可作为要点:interfaces.erl

    【讨论】:

    • 我相信上面的答案需要Erlang R14。我们设法解决了我们的父问题,而无需找到路由接口。我会接受答案,因为它确实有效(只是不适合我们)。
    • 是的 - 我认为 tuple_size 是 R14 特定的。你可以用 size 替换它,它应该适用于我认为的大多数版本。
    猜你喜欢
    • 2011-05-06
    • 1970-01-01
    • 1970-01-01
    • 2016-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-09
    相关资源
    最近更新 更多