example: Add simple router example (#12)
This commit is contained in:
parent
07821494dd
commit
5049e0643a
17 changed files with 621 additions and 0 deletions
|
|
@ -70,6 +70,7 @@ $ iex -S mix
|
|||
- learning-switch: Simple Layer2 switch
|
||||
- leader-example: Simple election based multiple controller using Ulf Wiger's Locks Leader
|
||||
- patch\_panel: inteligent patch\_panel example
|
||||
- simple\_router: An OpenFlow controller that emulates layer 3 switch (router).
|
||||
|
||||
License
|
||||
-------
|
||||
|
|
|
|||
BIN
bin/enum_gen
BIN
bin/enum_gen
Binary file not shown.
4
examples/simple_router/.formatter.exs
Normal file
4
examples/simple_router/.formatter.exs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
||||
24
examples/simple_router/.gitignore
vendored
Normal file
24
examples/simple_router/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
||||
# Where 3rd-party dependencies like ExDoc output generated docs.
|
||||
/doc/
|
||||
|
||||
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||
/.fetch
|
||||
|
||||
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||
erl_crash.dump
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# Ignore package tarball (built via "mix hex.build").
|
||||
simple_router-*.tar
|
||||
|
||||
47
examples/simple_router/README.md
Normal file
47
examples/simple_router/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# SimpleRouter
|
||||
|
||||
#### An OpenFlow controller that emulates layer 3 switch (router).
|
||||
|
||||
## Configuration
|
||||
|
||||
```elixir
|
||||
config :simple_router,
|
||||
interfaces: %{
|
||||
"veth1" => %{mac_address: "02:00:00:01:00:01", ip_address: "192.168.1.1/24"},
|
||||
"veth3" => %{mac_address: "02:00:00:01:00:02", ip_address: "192.168.2.1/24"},
|
||||
},
|
||||
routes: %{
|
||||
"0.0.0.0/0" => "192.168.1.2"
|
||||
}
|
||||
```
|
||||
|
||||
## Flow Tables
|
||||
|
||||
```
|
||||
> $ sudo ovs-ofctl dump-flows br0 -OOpenFlow13 --color=auto
|
||||
cookie=0x0, duration=10.217s, table=0, n_packets=3, n_bytes=126, priority=1,arp actions=goto_table:1
|
||||
cookie=0x0, duration=10.217s, table=0, n_packets=19, n_bytes=1862, priority=1,ip actions=goto_table:2
|
||||
cookie=0x0, duration=10.208s, table=1, n_packets=1, n_bytes=42, priority=0,arp,in_port=veth1,arp_tpa=192.168.1.1,arp_op=1 actions=learn(table=4,idle_timeout=300,priority=1,NXM_NX_REG0[]=NXM_OF_ARP_SPA[],load:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[]),push:NXM_OF_ARP_TPA[],push:NXM_NX_ARP_SHA[],push:NXM_OF_ARP_SPA[],push:NXM_OF_ETH_SRC[],pop:NXM_OF_ETH_DST[],pop:NXM_OF_ARP_TPA[],pop:NXM_NX_ARP_THA[],pop:NXM_OF_ARP_SPA[],set_field:2->arp_op,set_field:02:00:00:01:00:01->arp_sha,set_field:02:00:00:01:00:01->eth_src,IN_PORT
|
||||
cookie=0x0, duration=10.207s, table=1, n_packets=1, n_bytes=42, priority=0,arp,in_port=veth1,arp_tpa=192.168.1.1,arp_op=2 actions=learn(table=4,idle_timeout=300,priority=1,NXM_NX_REG0[]=NXM_OF_ARP_SPA[],load:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[])
|
||||
cookie=0x0, duration=10.206s, table=1, n_packets=1, n_bytes=42, priority=0,arp,in_port=veth3,arp_tpa=192.168.2.1,arp_op=1 actions=learn(table=4,idle_timeout=300,priority=1,NXM_NX_REG0[]=NXM_OF_ARP_SPA[],load:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[]),push:NXM_OF_ARP_TPA[],push:NXM_NX_ARP_SHA[],push:NXM_OF_ARP_SPA[],push:NXM_OF_ETH_SRC[],pop:NXM_OF_ETH_DST[],pop:NXM_OF_ARP_TPA[],pop:NXM_NX_ARP_THA[],pop:NXM_OF_ARP_SPA[],set_field:2->arp_op,set_field:02:00:00:01:00:02->arp_sha,set_field:02:00:00:01:00:02->eth_src,IN_PORT
|
||||
cookie=0x0, duration=10.206s, table=1, n_packets=0, n_bytes=0, priority=0,arp,in_port=veth3,arp_tpa=192.168.2.1,arp_op=2 actions=learn(table=4,idle_timeout=300,priority=1,NXM_NX_REG0[]=NXM_OF_ARP_SPA[],load:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[])
|
||||
cookie=0x0, duration=10.205s, table=2, n_packets=0, n_bytes=0, priority=124,ip,nw_dst=192.168.1.0/24 actions=move:NXM_OF_IP_DST[]->NXM_NX_REG0[],goto_table:3
|
||||
cookie=0x0, duration=10.205s, table=2, n_packets=9, n_bytes=882, priority=124,ip,nw_dst=192.168.2.0/24 actions=move:NXM_OF_IP_DST[]->NXM_NX_REG0[],goto_table:3
|
||||
cookie=0x0, duration=10.205s, table=2, n_packets=10, n_bytes=980, priority=0,ip actions=set_field:0xc0a80102->reg0,goto_table:3
|
||||
cookie=0x0, duration=10.212s, table=3, n_packets=10, n_bytes=980, priority=24,reg0=0xc0a80100/0xffffff00 actions=set_field:0x1->reg1,resubmit(,4),resubmit(,5)
|
||||
cookie=0x0, duration=10.212s, table=3, n_packets=9, n_bytes=882, priority=24,reg0=0xc0a80200/0xffffff00 actions=set_field:0x2->reg1,resubmit(,4),resubmit(,5)
|
||||
cookie=0x0, duration=9.891s, table=4, n_packets=9, n_bytes=882, idle_timeout=300, priority=1,reg0=0xc0a80202 actions=load:0x9a9114dd25d8->NXM_OF_ETH_DST[]
|
||||
cookie=0x0, duration=9.855s, table=4, n_packets=9, n_bytes=882, idle_timeout=300, priority=1,reg0=0xc0a80102 actions=load:0x7a69d574651c->NXM_OF_ETH_DST[]
|
||||
cookie=0x0, duration=10.207s, table=4, n_packets=1, n_bytes=98, priority=0,reg1=0x1 actions=controller(userdata=41.52.50.5f.6d.69.73.73.69.6e.67.ff.ff.ff.ff.ff.ff.02.00.00.01.00.01.08.06.00.01.08.00.06.04.00.01.02.00.00.01.00.01.c0.a8.01.01.00.00.00.00.00.00,pause)
|
||||
cookie=0x0, duration=10.206s, table=4, n_packets=0, n_bytes=0, priority=0,reg1=0x2 actions=controller(userdata=41.52.50.5f.6d.69.73.73.69.6e.67.ff.ff.ff.ff.ff.ff.02.00.00.01.00.02.08.06.00.01.08.00.06.04.00.01.02.00.00.01.00.02.c0.a8.02.01.00.00.00.00.00.00,pause)
|
||||
cookie=0x0, duration=10.206s, table=5, n_packets=9, n_bytes=882, priority=1,reg1=0x1 actions=group:1
|
||||
cookie=0x0, duration=10.205s, table=5, n_packets=9, n_bytes=882, priority=1,reg1=0x2 actions=group:2
|
||||
```
|
||||
|
||||
## Group Tables
|
||||
```
|
||||
> $ sudo ovs-ofctl dump-groups br0 -OOpenFlow13 --color=auto
|
||||
OFPST_GROUP_DESC reply (OF1.3) (xid=0x2):
|
||||
group_id=1,type=indirect,bucket=actions=set_field:02:00:00:01:00:01->eth_src,output:veth1
|
||||
group_id=2,type=indirect,bucket=actions=set_field:02:00:00:01:00:02->eth_src,output:veth3
|
||||
```
|
||||
20
examples/simple_router/config/config.exs
Normal file
20
examples/simple_router/config/config.exs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# This file is responsible for configuring your application
|
||||
# and its dependencies with the aid of the Mix.Config module.
|
||||
use Mix.Config
|
||||
|
||||
config :tres,
|
||||
protocol: :tcp,
|
||||
port: 6653,
|
||||
max_connections: 10,
|
||||
num_acceptors: 10,
|
||||
callback_module: SimpleRouter.Openflow.Controller,
|
||||
callback_args: []
|
||||
|
||||
config :simple_router,
|
||||
interfaces: %{
|
||||
"veth1" => %{mac_address: "02:00:00:01:00:01", ip_address: "192.168.1.1/24"},
|
||||
"veth3" => %{mac_address: "02:00:00:01:00:02", ip_address: "192.168.2.1/24"},
|
||||
},
|
||||
routes: %{
|
||||
"0.0.0.0/0" => "192.168.1.2"
|
||||
}
|
||||
18
examples/simple_router/lib/simple_router.ex
Normal file
18
examples/simple_router/lib/simple_router.ex
Normal 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
|
||||
9
examples/simple_router/lib/simple_router/application.ex
Normal file
9
examples/simple_router/lib/simple_router/application.ex
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
defmodule SimpleRouter.Application do
|
||||
@moduledoc false
|
||||
|
||||
use Application
|
||||
|
||||
def start(_type, _args) do
|
||||
SimpleRouter.Supervisor.start_link()
|
||||
end
|
||||
end
|
||||
55
examples/simple_router/lib/simple_router/config.ex
Normal file
55
examples/simple_router/lib/simple_router/config.ex
Normal 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
|
||||
137
examples/simple_router/lib/simple_router/openflow/controller.ex
Normal file
137
examples/simple_router/lib/simple_router/openflow/controller.ex
Normal 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
|
||||
222
examples/simple_router/lib/simple_router/openflow/flow_tables.ex
Normal file
222
examples/simple_router/lib/simple_router/openflow/flow_tables.ex
Normal 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
|
||||
|
|
@ -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
|
||||
17
examples/simple_router/lib/simple_router/supervisor.ex
Normal file
17
examples/simple_router/lib/simple_router/supervisor.ex
Normal 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
|
||||
27
examples/simple_router/mix.exs
Normal file
27
examples/simple_router/mix.exs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
defmodule SimpleRouter.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :simple_router,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.7",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger, :tres, :pkt],
|
||||
mod: {SimpleRouter.Application, []}
|
||||
]
|
||||
end
|
||||
|
||||
defp deps do
|
||||
[
|
||||
{:tres, path: "../../../tres"},
|
||||
{:pkt, github: "msantos/pkt"}
|
||||
]
|
||||
end
|
||||
end
|
||||
7
examples/simple_router/mix.lock
Normal file
7
examples/simple_router/mix.lock
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
%{
|
||||
"eovsdb": {:git, "https://github.com/shun159/eovsdb.git", "1ff1572708d72fd25631c681f2102407903252a3", [branch: "master"]},
|
||||
"jsone": {:git, "https://github.com/sile/jsone.git", "b23d312a5ed051ea7ad0989a9f2cb1a9c3f9a502", [tag: "1.4.6"]},
|
||||
"pkt": {:git, "https://github.com/msantos/pkt.git", "ff0e9a7d28cdae941bce935602cd252cad1ea296", []},
|
||||
"ranch": {:hex, :ranch, "1.6.2", "6db93c78f411ee033dbb18ba8234c5574883acb9a75af0fb90a9b82ea46afa00", [:rebar3], [], "hexpm"},
|
||||
"uuid": {:git, "https://github.com/avtobiff/erlang-uuid.git", "585c2474afb4a597ae8c8bf6d21e5a9c73f18e0b", [tag: "v0.5.0"]},
|
||||
}
|
||||
8
examples/simple_router/test/simple_router_test.exs
Normal file
8
examples/simple_router/test/simple_router_test.exs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
defmodule SimpleRouterTest do
|
||||
use ExUnit.Case
|
||||
doctest SimpleRouter
|
||||
|
||||
test "greets the world" do
|
||||
assert SimpleRouter.hello() == :world
|
||||
end
|
||||
end
|
||||
1
examples/simple_router/test/test_helper.exs
Normal file
1
examples/simple_router/test/test_helper.exs
Normal file
|
|
@ -0,0 +1 @@
|
|||
ExUnit.start()
|
||||
Loading…
Add table
Add a link
Reference in a new issue