Elixirでsoft ware routerを作っているので、今のところできているrouting tableについて実装をメモ的に残します。
動作環境
-
docker container
- OS: debian 8
elixir : 1.5.2
実装
routing tableの構成
name | description |
---|---|
id | routing tableのレコードの識別 |
destination | 送信先IP |
netmask | net mask |
nexthop | 次の宛先。直接つながってる場合は「connected」が入る |
interface | 送信するinterface |
metric | メトリック |
routeType | スタティックかダイナミックか |
sourceOfRoute | ルーティングプロトコル |
routeAge | レコードが追加された時間 |
routeInformation | ルーティングの情報 |
mtu | MTU |
保存先はETSを使う。データのIOでどれぐらいかかるか検証する必要あるかもしれないですけど、とりあえずETSで。
初期化時に直接接続されているネットワークをrouting tableに追加します。
defmodule Routing do
def init() do
table = :ets.new(:routing_table, [:set, :protected, :named_table])
:interface.get_interfaces()
|> Enum.each(fn({name, interface}) -> :ets.insert_new(:routing_table, {name,
{:netmask, Tuple.to_list(interface[:netmask])}, {:dest, Tuple.to_list(interface[:addr])}, {:nexthop, :connected}, name, 0, "static", "link", :calendar.local_time(), "-", 1500}) end)
end
interfaceの取得はErlangで取得してる。interfaceは"eth"がつくものに限定しています。
get_interface() ->
{ok, IfLists} = inet:getifaddrs(),
Listen = lists:filter(fun(Elm) -> interface_list(Elm) end, IfLists).
interface_list(Elm) ->
{Name, _} = Elm,
string:find(Name, "eth") =/= nomatch.
routing tableから送信先のIPから次に送信する宛先を決定します。
:ets.match(:user_lookup, {:"_", :"$1", :"$2", :"$3", :"_", :"_", :"_", :"_", :"_", :"_", :"_"})
|> Enum.map(fn(record) -> [{:address, List.zip([dest, record[:netmask], record[:dest]])}, {:nexthop, record[:nexthop]}] end)
|> Enum.filter(fn(record) -> record[:address]
|> Enum.all?(fn(octed) -> (elem(octed, 1) &&& elem(octed, 0)) == (elem(octed, 1) &&& elem(octed, 2)) end)
end)
|> Enum.map(fn(record) -> [{:dest, (for octed <- record[:address], do: elem(octed, 0))}, {:nexthop, record[:nexthop]}] end)
複雑になってしまったんで、軽く説明を付けておきます。
宛先IPとnetmaskとrouting tableに登録されている宛先のIPでオクテットごとにTupleを作っていきます。
# 例
# 宛先IP [192, 168, 0, 10]
# netmask [255, 255, 255, 0]
# routing tableに登録されている宛先IP [192, 168, 0, 0](直接接続されているnetwork)
# result : [{192, 255, 192}, {168, 255, 168}, {0, 255, 0}, {0, 0, 0}]
|> Enum.map(fn(record) -> [{:address, List.zip([dest, record[:netmask], record[:dest]])}, {:nexthop, record[:nexthop]}] end)
まとめたTupleから宛先とrouting tableに登録されている宛先のIPがnetmaskでマスクした結果が一致しているものを取得
|> Enum.filter(fn(record) -> record[:address]
|> Enum.all?(fn(octed) -> (elem(octed, 1) &&& elem(octed, 0)) == (elem(octed, 1) &&& elem(octed, 2)) end)
end)
宛先にマッチしたrouting tableのrecordでrouting tableに登録されている宛先のIPとnexthopを返します。
|> Enum.map(fn(record) -> [{:dest, (for octed <- record[:address], do: elem(octed, 0))}, {:nexthop, record[:nexthop]}] end)
これで宛先が決まりました。まだ実装してないですが、netmaskのロンゲストマッチ、コストでソートができてないため、それは今後実装予定。
とりあえず、これでETSにrouting tableのレコードの追加、検索ができるようになりました。
次は、宛先がARP tableに次に送信するホストの情報があるか調べて、なかった場合はARPのrequestを送って登録処理を実装します。