diff --git a/README.md b/README.md index 305c5c7..79d0845 100644 --- a/README.md +++ b/README.md @@ -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 ``` diff --git a/examples/heckle/.gitignore b/examples/heckle/.gitignore deleted file mode 100644 index 12179ea..0000000 --- a/examples/heckle/.gitignore +++ /dev/null @@ -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 diff --git a/examples/heckle/README.md b/examples/heckle/README.md deleted file mode 100644 index eabaf9d..0000000 --- a/examples/heckle/README.md +++ /dev/null @@ -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). - diff --git a/examples/heckle/config/config.exs b/examples/heckle/config/config.exs deleted file mode 100644 index 0b8ca46..0000000 --- a/examples/heckle/config/config.exs +++ /dev/null @@ -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 diff --git a/examples/heckle/lib/heckle.ex b/examples/heckle/lib/heckle.ex deleted file mode 100644 index ff5d007..0000000 --- a/examples/heckle/lib/heckle.ex +++ /dev/null @@ -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 diff --git a/examples/heckle/lib/heckle/application.ex b/examples/heckle/lib/heckle/application.ex deleted file mode 100644 index 7f46096..0000000 --- a/examples/heckle/lib/heckle/application.ex +++ /dev/null @@ -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 diff --git a/examples/heckle/lib/heckle/controller.ex b/examples/heckle/lib/heckle/controller.ex deleted file mode 100644 index 0eded9e..0000000 --- a/examples/heckle/lib/heckle/controller.ex +++ /dev/null @@ -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 <>, do: integer_to_hex(int) - end - - defp integer_to_hex(int) do - case Integer.to_string(int, 16) do - <> -> <<48, d>> - dd -> dd - end - end -end diff --git a/examples/heckle/lib/heckle/flow_patterns.ex b/examples/heckle/lib/heckle/flow_patterns.ex deleted file mode 100644 index 1c2e036..0000000 --- a/examples/heckle/lib/heckle/flow_patterns.ex +++ /dev/null @@ -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 diff --git a/examples/heckle/lib/heckle/pipeline_profiles.ex b/examples/heckle/lib/heckle/pipeline_profiles.ex deleted file mode 100644 index 6327d1a..0000000 --- a/examples/heckle/lib/heckle/pipeline_profiles.ex +++ /dev/null @@ -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 diff --git a/examples/heckle/mix.exs b/examples/heckle/mix.exs deleted file mode 100644 index 06e195a..0000000 --- a/examples/heckle/mix.exs +++ /dev/null @@ -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 diff --git a/examples/heckle/mix.lock b/examples/heckle/mix.lock deleted file mode 100644 index e337173..0000000 --- a/examples/heckle/mix.lock +++ /dev/null @@ -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"]}} diff --git a/examples/heckle/test/heckle_test.exs b/examples/heckle/test/heckle_test.exs deleted file mode 100644 index 855f884..0000000 --- a/examples/heckle/test/heckle_test.exs +++ /dev/null @@ -1,8 +0,0 @@ -defmodule HeckleTest do - use ExUnit.Case - doctest Heckle - - test "greets the world" do - assert Heckle.hello() == :world - end -end diff --git a/examples/heckle/test/test_helper.exs b/examples/heckle/test/test_helper.exs deleted file mode 100644 index 869559e..0000000 --- a/examples/heckle/test/test_helper.exs +++ /dev/null @@ -1 +0,0 @@ -ExUnit.start() diff --git a/lib/tres/secure_channel.ex b/lib/tres/secure_channel.ex index e44d022..d0c028b 100644 --- a/lib/tres/secure_channel.ex +++ b/lib/tres/secure_channel.ex @@ -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 diff --git a/test/flay.ex b/test/flay.ex deleted file mode 100644 index 2c26cf5..0000000 --- a/test/flay.ex +++ /dev/null @@ -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 <>, do: integer_to_hex(int) - end - - defp integer_to_hex(int) do - case Integer.to_string(int, 16) do - <> -> <<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 diff --git a/test/flog_test.exs b/test/flog_test.exs deleted file mode 100644 index dc31453..0000000 --- a/test/flog_test.exs +++ /dev/null @@ -1,1166 +0,0 @@ -defmodule FlogTest do - use ExUnit.Case, async: false - use Bitwise - - @listen_port 6653 - - # FIXME: - @vlan_trunk_port "veth0" - # FIXME: - @access_port1 "veth3" - # FIXME: - @access_port2 "veth4" - - # FIXME: - @vlan_trunk_port_sniffer "veth1" - # FIXME: - @access_port1_sniffer "veth2" - # FIXME: - @access_port2_sniffer "veth5" - - @bootnet_vid 0x1000 ||| 5 - @user_vid 0x1000 ||| 123 - @vlan_present {0x1000, 0x1000} - @mcast {"010000000000", "010000000000"} - @sdl_vmac "000000000001" - @bcast "ffffffffffff" - @mac "000102030405" - @auth_ipv4_address {192, 168, 5, 4} - @captive_ipv4_address {192, 168, 5, 5} - @quad_0_ip {0, 0, 0, 0} - @bcast_ip {255, 255, 255, 255} - @client_ip {192, 168, 5, 10} - @client_farm_ip {192, 168, 255, 1} - @gateway_ip {192, 168, 5, 4} - @farm_gw_ip {192, 168, 255, 254} - @farm_gw_mac "00000000000f" - @internet {8, 8, 8, 8} - @trusted_macs [ - "0800274d3297", - "0800274d3298", - "0800274d3299" - ] - - import Record - # Extract Erlang record for msantos/pkt - for {name, schema} <- extract_all(from_lib: "pkt/include/pkt.hrl") do - defrecord(name, schema) - end - - Code.load_file("test/flay.ex") - Code.load_file("test/pf.ex") - - # GIVEN - setup_all do - setup_applications() - wait_for_connected() - ports = get_ports_desc() - vlan_trunk = Enum.find(ports, fn port -> port.name == @vlan_trunk_port end) - port = Enum.find(ports, fn port -> port.name == @access_port1 end) - port2 = Enum.find(ports, fn port -> port.name == @access_port2 end) - cookie = 0x1000000000000001 - cookie2 = 0x2000000000000001 - timeout = 32_678 - - options = [ - vlan_trunk: vlan_trunk, - port: port, - port2: port2, - cookie: cookie, - cookie2: cookie2, - timeout: timeout - ] - - {:ok, options} - end - - describe "switch:merged_handler:table=0,priority=0,cookie=0x8000000000000000,actions=drop" do - test "Install Flow" do - options = [ - cookie: 0x8000000000000000, - table_id: 0, - priority: 0 - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - - test "Inject Packet(ARP)" do - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - - shost = Openflow.Match.Field.codec(@mac, :eth_src) - dhost = Openflow.Match.Field.codec(@bcast, :eth_dst) - payload = <<0::size(16)-unit(8)>> - - packet = [ - ether(dhost: dhost, shost: shost, type: 0x0806), - arp(op: 1, sha: shost, sip: @client_ip, tip: @gateway_ip) - ] - - Pf.inject!(pid1, packet, payload) - refute_receive {@vlan_trunk_port_sniffer, ^packet}, 1000 - Pf.stop(pid1) - Pf.stop(pid2) - end - end - - describe "switch:merged_handler:" <> - "table=0,priority=200,cookie=0x4000000000000000,in_port=%d,dl_dst=%s,dl_vlan=0x0000/0x1fff,dl_type=%s," <> - "actions=controller" do - test "Install Flow", state do - match = - Openflow.Match.new( - in_port: state.vlan_trunk.number, - eth_dst: @bcast, - vlan_vid: 0x0000, - eth_type: 0x88CC - ) - - action = Openflow.Action.Output.new(:controller) - ins = Openflow.Instruction.ApplyActions.new(action) - - options = [ - cookie: 0x4000000000000000, - table_id: 0, - priority: 200, - match: match, - instructions: [ins] - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - - shost = Openflow.Match.Field.codec(state.vlan_trunk.hw_addr, :eth_src) - dhost = Openflow.Match.Field.codec(@bcast, :eth_dst) - - packet = [ - ether(dhost: dhost, shost: shost, type: 0x88CC), - lldp( - pdus: [ - chassis_id(value: "hogehoge"), - port_id(subtype: :mac_address, value: shost), - ttl(value: 128) - ] - ) - ] - - Pf.inject!(pid2, packet) - in_port = state.vlan_trunk.number - assert_receive %Openflow.PacketIn{in_port: ^in_port}, 1000 - refute_receive {@vlan_trunk_port_sniffer, [^packet, ""]}, 1000 - refute_receive {@access_port1_sniffer, [^packet, ""]}, 1000 - Pf.stop(pid1) - Pf.stop(pid2) - end - end - - describe "switch:merged_handler:" <> - "table=0,priority=201,cookie=0x4000000000000000,in_port=%d,dl_src=%s,actions=drop" do - test "Install Flow", state do - for port <- [state.vlan_trunk, state.port] do - match = Openflow.Match.new(in_port: port.number, eth_src: port.hw_addr) - options = [cookie: 0x4000000000000000, table_id: 0, priority: 201, match: match] - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - end - end - - describe "switch:uplink_escalation_flow:" <> - "table=0,priority=10,cookie=0x1000000000000000,arp,actions=controller" do - test "Install Flow", state do - match = Openflow.Match.new(eth_type: 0x0806) - action = Openflow.Action.Output.new(:controller) - ins = Openflow.Instruction.ApplyActions.new(action) - - options = [ - cookie: 0x1000000000000000, - table_id: 0, - priority: 10, - match: match, - instructions: [ins] - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - - shost = Openflow.Match.Field.codec(@mac, :eth_src) - dhost = Openflow.Match.Field.codec(@bcast, :eth_dst) - payload = <<0::size(16)-unit(8)>> - - packet = [ - ether(dhost: dhost, shost: shost, type: 0x0806), - arp(op: 1, sha: shost, sip: @client_ip, tip: @gateway_ip) - ] - - Pf.inject!(pid1, packet, payload) - in_port = state.port.number - assert_receive %Openflow.PacketIn{in_port: ^in_port}, 1000 - refute_receive {@vlan_trunk_port_sniffer, ^packet}, 1000 - Pf.stop(pid1) - Pf.stop(pid2) - end - end - - describe "switch:uplink_escalation_flow:" <> - "table=0,priority=10,cookie=0x1000000000000000,udp,udp_dst=67,actions=controller" do - test "Install Flow", state do - match = Openflow.Match.new(eth_type: 0x0800, ip_proto: 17, udp_dst: 67) - action = Openflow.Action.Output.new(:controller) - ins = Openflow.Instruction.ApplyActions.new(action) - - options = [ - cookie: 0x1000000000000000, - table_id: 0, - priority: 10, - match: match, - instructions: [ins] - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - - shost = Openflow.Match.Field.codec(@mac, :eth_src) - dhost = Openflow.Match.Field.codec(@bcast, :eth_dst) - - payload = File.read!("test/packet_data/dhcp_discover.raw") - length = 8 + byte_size(payload) - udp_header = udp(sport: 68, dport: 67, ulen: length) - ipv4_header = ipv4(saddr: @quad_0_ip, daddr: @bcast_ip, p: 17, len: 20 + length) - udp_sum = :pkt.makesum([ipv4_header, udp_header, payload]) - - packet = [ - ether(dhost: dhost, shost: shost, type: 0x0800), - ipv4_header, - udp(udp_header, sum: udp_sum) - ] - - Pf.inject!(pid1, packet, <>) - in_port = state.port.number - assert_receive %Openflow.PacketIn{in_port: ^in_port}, 1000 - refute_receive {@vlan_trunk_port_sniffer, ^packet}, 1000 - Pf.stop(pid1) - Pf.stop(pid2) - end - end - - describe "switch:uplink_escalation_flow:" <> - "table=0,priority=11,cookie=0x1000000000000000,in_port={trunk_port},actions=drop" do - test "Install Flow", state do - match = Openflow.Match.new(in_port: state.vlan_trunk.number) - options = [cookie: 0x1000000000000000, table_id: 0, priority: 11, match: match] - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - - test "Inject Packet(ARP)" do - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - - shost = Openflow.Match.Field.codec(@mac, :eth_src) - dhost = Openflow.Match.Field.codec(@bcast, :eth_dst) - payload = <<0::size(16)-unit(8)>> - - packet = [ - ether(dhost: dhost, shost: shost, type: 0x0806), - arp(op: 1, sha: shost, sip: @client_ip, tip: @gateway_ip) - ] - - Pf.inject!(pid2, packet, payload) - refute_receive {@access_port1_sniffer, ^packet}, 1000 - Pf.stop(pid1) - Pf.stop(pid2) - end - end - - describe "associate:register_bootstrap_rule:" <> - "send_flow_rem," <> - "dl_src={mac},in_port={port_no}," <> - "actions=push_vlan:0x8100,set_field:{vlan}->vlan_vid,output:{vlan_trunk_port}" do - test "Install Flow", state do - match = - Openflow.Match.new( - in_port: state.port.number, - eth_src: @mac - ) - - actions = [ - Openflow.Action.PushVlan.new(), - Openflow.Action.SetField.new({:vlan_vid, @bootnet_vid}), - Openflow.Action.Output.new(state.vlan_trunk.number) - ] - - ins = Openflow.Instruction.ApplyActions.new(actions) - - options = [ - cookie: state.cookie, - table_id: 0, - priority: 20, - flags: [:send_flow_rem], - idle_timeout: state.timeout, - hard_timeout: state.timeout, - match: match, - instructions: [ins] - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - - test "Inject Packet(ARP)" do - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - - shost = Openflow.Match.Field.codec(@mac, :eth_src) - dhost = Openflow.Match.Field.codec(@bcast, :eth_dst) - - packet = [ - ether(dhost: dhost, shost: shost, type: 0x0806), - arp(op: 1, sha: shost, sip: @client_ip, tip: @gateway_ip) - ] - - expect = [ - ether(dhost: dhost, shost: shost, type: 0x8100), - {:"802.1q", 0, 0, 5, 0x0806}, - arp(op: 1, sha: shost, sip: @client_ip, tip: @gateway_ip) - ] - - Pf.inject!(pid1, packet) - assert_receive {@vlan_trunk_port_sniffer, ^expect}, 3000 - Pf.stop(pid1) - Pf.stop(pid2) - end - end - - describe "associate:register_bootstrap_rule:" <> - "send_flow_rem," <> - "in_port={vlan_trunk_port},dl_vlan={vlan},dl_dst={mcast}," <> - "actions=strip_vlan,{outputs}" do - test "Install Flow", state do - match = - Openflow.Match.new( - in_port: state.vlan_trunk.number, - vlan_vid: @bootnet_vid, - eth_dst: @mcast - ) - - actions = [ - Openflow.Action.PopVlan.new(), - Openflow.Action.Output.new(state.port.number), - Openflow.Action.Output.new(state.port2.number) - ] - - ins = Openflow.Instruction.ApplyActions.new(actions) - - options = [ - cookie: state.cookie, - table_id: 0, - priority: 50, - flags: [:send_flow_rem], - idle_timeout: state.timeout, - hard_timeout: state.timeout, - match: match, - instructions: [ins] - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - - test "Inject Packet(ARP)" do - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - {:ok, pid3} = Pf.start_link(@access_port2_sniffer) - - shost = Openflow.Match.Field.codec(@mac, :eth_src) - dhost = Openflow.Match.Field.codec(@bcast, :eth_dst) - - packet = [ - ether(dhost: dhost, shost: shost, type: 0x8100), - {:"802.1q", 0, 0, 5, 0x0806}, - arp(op: 1, sha: shost, sip: @client_ip, tip: @gateway_ip) - ] - - expect = [ - ether(dhost: dhost, shost: shost, type: 0x0806), - arp(op: 1, sha: shost, sip: @client_ip, tip: @gateway_ip) - ] - - Pf.inject!(pid2, packet) - assert_receive {@access_port1_sniffer, ^expect}, 3000 - assert_receive {@access_port2_sniffer, ^expect}, 3000 - Pf.stop(pid1) - Pf.stop(pid2) - Pf.stop(pid3) - end - end - - describe "associate:register_bootstrap_rule:" <> - "send_flow_rem," <> - "in_port={vlan_trunk_port},dl_vlan={vlan},dl_dst={mac}," <> - "actions=strip_vlan,output:{port_no}" do - test "Install Flow", state do - match = - Openflow.Match.new( - in_port: state.vlan_trunk.number, - vlan_vid: @bootnet_vid, - eth_dst: @mac - ) - - actions = [ - Openflow.Action.PopVlan.new(), - Openflow.Action.Output.new(state.port.number) - ] - - ins = Openflow.Instruction.ApplyActions.new(actions) - - options = [ - cookie: state.cookie, - table_id: 0, - priority: 50, - flags: [:send_flow_rem], - idle_timeout: state.timeout, - hard_timeout: state.timeout, - match: match, - instructions: [ins] - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - - test "Inject Packet(ARP)" do - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - - shost = Openflow.Match.Field.codec(@sdl_vmac, :eth_src) - dhost = Openflow.Match.Field.codec(@mac, :eth_dst) - - packet = [ - ether(dhost: dhost, shost: shost, type: 0x8100), - {:"802.1q", 0, 0, 5, 0x0806}, - arp(op: 2, sha: shost, tip: @client_ip, sip: @gateway_ip) - ] - - expect = [ - ether(dhost: dhost, shost: shost, type: 0x0806), - arp(op: 2, sha: shost, tip: @client_ip, sip: @gateway_ip) - ] - - Pf.inject!(pid2, packet) - assert_receive {@access_port1_sniffer, ^expect}, 3000 - Pf.stop(pid1) - Pf.stop(pid2) - end - end - - describe "associate:register_bootstrap_rule:" <> - "send_flow_rem," <> "dl_src={mac},in_port={vlan_trunk_port}," <> "actions=drop" do - test "Install Flow", state do - match = - Openflow.Match.new( - in_port: state.vlan_trunk.number, - eth_src: @mac - ) - - options = [ - cookie: state.cookie, - table_id: 0, - priority: 19, - flags: [:send_flow_rem], - idle_timeout: state.timeout, - hard_timeout: state.timeout, - match: match - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - - test "Inject Packet(ARP)" do - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - - shost = Openflow.Match.Field.codec(@mac, :eth_src) - dhost = Openflow.Match.Field.codec(@bcast, :eth_dst) - payload = <<0::size(16)-unit(8)>> - - packet = [ - ether(dhost: dhost, shost: shost, type: 0x0806), - arp(op: 2, sha: shost, sip: @client_ip, tip: @gateway_ip) - ] - - Pf.inject!(pid2, packet, payload) - refute_receive {@access_port1_sniffer, ^packet}, 3000 - Pf.stop(pid1) - Pf.stop(pid2) - end - end - - describe "associate:register_bootstrap_rule:" <> - "send_flow_rem," <> - "in_port={vlan_trunk_port},dl_vlan={vlan},dl_src={trusted},dl_dst={mac}," <> - "actions=strip_vlan,output:{port_no}" do - test "Install Flow", state do - for trusted <- @trusted_macs do - match = - Openflow.Match.new( - in_port: state.vlan_trunk.number, - vlan_vid: @bootnet_vid, - eth_src: trusted, - eth_dst: @mac - ) - - actions = [ - Openflow.Action.PopVlan.new(), - Openflow.Action.Output.new(state.port.number) - ] - - ins = Openflow.Instruction.ApplyActions.new(actions) - - options = [ - cookie: state.cookie, - table_id: 0, - priority: 50, - flags: [:send_flow_rem], - idle_timeout: state.timeout, - hard_timeout: state.timeout, - match: match, - instructions: [ins] - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - end - - test "Inject Packet(ARP)" do - for trusted <- @trusted_macs do - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - - shost = Openflow.Match.Field.codec(trusted, :eth_src) - dhost = Openflow.Match.Field.codec(@mac, :eth_dst) - payload = <<0::size(16)-unit(8)>> - - packet = [ - ether(dhost: dhost, shost: shost, type: 0x8100), - {:"802.1q", 0, 0, 5, 0x0806}, - arp(op: 2, sha: shost, tha: dhost, tip: @client_ip, sip: @gateway_ip) - ] - - expect = [ - ether(dhost: dhost, shost: shost, type: 0x0806), - arp(op: 2, sha: shost, tha: dhost, tip: @client_ip, sip: @gateway_ip) - ] - - Pf.inject!(pid2, packet, payload) - assert_receive {@access_port1_sniffer, ^expect}, 3000 - Pf.stop(pid1) - Pf.stop(pid2) - end - end - end - - describe "associate:register_bootstrap_rule:" <> - "send_flow_rem," <> "dl_src={trusted}," <> "actions=drop" do - test "Install Flow", state do - for trusted <- @trusted_macs do - match = Openflow.Match.new(eth_src: trusted) - - options = [ - cookie: state.cookie, - table_id: 0, - priority: 29, - flags: [:send_flow_rem], - idle_timeout: state.timeout, - hard_timeout: state.timeout, - match: match - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - end - end - - describe "associate:register_usernet_rule:" <> - "in_port={vlan_trunk_port},dl_vlan={vlan}," <> "actions=goto_table:1" do - setup [:flow_del_by_cookie] - - test "Install Flow", state do - match = Openflow.Match.new(in_port: state.vlan_trunk.number, vlan_vid: @user_vid) - ins = [Openflow.Instruction.GotoTable.new(1)] - - options = [ - cookie: state.cookie2, - table_id: 0, - priority: 50, - match: match, - instructions: ins - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - end - - describe "associate:register_usernet_rule:" <> - "idle_timeout={itimeout},hard_timeout={htimeout},send_flow_rem, dl_src={mac},in_port={port_no}," <> - "actions=push_vlan:0x8100,set_field:{vlan}->vlan_vid,goto_table:1" do - test "Install Flow", state do - match = Openflow.Match.new(in_port: state.port.number, eth_src: @mac) - - actions = [ - Openflow.Action.PushVlan.new(), - Openflow.Action.SetField.new({:vlan_vid, @user_vid}) - ] - - ins = [ - Openflow.Instruction.ApplyActions.new(actions), - Openflow.Instruction.GotoTable.new(1) - ] - - options = [ - cookie: state.cookie2, - table_id: 0, - priority: 20, - flags: [:send_flow_rem], - idle_timeout: state.timeout, - hard_timeout: state.timeout, - match: match, - instructions: ins - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - end - - describe "associate:register_usernet_rule:" <> - "vlan_vid=0x1000/0x1000,tcp,tcp_dst=443,nw_dst={auth_ipv4_address}," <> - "strip_vlan,set_field:{sdl_vmac}->eth_dst,output:{vlan_trunk_port}" do - test "Install Flow", state do - match = - Openflow.Match.new( - vlan_vid: @vlan_present, - eth_type: 0x0800, - ip_proto: 6, - ipv4_dst: @auth_ipv4_address, - tcp_dst: 443 - ) - - actions = [ - Openflow.Action.PopVlan.new(), - Openflow.Action.SetField.new({:eth_dst, @sdl_vmac}), - Openflow.Action.Output.new(state.vlan_trunk.number) - ] - - ins = Openflow.Instruction.ApplyActions.new(actions) - - options = [ - cookie: state.cookie2, - table_id: 1, - priority: 30, - match: match, - instructions: [ins] - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - - test "Inject Packet(TCP:443)" do - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - - shost = Openflow.Match.Field.codec(@mac, :eth_src) - dhost = Openflow.Match.Field.codec(@sdl_vmac, :eth_dst) - - payload = "" - - tcp_header = - tcp( - sport: 53_688, - dport: 443, - seqno: 1_488_352_223, - off: 10, - syn: 1, - win: 29_200, - opt: <<2, 4, 5, 180, 4, 2, 8, 10, 156, 30, 232, 154, 0, 0, 0, 0, 1, 3, 3, 7>> - ) - - ipv4_header = ipv4(saddr: @client_ip, daddr: @auth_ipv4_address, p: 6, len: 60) - tcp_sum = :pkt.makesum([ipv4_header, tcp_header, payload]) - ether_header = ether(dhost: dhost, shost: shost, type: 0x0800) - packet = [ether_header, ipv4_header, tcp(tcp_header, sum: tcp_sum)] - expect = [ether_header, ipv4_header, tcp(tcp_header, sum: tcp_sum)] - Pf.inject!(pid1, packet, <>) - assert_receive {@vlan_trunk_port_sniffer, ^expect}, 3000 - Pf.stop(pid1) - Pf.stop(pid2) - end - end - - describe "associate:register_usernet_rule:" <> - "vlan_vid=0x1000/0x1000,tcp,tcp_dst=80,nw_dst={captive_ipv4_address}," <> - "strip_vlan,set_field:{sdl_vmac}->eth_dst,output:{vlan_trunk_port}" do - test "Install Flow", state do - match = - Openflow.Match.new( - vlan_vid: @vlan_present, - eth_type: 0x0800, - ip_proto: 6, - ipv4_dst: @captive_ipv4_address, - tcp_dst: 80 - ) - - actions = [ - Openflow.Action.PopVlan.new(), - Openflow.Action.SetField.new({:eth_dst, @sdl_vmac}), - Openflow.Action.Output.new(state.vlan_trunk.number) - ] - - ins = Openflow.Instruction.ApplyActions.new(actions) - - options = [ - cookie: state.cookie2, - table_id: 1, - priority: 30, - match: match, - instructions: [ins] - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - - test "Inject Packet(TCP:80)" do - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - - shost = Openflow.Match.Field.codec(@mac, :eth_src) - dhost = Openflow.Match.Field.codec(@sdl_vmac, :eth_dst) - - payload = "" - - tcp_header = - tcp( - sport: 53_688, - dport: 80, - seqno: 1_488_352_223, - off: 10, - syn: 1, - win: 29_200, - opt: <<2, 4, 5, 180, 4, 2, 8, 10, 156, 30, 232, 154, 0, 0, 0, 0, 1, 3, 3, 7>> - ) - - ipv4_header = ipv4(saddr: @client_ip, daddr: @captive_ipv4_address, p: 6, len: 60) - tcp_sum = :pkt.makesum([ipv4_header, tcp_header, payload]) - ether_header = ether(dhost: dhost, shost: shost, type: 0x0800) - packet = [ether_header, ipv4_header, tcp(tcp_header, sum: tcp_sum)] - expect = [ether_header, ipv4_header, tcp(tcp_header, sum: tcp_sum)] - Pf.inject!(pid1, packet, <>) - assert_receive {@vlan_trunk_port_sniffer, ^expect}, 3000 - Pf.stop(pid1) - Pf.stop(pid2) - end - end - - describe "associate:register_usernet_rule:" <> - "idle_timeout={itimeout},hard_timeout={htimeout},send_flow_rem,dl_src={mac},in_port={vlan_trunk_port}," <> - "actions=drop" do - test "Install Flow", state do - match = Openflow.Match.new(in_port: state.vlan_trunk.number, eth_src: @mac) - - options = [ - cookie: state.cookie2, - table_id: 0, - priority: 19, - flags: [:send_flow_rem], - idle_timeout: state.timeout, - hard_timeout: state.timeout, - match: match - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - end - - describe "associate:register_usernet_rule:" <> - "idle_timeout={itimeout},hard_timeout={htimeout},send_flow_rem,dl_src={mac}," <> - "actions=controller" do - test "Install Flow", state do - match = Openflow.Match.new(eth_src: @mac) - actions = [Openflow.Action.Output.new(:controller)] - ins = [Openflow.Instruction.ApplyActions.new(actions)] - - options = [ - cookie: state.cookie2, - table_id: 0, - priority: 18, - flags: [:send_flow_rem], - idle_timeout: state.timeout, - hard_timeout: state.timeout, - match: match, - instructions: ins - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - end - - describe "associate:register_usernet_rule:" <> - "idle_timeout={itimeout},hard_timeout={htimeout},send_flow_rem,vlan_vid=0x1000/0x1000,dl_dst={mac}," <> - "actions=strip_vlan,output:{port_no}" do - test "Install Flow", state do - match = Openflow.Match.new(vlan_vid: @vlan_present, eth_dst: @mac) - actions = [Openflow.Action.PopVlan.new(), Openflow.Action.Output.new(state.port.number)] - ins = [Openflow.Instruction.ApplyActions.new(actions)] - - options = [ - cookie: state.cookie2, - table_id: 1, - priority: 50, - flags: [:send_flow_rem], - idle_timeout: state.timeout, - hard_timeout: state.timeout, - match: match, - instructions: ins - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - - test "Inject Packet(ARP)" do - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - - shost = Openflow.Match.Field.codec(@sdl_vmac, :eth_src) - dhost = Openflow.Match.Field.codec(@mac, :eth_dst) - vlan_ether_header = ether(dhost: dhost, shost: shost, type: 0x8100) - ether_header = ether(dhost: dhost, shost: shost, type: 0x0806) - vlan_header = {:"802.1q", 0, 1, 123, 0x0806} - arp_header = arp(op: 2, sha: shost, tha: dhost, tip: @client_ip, sip: @gateway_ip) - packet = [vlan_ether_header, vlan_header, arp_header] - expect = [ether_header, arp_header] - Pf.inject!(pid2, packet) - assert_receive {@access_port1_sniffer, ^expect}, 1000 - Pf.stop(pid1) - Pf.stop(pid2) - end - end - - describe "associate:register_usernet_rule:" <> - "dl_vlan={vlan},dl_dst={mcast}," <> - "actions=output:{vlan_trunk_port},strip_vlan,{outputs}" do - test "Install Flow", state do - match = Openflow.Match.new(vlan_vid: @user_vid, eth_dst: @mcast) - - actions = [ - Openflow.Action.Output.new(state.vlan_trunk.number), - Openflow.Action.PopVlan.new(), - Openflow.Action.Output.new(state.port.number), - Openflow.Action.Output.new(state.port2.number) - ] - - ins = [Openflow.Instruction.ApplyActions.new(actions)] - - options = [ - cookie: state.cookie2, - table_id: 1, - priority: 60, - match: match, - instructions: ins - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - - test "Inject Packet(ARP) from VLAN TRUNK" do - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - {:ok, pid3} = Pf.start_link(@access_port2_sniffer) - - shost = Openflow.Match.Field.codec(@sdl_vmac, :eth_src) - dhost = Openflow.Match.Field.codec(@bcast, :eth_dst) - vlan_ether_header = ether(dhost: dhost, shost: shost, type: 0x8100) - ether_header = ether(dhost: dhost, shost: shost, type: 0x0806) - vlan_header = {:"802.1q", 0, 1, 123, 0x0806} - arp_header = arp(op: 1, sha: shost, tip: @client_ip, sip: @gateway_ip) - packet = [vlan_ether_header, vlan_header, arp_header] - Pf.inject!(pid2, packet) - expect = [ether_header, arp_header] - assert_receive {@access_port1_sniffer, ^expect}, 3000 - assert_receive {@access_port2_sniffer, ^expect}, 3000 - Pf.stop(pid1) - Pf.stop(pid2) - Pf.stop(pid3) - end - - test "Inject Packet(ARP) from ACCESS" do - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - {:ok, pid3} = Pf.start_link(@access_port2_sniffer) - - shost = Openflow.Match.Field.codec(@mac, :eth_src) - dhost = Openflow.Match.Field.codec(@bcast, :eth_dst) - vlan_ether_header = ether(dhost: dhost, shost: shost, type: 0x8100) - ether_header = ether(dhost: dhost, shost: shost, type: 0x0806) - vlan_header = {:"802.1q", 0, 0, 123, 0x0806} - arp_header = arp(op: 1, sha: shost, tip: @client_ip, sip: @gateway_ip) - packet = [ether_header, arp_header] - Pf.inject!(pid1, packet) - - expects = [ - {@vlan_trunk_port_sniffer, [vlan_ether_header, vlan_header, arp_header]}, - {@access_port2_sniffer, [ether_header, arp_header]} - ] - - :ok = assert_receives(expects) - Pf.stop(pid1) - Pf.stop(pid2) - Pf.stop(pid3) - end - end - - describe "associate:register_usernet_rule:" <> - "dl_vlan={vlan}," <> "actions=output:{vlan_trunk_port},strip_vlan,{outputs}" do - test "Install Flow", state do - match = Openflow.Match.new(vlan_vid: @user_vid) - - actions = [ - Openflow.Action.Output.new(state.vlan_trunk.number), - Openflow.Action.PopVlan.new(), - Openflow.Action.Output.new(state.port.number) - ] - - ins = [Openflow.Instruction.ApplyActions.new(actions)] - - options = [ - cookie: state.cookie2, - table_id: 1, - priority: 20, - match: match, - instructions: ins - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - end - - describe "switch:register_farm_nat_rule:" <> - "dl_src={src_mac},arp,arp_spa={src_ip},arp_tpa={dst_ip}," <> - "actions=set_field:{farm_vmac}->eth_dst,set_field:{src_fip}->arp_spa," <> - "set_field:{dst_fip}->arp_tpa,output:{vlan_trunk_port}" do - test "Install Flow", state do - match = - Openflow.Match.new( - eth_src: @mac, - eth_type: 0x0806, - arp_spa: @client_ip, - arp_tpa: @gateway_ip - ) - - actions = [ - Openflow.Action.SetField.new({:eth_dst, @farm_gw_mac}), - Openflow.Action.SetField.new({:arp_spa, @client_farm_ip}), - Openflow.Action.SetField.new({:arp_tpa, @farm_gw_ip}), - # Following is not work properly in "SwitchNode" configuration. - # Openflow.Action.PushVlan.new, - # Openflow.Action.SetField.new({:vlan_vid, @user_vid}), - Openflow.Action.Output.new(state.vlan_trunk.number) - ] - - ins = [Openflow.Instruction.ApplyActions.new(actions)] - - options = [ - cookie: 0x3000000000000001, - table_id: 1, - priority: 30, - idle_timeout: 60, - match: match, - instructions: ins - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - end - - describe "switch:register_farm_nat_rule:" <> - "dl_src={src_mac},ip,nw_src={src_ip},nw_dst={dst_ip}," <> - "actions=push_vlan:0x8100,set_field:{vlan}->vlan_vid,set_field:{farm_vmac}->eth_dst," <> - "set_field:{src_fip}->nw_src,output:{vlan_trunk_port}" do - test "Install Flow", state do - match = - Openflow.Match.new( - eth_src: @mac, - eth_type: 0x0800, - ipv4_src: @client_ip, - ipv4_dst: @internet - ) - - actions = [ - Openflow.Action.SetField.new({:eth_dst, @farm_gw_mac}), - # Following is not work properly in "SwitchNode" configuration. - # Openflow.Action.PushVlan.new, - # Openflow.Action.SetField.new({:vlan_vid, @user_vid}), - Openflow.Action.SetField.new({:ipv4_src, @client_farm_ip}), - Openflow.Action.Output.new(state.vlan_trunk.number) - ] - - ins = [Openflow.Instruction.ApplyActions.new(actions)] - - options = [ - cookie: 0x3000000000000001, - table_id: 1, - priority: 30, - idle_timeout: 60, - match: match, - instructions: ins - ] - - :ok = GenServer.cast(Flay, {:flow_install, options, self()}) - :timer.sleep(1000) - refute_receive %Openflow.ErrorMsg{}, 1000 - end - - test "Inject Packet(TCP:80)" do - {:ok, pid1} = Pf.start_link(@access_port1_sniffer) - {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) - - shost = Openflow.Match.Field.codec(@mac, :eth_src) - dhost = Openflow.Match.Field.codec(@sdl_vmac, :eth_dst) - - payload = "" - - tcp_header = - tcp( - sport: 53_688, - dport: 80, - seqno: 1_488_352_223, - off: 10, - syn: 1, - win: 29_200, - opt: <<2, 4, 5, 180, 4, 2, 8, 10, 156, 30, 232, 154, 0, 0, 0, 0, 1, 3, 3, 7>> - ) - - ipv4_header = ipv4(saddr: @client_ip, daddr: @internet, p: 6, len: 60) - tcp_sum = :pkt.makesum([ipv4_header, tcp_header, payload]) - ether_header = ether(dhost: dhost, shost: shost, type: 0x0800) - packet = [ether_header, ipv4_header, tcp(tcp_header, sum: tcp_sum)] - Pf.inject!(pid1, packet, <>) - - shost = Openflow.Match.Field.codec(@mac, :eth_src) - dhost = Openflow.Match.Field.codec(@farm_gw_mac, :eth_dst) - ether_header = ether(dhost: dhost, shost: shost, type: 0x8100) - vlan_header = {:"802.1q", 0, 0, 123, 0x0800} - ipv4_header = ipv4(saddr: @client_farm_ip, daddr: @internet, p: 6, len: 60) - tcp_sum = :pkt.makesum([ipv4_header, tcp_header, payload]) - - expect = [ - ether_header, - vlan_header, - ipv4(ipv4_header, sum: 1544), - tcp(tcp_header, sum: tcp_sum) - ] - - assert_receives([{@vlan_trunk_port_sniffer, expect}], 5000) - Pf.stop(pid1) - Pf.stop(pid2) - end - end - - # private functions - - defp assert_receives(expects, timeout \\ 1000) do - assert_receives(expects, length(expects), timeout) - end - - defp assert_receives(_expects, 0, _timeout), do: :ok - - defp assert_receives(expects, count, timeout) do - receive do - message -> - assert(message in expects) - assert_receives(expects, count - 1, timeout) - after - timeout -> - flunk("Timeout") - end - end - - defp setup_applications do - Application.put_env(:tres, :protocol, :tcp, persistent: true) - Application.put_env(:tres, :port, @listen_port, persistent: true) - Application.put_env(:tres, :mac_connections, 1, persistent: true) - Application.put_env(:tres, :mac_acceptors, 1, persistent: true) - Application.put_env(:tres, :callback_module, Flay, persistent: true) - Application.put_env(:tres, :callback_args, self(), persistent: true) - Application.start(:binpp) - Application.start(:pkt) - Application.start(:epcap) - Application.start(:ranch) - Application.start(:eovsdb) - Application.start(:tres) - end - - defp wait_for_connected do - case Process.whereis(Flay) do - nil -> - wait_for_connected() - - pid when is_pid(pid) -> - :ok - end - end - - defp flow_del_by_cookie(context) do - :ok = GenServer.cast(Flay, {:flow_del, context.cookie}) - :timer.sleep(3000) - end - - defp get_ports_desc do - port_desc = GenServer.call(Flay, :port_desc_stats, 5000) - port_desc.ports - end -end diff --git a/test/pf.ex b/test/pf.ex deleted file mode 100644 index e96109c..0000000 --- a/test/pf.ex +++ /dev/null @@ -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, <>) - {: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