diff --git a/test/flay.ex b/test/flay.ex index ffda1f3..7016669 100644 --- a/test/flay.ex +++ b/test/flay.ex @@ -21,6 +21,7 @@ defmodule Flay do def init(args) do state = init_controller(args) init_bridge(state.datapath_id) + GenServer.cast(Flay, :desc_stats) {:ok, state} end @@ -28,15 +29,15 @@ defmodule Flay 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_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 @@ -49,10 +50,6 @@ defmodule Flay do send_flow_mod_delete(state.datapath_id, match: Match.new) {:noreply, state} end - def handle_cast(:restore_flow_profile, state) do - send_message(state.default_profile, state.datapath_id) - {:noreply, state} - end def handle_info(%ErrorMsg{} = error, state) do send(state.tester_pid, error) @@ -70,8 +67,8 @@ defmodule Flay do {: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}} + IO.inspect(desc) + {:noreply, state} end def handle_info(%Flow.Reply{} = desc, state) do GenServer.reply(state.reply_to, desc) diff --git a/test/flog_test.exs b/test/flog_test.exs index 669786f..31db334 100644 --- a/test/flog_test.exs +++ b/test/flog_test.exs @@ -2,18 +2,27 @@ defmodule FlogTest do use ExUnit.Case, async: false use Bitwise - @vlan_trunk_port "1" # FIXME: - @access_port "2" # FIXME: - @vxlan_port "5" # FIXME: + @listen_port 6653 + + @vlan_trunk_port "veth0" # FIXME: + @access_port "veth3" # FIXME: + @vxlan_port "veth4" # FIXME: + + @vlan_trunk_port_sniffer "veth1" # FIXME: + @access_port_sniffer "veth2" # FIXME: + @vxlan_port_sniffer "veth5" # FIXME: + @bootnet_vid 0x1000 ||| 5 @user_vid 0x1000 ||| 123 @vlan_present {0x1000, 0x1000} @mcast {"010000000000", "010000000000"} @sdl_vmac "000000000001" @bcast "ffffffffffff" - @mac "010203040506" + @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} @@ -51,10 +60,6 @@ defmodule FlogTest do cookie: cookie, timeout: timeout ] - - on_exit fn -> - GenServer.cast(Flay, :restore_flow_profile) - end {:ok, options} end @@ -68,6 +73,26 @@ defmodule FlogTest do :ok = GenServer.cast(Flay, {:flow_install, options, self()}) refute_receive %Openflow.ErrorMsg{}, 1000 end + + test "Inject Packet(ARP)" do + {:ok, pid1} = Pf.start_link(@access_port_sniffer) + {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) + {:ok, pid3} = Pf.start_link(@vxlan_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, ^payload]}, 1000 + refute_receive {@vxlan_port_sniffer, [^packet, ^payload]}, 1000 + Pf.stop(pid1) + Pf.stop(pid2) + Pf.stop(pid3) + end end describe("switch:merged_handler:" <> @@ -77,7 +102,7 @@ defmodule FlogTest do match = Openflow.Match.new( in_port: state.vlan_trunk.number, eth_dst: @bcast, - vlan_vid: {0x0000, 0x1fff}, + vlan_vid: 0x0000, eth_type: 0x88cc ) action = Openflow.Action.Output.new(:controller) @@ -90,6 +115,28 @@ defmodule FlogTest do instructions: [ins]] :ok = GenServer.cast(Flay, {:flow_install, options, self()}) refute_receive %Openflow.ErrorMsg{}, 1000 + + {:ok, pid1} = Pf.start_link(@access_port_sniffer) + {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) + {:ok, pid3} = Pf.start_link(@vxlan_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}, 3000 + refute_receive {@vlan_trunk_port_sniffer, [^packet, ""]}, 1000 + refute_receive {@vxlan_port_sniffer, [^packet, ""]}, 1000 + refute_receive {@access_port_sniffer, [^packet, ""]}, 1000 + Pf.stop(pid1) + Pf.stop(pid2) + Pf.stop(pid3) end end @@ -111,7 +158,7 @@ defmodule FlogTest do describe("switch:uplink_escalation_flow:" <> "table=0,priority=10,cookie=0x2000000000000000,arp,actions=controller") do - test "Install Flow" 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) @@ -123,14 +170,34 @@ defmodule FlogTest do instructions: [ins]] :ok = GenServer.cast(Flay, {:flow_install, options, self()}) refute_receive %Openflow.ErrorMsg{}, 1000 + + {:ok, pid1} = Pf.start_link(@access_port_sniffer) + {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) + {:ok, pid3} = Pf.start_link(@vxlan_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}, 3000 + refute_receive {@vlan_trunk_port_sniffer, [^packet, ^payload]}, 1000 + refute_receive {@vxlan_port_sniffer, [^packet, ^payload]}, 1000 + Pf.stop(pid1) + Pf.stop(pid2) + Pf.stop(pid3) end end describe("switch:uplink_escalation_flow:" <> "table=0,priority=10,cookie=0x2000000000000000,udp,udp_dst=67,actions=controller") do - test "Install Flow" 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) + action = Openflow.Action.Output.new(:controller) ins = Openflow.Instruction.ApplyActions.new(action) options = [cookie: 0x2000000000000000, @@ -140,6 +207,32 @@ defmodule FlogTest do instructions: [ins]] :ok = GenServer.cast(Flay, {:flow_install, options, self()}) refute_receive %Openflow.ErrorMsg{}, 1000 + + {:ok, pid1} = Pf.start_link(@access_port_sniffer) + {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) + {:ok, pid3} = Pf.start_link(@vxlan_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}, 3000 + refute_receive {@vlan_trunk_port_sniffer, [^packet, ^payload]}, 1000 + refute_receive {@vxlan_port_sniffer, [^packet, ^payload]}, 1000 + Pf.stop(pid1) + Pf.stop(pid2) + Pf.stop(pid3) end end @@ -155,6 +248,26 @@ defmodule FlogTest do :ok = GenServer.cast(Flay, {:flow_install, options, self()}) refute_receive %Openflow.ErrorMsg{}, 1000 end + + test "Inject Packet(ARP)" do + {:ok, pid1} = Pf.start_link(@access_port_sniffer) + {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) + {:ok, pid3} = Pf.start_link(@vxlan_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_port_sniffer, [^packet, ^payload]}, 1000 + refute_receive {@vxlan_port_sniffer, [^packet, ^payload]}, 1000 + Pf.stop(pid1) + Pf.stop(pid2) + Pf.stop(pid3) + end end describe("associate:register_bootstrap_rule:" <> @@ -184,6 +297,28 @@ defmodule FlogTest do :ok = GenServer.cast(Flay, {:flow_install, options, self()}) refute_receive %Openflow.ErrorMsg{}, 1000 end + + test "Inject Packet(ARP)" do + {:ok, pid1} = Pf.start_link(@access_port_sniffer) + {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) + {:ok, pid3} = Pf.start_link(@vxlan_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}, 1000 + Pf.stop(pid1) + Pf.stop(pid2) + Pf.stop(pid3) + end end describe("associate:register_bootstrap_rule:" <> @@ -213,6 +348,30 @@ defmodule FlogTest do :ok = GenServer.cast(Flay, {:flow_install, options, self()}) refute_receive %Openflow.ErrorMsg{}, 1000 end + + test "Inject Packet(ARP)" do + {:ok, pid1} = Pf.start_link(@access_port_sniffer) + {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) + {:ok, pid3} = Pf.start_link(@vxlan_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: 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_port_sniffer, ^expect}, 1000 + Pf.stop(pid1) + Pf.stop(pid2) + Pf.stop(pid3) + end end describe("associate:register_bootstrap_rule:" <> @@ -242,6 +401,30 @@ defmodule FlogTest do :ok = GenServer.cast(Flay, {:flow_install, options, self()}) refute_receive %Openflow.ErrorMsg{}, 1000 end + + test "Inject Packet(ARP)" do + {:ok, pid1} = Pf.start_link(@access_port_sniffer) + {:ok, pid2} = Pf.start_link(@vlan_trunk_port_sniffer) + {:ok, pid3} = Pf.start_link(@vxlan_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: 1, sha: shost, tip: @client_ip, sip: @gateway_ip), + ] + expect = [ + ether(dhost: dhost, shost: shost, type: 0x0806), + arp(op: 1, sha: shost, tip: @client_ip, sip: @gateway_ip), + "" + ] + Pf.inject!(pid2, packet) + assert_receive {@access_port_sniffer, ^expect}, 1000 + Pf.stop(pid1) + Pf.stop(pid2) + Pf.stop(pid3) + end end describe("associate:register_bootstrap_rule:" <> @@ -615,7 +798,7 @@ defmodule FlogTest do defp setup_applications do Application.put_env(:tres, :protocol, :tcp, persistent: true) - Application.put_env(:tres, :port, 6633, 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) @@ -641,11 +824,4 @@ defmodule FlogTest do port_desc = GenServer.call(Flay, :port_desc_stats, 5000) port_desc.ports end - - defp print_flows do - flow_stats = GenServer.call(Flay, :flow_stats, 5000) - for flow <- flow_stats.flows do - IO.inspect(flow) - end - end end diff --git a/test/packet_data/dhcp_discover.raw b/test/packet_data/dhcp_discover.raw new file mode 100644 index 0000000..55f86d1 Binary files /dev/null and b/test/packet_data/dhcp_discover.raw differ diff --git a/test/pf.ex b/test/pf.ex index 408e4fa..cad8f60 100644 --- a/test/pf.ex +++ b/test/pf.ex @@ -1,6 +1,12 @@ 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, @@ -9,8 +15,12 @@ defmodule Pf do ] end - def inject!(pid, packet) do - GenServer.cast(pid, {:inject, packet}) + 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 @@ -19,22 +29,23 @@ defmodule Pf do end def init([ifname, tester_pid]) do - {:ok, epcap_pid} = - :epcap.start_link( - interface: ifname, - promiscuous: true, - inject: true - ) - state = %State{ - pcap_ref: epcap_pid, - ifname: ifname, - tester_pid: tester_pid - } - {:ok, state} + {:ok, init_pf(ifname, tester_pid)} end - def handle_cast({:inject, packet}, state) do - :epcap.send(state.pcap_ref, packet) + 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 @@ -46,10 +57,17 @@ defmodule Pf do def handle_info({:packet, _dlt, _time, _len, data}, state) do pkt = :pkt.decapsulate(data) - send(state.tester_pid, {pkt, self()}) + send(state.tester_pid, {to_string(state.ifname), pkt}) {: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 diff --git a/test/test_helper.exs b/test/test_helper.exs index 8f72200..135c19a 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1 +1,2 @@ +ExUnit.configure(seed: 0) ExUnit.start(trace: true)