Add an example controller for hardware switch test
This commit is contained in:
parent
cfc2152b7d
commit
f2297c551a
16 changed files with 501 additions and 50 deletions
20
examples/heckle/.gitignore
vendored
Normal file
20
examples/heckle/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# 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
|
||||||
21
examples/heckle/README.md
Normal file
21
examples/heckle/README.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# 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).
|
||||||
|
|
||||||
30
examples/heckle/config/config.exs
Normal file
30
examples/heckle/config/config.exs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# 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_trunk: "veth0",
|
||||||
|
access_port1: "veth4",
|
||||||
|
access_port2: "veth3",
|
||||||
|
receiver_mac: "00000000000f",
|
||||||
|
receiver_ip: {8,8,8,8},
|
||||||
|
sender_mac: "001122334455",
|
||||||
|
inside_local: {192,168,5,10},
|
||||||
|
outside_local: {192,168,255,1},
|
||||||
|
flow_pattern: :bum
|
||||||
|
|
||||||
|
config :tres,
|
||||||
|
protocol: :tcp,
|
||||||
|
port: 6653,
|
||||||
|
max_connections: 10,
|
||||||
|
num_acceptors: 10,
|
||||||
|
callback_module: Heckle.Controller,
|
||||||
|
callback_args: ["0000d2851d52d749"]
|
||||||
|
|
||||||
|
config :logger,
|
||||||
|
level: :debug,
|
||||||
|
format: "$date $time [$level] $metadata$message\n",
|
||||||
|
metadata: [:application],
|
||||||
|
handle_otp_reports: true
|
||||||
18
examples/heckle/lib/heckle.ex
Normal file
18
examples/heckle/lib/heckle.ex
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
defmodule Heckle do
|
||||||
|
@moduledoc """
|
||||||
|
Documentation for Heckle.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Hello world.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> Heckle.hello
|
||||||
|
:world
|
||||||
|
|
||||||
|
"""
|
||||||
|
def hello do
|
||||||
|
:world
|
||||||
|
end
|
||||||
|
end
|
||||||
20
examples/heckle/lib/heckle/application.ex
Normal file
20
examples/heckle/lib/heckle/application.ex
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
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
|
||||||
133
examples/heckle/lib/heckle/controller.ex
Normal file
133
examples/heckle/lib/heckle/controller.ex
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
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_port_name: nil,
|
||||||
|
access_port1: nil,
|
||||||
|
access_port2: nil,
|
||||||
|
trunk_port: 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
|
||||||
|
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(%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_port = Enum.find(ports, fn(port) -> port.name == state.trunk_port_name end)
|
||||||
|
:ok = desc_stats_request(state.dpid)
|
||||||
|
{:noreply, %{state|access_port1: access_port1, access_port2: access_port2, trunk_port: trunk_port}}
|
||||||
|
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({:'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
|
||||||
|
{: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_port_name: config[:vlan_trunk],
|
||||||
|
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
|
||||||
|
end
|
||||||
113
examples/heckle/lib/heckle/flow_patterns.ex
Normal file
113
examples/heckle/lib/heckle/flow_patterns.ex
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
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_port.number)
|
||||||
|
])]
|
||||||
|
],
|
||||||
|
[table_id: 1,
|
||||||
|
priority: 30,
|
||||||
|
cookie: 0x3000000000000001,
|
||||||
|
match: Match.new(
|
||||||
|
eth_dst: state.sender_mac,
|
||||||
|
eth_src: state.receiver_mac,
|
||||||
|
eth_type: 0x0800,
|
||||||
|
ipv4_src: state.receiver_ip,
|
||||||
|
ipv4_dst: state.outside_local
|
||||||
|
),
|
||||||
|
instructions: [
|
||||||
|
ApplyActions.new([
|
||||||
|
SetField.new({:eth_src, state.receiver_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_port.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_port.number,
|
||||||
|
vlan_vid: state.vlan_id
|
||||||
|
),
|
||||||
|
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_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)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
31
examples/heckle/lib/heckle/pipeline_profiles.ex
Normal file
31
examples/heckle/lib/heckle/pipeline_profiles.ex
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
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, :masked_eth_src],
|
||||||
|
wildcards: [:in_port, :masked_eth_src],
|
||||||
|
instructions: [GotoTable],
|
||||||
|
write_actions: [Output],
|
||||||
|
next_tables: [1],
|
||||||
|
),
|
||||||
|
TableFeatures.Body.new(
|
||||||
|
table_id: 1,
|
||||||
|
name: "NAT",
|
||||||
|
max_entries: 10,
|
||||||
|
config: [:table_miss_mask],
|
||||||
|
match: [:eth_dst, :eth_src, :eth_type, :ipv4_src, :ipv4_dst],
|
||||||
|
wildcards: [:eth_dst, :eth_src, :eth_type, :ipv4_src, :ipv4_dst],
|
||||||
|
instructions: [ApplyActions],
|
||||||
|
write_actions: [SetField, PopVlan, PushVlan, Output],
|
||||||
|
apply_setfield: [:eth_dst, :vlan_vid, :ipv4_src, :ipv4_dst],
|
||||||
|
next_tables: [],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
22
examples/heckle/mix.exs
Normal file
22
examples/heckle/mix.exs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
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}]
|
||||||
|
end
|
||||||
|
end
|
||||||
5
examples/heckle/mix.lock
Normal file
5
examples/heckle/mix.lock
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
%{"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"]},
|
||||||
|
"ranch": {:hex, :ranch, "1.4.0", "10272f95da79340fa7e8774ba7930b901713d272905d0012b06ca6d994f8826b", [], [], "hexpm"},
|
||||||
|
"uuid": {:git, "https://github.com/avtobiff/erlang-uuid.git", "585c2474afb4a597ae8c8bf6d21e5a9c73f18e0b", [tag: "v0.5.0"]}}
|
||||||
8
examples/heckle/test/heckle_test.exs
Normal file
8
examples/heckle/test/heckle_test.exs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
defmodule HeckleTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
doctest Heckle
|
||||||
|
|
||||||
|
test "greets the world" do
|
||||||
|
assert Heckle.hello() == :world
|
||||||
|
end
|
||||||
|
end
|
||||||
1
examples/heckle/test/test_helper.exs
Normal file
1
examples/heckle/test/test_helper.exs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ExUnit.start()
|
||||||
|
|
@ -189,10 +189,13 @@ defmodule Tres.SecureChannel do
|
||||||
:keep_state_and_data
|
:keep_state_and_data
|
||||||
end
|
end
|
||||||
defp handle_CONNECTED(:cast, {:send_message, message} = action, state_data) do
|
defp handle_CONNECTED(:cast, {:send_message, message} = action, state_data) do
|
||||||
if Queue.is_empty(state_data.action_queue),
|
new_action_queue = if XACT_KV.is_empty(state_data.xact_kv_ref) do
|
||||||
do: xactional_send_message(message, state_data)
|
xactional_send_message(message, state_data)
|
||||||
action_queue = Queue.in(action, state_data.action_queue)
|
state_data.action_queue
|
||||||
{:keep_state, %{state_data|action_queue: action_queue}}
|
else
|
||||||
|
Queue.in(action, state_data.action_queue)
|
||||||
|
end
|
||||||
|
{:keep_state, %{state_data|action_queue: new_action_queue}}
|
||||||
end
|
end
|
||||||
|
|
||||||
# WATING state
|
# WATING state
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
-include_lib("stdlib/include/ms_transform.hrl").
|
-include_lib("stdlib/include/ms_transform.hrl").
|
||||||
|
|
||||||
-export([create/0, drop/1]).
|
-export([create/0, drop/1]).
|
||||||
-export([insert/3, update/3, get/2, delete/2, is_exists/2]).
|
-export([insert/3, update/3, get/2, delete/2, is_exists/2, is_empty/1]).
|
||||||
|
|
||||||
-define(TABLE, xact_kv).
|
-define(TABLE, xact_kv).
|
||||||
-define(ENTRY, xact_entry).
|
-define(ENTRY, xact_entry).
|
||||||
|
|
@ -44,6 +44,13 @@ is_exists(Tid, Xid) ->
|
||||||
[] -> false
|
[] -> false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec is_empty(reference()) -> boolean().
|
||||||
|
is_empty(Tid) ->
|
||||||
|
case ets:info(Tid, size) of
|
||||||
|
0 -> true;
|
||||||
|
_ -> false
|
||||||
|
end.
|
||||||
|
|
||||||
%% Private functions
|
%% Private functions
|
||||||
|
|
||||||
ms_for_exists(Xid) ->
|
ms_for_exists(Xid) ->
|
||||||
|
|
|
||||||
|
|
@ -195,9 +195,7 @@ defmodule Flay do
|
||||||
:udp_dst,
|
:udp_dst,
|
||||||
:tcp_dst,
|
:tcp_dst,
|
||||||
:ipv4_src,
|
:ipv4_src,
|
||||||
:ipv4_dst,
|
:ipv4_dst
|
||||||
:arp_spa,
|
|
||||||
:arp_tpa
|
|
||||||
],
|
],
|
||||||
wildcards: [
|
wildcards: [
|
||||||
:eth_type,
|
:eth_type,
|
||||||
|
|
@ -208,9 +206,7 @@ defmodule Flay do
|
||||||
:udp_dst,
|
:udp_dst,
|
||||||
:tcp_dst,
|
:tcp_dst,
|
||||||
:ipv4_src,
|
:ipv4_src,
|
||||||
:ipv4_dst,
|
:ipv4_dst
|
||||||
:arp_spa,
|
|
||||||
:arp_tpa
|
|
||||||
],
|
],
|
||||||
instructions: [
|
instructions: [
|
||||||
Openflow.Instruction.GotoTable,
|
Openflow.Instruction.GotoTable,
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,8 @@ defmodule FlogTest do
|
||||||
priority: 0
|
priority: 0
|
||||||
]
|
]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Inject Packet(ARP)" do
|
test "Inject Packet(ARP)" do
|
||||||
|
|
@ -89,7 +90,7 @@ defmodule FlogTest do
|
||||||
arp(op: 1, sha: shost, sip: @client_ip, tip: @gateway_ip)
|
arp(op: 1, sha: shost, sip: @client_ip, tip: @gateway_ip)
|
||||||
]
|
]
|
||||||
Pf.inject!(pid1, packet, payload)
|
Pf.inject!(pid1, packet, payload)
|
||||||
refute_receive {@vlan_trunk_port_sniffer, ^packet}, 3000
|
refute_receive {@vlan_trunk_port_sniffer, ^packet}, 1000
|
||||||
Pf.stop(pid1)
|
Pf.stop(pid1)
|
||||||
Pf.stop(pid2)
|
Pf.stop(pid2)
|
||||||
end
|
end
|
||||||
|
|
@ -114,7 +115,8 @@ defmodule FlogTest do
|
||||||
match: match,
|
match: match,
|
||||||
instructions: [ins]]
|
instructions: [ins]]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
|
|
||||||
{:ok, pid1} = Pf.start_link(@access_port1_sniffer)
|
{:ok, pid1} = Pf.start_link(@access_port1_sniffer)
|
||||||
{:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer)
|
{:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer)
|
||||||
|
|
@ -129,9 +131,9 @@ defmodule FlogTest do
|
||||||
]
|
]
|
||||||
Pf.inject!(pid2, packet)
|
Pf.inject!(pid2, packet)
|
||||||
in_port = state.vlan_trunk.number
|
in_port = state.vlan_trunk.number
|
||||||
assert_receive %Openflow.PacketIn{in_port: ^in_port}, 3000
|
assert_receive %Openflow.PacketIn{in_port: ^in_port}, 1000
|
||||||
refute_receive {@vlan_trunk_port_sniffer, [^packet, ""]}, 3000
|
refute_receive {@vlan_trunk_port_sniffer, [^packet, ""]}, 1000
|
||||||
refute_receive {@access_port1_sniffer, [^packet, ""]}, 3000
|
refute_receive {@access_port1_sniffer, [^packet, ""]}, 1000
|
||||||
Pf.stop(pid1)
|
Pf.stop(pid1)
|
||||||
Pf.stop(pid2)
|
Pf.stop(pid2)
|
||||||
end
|
end
|
||||||
|
|
@ -148,25 +150,27 @@ defmodule FlogTest do
|
||||||
priority: 201,
|
priority: 201,
|
||||||
match: match]
|
match: match]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe("switch:uplink_escalation_flow:" <>
|
describe("switch:uplink_escalation_flow:" <>
|
||||||
"table=0,priority=10,cookie=0x3000000000000000,arp,actions=controller") do
|
"table=0,priority=10,cookie=0x1000000000000000,arp,actions=controller") do
|
||||||
test "Install Flow", state do
|
test "Install Flow", state do
|
||||||
match = Openflow.Match.new(eth_type: 0x0806)
|
match = Openflow.Match.new(eth_type: 0x0806)
|
||||||
action = Openflow.Action.Output.new(:controller)
|
action = Openflow.Action.Output.new(:controller)
|
||||||
ins = Openflow.Instruction.ApplyActions.new(action)
|
ins = Openflow.Instruction.ApplyActions.new(action)
|
||||||
options =
|
options =
|
||||||
[cookie: 0x3000000000000000,
|
[cookie: 0x1000000000000000,
|
||||||
table_id: 0,
|
table_id: 0,
|
||||||
priority: 10,
|
priority: 10,
|
||||||
match: match,
|
match: match,
|
||||||
instructions: [ins]]
|
instructions: [ins]]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
|
|
||||||
{:ok, pid1} = Pf.start_link(@access_port1_sniffer)
|
{:ok, pid1} = Pf.start_link(@access_port1_sniffer)
|
||||||
{:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer)
|
{:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer)
|
||||||
|
|
@ -180,27 +184,28 @@ defmodule FlogTest do
|
||||||
]
|
]
|
||||||
Pf.inject!(pid1, packet, payload)
|
Pf.inject!(pid1, packet, payload)
|
||||||
in_port = state.port.number
|
in_port = state.port.number
|
||||||
assert_receive %Openflow.PacketIn{in_port: ^in_port}, 3000
|
assert_receive %Openflow.PacketIn{in_port: ^in_port}, 1000
|
||||||
refute_receive {@vlan_trunk_port_sniffer, ^packet}, 3000
|
refute_receive {@vlan_trunk_port_sniffer, ^packet}, 1000
|
||||||
Pf.stop(pid1)
|
Pf.stop(pid1)
|
||||||
Pf.stop(pid2)
|
Pf.stop(pid2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe("switch:uplink_escalation_flow:" <>
|
describe("switch:uplink_escalation_flow:" <>
|
||||||
"table=0,priority=10,cookie=0x3000000000000000,udp,udp_dst=67,actions=controller") do
|
"table=0,priority=10,cookie=0x1000000000000000,udp,udp_dst=67,actions=controller") do
|
||||||
test "Install Flow", state do
|
test "Install Flow", state do
|
||||||
match = Openflow.Match.new(eth_type: 0x0800, ip_proto: 17, udp_dst: 67)
|
match = Openflow.Match.new(eth_type: 0x0800, ip_proto: 17, udp_dst: 67)
|
||||||
action = Openflow.Action.Output.new(:controller)
|
action = Openflow.Action.Output.new(:controller)
|
||||||
ins = Openflow.Instruction.ApplyActions.new(action)
|
ins = Openflow.Instruction.ApplyActions.new(action)
|
||||||
options =
|
options =
|
||||||
[cookie: 0x3000000000000000,
|
[cookie: 0x1000000000000000,
|
||||||
table_id: 0,
|
table_id: 0,
|
||||||
priority: 10,
|
priority: 10,
|
||||||
match: match,
|
match: match,
|
||||||
instructions: [ins]]
|
instructions: [ins]]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
|
|
||||||
{:ok, pid1} = Pf.start_link(@access_port1_sniffer)
|
{:ok, pid1} = Pf.start_link(@access_port1_sniffer)
|
||||||
{:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer)
|
{:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer)
|
||||||
|
|
@ -220,24 +225,25 @@ defmodule FlogTest do
|
||||||
]
|
]
|
||||||
Pf.inject!(pid1, packet, <<payload::bytes>>)
|
Pf.inject!(pid1, packet, <<payload::bytes>>)
|
||||||
in_port = state.port.number
|
in_port = state.port.number
|
||||||
assert_receive %Openflow.PacketIn{in_port: ^in_port}, 3000
|
assert_receive %Openflow.PacketIn{in_port: ^in_port}, 1000
|
||||||
refute_receive {@vlan_trunk_port_sniffer, ^packet}, 3000
|
refute_receive {@vlan_trunk_port_sniffer, ^packet}, 1000
|
||||||
Pf.stop(pid1)
|
Pf.stop(pid1)
|
||||||
Pf.stop(pid2)
|
Pf.stop(pid2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe("switch:uplink_escalation_flow:" <>
|
describe("switch:uplink_escalation_flow:" <>
|
||||||
"table=0,priority=11,cookie=0x3000000000000000,in_port={trunk_port},actions=drop") do
|
"table=0,priority=11,cookie=0x1000000000000000,in_port={trunk_port},actions=drop") do
|
||||||
test "Install Flow", state do
|
test "Install Flow", state do
|
||||||
match = Openflow.Match.new(in_port: state.vlan_trunk.number)
|
match = Openflow.Match.new(in_port: state.vlan_trunk.number)
|
||||||
options =
|
options =
|
||||||
[cookie: 0x3000000000000000,
|
[cookie: 0x1000000000000000,
|
||||||
table_id: 0,
|
table_id: 0,
|
||||||
priority: 11,
|
priority: 11,
|
||||||
match: match]
|
match: match]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Inject Packet(ARP)" do
|
test "Inject Packet(ARP)" do
|
||||||
|
|
@ -252,7 +258,7 @@ defmodule FlogTest do
|
||||||
arp(op: 1, sha: shost, sip: @client_ip, tip: @gateway_ip)
|
arp(op: 1, sha: shost, sip: @client_ip, tip: @gateway_ip)
|
||||||
]
|
]
|
||||||
Pf.inject!(pid2, packet, payload)
|
Pf.inject!(pid2, packet, payload)
|
||||||
refute_receive {@access_port1_sniffer, ^packet}, 3000
|
refute_receive {@access_port1_sniffer, ^packet}, 1000
|
||||||
Pf.stop(pid1)
|
Pf.stop(pid1)
|
||||||
Pf.stop(pid2)
|
Pf.stop(pid2)
|
||||||
end
|
end
|
||||||
|
|
@ -283,7 +289,8 @@ defmodule FlogTest do
|
||||||
match: match,
|
match: match,
|
||||||
instructions: [ins]]
|
instructions: [ins]]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Inject Packet(ARP)" do
|
test "Inject Packet(ARP)" do
|
||||||
|
|
@ -332,7 +339,8 @@ defmodule FlogTest do
|
||||||
match: match,
|
match: match,
|
||||||
instructions: [ins]]
|
instructions: [ins]]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Inject Packet(ARP)" do
|
test "Inject Packet(ARP)" do
|
||||||
|
|
@ -385,7 +393,8 @@ defmodule FlogTest do
|
||||||
match: match,
|
match: match,
|
||||||
instructions: [ins]]
|
instructions: [ins]]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Inject Packet(ARP)" do
|
test "Inject Packet(ARP)" do
|
||||||
|
|
@ -428,7 +437,8 @@ defmodule FlogTest do
|
||||||
hard_timeout: state.timeout,
|
hard_timeout: state.timeout,
|
||||||
match: match]
|
match: match]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Inject Packet(ARP)" do
|
test "Inject Packet(ARP)" do
|
||||||
|
|
@ -476,7 +486,8 @@ defmodule FlogTest do
|
||||||
match: match,
|
match: match,
|
||||||
instructions: [ins]]
|
instructions: [ins]]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -521,7 +532,8 @@ defmodule FlogTest do
|
||||||
hard_timeout: state.timeout,
|
hard_timeout: state.timeout,
|
||||||
match: match]
|
match: match]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -541,7 +553,8 @@ defmodule FlogTest do
|
||||||
match: match,
|
match: match,
|
||||||
instructions: ins]
|
instructions: ins]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -568,7 +581,8 @@ defmodule FlogTest do
|
||||||
match: match,
|
match: match,
|
||||||
instructions: ins]
|
instructions: ins]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -596,7 +610,8 @@ defmodule FlogTest do
|
||||||
match: match,
|
match: match,
|
||||||
instructions: [ins]]
|
instructions: [ins]]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Inject Packet(TCP:443)" do
|
test "Inject Packet(TCP:443)" do
|
||||||
|
|
@ -652,7 +667,8 @@ defmodule FlogTest do
|
||||||
match: match,
|
match: match,
|
||||||
instructions: [ins]]
|
instructions: [ins]]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Inject Packet(TCP:80)" do
|
test "Inject Packet(TCP:80)" do
|
||||||
|
|
@ -698,7 +714,8 @@ defmodule FlogTest do
|
||||||
hard_timeout: state.timeout,
|
hard_timeout: state.timeout,
|
||||||
match: match]
|
match: match]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -719,7 +736,8 @@ defmodule FlogTest do
|
||||||
match: match,
|
match: match,
|
||||||
instructions: ins]
|
instructions: ins]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -742,7 +760,8 @@ defmodule FlogTest do
|
||||||
match: match,
|
match: match,
|
||||||
instructions: ins]
|
instructions: ins]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Inject Packet(ARP)" do
|
test "Inject Packet(ARP)" do
|
||||||
|
|
@ -758,7 +777,7 @@ defmodule FlogTest do
|
||||||
packet = [vlan_ether_header, vlan_header, arp_header]
|
packet = [vlan_ether_header, vlan_header, arp_header]
|
||||||
expect = [ether_header, arp_header]
|
expect = [ether_header, arp_header]
|
||||||
Pf.inject!(pid2, packet)
|
Pf.inject!(pid2, packet)
|
||||||
assert_receive {@access_port1_sniffer, ^expect}, 3000
|
assert_receive {@access_port1_sniffer, ^expect}, 1000
|
||||||
Pf.stop(pid1)
|
Pf.stop(pid1)
|
||||||
Pf.stop(pid2)
|
Pf.stop(pid2)
|
||||||
end
|
end
|
||||||
|
|
@ -783,7 +802,8 @@ defmodule FlogTest do
|
||||||
match: match,
|
match: match,
|
||||||
instructions: ins]
|
instructions: ins]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Inject Packet(ARP) from VLAN TRUNK" do
|
test "Inject Packet(ARP) from VLAN TRUNK" do
|
||||||
|
|
@ -848,7 +868,8 @@ defmodule FlogTest do
|
||||||
match: match,
|
match: match,
|
||||||
instructions: ins]
|
instructions: ins]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -881,7 +902,8 @@ defmodule FlogTest do
|
||||||
match: match,
|
match: match,
|
||||||
instructions: ins]
|
instructions: ins]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -913,7 +935,8 @@ defmodule FlogTest do
|
||||||
match: match,
|
match: match,
|
||||||
instructions: ins]
|
instructions: ins]
|
||||||
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
:ok = GenServer.cast(Flay, {:flow_install, options, self()})
|
||||||
refute_receive %Openflow.ErrorMsg{}, 3000
|
:timer.sleep 1000
|
||||||
|
refute_receive %Openflow.ErrorMsg{}, 1000
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Inject Packet(TCP:80)" do
|
test "Inject Packet(TCP:80)" do
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue