Fix to handler start_link fail
This commit is contained in:
parent
0df6336f5e
commit
658b7448da
17 changed files with 17 additions and 2012 deletions
|
|
@ -4,14 +4,12 @@
|
|||
|
||||
Tres is a framework and set of helper libraries to develop OpenFlow controllers in Elixir.
|
||||
|
||||
the purpose of this project is to design and prototype an experimental network switch controller that implements the OpenFlow 1.3.x. The project explores the scalability and robustness of such controllers on a scale much larger than typically considered: 100,000s of endpoints, 10,000s of switches.
|
||||
|
||||
## Installation
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:tres, git: "https://gh.iiji.jp/Isono/tres", branch: "develop"}
|
||||
{:tres, github: "shun159/tres", branch: "develop"}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
|
|
|||
20
examples/heckle/.gitignore
vendored
20
examples/heckle/.gitignore
vendored
|
|
@ -1,20 +0,0 @@
|
|||
# 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
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
# Heckle
|
||||
|
||||
**TODO: Add description**
|
||||
|
||||
## Installation
|
||||
|
||||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||
by adding `heckle` to your list of dependencies in `mix.exs`:
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:heckle, "~> 0.1.0"}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
||||
be found at [https://hexdocs.pm/heckle](https://hexdocs.pm/heckle).
|
||||
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
# This file is responsible for configuring your application
|
||||
# and its dependencies with the aid of the Mix.Config module.
|
||||
use Mix.Config
|
||||
|
||||
config :heckle,
|
||||
vlan_tagging: true,
|
||||
vlan_id: 123,
|
||||
vlan_trunk1: "1",
|
||||
vlan_trunk2: "4",
|
||||
access_port1: "2",
|
||||
access_port2: "3",
|
||||
receiver_mac: "1a2c3d0e0191",
|
||||
receiver_ip: {192,168,123,1},
|
||||
sender_mac: "cec9bd531dfc",
|
||||
inside_local: {192,168,11,1},
|
||||
outside_local: {192,168,123,2},
|
||||
flow_pattern: :nat # :nat || :bum
|
||||
|
||||
config :tres,
|
||||
protocol: :tcp,
|
||||
port: 6633,
|
||||
max_connections: 10,
|
||||
num_acceptors: 10,
|
||||
callback_module: Heckle.Controller,
|
||||
callback_args: ["0002b05ada98d790"]
|
||||
|
||||
config :logger,
|
||||
level: :info,
|
||||
format: "$date $time [$level] $metadata$message\n",
|
||||
metadata: [:application],
|
||||
handle_otp_reports: true
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
defmodule Heckle do
|
||||
@moduledoc """
|
||||
Documentation for Heckle.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Hello world.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Heckle.hello
|
||||
:world
|
||||
|
||||
"""
|
||||
def hello do
|
||||
:world
|
||||
end
|
||||
end
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
defmodule Heckle.Application do
|
||||
# See https://hexdocs.pm/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@moduledoc false
|
||||
|
||||
use Application
|
||||
|
||||
def start(_type, _args) do
|
||||
# List all child processes to be supervised
|
||||
children = [
|
||||
# Starts a worker by calling: Heckle.Worker.start_link(arg)
|
||||
# {Heckle.Worker, arg},
|
||||
]
|
||||
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: Heckle.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
end
|
||||
|
|
@ -1,201 +0,0 @@
|
|||
defmodule Heckle.Controller do
|
||||
use GenServer
|
||||
use Bitwise
|
||||
use Tres.Controller
|
||||
|
||||
import Logger
|
||||
|
||||
alias Heckle.PipelineProfiles
|
||||
alias Heckle.FlowPatterns
|
||||
|
||||
defmodule State do
|
||||
defstruct [
|
||||
dpid: nil,
|
||||
conn_ref: nil,
|
||||
access_port1_name: nil,
|
||||
access_port2_name: nil,
|
||||
trunk_port1_name: nil,
|
||||
trunk_port2_name: nil,
|
||||
access_port1: nil,
|
||||
access_port2: nil,
|
||||
trunk_port1: nil,
|
||||
trunk_port2: nil,
|
||||
vlan_tagging: nil,
|
||||
vlan_id: nil,
|
||||
receiver_mac: nil,
|
||||
receiver_ip: nil,
|
||||
sender_mac: nil,
|
||||
inside_local: nil,
|
||||
outside_local: nil,
|
||||
flow_pattern: nil
|
||||
]
|
||||
end
|
||||
|
||||
def start_link(dpid, args) do
|
||||
GenServer.start(__MODULE__, [dpid, args])
|
||||
end
|
||||
|
||||
def init([{dpid, _aux_id}, [dpid]]) do
|
||||
:ok = info("Switch Ready: dpid: #{inspect(dpid)} on #{inspect(self())}")
|
||||
state = init_state(dpid)
|
||||
{:ok, state}
|
||||
end
|
||||
def init([{dpid, _aux_id}, [_dpid]]) do
|
||||
:ok = info("Switch Ready: dpid: #{inspect(dpid)} but not acceptable")
|
||||
:ignore
|
||||
end
|
||||
|
||||
def handle_cast(:send_flows, state) do
|
||||
:ok = print_flows(state)
|
||||
|
||||
state
|
||||
|> FlowPatterns.flows
|
||||
|> Enum.each(&send_flow_mod_add(state.dpid, &1))
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(%Desc.Reply{mfr_desc: "Aruba"} = desc, %State{dpid: dpid} = state) do
|
||||
info("Switch Desc: mfr = #{desc.mfr_desc} hw = #{desc.hw_desc} sw = #{desc.sw_desc}")
|
||||
:ok = PipelineProfiles.of_aruba()
|
||||
|> TableFeatures.Request.new
|
||||
|> send_message(dpid)
|
||||
{:noreply, state}
|
||||
end
|
||||
def handle_info(%Desc.Reply{} = desc, state) do
|
||||
:ok = info("Switch Desc: mfr = #{desc.mfr_desc} hw = #{desc.hw_desc} sw = #{desc.sw_desc}")
|
||||
:ok = GenServer.cast(self(), :send_flows)
|
||||
{:noreply, state}
|
||||
end
|
||||
def handle_info(%TableFeatures.Reply{xid: xid}, state) do
|
||||
:ok = info("Pipeline modification is success (xid: #{xid})")
|
||||
:ok = GenServer.cast(self(), :send_flows)
|
||||
{:noreply, state}
|
||||
end
|
||||
def handle_info(%Flow.Reply{flows: flows}, state) do
|
||||
for flow <- flows do
|
||||
:ok = info(
|
||||
"table_id: #{flow.table_id} " <>
|
||||
"pkt_count: #{flow.packet_count} "<>
|
||||
"byt_count: #{flow.byte_count} "<>
|
||||
"match: #{inspect(flow.match)} "<>
|
||||
"insts: #{inspect(flow.instructions)} "
|
||||
)
|
||||
end
|
||||
{:noreply, state}
|
||||
end
|
||||
def handle_info(%PortDesc.Reply{ports: ports}, state) do
|
||||
info("Received Port Desc")
|
||||
access_port1 = Enum.find(ports, fn(port) -> port.name == state.access_port1_name end)
|
||||
access_port2 = Enum.find(ports, fn(port) -> port.name == state.access_port2_name end)
|
||||
trunk_port1 = Enum.find(ports, fn(port) -> port.name == state.trunk_port1_name end)
|
||||
trunk_port2 = Enum.find(ports, fn(port) -> port.name == state.trunk_port2_name end)
|
||||
:ok = desc_stats_request(state.dpid)
|
||||
{:noreply, %{state|
|
||||
access_port1: access_port1,
|
||||
access_port2: access_port2,
|
||||
trunk_port1: trunk_port1,
|
||||
trunk_port2: trunk_port2}
|
||||
}
|
||||
end
|
||||
def handle_info(%ErrorMsg{code: code, type: type, data: data, xid: xid}, state) do
|
||||
:ok = warn("Request Failed(xid: #{xid}):"<>
|
||||
" code: #{code}"<>
|
||||
" type: #{type}"<>
|
||||
" data: #{inspect(data)}"<>
|
||||
" dpid: #{inspect(state.dpid)}")
|
||||
{:stop, :request_failed, state}
|
||||
end
|
||||
def handle_info(%PacketIn{data: data}, state) do
|
||||
:ok = warn("Table miss occured:"<>
|
||||
" data: #{inspect(:pkt.decapsulate(data))}"<>
|
||||
" dpid: #{inspect(state.dpid)}")
|
||||
{:noreply, state}
|
||||
end
|
||||
def handle_info({:'DOWN', ref, :process, _pid, _reason}, %State{conn_ref: ref} = state) do
|
||||
:ok = debug("Switch Disconnected: dpid: #{inspect(state.dpid)}")
|
||||
{:stop, :normal, state}
|
||||
end
|
||||
def handle_info(info, state) do
|
||||
:ok = info("Unhandled message: #{inspect(info)}")
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
# private functions
|
||||
defp init_state(dpid) do
|
||||
:ok = init_datapath(dpid)
|
||||
conn_ref = SwitchRegistry.monitor(dpid)
|
||||
config = Application.get_all_env(:heckle)
|
||||
%State{
|
||||
dpid: dpid,
|
||||
conn_ref: conn_ref,
|
||||
access_port1_name: config[:access_port1],
|
||||
access_port2_name: config[:access_port2],
|
||||
trunk_port1_name: config[:vlan_trunk1],
|
||||
trunk_port2_name: config[:vlan_trunk2],
|
||||
vlan_tagging: config[:vlan_tagging] || true,
|
||||
vlan_id: 0x1000 ||| (config[:vlan_id] || 0),
|
||||
receiver_mac: config[:receiver_mac],
|
||||
receiver_ip: config[:receiver_ip],
|
||||
sender_mac: config[:sender_mac],
|
||||
inside_local: config[:inside_local],
|
||||
outside_local: config[:outside_local],
|
||||
flow_pattern: config[:flow_pattern] || :nat
|
||||
}
|
||||
end
|
||||
|
||||
defp init_datapath(dpid) do
|
||||
:ok = send_flow_mod_delete(dpid)
|
||||
:ok = port_desc_stats_request(dpid)
|
||||
:ok = set_config(dpid)
|
||||
end
|
||||
|
||||
defp desc_stats_request(dpid) do
|
||||
:ok = send_message(Desc.Request.new, dpid)
|
||||
end
|
||||
|
||||
defp port_desc_stats_request(dpid) do
|
||||
:ok = send_message(PortDesc.Request.new, dpid)
|
||||
end
|
||||
|
||||
defp set_config(dpid) do
|
||||
:ok = send_message(SetConfig.new(miss_send_len: :no_buffer), dpid)
|
||||
end
|
||||
|
||||
defp print_flows(state) do
|
||||
state
|
||||
|> FlowPatterns.flows
|
||||
|> Enum.each(&print_flow/1)
|
||||
end
|
||||
|
||||
defp print_flow(flow_opts) do
|
||||
flow_opts
|
||||
|> FlowMod.new
|
||||
|> Openflow.to_binary
|
||||
|> binary_to_space_delimited_hex
|
||||
|> ofp_print_cmd
|
||||
|> Logger.info
|
||||
end
|
||||
|
||||
defp ofp_print_cmd(print_args) do
|
||||
{result, _code} = System.cmd("ovs-ofctl", ["ofp-print", "#{print_args}"])
|
||||
result
|
||||
end
|
||||
|
||||
defp binary_to_space_delimited_hex(binary) do
|
||||
binary
|
||||
|> split_to_hex_string
|
||||
|> Enum.join(" ")
|
||||
|> String.downcase
|
||||
end
|
||||
|
||||
defp split_to_hex_string(binary) do
|
||||
for <<int <- binary>>, do: integer_to_hex(int)
|
||||
end
|
||||
|
||||
defp integer_to_hex(int) do
|
||||
case Integer.to_string(int, 16) do
|
||||
<<d>> -> <<48, d>>
|
||||
dd -> dd
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
defmodule Heckle.FlowPatterns do
|
||||
use Bitwise
|
||||
use Tres.Controller
|
||||
|
||||
@mcast {"010000000000", "010000000000"}
|
||||
|
||||
def flows(%Heckle.Controller.State{flow_pattern: :nat} = state) do
|
||||
[[table_id: 1,
|
||||
priority: 30,
|
||||
cookie: 0x3000000000000001,
|
||||
match: Match.new(
|
||||
eth_dst: state.receiver_mac,
|
||||
eth_src: state.sender_mac,
|
||||
eth_type: 0x0800,
|
||||
ipv4_src: state.inside_local,
|
||||
ipv4_dst: state.receiver_ip
|
||||
),
|
||||
instructions: [
|
||||
ApplyActions.new([
|
||||
SetField.new({:eth_dst, state.receiver_mac}),
|
||||
SetField.new({:ipv4_src, state.outside_local}),
|
||||
Output.new(state.trunk_port2.number)
|
||||
])]
|
||||
],
|
||||
[table_id: 1,
|
||||
priority: 30,
|
||||
cookie: 0x3000000000000001,
|
||||
match: Match.new(
|
||||
eth_dst: state.sender_mac,
|
||||
eth_src: state.receiver_mac,
|
||||
eth_type: 0x0800,
|
||||
vlan_vid: 123,
|
||||
ipv4_src: state.receiver_ip,
|
||||
ipv4_dst: state.outside_local
|
||||
),
|
||||
instructions: [
|
||||
ApplyActions.new([
|
||||
PopVlan.new,
|
||||
SetField.new({:eth_src, state.receiver_mac}),
|
||||
SetField.new({:eth_dst, state.sender_mac}),
|
||||
SetField.new({:ipv4_dst, state.inside_local}),
|
||||
Output.new(state.access_port1.number)
|
||||
])]
|
||||
],
|
||||
] ++ classifier(state)
|
||||
end
|
||||
def flows(%Heckle.Controller.State{flow_pattern: :bum} = state) do
|
||||
[[table_id: 1,
|
||||
priority: 30,
|
||||
cookie: 0x3000000000000001,
|
||||
match: Match.new(
|
||||
vlan_vid: state.vlan_id,
|
||||
eth_dst: @mcast
|
||||
),
|
||||
instructions: [
|
||||
ApplyActions.new([
|
||||
Openflow.Action.Output.new(state.trunk_port1.number),
|
||||
Openflow.Action.PopVlan.new,
|
||||
Openflow.Action.Output.new(state.access_port1.number),
|
||||
Openflow.Action.Output.new(state.access_port2.number)
|
||||
])]
|
||||
]] ++ classifier(state)
|
||||
end
|
||||
|
||||
# private functions
|
||||
|
||||
defp classifier(state) do
|
||||
[
|
||||
[table_id: 0,
|
||||
priority: 50,
|
||||
cookie: 0x1000000000000001,
|
||||
match: Match.new(
|
||||
in_port: state.trunk_port1.number,
|
||||
vlan_vid: state.vlan_id
|
||||
),
|
||||
instructions: [GotoTable.new(1)]
|
||||
],
|
||||
[table_id: 0,
|
||||
priority: 50,
|
||||
cookie: 0x1000000000000001,
|
||||
match: Match.new(
|
||||
in_port: state.trunk_port2.number,
|
||||
vlan_vid: state.vlan_id
|
||||
),
|
||||
instructions: [GotoTable.new(1)]
|
||||
],
|
||||
[table_id: 0,
|
||||
priority: 20,
|
||||
cookie: 0x1000000000000001,
|
||||
match: Match.new(
|
||||
in_port: state.access_port1.number,
|
||||
eth_src: state.sender_mac,
|
||||
),
|
||||
instructions: [
|
||||
ApplyActions.new([
|
||||
PushVlan.new,
|
||||
SetField.new({:vlan_vid, state.vlan_id})
|
||||
]),
|
||||
GotoTable.new(1)
|
||||
]
|
||||
],
|
||||
[table_id: 0,
|
||||
priority: 20,
|
||||
cookie: 0x1000000000000001,
|
||||
match: Match.new(
|
||||
in_port: state.access_port2.number,
|
||||
eth_src: state.sender_mac,
|
||||
),
|
||||
instructions: [
|
||||
ApplyActions.new([
|
||||
PushVlan.new,
|
||||
SetField.new({:vlan_vid, state.vlan_id})
|
||||
]),
|
||||
GotoTable.new(1)
|
||||
]
|
||||
],
|
||||
[table_id: 0,
|
||||
priority: 1,
|
||||
match: Match.new,
|
||||
cookie: 0x1000000000000001,
|
||||
instructions: [ApplyActions.new(Output.new(:controller))]
|
||||
],
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
defmodule Heckle.PipelineProfiles do
|
||||
use Tres.Controller
|
||||
|
||||
def of_aruba do
|
||||
[
|
||||
TableFeatures.Body.new(
|
||||
table_id: 0,
|
||||
name: "classifier",
|
||||
max_entries: 10,
|
||||
config: [:table_miss_mask], # deprecated mask.
|
||||
match: [:in_port, :vlan_vid, :eth_src],
|
||||
wildcards: [:in_port, :vlan_vid, :eth_src],
|
||||
instructions: [GotoTable, ApplyActions],
|
||||
apply_actions: [Output, PushVlan, SetField, PopVlan],
|
||||
apply_setfield: [:eth_dst, :vlan_vid],
|
||||
next_tables: [1],
|
||||
),
|
||||
TableFeatures.Body.new(
|
||||
table_id: 1,
|
||||
name: "process",
|
||||
max_entries: 10,
|
||||
config: [:table_miss_mask],
|
||||
match: [:eth_src, :masked_eth_dst, :vlan_vid, :eth_type, :ipv4_src, :ipv4_dst],
|
||||
wildcards: [:eth_src, :masked_eth_dst, :vlan_vid, :eth_type, :ipv4_src, :ipv4_dst],
|
||||
instructions: [ApplyActions],
|
||||
apply_actions: [SetField, PopVlan, PushVlan, Output],
|
||||
apply_setfield: [:eth_src, :eth_dst, :vlan_vid, :ipv4_src, :ipv4_dst],
|
||||
next_tables: [],
|
||||
)
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
defmodule Heckle.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
@tres_path "../../../tres"
|
||||
|
||||
def project do
|
||||
[app: :heckle,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.5",
|
||||
start_permanent: Mix.env == :prod,
|
||||
deps: deps()]
|
||||
end
|
||||
|
||||
def application do
|
||||
[extra_applications: [:logger, :tres],
|
||||
mod: {Heckle.Application, []}]
|
||||
end
|
||||
|
||||
defp deps do
|
||||
[{:tres, path: @tres_path},
|
||||
{:pkt, github: "msantos/pkt", branch: "master"}]
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
%{"binpp": {:git, "https://github.com/jtendo/binpp.git", "64bd68d215d1a6cd35871e7c134d7fe2e46214ea", [branch: "master"]},
|
||||
"eovsdb": {:git, "https://github.com/shun159/eovsdb.git", "1ff1572708d72fd25631c681f2102407903252a3", [branch: "master"]},
|
||||
"jsone": {:git, "https://github.com/sile/jsone.git", "eecc9666c7165e1870b78a7a762549ae8d1c391b", [tag: "1.2.1"]},
|
||||
"pkt": {:git, "https://github.com/msantos/pkt.git", "3afb1967f34324c1dec5035a6e36232da815c2e6", [branch: "master"]},
|
||||
"ranch": {:hex, :ranch, "1.4.0", "10272f95da79340fa7e8774ba7930b901713d272905d0012b06ca6d994f8826b", [], [], "hexpm"},
|
||||
"uuid": {:git, "https://github.com/avtobiff/erlang-uuid.git", "585c2474afb4a597ae8c8bf6d21e5a9c73f18e0b", [tag: "v0.5.0"]}}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
defmodule HeckleTest do
|
||||
use ExUnit.Case
|
||||
doctest Heckle
|
||||
|
||||
test "greets the world" do
|
||||
assert Heckle.hello() == :world
|
||||
end
|
||||
end
|
||||
|
|
@ -1 +0,0 @@
|
|||
ExUnit.start()
|
||||
|
|
@ -124,15 +124,19 @@ defmodule Tres.SecureChannel do
|
|||
|
||||
defp init_handler(state_data) do
|
||||
%State{datapath_id: dpid, aux_id: aux_id} = state_data
|
||||
{:ok, pid} = MessageHandlerSup.start_child({dpid, aux_id})
|
||||
ref = Process.monitor(pid)
|
||||
%{state_data | handler_pid: pid, handler_ref: ref}
|
||||
case MessageHandlerSup.start_child({dpid, aux_id}) do
|
||||
{:ok, pid} ->
|
||||
ref = Process.monitor(pid)
|
||||
%{state_data | handler_pid: pid, handler_ref: ref}
|
||||
{:error, reason} ->
|
||||
{:stop, reason}
|
||||
end
|
||||
end
|
||||
|
||||
# INIT state
|
||||
defp handle_INIT(:enter, _old_state, state_data) do
|
||||
debug(
|
||||
"[#{__MODULE__}] Initiate HELLO handshake: " <> ">#{state_data.ip_addr}:#{state_data.port}"
|
||||
"[#{__MODULE__}] Initiate HELLO handshake: " <> "#{state_data.ip_addr}:#{state_data.port}"
|
||||
)
|
||||
|
||||
initiate_hello_handshake(state_data)
|
||||
|
|
@ -202,9 +206,13 @@ defmodule Tres.SecureChannel do
|
|||
|
||||
# CONNECTED state
|
||||
defp handle_CONNECTED(:enter, :CONNECTING, state_data) do
|
||||
new_state_data = init_handler(state_data)
|
||||
start_periodic_idle_check()
|
||||
{:keep_state, new_state_data}
|
||||
case init_handler(state_data) do
|
||||
%State{} = new_state_data ->
|
||||
start_periodic_idle_check()
|
||||
{:keep_state, new_state_data}
|
||||
{:stop, reason} ->
|
||||
close_connection({:handler_down, reason}, state_data)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_CONNECTED(:info, :idle_check, state_data) do
|
||||
|
|
@ -563,10 +571,8 @@ defmodule Tres.SecureChannel do
|
|||
{:stop, :normal, %{state_data | socket: nil}}
|
||||
end
|
||||
|
||||
defp close_connection({:handler_down = disconnected_reason, reason}, state_data) do
|
||||
defp close_connection({:handler_down = _disconnected_reason, reason}, state_data) do
|
||||
warn("[#{__MODULE__}] connection terminated: Handler process down by #{reason}")
|
||||
%State{handler_pid: handler_pid} = state_data
|
||||
send(handler_pid, {:switch_disconnected, disconnected_reason})
|
||||
{:stop, :normal, %{state_data | socket: nil}}
|
||||
end
|
||||
|
||||
|
|
|
|||
250
test/flay.ex
250
test/flay.ex
|
|
@ -1,250 +0,0 @@
|
|||
defmodule Flay do
|
||||
use GenServer
|
||||
use Tres.Controller
|
||||
|
||||
import Logger
|
||||
|
||||
defmodule State do
|
||||
defstruct datapath_id: nil,
|
||||
tester_pid: nil,
|
||||
conn_ref: nil,
|
||||
reply_to: nil,
|
||||
default_profile: nil
|
||||
end
|
||||
|
||||
def start_link(datapath, args) do
|
||||
GenServer.start_link(__MODULE__, [datapath, args], name: __MODULE__)
|
||||
end
|
||||
|
||||
def init(args) do
|
||||
state = init_controller(args)
|
||||
GenServer.cast(Flay, :desc_stats)
|
||||
GenServer.cast(Flay, :flow_del)
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
def handle_call(:port_desc_stats, from, state) do
|
||||
send_message(PortDesc.Request.new(), state.datapath_id)
|
||||
{:noreply, %{state | reply_to: from}}
|
||||
end
|
||||
|
||||
def handle_call(:flow_stats, from, state) do
|
||||
send_message(Flow.Request.new(), state.datapath_id)
|
||||
{:noreply, %{state | reply_to: from}}
|
||||
end
|
||||
|
||||
def handle_cast(:desc_stats, state) do
|
||||
send_message(Desc.Request.new(), state.datapath_id)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({:register_pid, tester_pid}, state) do
|
||||
{:noreply, %{state | tester_pid: tester_pid}}
|
||||
end
|
||||
|
||||
def handle_cast({:flow_install, flow_opts, tester_pid}, state) do
|
||||
send_flow_mod_add(state.datapath_id, flow_opts)
|
||||
flow_opts_to_ofp_print(flow_opts)
|
||||
{:noreply, %{state | tester_pid: tester_pid}}
|
||||
end
|
||||
|
||||
def handle_cast(:flow_del, state) do
|
||||
send_flow_mod_delete(state.datapath_id)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({:flow_del, cookie}, state) do
|
||||
send_flow_mod_delete(state.datapath_id, cookie: cookie, cookie_mask: 0xFFFFFFFFFFFFFFFF)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(%ErrorMsg{} = error, state) do
|
||||
send(state.tester_pid, error)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(%PacketIn{} = pktin, state) do
|
||||
send(state.tester_pid, pktin)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(%TableFeatures.Reply{} = table, state) do
|
||||
{:noreply, %{state | default_profile: table}}
|
||||
end
|
||||
|
||||
def handle_info(%PortDesc.Reply{} = desc, state) do
|
||||
GenServer.reply(state.reply_to, desc)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(%Desc.Reply{} = desc, state) do
|
||||
info(
|
||||
"[#{__MODULE__}] Switch Desc: " <>
|
||||
"mfr = #{desc.mfr_desc} " <> "hw = #{desc.hw_desc} " <> "sw = #{desc.sw_desc} "
|
||||
)
|
||||
|
||||
init_bridge(state.datapath_id, desc)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(%Flow.Reply{} = desc, state) do
|
||||
GenServer.reply(state.reply_to, desc)
|
||||
{:noreply, %{state | reply_to: nil}}
|
||||
end
|
||||
|
||||
# `Catch all` function is required.
|
||||
def handle_info(info, state) do
|
||||
:ok = warn("[#{__MODULE__}] unhandled message #{inspect(info)}")
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
# private functions
|
||||
|
||||
defp flow_opts_to_ofp_print(flow_opts) do
|
||||
flow_opts
|
||||
|> FlowMod.new()
|
||||
|> Openflow.to_binary()
|
||||
|> binary_to_space_delimited_hex
|
||||
|> ofp_print_cmd
|
||||
|> Logger.info()
|
||||
end
|
||||
|
||||
defp ofp_print_cmd(print_args) do
|
||||
{result, _code} = System.cmd("ovs-ofctl", ["ofp-print", "#{print_args}"])
|
||||
result
|
||||
end
|
||||
|
||||
defp binary_to_space_delimited_hex(binary) do
|
||||
binary
|
||||
|> split_to_hex_string
|
||||
|> Enum.join(" ")
|
||||
|> String.downcase()
|
||||
end
|
||||
|
||||
defp split_to_hex_string(binary) do
|
||||
for <<int <- binary>>, do: integer_to_hex(int)
|
||||
end
|
||||
|
||||
defp integer_to_hex(int) do
|
||||
case Integer.to_string(int, 16) do
|
||||
<<d>> -> <<48, d>>
|
||||
dd -> dd
|
||||
end
|
||||
end
|
||||
|
||||
defp init_controller([datapath_id, tester_pid]) do
|
||||
conn_ref = SwitchRegistry.monitor(datapath_id)
|
||||
|
||||
%State{
|
||||
datapath_id: datapath_id,
|
||||
tester_pid: tester_pid,
|
||||
conn_ref: conn_ref
|
||||
}
|
||||
end
|
||||
|
||||
defp init_bridge(datapath_id, %Desc.Reply{mfr_desc: "Aruba"}) do
|
||||
:ok = info("Transform flow table pipeline")
|
||||
|
||||
tables = [
|
||||
TableFeatures.Body.new(
|
||||
table_id: 0,
|
||||
name: "classifier",
|
||||
max_entries: 50,
|
||||
config: [:table_miss_mask],
|
||||
match: [
|
||||
:in_port,
|
||||
:eth_type,
|
||||
:eth_src,
|
||||
:masked_eth_dst,
|
||||
:ip_proto,
|
||||
:vlan_vid,
|
||||
:ipv4_src,
|
||||
:udp_dst,
|
||||
:tcp_dst
|
||||
],
|
||||
wildcards: [
|
||||
:in_port,
|
||||
:eth_src,
|
||||
:eth_type,
|
||||
:masked_eth_dst,
|
||||
:vlan_vid,
|
||||
:ip_proto,
|
||||
:ipv4_src,
|
||||
:udp_dst,
|
||||
:tcp_dst
|
||||
],
|
||||
instructions: [
|
||||
Openflow.Instruction.GotoTable,
|
||||
Openflow.Instruction.ApplyActions
|
||||
],
|
||||
apply_actions: [
|
||||
Openflow.Action.Output,
|
||||
Openflow.Action.PushVlan,
|
||||
Openflow.Action.PopVlan,
|
||||
Openflow.Action.SetField
|
||||
],
|
||||
apply_setfield: [
|
||||
:eth_dst,
|
||||
:vlan_vid
|
||||
],
|
||||
next_tables: [
|
||||
1
|
||||
]
|
||||
),
|
||||
TableFeatures.Body.new(
|
||||
table_id: 1,
|
||||
name: "admission_control",
|
||||
max_entries: 50,
|
||||
config: [:table_miss_mask],
|
||||
match: [
|
||||
:eth_type,
|
||||
:eth_src,
|
||||
:masked_eth_dst,
|
||||
:vlan_vid,
|
||||
:ip_proto,
|
||||
:udp_dst,
|
||||
:tcp_dst,
|
||||
:ipv4_src,
|
||||
:ipv4_dst
|
||||
],
|
||||
wildcards: [
|
||||
:eth_type,
|
||||
:eth_src,
|
||||
:masked_eth_dst,
|
||||
:vlan_vid,
|
||||
:ip_proto,
|
||||
:udp_dst,
|
||||
:tcp_dst,
|
||||
:ipv4_src,
|
||||
:ipv4_dst
|
||||
],
|
||||
instructions: [
|
||||
Openflow.Instruction.GotoTable,
|
||||
Openflow.Instruction.ApplyActions
|
||||
],
|
||||
apply_actions: [
|
||||
Openflow.Action.Output,
|
||||
Openflow.Action.PushVlan,
|
||||
Openflow.Action.PopVlan,
|
||||
Openflow.Action.SetField
|
||||
],
|
||||
apply_setfield: [
|
||||
:eth_dst,
|
||||
:vlan_vid,
|
||||
:ipv4_src,
|
||||
:ipv4_dst
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
TableFeatures.Request.new(tables)
|
||||
|> send_message(datapath_id)
|
||||
|
||||
send_flow_mod_delete(datapath_id, table_id: :all)
|
||||
end
|
||||
|
||||
defp init_bridge(_datapath_id, _mfr) do
|
||||
:ok = info("Flow pipeline profile is not defined")
|
||||
:ok
|
||||
end
|
||||
end
|
||||
1166
test/flog_test.exs
1166
test/flog_test.exs
File diff suppressed because it is too large
Load diff
77
test/pf.ex
77
test/pf.ex
|
|
@ -1,77 +0,0 @@
|
|||
defmodule Pf do
|
||||
use GenServer
|
||||
|
||||
import Record
|
||||
# Extract Erlang record for msantos/pkt
|
||||
for {name, schema} <- extract_all(from_lib: "pkt/include/pkt.hrl") do
|
||||
defrecord(name, schema)
|
||||
end
|
||||
|
||||
defmodule State do
|
||||
defstruct ifname: nil,
|
||||
pcap_ref: nil,
|
||||
tester_pid: nil
|
||||
end
|
||||
|
||||
def inject!(pid, packet, payload \\ "") do
|
||||
GenServer.cast(pid, {:inject, {packet, payload}})
|
||||
end
|
||||
|
||||
def stop(pid) do
|
||||
GenServer.cast(pid, :stop)
|
||||
end
|
||||
|
||||
def start_link(ifname) do
|
||||
ifname = String.to_charlist(ifname)
|
||||
GenServer.start_link(__MODULE__, [ifname, self()])
|
||||
end
|
||||
|
||||
def init([ifname, tester_pid]) do
|
||||
{:ok, init_pf(ifname, tester_pid)}
|
||||
end
|
||||
|
||||
def handle_cast({:inject, {headers, payload}}, state) do
|
||||
headers_bin =
|
||||
for header <- headers do
|
||||
case header do
|
||||
ether() -> :pkt.ether(header)
|
||||
{:"802.1q", _, _, _, _} = vlan -> :pkt_802_1q.codec(vlan)
|
||||
arp() -> :pkt.arp(header)
|
||||
ipv4() -> :pkt.ipv4(header)
|
||||
lldp() -> :pkt.lldp(header)
|
||||
udp() -> :pkt.udp(header)
|
||||
tcp() -> :pkt.tcp(header)
|
||||
end
|
||||
end
|
||||
|
||||
binary = Enum.join(headers_bin, "")
|
||||
:epcap.send(state.pcap_ref, <<binary::bytes, payload::bytes>>)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast(:stop, state) do
|
||||
{:stop, :normal, state}
|
||||
end
|
||||
|
||||
def handle_cast(_req, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info({:packet, _dlt, _time, _len, data}, state) do
|
||||
packet = :pkt.decapsulate(data)
|
||||
packet_len = length(packet)
|
||||
send(state.tester_pid, {to_string(state.ifname), Enum.take(packet, packet_len - 1)})
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(_info, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
# private functions
|
||||
|
||||
defp init_pf(ifname, tester_pid) do
|
||||
{:ok, epcap_pid} = :epcap.start_link(interface: ifname, promiscuous: true, inject: true)
|
||||
%State{pcap_ref: epcap_pid, ifname: ifname, tester_pid: tester_pid}
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue