Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>While you can read the list of interfaces and their addresses and masks in standard (if undocumented) Erlang with <code>inet:getifaddrs/0</code>, you can't get the list of routes which provides the other information you need to get the route for address. On most hosts (those that don't have a complicated routing setup as they generally don't forward packets) you usually just need the default route together with the interface addresses and masks in order to figure out how the host will route a packet for a given address.</p> <p>To begin, you need a list of interfaces and their routes. The <code>getifaddrs/0</code> function gives you a proplist by interface and a list of addresses and masks. Because interfaces can have multiple addresses assigned to them you need a little bit of list parsing:</p> <pre><code>routes() -&gt; {ok, IFData} = inet:getifaddrs(), lists:append([ routes(IF, IFOpts) || {IF, IFOpts} &lt;- IFData ]). routes(IF, Opts) -&gt; {_,Routes} = lists:foldl(fun parse_opts/2, {undefined, []}, Opts), [{IF, Route} || Route &lt;- Routes]. parse_opts({addr, Addr}, {undefined, Routes}) -&gt; {{addr, Addr}, Routes}; parse_opts({netmask, Mask}, {{addr, Addr}, Routes}) when tuple_size(Mask) =:= tuple_size(Addr) -&gt; {undefined, [{Addr, Mask} | Routes]}; parse_opts(_, Acc) -&gt; Acc. </code></pre> <p>Now that you have a list of route information <code>Routes::[Route::{Interface::string(), {Addr::tuple, Mask::Tuple}}]</code> you can find the matching routes:</p> <pre><code>routes_for(Targ, Routes) -&gt; [ RT || RT = {_IF, {Addr, Mask}} &lt;- Routes, tuple_size(Targ) =:= tuple_size(Addr), match_route(Targ, Addr, Mask) ]. </code></pre> <p>To match a route you need to mask the target address and compare that to the masked interface address. Masking an address provides the network address.</p> <pre><code>match_route(Targ, Addr, Mask) when tuple_size(Targ) =:= tuple_size(Addr), tuple_size(Targ) =:= tuple_size(Mask) -&gt; lists:all(fun (A) -&gt; A end, [element(I, Targ) band element(I, Mask) =:= element(I, Addr) band element(I, Mask) || I &lt;- lists:seq(1, tuple_size(Targ)) ]). </code></pre> <p>You can then sort the routes in order of preference by comparing the number of high one bits in the mask. Handily, the bytes-of-mask-as-tuple format Erlang uses to represent masks compares directly as below:</p> <pre><code>sort_routes(Routes) -&gt; lists:sort(fun ({_, {_AddrA, MaskA}}, {_, {_AddrB, MaskB}}) -&gt; MaskA &gt; MaskB end, Routes). </code></pre> <p>Now put it all together:</p> <pre><code>route(Targ) -&gt; route(Targ, routes()). route(Targ, Routes) -&gt; sort_routes(routes_for(Targ, Routes)). routes_for(Targ, Routes) -&gt; [ RT || RT = {_IF, {Addr, Mask}} &lt;- Routes, tuple_size(Targ) =:= tuple_size(Addr), match_route(Targ, Addr, Mask) ]. </code></pre> <hr> <p>On my machine right now I have the following routes:</p> <pre><code>[{"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}}}] </code></pre> <p>So when finding a route to 127.0.1.1 (an address in the 127.0.0.0/8 network), I get: <code>route({127,0,0,1}) -&gt; [{"lo0",{{127,0,0,1},{255,0,0,0}}}]</code>. Unfortunately I can't get a route for <code>{8,8,8,8}</code> for instance, because I only have information on directly attached networks. If I add the default route to the mix (via 192.168.1.1), I get:</p> <pre><code>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}}}] </code></pre> <p>From here you'd have to a) come up with that extra route information I had to add manually (call os:cmd("netstat -rn") maybe?) and b) implement a second level of lookup for routes that return gateways (you need to recursively call route until you get back a route with an interface name -- a directly attached network, not a gateway address).</p> <p>The above code is available as a gist: <a href="https://gist.github.com/1247174" rel="noreferrer">interfaces.erl</a></p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload