example: Add simple router example (#12)

This commit is contained in:
Eishun Kondoh 2018-10-09 18:55:41 +09:00 committed by GitHub
parent 07821494dd
commit 5049e0643a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 621 additions and 0 deletions

View file

@ -0,0 +1,18 @@
defmodule SimpleRouter do
@moduledoc """
Documentation for SimpleRouter.
"""
@doc """
Hello world.
## Examples
iex> SimpleRouter.hello()
:world
"""
def hello do
:world
end
end

View file

@ -0,0 +1,9 @@
defmodule SimpleRouter.Application do
@moduledoc false
use Application
def start(_type, _args) do
SimpleRouter.Supervisor.start_link()
end
end

View file

@ -0,0 +1,55 @@
defmodule SimpleRouter.Config do
@moduledoc false
alias Tres.IPv4Address
@spec interfaces() :: %{String.t() => map()}
def interfaces do
:interfaces
|> get_env(%{})
|> Enum.reduce(%{}, &interfaces_to_erl/2)
end
@spec routes() :: [tuple()]
def routes do
:routes
|> get_env(%{})
|> Enum.reduce([], &routes_to_erl/2)
end
# private functions
defp interfaces_to_erl({ifname, %{mac_address: mac, ip_address: ip}}, acc) do
{ipaddr, mask} = IPv4Address.parse(ip)
entry =
%{
mac_address: String.replace(mac, ~r/:/, ""),
ip_address: ipaddr,
subnet_mask: mask,
network_address: IPv4Address.to_network({ipaddr, mask}),
prefix_length: get_cidr(ip)
}
Map.merge(acc, %{ifname => entry})
end
defp routes_to_erl({dst, nexthop}, acc) do
{dstaddr, mask} = IPv4Address.parse(dst)
{nexthop, _netmask} = IPv4Address.parse(nexthop)
entry = %{dst: dstaddr, mask: mask, prefix_length: get_cidr(dst), nexthop: nexthop}
[entry | acc]
end
defp get_cidr(ip) do
case String.split(ip, ~r/\//) do
[^ip] ->
32
[_addr, cidr] ->
String.to_integer(cidr)
end
end
defp get_env(key, default),
do: Application.get_env(:simple_router, key, default)
end

View file

@ -0,0 +1,137 @@
defmodule SimpleRouter.Openflow.Controller do
@moduledoc """
Emulates Router
"""
use GenServer
use Tres.Controller
import Logger
alias SimpleRouter.Config
alias SimpleRouter.Openflow.FlowTables
alias SimpleRouter.Openflow.GroupTables
defmodule State do
@moduledoc false
defstruct [
datapath_id: nil,
ports: %{}
]
end
def start_link([{datapath_id, _aux_id}, _start_args]) do
GenServer.start_link(__MODULE__, [datapath_id])
end
def init([datapath_id]) do
:ok = debug("Switch Connected: datapath_id = #{datapath_id}")
{:ok, %State{datapath_id: datapath_id}, {:continue, :init_datapath}}
end
def handle_continue(:init_datapath, state) do
:ok = init_datapath(state.datapath_id)
{:noreply, state, {:continue, :init_interface}}
end
def handle_continue(:init_interface, state) do
port_desc = PortDesc.Request.new()
:ok = send_message(port_desc, state.datapath_id)
{:noreply, state}
end
def handle_info(%PortDesc.Reply{datapath_id: dpid, ports: ports}, state) do
:ok = debug("port_reply from dpid: #{dpid}")
ports = handle_ports(ports)
routes = Config.routes()
:ok = FlowTables.classifier(dpid)
:ok = init_egress_groups(ports, dpid)
:ok = init_interface_lookup_flows(ports, dpid)
:ok = init_arp_handler_flows(ports, dpid)
:ok = init_egress_flows(ports, dpid)
:ok = init_connected_routes(ports, dpid)
:ok = init_static_routes(routes, dpid)
{:noreply, %{state | ports: ports}}
end
def handle_info(%NxPacketIn2{userdata: <<"ARP_missing", arp_req::bytes>>} = pin, state) do
:ok = debug("ARP Entry missing: #{inspect(pin)}")
nexthop_address = pin.metadata[:reg0]
send_packet_out(
packet_in: pin,
metadata: Keyword.replace!(pin.metadata, :in_port, :controller),
data: <<arp_req::bytes, nexthop_address::32>>
)
{:noreply, state}
end
def handle_info(info, state) do
:ok = warn("Unhandled info received: #{inspect(info)}")
{:noreply, state}
end
# private functions
@spec handle_ports([%Openflow.Port{}]) :: [map()]
defp handle_ports(ports) do
ifaces = Config.interfaces()
handle_ports([], ports, ifaces)
end
defp handle_ports(acc, [], _ifaces), do: acc
defp handle_ports(acc0, [port|rest], ifaces) do
case Map.get(ifaces, port.name) do
iface0 when is_map(iface0) ->
iface = Map.merge(iface0, %{number: port.number})
handle_ports([iface|acc0], rest, ifaces)
_ ->
handle_ports(acc0, rest, ifaces)
end
end
@spec init_egress_groups([map()], String.t()) :: :ok
defp init_egress_groups(ifaces, datapath_id),
do: Enum.each(ifaces, &GroupTables.egress(&1, datapath_id))
@spec init_interface_lookup_flows([map()], String.t()) :: :ok
defp init_interface_lookup_flows(ifaces, datapath_id),
do: Enum.each(ifaces, &FlowTables.lookup_iface(&1, datapath_id))
@spec init_arp_handler_flows([map()], String.t()) :: :ok
defp init_arp_handler_flows(ifaces, datapath_id),
do: Enum.each(ifaces, &FlowTables.arp_handlers(&1, datapath_id))
@spec init_egress_flows([map()], String.t()) :: :ok
defp init_egress_flows(ifaces, datapath_id),
do: Enum.each(ifaces, &FlowTables.egress(&1, datapath_id))
@spec init_connected_routes([map()], String.t()) :: :ok
defp init_connected_routes(ifaces, datapath_id),
do: Enum.each(ifaces, &FlowTables.connected_route(&1, datapath_id))
@spec init_static_routes([map()], String.t()) :: :ok
defp init_static_routes(routes, datapath_id),
do: Enum.each(routes, &FlowTables.static_route(&1, datapath_id))
@spec init_datapath(String.t()) :: :ok
defp init_datapath(datapath_id) do
:ok = set_config(datapath_id)
:ok = set_packet_in_format(datapath_id)
end
@spec set_config(String.t()) :: :ok
defp set_config(datapath_id) do
set_config = SetConfig.new(miss_send_len: :no_buffer)
:ok = send_message(set_config, datapath_id)
end
@spec set_packet_in_format(String.t()) :: :ok
defp set_packet_in_format(datapath_id) do
pin_format = NxSetPacketInFormat.new(:nxt_packet_in2)
:ok = send_message(pin_format, datapath_id)
end
end

View file

@ -0,0 +1,222 @@
defmodule SimpleRouter.Openflow.FlowTables do
@moduledoc """
Flow Table Definitions
"""
use Tres.Controller
alias Tres.IPv4Address
alias Tres.MacAddress
@classifier_table_id 0
@arp_handler_table_id 1
@routing_table_id 2
@interface_lookup_table_id 3
@arp_lookup_table_id 4
@egress_table_id 5
def classifier(datapath_id) do
# ARP
send_flow_mod_add(
datapath_id,
table_id: @classifier_table_id,
priority: 1,
match: Match.new(eth_type: 0x0806),
instructions: [GotoTable.new(@arp_handler_table_id)]
)
# IPv4
send_flow_mod_add(
datapath_id,
table_id: @classifier_table_id,
priority: 1,
match: Match.new(eth_type: 0x0800),
instructions: [GotoTable.new(@routing_table_id)]
)
end
def connected_route(iface, datapath_id) do
send_flow_mod_add(
datapath_id,
table_id: @routing_table_id,
priority: 100 + iface.prefix_length,
match: Match.new(
eth_type: 0x0800,
ipv4_dst: {iface.network_address, iface.subnet_mask}
),
instructions: [
ApplyActions.new(NxRegMove.new(src_field: :ipv4_dst, dst_field: :reg0)),
GotoTable.new(@interface_lookup_table_id)
]
)
end
def static_route(route, datapath_id) do
send_flow_mod_add(
datapath_id,
table_id: @routing_table_id,
priority: route.prefix_length,
match: Match.new(
eth_type: 0x0800,
ipv4_dst: {route.dst, route.mask}
),
instructions: [
ApplyActions.new(SetField.new({:reg0, IPv4Address.to_int(route.nexthop)})),
GotoTable.new(@interface_lookup_table_id)
]
)
end
def arp_handlers(iface, datapath_id) do
:ok = arp_request_handler(iface, datapath_id)
:ok = arp_reply_handler(iface, datapath_id)
:ok = arp_entry_is_missing(iface, datapath_id)
end
def arp_entry_is_missing(iface, datapath_id) do
send_flow_mod_add(
datapath_id,
priority: 0,
table_id: @arp_lookup_table_id,
match: Match.new(reg1: iface.number),
instructions: [
ApplyActions.new(
NxController2.new(userdata: arp_packet(iface), pause: true)
)
]
)
end
def lookup_iface(iface, datapath_id) do
match =
Match.new(
reg0: {
IPv4Address.to_int(iface.network_address),
IPv4Address.to_int(iface.subnet_mask)
}
)
send_flow_mod_add(
datapath_id,
table_id: @interface_lookup_table_id,
priority: iface.prefix_length,
match: match,
instructions: [
ApplyActions.new([
SetField.new({:reg1, iface.number}),
NxResubmitTable.new(@arp_lookup_table_id),
NxResubmitTable.new(@egress_table_id)
])
]
)
end
def egress(iface, datapath_id) do
send_flow_mod_add(
datapath_id,
table_id: @egress_table_id,
priority: 1,
match: Match.new(reg1: iface.number),
instructions: ApplyActions.new(Group.new(iface.number))
)
end
# private functions
defp arp_request_handler(iface, datapath_id) do
send_flow_mod_add(
datapath_id,
table_id: @arp_handler_table_id,
priority: 0,
match: Match.new(
in_port: iface.number,
eth_type: 0x0806,
# Request
arp_op: 0x1,
arp_tpa: iface.ip_address
),
instructions: ApplyActions.new(arp_respond_and_learn_actions(iface.mac_address))
)
end
defp arp_reply_handler(iface, datapath_id) do
send_flow_mod_add(
datapath_id,
table_id: @arp_handler_table_id,
priority: 0,
match: Match.new(
in_port: iface.number,
eth_type: 0x0806,
# Reply
arp_op: 0x2,
arp_tpa: iface.ip_address
),
instructions: ApplyActions.new(learn_arp_reply_actions())
)
end
defp arp_respond_and_learn_actions(mac) do
[
NxLearn.new(
priority: 1,
table_id: @arp_lookup_table_id,
idle_timeout: 300,
flow_specs: [
NxFlowSpecMatch.new(src: :arp_spa, dst: :reg0),
NxFlowSpecLoad.new(src: :eth_src, dst: :eth_dst)
]
),
NxStackPush.new(field: :arp_tpa),
NxStackPush.new(field: :arp_sha),
NxStackPush.new(field: :arp_spa),
NxStackPush.new(field: :eth_src),
NxStackPop.new(field: :eth_dst),
NxStackPop.new(field: :arp_tpa),
NxStackPop.new(field: :arp_tha),
NxStackPop.new(field: :arp_spa),
SetField.new({:arp_op, 0x2}),
SetField.new({:arp_sha, mac}),
SetField.new({:eth_src, mac}),
Output.new(:in_port)
]
end
defp learn_arp_reply_actions do
[
NxLearn.new(
priority: 1,
table_id: @arp_lookup_table_id,
idle_timeout: 300,
flow_specs: [
NxFlowSpecMatch.new(src: :arp_spa, dst: :reg0),
NxFlowSpecLoad.new(src: :eth_src, dst: :eth_dst)
]
)
]
end
@arphrd_ether 1
@eth_p_ip 0x0800
defp arp_packet(iface) do
ether_header = <<
0xffffffffffff::48, # destination ethernet address
(MacAddress.to_i(iface.mac_address))::48, # source ethernet address
0x0806::16 # ethernet type
>>
# Target Protocol Address will append in NX_PACKET_IN2
arp_header = <<
@arphrd_ether::16, # hardware type
@eth_p_ip::16, # protocol type
6::8, # hardware address length
4::8, # protocol address length
1::16, # ARPOP_REQUEST
(MacAddress.to_i(iface.mac_address))::48, # Source Hardware Address
(IPv4Address.to_int(iface.ip_address))::32, # Source Protocol Address
0x000000000000::48 # Target Hardware Address
>>
<<"ARP_missing", ether_header::bytes, arp_header::bytes>>
end
end

View file

@ -0,0 +1,24 @@
defmodule SimpleRouter.Openflow.GroupTables do
@moduledoc """
Group Table Definitions
"""
use Tres.Controller
def egress(iface, datapath_id) do
bucket =
Openflow.Bucket.new(
actions: [
SetField.new({:eth_src, iface.mac_address}),
Output.new(iface.number)
]
)
send_group_mod_add(
datapath_id,
type: :indirect,
group_id: iface.number,
buckets: [bucket]
)
end
end

View file

@ -0,0 +1,17 @@
defmodule SimpleRouter.Supervisor do
@moduledoc """
Top Level supervisor
"""
use Supervisor
@children []
def start_link do
Supervisor.start_link(__MODULE__, [], name: __MODULE__)
end
def init(_args) do
Supervisor.init(@children, strategy: :one_for_one)
end
end