Поиск интерфейса маршрутизации в Erlang - PullRequest
2 голосов
/ 28 сентября 2011

У меня есть машина с несколькими сетевыми интерфейсами, каждый из которых подключается к другой сети. Я хочу из приложения 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.

1 Ответ

7 голосов
/ 28 сентября 2011

Хотя вы можете прочитать список интерфейсов, их адреса и маски в стандартном (если не задокументированном) Erlang с помощью inet:getifaddrs/0, вы не можете получить список маршрутов, который предоставляет другую информацию, необходимую для получения маршрута для адреса.,На большинстве хостов (тех, которые не имеют сложной настройки маршрутизации, поскольку они обычно не пересылают пакеты), вам обычно просто нужен маршрут по умолчанию вместе с адресами интерфейса и масками, чтобы выяснить, как хост будет маршрутизировать пакет длязаданный адрес.

Для начала вам нужен список интерфейсов и их маршрутов.Функция 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_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»), может быть?) и б) реализует второй уровень поиска маршрутов, которые возвращают шлюзы (вам нужно рекурсивно вызывать маршрут до тех пор, пока вы не получите маршрут с именем интерфейса - напрямую подключеннымсеть, а не адрес шлюза).

Приведенный выше код доступен в виде гисти: interfaces.erl

...