From 2c0b0024a1583a92d494a8bb125f6f311220bc4e Mon Sep 17 00:00:00 2001 From: Eishun Kondoh Date: Mon, 20 Nov 2017 16:57:37 +0900 Subject: [PATCH] Work on test case for secure channel --- config/config.exs | 2 +- lib/openflow/actions/push_vlan.ex | 2 +- lib/tres/message_helper.ex | 4 +- lib/tres/secure_channel.ex | 16 +- mix.exs | 6 +- mix.lock | 1 + src/tres_xact_kv.erl | 4 +- test/flay.ex | 106 ++++++++ test/flay_test.exs | 436 +++++++++++++++++++++++++++++- test/pf.ex | 40 +++ test/test_helper.exs | 2 +- 11 files changed, 603 insertions(+), 16 deletions(-) create mode 100644 test/flay.ex create mode 100644 test/pf.ex diff --git a/config/config.exs b/config/config.exs index 4113ff7..e8565e6 100644 --- a/config/config.exs +++ b/config/config.exs @@ -4,7 +4,7 @@ use Mix.Config config :tres, protocol: :tcp, - port: 6633, + port: 6653, max_connections: 10, num_acceptors: 10, callback_module: Tres.ExampleHandler, diff --git a/lib/openflow/actions/push_vlan.ex b/lib/openflow/actions/push_vlan.ex index 056f033..8c8ca39 100644 --- a/lib/openflow/actions/push_vlan.ex +++ b/lib/openflow/actions/push_vlan.ex @@ -5,7 +5,7 @@ defmodule Openflow.Action.PushVlan do def ofpat, do: 17 - def new(ethertype) do + def new(ethertype \\ 0x8100) do %PushVlan{ethertype: ethertype} end diff --git a/lib/tres/message_helper.ex b/lib/tres/message_helper.ex index 8080d5b..8cfe40e 100644 --- a/lib/tres/message_helper.ex +++ b/lib/tres/message_helper.ex @@ -20,7 +20,7 @@ defmodule Tres.MessageHelper do end defp send_flow_mod_modify(datapath_id, options) do - command = Tres.Utils.flow_command(options, :modify) + command = Tres.Utils.flow_command(:modify, options) flow_mod = %Openflow.FlowMod{ cookie: options[:cookie] || 0, table_id: options[:table_id] || 0, @@ -36,7 +36,7 @@ defmodule Tres.MessageHelper do end defp send_flow_mod_delete(datapath_id, options) do - command = Tres.Utils.flow_command(options, :delete) + command = Tres.Utils.flow_command(:delete, options) flow_mod = %Openflow.FlowMod{ cookie: options[:cookie] || 0, cookie_mask: options[:cookie_mask] || 0, diff --git a/lib/tres/secure_channel.ex b/lib/tres/secure_channel.ex index 187b693..48fd016 100644 --- a/lib/tres/secure_channel.ex +++ b/lib/tres/secure_channel.ex @@ -18,7 +18,7 @@ defmodule Tres.SecureChannel do @hello_handshake_timeout 1000 @features_handshake_timeout 1000 @ping_timeout 5000 - @transaction_timeout 5000 + # @transaction_timeout 5000 @ping_interval 5000 @ping_fail_max_count 10 @@ -193,12 +193,16 @@ defmodule Tres.SecureChannel do end defp handle_message(_in_xact = true, message, state_data) do - [{:xact_entry, _xid, prev_message, _orig}|_] = XACT_KV.get(state_data.xact_kv_ref, message.xid) - new_message = Openflow.append_body(prev_message, message) - XACT_KV.update(state_data.xact_kv_ref, message.xid, new_message) + case XACT_KV.get(state_data.xact_kv_ref, message.xid) do + [{:xact_entry, _xid, prev_message, _orig}|_] -> + new_message = Openflow.append_body(prev_message, message) + XACT_KV.update(state_data.xact_kv_ref, message.xid, new_message) + _ -> + XACT_KV.delete(state_data.xact_kv_ref, message.xid) + end end - defp handle_message(_in_xact = false, message, %SecureChannelState{handler_pid: handler_pid}) do - send(handler_pid, message) + defp handle_message(_in_xact = false, message, state_data) do + send(state_data.handler_pid, message) end # WATING state diff --git a/mix.exs b/mix.exs index 8aa92e7..c6707a5 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,8 @@ defmodule Tres.Mixfile do elixir: "~> 1.5", start_permanent: Mix.env == :prod, compilers: [:erlang] ++ Mix.compilers, - deps: deps()] + deps: deps(), + aliases: [test: "test --no-start"]] end # Run "mix help compile.app" to learn about applications. @@ -20,6 +21,7 @@ defmodule Tres.Mixfile do defp deps do [{:ranch, "~> 1.4.0"}, {:binpp, github: "jtendo/binpp", branch: "master"}, - {:pkt, github: "msantos/pkt", ref: "3afb196"}] + {:pkt, github: "msantos/pkt", ref: "3afb196", only: :test, override: true}, + {:epcap, github: "msantos/epcap", branch: "master", only: :test}] end end diff --git a/mix.lock b/mix.lock index e371c67..c50c3ed 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,5 @@ %{"binpp": {:git, "https://github.com/jtendo/binpp.git", "64bd68d215d1a6cd35871e7c134d7fe2e46214ea", [branch: "master"]}, + "epcap": {:git, "https://github.com/msantos/epcap.git", "9566f0420a4dcf1292c1a1afd9339c35dbdfd041", [branch: "master"]}, "flow": {:hex, :flow, "0.12.0", "32c5a5f3ff6693e004b6c17a8c64dce2f8cdaf9564912d79427176013a586ab6", [], [{:gen_stage, "~> 0.12.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm"}, "gen_stage": {:hex, :gen_stage, "0.12.2", "e0e347cbb1ceb5f4e68a526aec4d64b54ad721f0a8b30aa9d28e0ad749419cbb", [:mix], [], "hexpm"}, "gen_state_machine": {:hex, :gen_state_machine, "2.0.1", "85efd5a0376929c3a4246dd943e17564a2908c7ddd7acd242d84594e785d83f8", [], [], "hexpm"}, diff --git a/src/tres_xact_kv.erl b/src/tres_xact_kv.erl index 6e41e90..e7a99a1 100644 --- a/src/tres_xact_kv.erl +++ b/src/tres_xact_kv.erl @@ -47,7 +47,7 @@ is_exists(Tid, Xid) -> %% Private functions ms_for_exists(Xid) -> - ets:fun2ms(fun(#?ENTRY{xid = TXid}) when TXid < Xid -> true end). + ets:fun2ms(fun(#?ENTRY{xid = TXid}) when TXid == Xid -> true end). ms_for_get(Xid) -> ets:fun2ms(fun(#?ENTRY{xid = TXid} = E) when TXid == Xid -> E end). @@ -56,6 +56,6 @@ ms_for_update(Xid, Msg) -> ets:fun2ms(fun(#?ENTRY{xid = TXid} = E) when TXid == Xid -> E#?ENTRY{pending = Msg} end). ms_for_handle_error(Tid, Xid, Error) -> - [Orig|_] = get(Tid, Xid), + [#?ENTRY{orig = Orig}|_] = get(Tid, Xid), Error1 = maps:merge(Error, #{data => Orig}), ets:fun2ms(fun(#?ENTRY{xid = TXid} = E) when TXid == Xid -> E#?ENTRY{pending = Error1} end). diff --git a/test/flay.ex b/test/flay.ex new file mode 100644 index 0000000..d218886 --- /dev/null +++ b/test/flay.ex @@ -0,0 +1,106 @@ +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 + ] + end + + def start_link(datapath, args) do + GenServer.start_link(__MODULE__, [datapath, args], name: __MODULE__) + end + + def init(args) do + state = init_controller(args) + init_bridge(state.datapath_id) + {: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(:desc_stats, from, state) do + send_message(Desc.Request.new, state.datapath_id) + {:noreply, %{state|reply_to: from}} + 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_info(%ErrorMsg{} = error, state) do + send(state.tester_pid, error) + {:noreply, state} + end + def handle_info(%PortDesc.Reply{} = desc, state) do + GenServer.reply(state.reply_to, desc) + {:noreply, %{state|reply_to: nil}} + end + def handle_info(%Desc.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 + |> IO.inspect + end + + defp ofp_print_cmd(print_args) do + IO.inspect("\n") + {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) do + send_flow_mod_delete(datapath_id, table_id: :all) + end +end diff --git a/test/flay_test.exs b/test/flay_test.exs index 22285b8..8597de1 100644 --- a/test/flay_test.exs +++ b/test/flay_test.exs @@ -1,5 +1,439 @@ defmodule FlayTest do use ExUnit.Case + use Bitwise - + @vlan_trunk_port 1 + @access_port 2 + @vxlan_port 3 + @bootnet_vid 0x1000 ||| 5 + @user_vid 0x1000 ||| 123 + @mcast {"010000000000", "010000000000"} + @mac "010203040506" + @sdl_vmac "000000000001" + @trusted_macs [ + "0800274d3297", + "0800274d3298", + "0800274d3299" + ] + + import Record + # Extract Erlang record for klarna/brod + for {name, schema} <- extract_all(from_lib: "pkt/include/pkt.hrl") do + defrecord(name, schema) + end + + setup do + Code.load_file("test/flay.ex") + Code.load_file("test/pf.ex") + Application.put_env(:tres, :protocol, :tcp, persistent: true) + Application.put_env(:tres, :port, 6633, 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(:tres) + wait_for_connected() + end + + describe "switch:merged_handler" do + test "OFPMP_DESC" do + %Openflow.Multipart.Desc.Reply{} = GenServer.call(Flay, :desc_stats, 5000) + end + + test "Flow install" do + options = [cookie: 0x8000000000000000] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + match = Openflow.Match.new( + in_port: @vlan_trunk_port, + eth_dst: "ffffffffffff", + vlan_vid: {0x0000, 0x1fff}, + eth_type: 0x88cc + ) + ins = Openflow.Instruction.ApplyActions.new(Openflow.Action.Output.new(:controller)) + options = [ + cookie: 0x4000000000000000, + table_id: 0, + priority: 200, + match: match, + instructions: [ins] + ] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + # LLDP Packet to vlan trunk port mac_address + src = "00000000000a" + src_mac = <<(String.to_integer(src, 16))::48>> + dst = "ffffffffffff" + dst_mac = <<(String.to_integer(dst, 16))::48>> + lldp = lldp(pdus: [ + {:chassis_id, :mac_address, src_mac}, + {:port_id, :mac_address, src_mac}, + {:ttl, 120} + ]) + ether = ether(shost: src_mac, dhost: dst_mac, type: 0x88cc) + _packet = <<(:pkt.ether(ether))::bytes, (:pkt.lldp(lldp))::bytes>> + + %Openflow.Multipart.PortDesc.Reply{ports: ports} = GenServer.call(Flay, :port_desc_stats, 5000) + for port <- ports do + match = Openflow.Match.new(in_port: port.number, eth_src: port.hw_addr) + options = [ + cookie: 0x4000000000000000, + table_id: 0, + priority: 201, + match: match, + instructions: [] + ] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + # ARP Packet to vlan_trunk port mac address + arp = arp(arp_sha: src_mac, arp_sha: dst_mac) + ether = ether(shost: src_mac, dhost: dst_mac, type: 0x0806) + _packet = <<(:pkt.ether(ether))::bytes, (:pkt.arp(arp))::bytes>> + end + refute_receive %Openflow.ErrorMsg{}, 1000 + end + end + + describe "switch:uplink_escalation_flow" do + test "Flow install" do + match = Openflow.Match.new(eth_type: 0x0806) + action = Openflow.Action.Output.new(:controller) + ins = Openflow.Instruction.ApplyActions.new(action) + options = [ + cookie: 0x2000000000000000, + table_id: 0, + priority: 10, + match: match, + instructions: ins + ] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + 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: 0x2000000000000000, + table_id: 0, + priority: 10, + match: match, + instructions: ins + ] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + match = Openflow.Match.new(in_port: @vlan_trunk_port) + options = [ + cookie: 0x2000000000000000, + table_id: 0, + priority: 11, + match: match, + instructions: [] + ] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + refute_receive %Openflow.ErrorMsg{}, 1000 + end + end + + describe "associate:register_bootstrap_rule" do + test "Flow install" do + cookie = :crypto.strong_rand_bytes(8) |> :binary.decode_unsigned(:big) + match = Openflow.Match.new(in_port: @access_port, eth_src: @mac) + actions = [ + Openflow.Action.PushVlan.new, + Openflow.Action.SetField.new({:vlan_vid, @bootnet_vid}), + Openflow.Action.Output.new(@vlan_trunk_port) + ] + ins = Openflow.Instruction.ApplyActions.new(actions) + options = + [table_id: 0, + priority: 50, + cookie: cookie, + idle_timeout: 300, + hard_timeout: 300, + flags: [:send_flow_rem], + match: match, + instructions: [ins]] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + # trusted mac + match = Openflow.Match.new(in_port: @vlan_trunk_port, vlan_vid: @bootnet_vid, eth_dst: @mcast) + actions = [ + Openflow.Action.PopVlan.new, + Openflow.Action.Output.new(@access_port) + ] + ins = Openflow.Instruction.ApplyActions.new(actions) + options = + [table_id: 0, + priority: 50, + cookie: cookie, + idle_timeout: 300, + hard_timeout: 300, + flags: [:send_flow_rem], + match: match, + instructions: [ins]] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + match = Openflow.Match.new(in_port: @vlan_trunk_port, vlan_vid: @bootnet_vid, eth_dst: @mac) + actions = [ + Openflow.Action.PopVlan.new, + Openflow.Action.Output.new(@access_port) + ] + ins = Openflow.Instruction.ApplyActions.new(actions) + options = + [table_id: 0, + priority: 50, + cookie: cookie, + idle_timeout: 300, + hard_timeout: 300, + flags: [:send_flow_rem], + match: match, + instructions: [ins]] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + match = Openflow.Match.new(in_port: @vlan_trunk_port, eth_src: @mac) + options = + [table_id: 0, + priority: 29, + cookie: cookie, + idle_timeout: 300, + hard_timeout: 300, + flags: [:send_flow_rem], + match: match] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + # Trunk Port, w/Trusted MAC -> Pop VLAN -> Access Port + for trusted <- @trusted_macs do + match = Openflow.Match.new( + in_port: @vlan_trunk_port, + eth_src: trusted, + eth_dst: @mac, + vlan_vid: @bootnet_vid + ) + actions = [ + Openflow.Action.PopVlan.new, + Openflow.Action.Output.new(@access_port) + ] + ins = Openflow.Instruction.ApplyActions.new(actions) + options = + [table_id: 0, + priority: 50, + cookie: cookie, + idle_timeout: 300, + hard_timeout: 300, + flags: [:send_flow_rem], + match: match, + instructions: ins] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + match = Openflow.Match.new(eth_src: trusted) + options = + [table_id: 0, + priority: 29, + cookie: cookie, + idle_timeout: 300, + hard_timeout: 300, + flags: [:send_flow_rem], + match: match] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + end + refute_receive %Openflow.ErrorMsg{}, 1000 + end + end + + describe "associate:register_usernet_rule" do + test "Flow install" do + # BOOTNET_USE_USER_NETWORK + # + # Reply from SDL to the authorized client + cookie = :crypto.strong_rand_bytes(8) |> :binary.decode_unsigned(:big) + match = Openflow.Match.new(in_port: @vxlan_port, eth_dst: @mac) + action = Openflow.Action.Output.new(@access_port) + ins = Openflow.Instruction.ApplyActions.new(action) + options = [ + cookie: cookie, + table_id: 0, + priority: 40, + idle_timeout: 32_768, + hard_timeout: 32_768, + match: match, + instructions: [ins] + ] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + # HTTP request to the `CaptivePortal` via the SDL controller + match = Openflow.Match.new( + eth_type: 0x0800, + vlan_vid: {0x1000, 0x1000}, + ip_proto: 6, + tcp_dst: 443, + ipv4_dst: {192,168,5,4} + ) + actions = [ + Openflow.Action.PopVlan.new, + Openflow.Action.SetField.new({:eth_dst, @sdl_vmac}), + Openflow.Action.Output.new(@vxlan_port) + ] + ins = Openflow.Instruction.ApplyActions.new(actions) + options = [ + cookie: 0x2000000000000001, + table_id: 1, + priority: 30, + match: match, + instructions: [ins] + ] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + match = Openflow.Match.new( + eth_type: 0x0800, + vlan_vid: {0x1000, 0x1000}, + ip_proto: 6, + tcp_dst: 80, + ipv4_dst: {192,168,5,5} + ) + actions = [ + Openflow.Action.PopVlan.new, + Openflow.Action.SetField.new({:eth_dst, @sdl_vmac}), + Openflow.Action.Output.new(@vxlan_port) + ] + ins = Openflow.Instruction.ApplyActions.new(actions) + options = [ + cookie: 0x2000000000000001, + table_id: 1, + priority: 30, + match: match, + instructions: [ins] + ] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + # Access Port, w/sMAC -> Push VLAN -> Table1 + # Table1, w/dMAC, w/VLAN -> Pop VLAN -> Access Port + match = Openflow.Match.new(in_port: @access_port, eth_src: @mac) + actions = [ + Openflow.Action.PushVlan.new, + Openflow.Action.SetField.new({:vlan_vid, @bootnet_vid}) + ] + insts = [ + Openflow.Instruction.ApplyActions.new(actions), + Openflow.Instruction.GotoTable.new(1) + ] + options = [ + cookie: cookie, + table_id: 0, + priority: 20, + idle_timeout: 32_768, + hard_timeout: 32_768, + flags: [:send_flow_rem], + match: match, + instructions: insts + ] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + match = Openflow.Match.new(in_port: @vlan_trunk_port, eth_src: @mac) + options = [ + cookie: cookie, + table_id: 0, + priority: 19, + idle_timeout: 32_768, + hard_timeout: 32_768, + flags: [:send_flow_rem], + match: match, + ] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + match = Openflow.Match.new(eth_src: @mac) + actions = [Openflow.Action.Output.new(:controller)] + insts = [Openflow.Instruction.ApplyActions.new(actions)] + options = [ + cookie: cookie, + table_id: 0, + priority: 18, + idle_timeout: 32_768, + hard_timeout: 32_768, + flags: [:send_flow_rem], + match: match, + instructions: insts + ] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + match = Openflow.Match.new(vlan_vid: {0x1000, 0x1000}, eth_dst: @mac) + actions = [ + Openflow.Action.PopVlan.new, + Openflow.Action.Output.new(@access_port) + ] + insts = [Openflow.Instruction.ApplyActions.new(actions)] + options = [ + cookie: cookie, + table_id: 1, + priority: 50, + idle_timeout: 32_768, + hard_timeout: 32_768, + flags: [:send_flow_rem], + match: match, + instructions: insts + ] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + # VLAN 毎に登録する共通ルール + # Table1, w/VLAN -+-> Trunk + # +-> Pop VLAN -> Access Ports + # Trunk Port, w/VLAN -> Table1 + match = Openflow.Match.new(vlan_vid: @user_vid, eth_dst: @mcast) + actions = [ + Openflow.Action.Output.new(@vlan_trunk_port), + Openflow.Action.PopVlan.new, + Openflow.Action.Output.new(@access_port) + ] + insts = [Openflow.Instruction.ApplyActions.new(actions)] + options = [ + cookie: 0x200000000000001, + table_id: 1, + priority: 60, + match: match, + instructions: insts + ] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + match = Openflow.Match.new(vlan_vid: @user_vid) + actions = [ + Openflow.Action.Output.new(@vlan_trunk_port), + Openflow.Action.PopVlan.new, + Openflow.Action.Output.new(@access_port) + ] + insts = [Openflow.Instruction.ApplyActions.new(actions)] + options = [ + cookie: 0x200000000000001, + table_id: 1, + priority: 20, + match: match, + instructions: insts + ] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + match = Openflow.Match.new(in_port: @vlan_trunk_port, vlan_vid: @user_vid) + insts = [Openflow.Instruction.GotoTable.new(1)] + options = [ + cookie: 0x200000000000001, + table_id: 0, + priority: 50, + match: match, + instructions: insts + ] + :ok = GenServer.cast(Flay, {:flow_install, options, self()}) + + refute_receive %Openflow.ErrorMsg{}, 1000 + end + end + + defp wait_for_connected do + case Process.whereis(Flay) do + nil -> + wait_for_connected() + pid when is_pid(pid) -> + :ok + end + end end diff --git a/test/pf.ex b/test/pf.ex new file mode 100644 index 0000000..5762578 --- /dev/null +++ b/test/pf.ex @@ -0,0 +1,40 @@ +defmodule Pf do + use GenServer + + defmodule State do + defstruct [ + ifname: nil, + pcap_ref: nil, + tester_pid: nil + ] + end + + def start_link(ifname, pid) do + ifname = String.to_charlist(ifname) + GenServer.start_link(__MODULE__, [ifname, pid]) + end + + def init([ifname, pid]) do + {:ok, pid} = :epcap.start_link(interface: ifname, chroot: 'priv/tmp', inject: true) + %State{pcap_ref: pid, ifname: ifname, tester_pid: pid} + end + + def handle_cast({:inject, packet}, state) do + :epcap.send(state.pcap_ref, packet) + {: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 + send(state.tester_pid, data) + {:noreply, state} + end + def handle_info(_info, state) do + {:noreply, state} + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs index 869559e..8f72200 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1 +1 @@ -ExUnit.start() +ExUnit.start(trace: true)