From c9df968706ad710182f7f7c4908a0c459e3b433b Mon Sep 17 00:00:00 2001 From: Eishun Kondoh Date: Wed, 22 Aug 2018 23:12:52 +0900 Subject: [PATCH] NX_RESUME parser and helpers --- bin/enum_gen | Bin 1460249 -> 1466455 bytes lib/openflow.ex | 23 +- lib/openflow/hello.ex | 4 +- lib/openflow/nx_packet_in2.ex | 202 ++++++++++++++- lib/openflow/nx_resume.ex | 418 ++++++++++++++++++++++++++++++++ lib/ovsdb/open_vswitch.ex | 54 +++-- lib/tres/controller.ex | 1 + lib/tres/message_handler_sup.ex | 1 + lib/tres/message_helper.ex | 7 +- lib/tres/secure_channel.ex | 31 +-- lib/tres/switch_registry.ex | 1 + lib/tres/utils.ex | 8 +- test/ofp_action_test.exs | 14 +- test/ofp_packet_in2_test.exs | 4 +- 14 files changed, 698 insertions(+), 70 deletions(-) create mode 100644 lib/openflow/nx_resume.ex diff --git a/bin/enum_gen b/bin/enum_gen index bf594917a60fbde2273419b8eb90aaceaa19a407..1aee886b8e41a58dbac517b8941d4e297299a4da 100755 GIT binary patch delta 50203 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 cG7gJE+Rw%bVq$!|7>) do - try do - body_len = len - @ofp_header_size - <> = binary2 + try do + body_len = len - @ofp_header_size + <> = binary2 - result = type + result = + type |> Openflow.Enums.to_atom(:openflow_codec) |> do_read(body_bin) - case result do - {:ok, struct} -> {:ok, %{struct | version: ver, xid: xid}, rest} - {:error, reason} -> {:error, reason} - end - catch - _c, _e -> - {:error, :malformed_packet} + case result do + {:ok, struct} -> {:ok, %{struct | version: ver, xid: xid}, rest} + {:error, reason} -> {:error, reason} end + catch + _c, reason -> + {:error, {:malformed_packet, {reason, __STACKTRACE__}}} + end end def to_binary(messages) when is_list(messages) do diff --git a/lib/openflow/hello.ex b/lib/openflow/hello.ex index aa3da17..415ccc9 100644 --- a/lib/openflow/hello.ex +++ b/lib/openflow/hello.ex @@ -24,8 +24,8 @@ defmodule Openflow.Hello do def supported_version?(%Hello{elements: elements}) do elements - |> Enum.reduce([], fn({:versionbitmap, versions}, acc) -> acc ++ versions end) - |> Enum.any?(fn(version) -> version == 4 end) + |> Enum.reduce([], fn {:versionbitmap, versions}, acc -> acc ++ versions end) + |> Enum.any?(fn version -> version == 4 end) end def read(binary), do: %Hello{elements: decode([], binary)} diff --git a/lib/openflow/nx_packet_in2.ex b/lib/openflow/nx_packet_in2.ex index d03500c..02e2572 100644 --- a/lib/openflow/nx_packet_in2.ex +++ b/lib/openflow/nx_packet_in2.ex @@ -14,12 +14,13 @@ defmodule Openflow.NxPacketIn2 do metadata: nil, userdata: nil, # continuation properties: - continuation_bridge: "", - continuation_stack: [], - continuation_conntracked: false, + continuation_bridge: nil, + continuation_stack: nil, + continuation_conntracked: nil, + continuation_mirrors: nil, continuation_table_id: nil, continuation_cookie: nil, - continuation_actions: [], + continuation_actions: nil, continuation_action_set: nil ) @@ -34,12 +35,16 @@ defmodule Openflow.NxPacketIn2 do reason metadata userdata + )a + + @continuation_keys ~w( continuation_bridge continuation_stack continuation_conntracked continuation_table_id continuation_cookie continuation_actions + continuation_mirrors continuation_action_set )a @@ -69,6 +74,12 @@ defmodule Openflow.NxPacketIn2 do def ofp_type, do: 4 + def to_binary(%NxPacketIn2{} = pin) do + props_bin = encode_props("", pin, @encode_keys) + continuations_bin = encode_continuations("", pin, @continuation_keys) + <<@experimenter::32, @nx_type::32, props_bin::bytes, continuations_bin::bytes>> + end + def read(<<@experimenter::32, @nx_type::32, props_bin::bytes>>) do %NxPacketIn2{} |> decode_props(props_bin) @@ -76,6 +87,189 @@ defmodule Openflow.NxPacketIn2 do ## private functions + defp encode_props(acc, _pin, []), do: acc + + defp encode_props(acc, %NxPacketIn2{packet: packet} = pin, [:packet | rest]) + when not is_nil(packet) and is_binary(packet) do + length = @prop_header_length + byte_size(packet) + pad_length = Openflow.Utils.pad_length(length, 8) + binary = <<@packet::16, length::16, packet::bytes, 0::size(pad_length)-unit(8)>> + encode_props(<>, pin, rest) + end + + defp encode_props(acc, %NxPacketIn2{full_len: full_len} = pin, [:full_len | rest]) + when not is_nil(full_len) and is_integer(full_len) do + length = @prop_header_length + 4 + binary = <<@full_len::16, length::16, full_len::32>> + encode_props(<>, pin, rest) + end + + defp encode_props(acc, %NxPacketIn2{buffer_id: buffer_id} = pin, [:buffer_id | rest]) + when not is_nil(buffer_id) and is_integer(buffer_id) do + length = @prop_header_length + 4 + binary = <<@buffer_id::16, length::16, buffer_id::32>> + encode_props(<>, pin, rest) + end + + defp encode_props(acc, %NxPacketIn2{table_id: table_id} = pin, [:table_id | rest]) + when not is_nil(table_id) and is_integer(table_id) do + length = @prop_header_length + 4 + binary = <<@table_id::16, length::16, table_id::8, 0::24>> + encode_props(<>, pin, rest) + end + + defp encode_props(acc, %NxPacketIn2{cookie: cookie} = pin, [:cookie | rest]) + when not is_nil(cookie) and is_integer(cookie) do + length = @prop_header_length + 4 + binary = <<@cookie::16, length::16, 0::32, cookie::64>> + encode_props(<>, pin, rest) + end + + defp encode_props(acc, %NxPacketIn2{metadata: metadata} = pin, [:metadata | rest]) + when not is_nil(metadata) and is_list(metadata) do + pad_match_bin = + metadata + |> Openflow.Match.new() + |> Openflow.Match.to_binary() + + <<1::16, match_len_with_header::16, padded_match_bin::bytes>> = pad_match_bin + match_len = match_len_with_header - 4 + <> = padded_match_bin + length = @prop_header_length + match_len + pad_length = Openflow.Utils.pad_length(length, 8) + binary = <<@metadata::16, length::16, match_bin::bytes, 0::size(pad_length)-unit(8)>> + encode_props(<>, pin, rest) + end + + defp encode_props(acc, %NxPacketIn2{userdata: userdata} = pin, [:userdata | rest]) + when not is_nil(userdata) and is_binary(userdata) do + length = @prop_header_length + byte_size(userdata) + pad_length = Openflow.Utils.pad_length(length, 8) + binary = <<@userdata::16, length::16, userdata::bytes, 0::size(pad_length)-unit(8)>> + encode_props(<>, pin, rest) + end + + defp encode_props(acc, pin, [_ | rest]) do + encode_props(acc, pin, rest) + end + + defp encode_continuations(acc, _pin, []) do + if byte_size(acc) > 0 do + length = @prop_header_length + byte_size(acc) + pad_length = Openflow.Utils.pad_length(length, 8) + <<@continuation::16, acc::bytes, 0::size(pad_length)-unit(8)>> + else + <<>> + end + end + + defp encode_continuations( + acc, + %NxPacketIn2{continuation_bridge: br} = pin, + [:continuation_bridge | rest] + ) + when not is_nil(br) do + length = @prop_header_length + byte_size(br) + pad_length = Openflow.Utils.pad_length(length, 8) + bridge_bin = <> + binary = <<@nxcpt_bridge::16, length::16, bridge_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations( + acc, + %NxPacketIn2{continuation_stack: stack} = pin, + [:continuation_stack | rest] + ) + when not is_nil(stack) do + length = @prop_header_length + byte_size(stack) + pad_length = Openflow.Utils.pad_length(length, 8) + stack_bin = <> + binary = <<@nxcpt_stack::16, length::16, stack_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations( + acc, + %NxPacketIn2{continuation_mirrors: mirrors} = pin, + [:continuation_mirrors | rest] + ) + when not is_nil(mirrors) do + length = @prop_header_length + byte_size(mirrors) + pad_length = Openflow.Utils.pad_length(length, 8) + mirrors_bin = <> + binary = <<@nxcpt_mirrors::16, length::16, mirrors_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations( + acc, + %NxPacketIn2{continuation_conntracked: true} = pin, + [:continuation_conntracked | rest] + ) do + length = @prop_header_length + 1 + pad_length = Openflow.Utils.pad_length(length, 8) + conntracked_bin = <<1::size(1)-unit(8), 0::size(pad_length)-unit(8), acc::bytes>> + binary = <<@nxcpt_conntracked::16, length::16, conntracked_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations( + acc, + %NxPacketIn2{continuation_table_id: table_id} = pin, + [:continuation_table_id | rest] + ) + when not is_nil(table_id) do + length = @prop_header_length + 1 + pad_length = Openflow.Utils.pad_length(length, 8) + table_id_bin = <> + binary = <<@nxcpt_table_id::16, length::16, table_id_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations( + acc, + %NxPacketIn2{continuation_cookie: cookie} = pin, + [:continuation_cookie | rest] + ) + when not is_nil(cookie) do + length = @prop_header_length + 8 + pad_length = Openflow.Utils.pad_length(length, 8) + cookie_bin = <> + binary = <<@nxcpt_cookie::16, length::16, cookie_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations( + acc, + %NxPacketIn2{continuation_actions: actions} = pin, + [:continuation_actions | rest] + ) do + actions_bin = Openflow.Action.to_binary(actions) + length = @prop_header_length + byte_size(actions_bin) + pad_length = Openflow.Utils.pad_length(length, 8) + actions_bin = <> + binary = <<@nxcpt_actions::16, length::16, actions_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations( + acc, + %NxPacketIn2{continuation_action_set: action_set} = pin, + [:continuation_action_set | rest] + ) + when not is_nil(action_set) do + length = @prop_header_length + byte_size(action_set) + pad_length = Openflow.Utils.pad_length(length, 8) + action_set_bin = <> + binary = <<@nxcpt_action_set::16, length::16, action_set_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations(acc, pin, [_ | rest]) do + encode_continuations(acc, pin, rest) + end + defp decode_props(pktin, ""), do: pktin defp decode_props(pktin, <<@packet::16, length::16, tail::bytes>>) do diff --git a/lib/openflow/nx_resume.ex b/lib/openflow/nx_resume.ex new file mode 100644 index 0000000..1c6bda6 --- /dev/null +++ b/lib/openflow/nx_resume.ex @@ -0,0 +1,418 @@ +defmodule Openflow.NxResume do + defstruct( + version: 4, + xid: 0, + datapath_id: "", + aux_id: 0, + # packet_in properties: + packet: nil, + full_len: nil, + buffer_id: nil, + table_id: nil, + cookie: nil, + reason: nil, + metadata: nil, + userdata: nil, + # continuation properties: + continuation_bridge: nil, + continuation_stack: nil, + continuation_conntracked: nil, + continuation_mirrors: nil, + continuation_table_id: nil, + continuation_cookie: nil, + continuation_actions: nil, + continuation_action_set: nil + ) + + alias __MODULE__ + + @encode_keys ~w( + packet + full_len + buffer_id + table_id + cookie + reason + metadata + userdata + )a + + @continuation_keys ~w( + continuation_bridge + continuation_stack + continuation_conntracked + continuation_table_id + continuation_cookie + continuation_actions + continuation_mirrors + continuation_action_set + )a + + @experimenter 0x00002320 + @nx_type 28 + + @packet 0 + @full_len 1 + @buffer_id 2 + @table_id 3 + @cookie 4 + @reason 5 + @metadata 6 + @userdata 7 + @continuation 8 + + @nxcpt_bridge 0x8000 + @nxcpt_stack 0x8001 + @nxcpt_mirrors 0x8002 + @nxcpt_conntracked 0x8003 + @nxcpt_table_id 0x8004 + @nxcpt_cookie 0x8005 + @nxcpt_actions 0x8006 + @nxcpt_action_set 0x8007 + + @prop_header_length 4 + + def ofp_type, do: 4 + + def new(options \\ []) do + packet_in = options[:packet_in] + %NxResume{ + packet: options[:packet] || packet_in.packet, + continuation_bridge: packet_in.continuation_bridge, + continuation_stack: packet_in.continuation_stack, + continuation_conntracked: packet_in.continuation_conntracked, + continuation_table_id: packet_in.continuation_table_id, + continuation_cookie: packet_in.continuation_cookie, + continuation_actions: packet_in.continuation_actions, + continuation_action_set: packet_in.continuation_action_set, + continuation_mirrors: packet_in.continuation_mirrors + } + end + + def to_binary(%NxResume{} = pin) do + props_bin = encode_props("", pin, @encode_keys) + continuations_bin = encode_continuations("", pin, @continuation_keys) + <<@experimenter::32, @nx_type::32, props_bin::bytes, continuations_bin::bytes>> + end + + def read(<<@experimenter::32, @nx_type::32, props_bin::bytes>>) do + %NxResume{} + |> decode_props(props_bin) + end + + ## private functions + + defp encode_props(acc, _pin, []), do: acc + + defp encode_props(acc, %NxResume{packet: packet} = pin, [:packet | rest]) + when not is_nil(packet) and is_binary(packet) do + length = @prop_header_length + byte_size(packet) + pad_length = Openflow.Utils.pad_length(length, 8) + binary = <<@packet::16, length::16, packet::bytes, 0::size(pad_length)-unit(8)>> + encode_props(<>, pin, rest) + end + + defp encode_props(acc, %NxResume{full_len: full_len} = pin, [:full_len | rest]) + when not is_nil(full_len) and is_integer(full_len) do + length = @prop_header_length + 4 + binary = <<@full_len::16, length::16, full_len::32>> + encode_props(<>, pin, rest) + end + + defp encode_props(acc, %NxResume{buffer_id: buffer_id} = pin, [:buffer_id | rest]) + when not is_nil(buffer_id) and is_integer(buffer_id) do + length = @prop_header_length + 4 + binary = <<@buffer_id::16, length::16, buffer_id::32>> + encode_props(<>, pin, rest) + end + + defp encode_props(acc, %NxResume{table_id: table_id} = pin, [:table_id | rest]) + when not is_nil(table_id) and is_integer(table_id) do + length = @prop_header_length + 4 + binary = <<@table_id::16, length::16, table_id::8, 0::24>> + encode_props(<>, pin, rest) + end + + defp encode_props(acc, %NxResume{cookie: cookie} = pin, [:cookie | rest]) + when not is_nil(cookie) and is_integer(cookie) do + length = @prop_header_length + 4 + binary = <<@cookie::16, length::16, 0::32, cookie::64>> + encode_props(<>, pin, rest) + end + + defp encode_props(acc, %NxResume{metadata: metadata} = pin, [:metadata | rest]) + when not is_nil(metadata) and is_list(metadata) do + pad_match_bin = + metadata + |> Openflow.Match.new() + |> Openflow.Match.to_binary() + + <<1::16, match_len_with_header::16, padded_match_bin::bytes>> = pad_match_bin + match_len = match_len_with_header - 4 + <> = padded_match_bin + length = @prop_header_length + match_len + pad_length = Openflow.Utils.pad_length(length, 8) + binary = <<@metadata::16, length::16, match_bin::bytes, 0::size(pad_length)-unit(8)>> + encode_props(<>, pin, rest) + end + + defp encode_props(acc, %NxResume{userdata: userdata} = pin, [:userdata | rest]) + when not is_nil(userdata) and is_binary(userdata) do + length = @prop_header_length + byte_size(userdata) + pad_length = Openflow.Utils.pad_length(length, 8) + binary = <<@userdata::16, length::16, userdata::bytes, 0::size(pad_length)-unit(8)>> + encode_props(<>, pin, rest) + end + + defp encode_props(acc, pin, [_ | rest]) do + encode_props(acc, pin, rest) + end + + defp encode_continuations(acc, _pin, []) do + if byte_size(acc) > 0 do + length = @prop_header_length + byte_size(acc) + pad_length = Openflow.Utils.pad_length(length, 8) + <<@continuation::16, acc::bytes, 0::size(pad_length)-unit(8)>> + else + <<>> + end + end + + defp encode_continuations( + acc, + %NxResume{continuation_bridge: br} = pin, + [:continuation_bridge | rest] + ) + when not is_nil(br) do + length = @prop_header_length + byte_size(br) + pad_length = Openflow.Utils.pad_length(length, 8) + bridge_bin = <> + binary = <<@nxcpt_bridge::16, length::16, bridge_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations( + acc, + %NxResume{continuation_stack: stack} = pin, + [:continuation_stack | rest] + ) + when not is_nil(stack) do + length = @prop_header_length + byte_size(stack) + pad_length = Openflow.Utils.pad_length(length, 8) + stack_bin = <> + binary = <<@nxcpt_stack::16, length::16, stack_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations( + acc, + %NxResume{continuation_mirrors: mirrors} = pin, + [:continuation_mirrors | rest] + ) + when not is_nil(mirrors) do + length = @prop_header_length + byte_size(mirrors) + pad_length = Openflow.Utils.pad_length(length, 8) + mirrors_bin = <> + binary = <<@nxcpt_mirrors::16, length::16, mirrors_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations( + acc, + %NxResume{continuation_conntracked: true} = pin, + [:continuation_conntracked | rest] + ) do + length = @prop_header_length + 1 + pad_length = Openflow.Utils.pad_length(length, 8) + conntracked_bin = <<1::size(1)-unit(8), 0::size(pad_length)-unit(8), acc::bytes>> + binary = <<@nxcpt_conntracked::16, length::16, conntracked_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations( + acc, + %NxResume{continuation_table_id: table_id} = pin, + [:continuation_table_id | rest] + ) + when not is_nil(table_id) do + length = @prop_header_length + 1 + pad_length = Openflow.Utils.pad_length(length, 8) + table_id_bin = <> + binary = <<@nxcpt_table_id::16, length::16, table_id_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations( + acc, + %NxResume{continuation_cookie: cookie} = pin, + [:continuation_cookie | rest] + ) + when not is_nil(cookie) do + length = @prop_header_length + 8 + pad_length = Openflow.Utils.pad_length(length, 8) + cookie_bin = <> + binary = <<@nxcpt_cookie::16, length::16, cookie_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations( + acc, + %NxResume{continuation_actions: actions} = pin, + [:continuation_actions | rest] + ) do + actions_bin = Openflow.Action.to_binary(actions) + length = @prop_header_length + byte_size(actions_bin) + pad_length = Openflow.Utils.pad_length(length, 8) + actions_bin = <> + binary = <<@nxcpt_actions::16, length::16, actions_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations( + acc, + %NxResume{continuation_action_set: action_set} = pin, + [:continuation_action_set | rest] + ) + when not is_nil(action_set) do + length = @prop_header_length + byte_size(action_set) + pad_length = Openflow.Utils.pad_length(length, 8) + action_set_bin = <> + binary = <<@nxcpt_action_set::16, length::16, action_set_bin::bytes, acc::bytes>> + encode_continuations(binary, pin, rest) + end + + defp encode_continuations(acc, pin, [_ | rest]) do + encode_continuations(acc, pin, rest) + end + + defp decode_props(pktin, ""), do: pktin + + defp decode_props(pktin, <<@packet::16, length::16, tail::bytes>>) do + pad_length = Openflow.Utils.pad_length(length, 8) + packet_length = length - @prop_header_length + <> = tail + decode_props(%{pktin | packet: packet}, rest) + end + + defp decode_props(pktin, <<@full_len::16, _length::16, full_len::32, rest::bytes>>) do + decode_props(%{pktin | full_len: full_len}, rest) + end + + defp decode_props(pktin, <<@buffer_id::16, _length::16, buffer_id::32, rest::bytes>>) do + decode_props(%{pktin | buffer_id: buffer_id}, rest) + end + + defp decode_props(pktin, <<@table_id::16, _length::16, table_id::8, _::24, rest::bytes>>) do + decode_props(%{pktin | table_id: table_id}, rest) + end + + defp decode_props(pktin, <<@cookie::16, _length::16, _::32, cookie::64, rest::bytes>>) do + decode_props(%{pktin | cookie: cookie}, rest) + end + + defp decode_props(pktin, <<@reason::16, _length::16, reason_int::8, _::24, rest::bytes>>) do + reason = Openflow.Enums.to_atom(reason_int, :packet_in_reason) + decode_props(%{pktin | reason: reason}, rest) + end + + defp decode_props(pktin, <<@metadata::16, length::16, tail::bytes>>) do + pad_length = Openflow.Utils.pad_length(length, 8) + match_field_length = length - @prop_header_length + + <> = + tail + + match_len = 4 + byte_size(match_fields_bin) + padding = Openflow.Utils.pad_length(match_len, 8) + match_bin = <<1::16, match_len::16, match_fields_bin::bytes, 0::size(padding)-unit(8)>> + {fields, _rest} = Openflow.Match.read(match_bin) + decode_props(%{pktin | metadata: fields}, rest) + end + + defp decode_props(pktin, <<@userdata::16, length::16, tail::bytes>>) do + pad_length = Openflow.Utils.pad_length(length, 8) + userdata_length = length - @prop_header_length + <> = tail + decode_props(%{pktin | userdata: userdata}, rest) + end + + defp decode_props(pktin, <<@continuation::16, length::16, tail::bytes>>) do + pad_length = Openflow.Utils.pad_length(length, 8) + data_length = length - @prop_header_length - 4 + <<_pad::32, data::size(data_length)-bytes, _::size(pad_length)-unit(8), rest::bytes>> = tail + + pktin + |> decode_continuations(data) + |> decode_props(rest) + end + + defp decode_props(pktin, <<_::16, length::16, tail::bytes>>) do + pad_length = Openflow.Utils.pad_length(length, 8) + data_length = length - @prop_header_length + <<_data::size(data_length)-bytes, _::size(pad_length)-unit(8), rest::bytes>> = tail + decode_props(pktin, rest) + end + + defp decode_continuations(pktin, ""), do: pktin + + defp decode_continuations(pktin, <<@nxcpt_bridge::16, length::16, tail::bytes>>) do + pad_length = Openflow.Utils.pad_length(length, 8) + data_length = length - @prop_header_length + <> = tail + decode_continuations(%{pktin | continuation_bridge: bridge}, rest) + end + + defp decode_continuations(pktin, <<@nxcpt_stack::16, length::16, tail::bytes>>) do + pad_length = Openflow.Utils.pad_length(length, 8) + data_length = (length - @prop_header_length) * 8 + <> = tail + decode_continuations(%{pktin | continuation_stack: pktin.continuation_stack ++ [stack]}, rest) + end + + defp decode_continuations(pktin, <<@nxcpt_mirrors::16, length::16, tail::bytes>>) do + pad_length = Openflow.Utils.pad_length(length, 8) + data_length = length - @prop_header_length + <> = tail + decode_continuations(%{pktin | continuation_mirrors: mirrors}, rest) + end + + defp decode_continuations(pktin, <<@nxcpt_conntracked::16, length::16, tail::bytes>>) do + pad_length = Openflow.Utils.pad_length(length, 8) + data_length = length - @prop_header_length + <<_::size(data_length)-bytes, _::size(pad_length)-unit(8), rest::bytes>> = tail + decode_continuations(%{pktin | continuation_conntracked: true}, rest) + end + + defp decode_continuations(pktin, <<@nxcpt_table_id::16, length::16, tail::bytes>>) do + pad_length = Openflow.Utils.pad_length(length, 8) + <> = tail + decode_continuations(%{pktin | continuation_table_id: table_id}, rest) + end + + defp decode_continuations(pktin, <<@nxcpt_cookie::16, length::16, tail::bytes>>) do + pad_length = Openflow.Utils.pad_length(length, 8) + <> = tail + decode_continuations(%{pktin | continuation_cookie: cookie}, rest) + end + + defp decode_continuations(pktin, <<@nxcpt_actions::16, length::16, tail::bytes>>) do + pad_length = Openflow.Utils.pad_length(length, 8) + data_length = length - @prop_header_length - 4 + + <<_pad::32, actions::size(data_length)-bytes, _::size(pad_length)-unit(8), rest::bytes>> = + tail + + decode_continuations(%{pktin | continuation_actions: Openflow.Action.read(actions)}, rest) + end + + defp decode_continuations(pktin, <<@nxcpt_action_set::16, length::16, tail::bytes>>) do + pad_length = Openflow.Utils.pad_length(length, 8) + data_length = length - @prop_header_length + <> = tail + decode_continuations(%{pktin | continuation_action_set: action_set}, rest) + end + + defp decode_continuations(pktin, _) do + decode_continuations(pktin, "") + end +end diff --git a/lib/ovsdb/open_vswitch.ex b/lib/ovsdb/open_vswitch.ex index 3c7dbdf..530c670 100644 --- a/lib/ovsdb/open_vswitch.ex +++ b/lib/ovsdb/open_vswitch.ex @@ -34,12 +34,12 @@ defmodule OVSDB.OpenvSwitch do disable_in_band: "true" ) """ - def add_br(pid, [_|_] = options) do + def add_br(pid, [_ | _] = options) do br_uuids = GenServer.call(pid, {:sync_get, @open_vswitch, "bridges"}) add_br_options = [ bridge: options[:name], - fail_mode: options[:fail_mode] || "standalone", + fail_mode: options[:fail_mode] || "standalone", datapath_id: options[:datapath_id], disable_in_band: options[:disable_in_band], br_uuids: br_uuids @@ -83,10 +83,11 @@ defmodule OVSDB.OpenvSwitch do protocol: "OpenFlow13" ) """ - def set_controller(pid, [_|_] = options) do + def set_controller(pid, [_ | _] = options) do case find_by_name(pid, @bridge, options[:bridge]) do :not_found -> {:error, :not_found} + %{"_uuid" => _uuid} -> set_ctl_opts = [ bridge: options[:bridge], @@ -94,7 +95,7 @@ defmodule OVSDB.OpenvSwitch do connection_mode: options[:connection_mode] || "out-of-band", controller_rate_limit: options[:controller_rate_limit] || 1000, controller_burst_limit: options[:controller_burst_limit] || 100, - protocol: options[:protocol] || "OpenFlow13", + protocol: options[:protocol] || "OpenFlow13" ] GenServer.call(pid, {:set_controller, set_ctl_opts}) @@ -111,10 +112,11 @@ defmodule OVSDB.OpenvSwitch do ofport_request: 99 ) """ - def add_port(pid, [_|_] = options) do + def add_port(pid, [_ | _] = options) do case find_by_name(pid, @bridge, options[:bridge]) do :not_found -> {:error, :not_found} + %{"_uuid" => _uuid, "ports" => ports} -> port_opts = [ bridge: options[:bridge], @@ -146,14 +148,16 @@ defmodule OVSDB.OpenvSwitch do @doc """ iex> OpenvSwitch.del_port(client_pid, bridge: "br0", port: "vxlan5") """ - def del_port(pid, [_|_] = options) do + def del_port(pid, [_ | _] = options) do case find_by_name(pid, @bridge, options[:bridge]) do :not_found -> {:error, :not_found} + %{"_uuid" => _uuid, "ports" => ports} -> case find_by_name(pid, @port, options[:port]) do :not_found -> {:error, :not_found} + %{"_uuid" => port_uuid} -> new_ports = del_elem_from_set(ports, port_uuid) @@ -257,34 +261,41 @@ defmodule OVSDB.OpenvSwitch do bridge = %{ports: options[:ports]} replies = - xact([ - :eovsdb_op.update(@bridge, eq_br_name, bridge), - :eovsdb_op.mutate(@open_vswitch, eq_ovs_uuid, next_config) - ], pid) + xact( + [ + :eovsdb_op.update(@bridge, eq_br_name, bridge), + :eovsdb_op.mutate(@open_vswitch, eq_ovs_uuid, next_config) + ], + pid + ) {:reply, replies, state} end def handle_call({:set_controller, options}, _from, state) do %State{client_pid: pid, ovs_uuid: ovs} = state + controller = %{ target: options[:target], connection_mode: options[:connection_mode], controller_rate_limit: options[:controller_rate_limit], - controller_burst_limit: options[:controller_burst_limit], + controller_burst_limit: options[:controller_burst_limit] } bridge = %{ protocols: options[:protocol], - controller: ["named-uuid", "controller"], + controller: ["named-uuid", "controller"] } replies = - xact([ - :eovsdb_op.insert(@controller, controller, "controller"), - :eovsdb_op.update(@bridge, [{"name", "==", options[:bridge]}], bridge), - :eovsdb_op.mutate(@open_vswitch, [{"_uuid", "==", ovs}], [{"next_cfg", "+=", 1}]) - ], pid) + xact( + [ + :eovsdb_op.insert(@controller, controller, "controller"), + :eovsdb_op.update(@bridge, [{"name", "==", options[:bridge]}], bridge), + :eovsdb_op.mutate(@open_vswitch, [{"_uuid", "==", ovs}], [{"next_cfg", "+=", 1}]) + ], + pid + ) {:reply, replies, state} end @@ -294,7 +305,7 @@ defmodule OVSDB.OpenvSwitch do port = %{ name: options[:name], - interfaces: ["named-uuid", "interface"], + interfaces: ["named-uuid", "interface"] } iface_options = [ @@ -377,6 +388,7 @@ defmodule OVSDB.OpenvSwitch do defp do_find_by_name(pid, table, name) do query = :eovsdb_op.select('*', table, [{"name", "==", name}]) + case xact(query, pid) do [%{"rows" => []}] -> :not_found [%{"rows" => [row]}] -> row @@ -386,11 +398,11 @@ defmodule OVSDB.OpenvSwitch do defp make_ovsdb_map(map), do: make_ovsdb_map([], map) defp make_ovsdb_map(acc, []), do: ["map", Enum.reverse(acc)] - defp make_ovsdb_map(acc, [[_key, nil]|rest]), do: make_ovsdb_map(acc, rest) - defp make_ovsdb_map(acc, [attr|rest]), do: make_ovsdb_map([attr|acc], rest) + defp make_ovsdb_map(acc, [[_key, nil] | rest]), do: make_ovsdb_map(acc, rest) + defp make_ovsdb_map(acc, [attr | rest]), do: make_ovsdb_map([attr | acc], rest) defp make_ovsdb_set(nil), do: ["set", []] - defp make_ovsdb_set([_|_] = list), do: ["set", list] + defp make_ovsdb_set([_ | _] = list), do: ["set", list] defp make_ovsdb_set(value), do: value defp add_elem_to_set(["set", []], value), do: value diff --git a/lib/tres/controller.ex b/lib/tres/controller.ex index 62cd4ed..19abd7f 100644 --- a/lib/tres/controller.ex +++ b/lib/tres/controller.ex @@ -14,6 +14,7 @@ defmodule Tres.Controller do def handler_spec(dpid) do {cb_mod, cb_args} = Tres.Utils.get_callback_module() + %{ id: {__MODULE__, dpid}, start: {cb_mod, :start_link, [[dpid, cb_args]]}, diff --git a/lib/tres/message_handler_sup.ex b/lib/tres/message_handler_sup.ex index 562b476..24bb2f2 100644 --- a/lib/tres/message_handler_sup.ex +++ b/lib/tres/message_handler_sup.ex @@ -7,6 +7,7 @@ defmodule Tres.MessageHandlerSup do def init(_init_args) do children = [] + Supervisor.init( children, strategy: :one_for_one, diff --git a/lib/tres/message_helper.ex b/lib/tres/message_helper.ex index 70d911b..f9f8786 100644 --- a/lib/tres/message_helper.ex +++ b/lib/tres/message_helper.ex @@ -147,11 +147,16 @@ defmodule Tres.MessageHelper do Openflow.MeterMod.new( xid: options[:xid] || 0, command: :delete, - meter_id: options[:meter_id] || 0, + meter_id: options[:meter_id] || 0 ) send_message(meter_mod, datapath_id, options[:blocking] || false) end + + defp send_resume(datapath_id, options) do + resume = Openflow.NxResume.new(options) + send_message(resume, datapath_id, options[:blocking] || false) + end end end end diff --git a/lib/tres/secure_channel.ex b/lib/tres/secure_channel.ex index 05e723d..69a3cd3 100644 --- a/lib/tres/secure_channel.ex +++ b/lib/tres/secure_channel.ex @@ -50,7 +50,7 @@ defmodule Tres.SecureChannel do {:tcp, socket, packet}, state, %State{socket: socket, transport: transport} = state_data - ) do + ) do transport.setopts(socket, active: :once) handle_packet(packet, state_data, state, []) end @@ -137,9 +137,7 @@ defmodule Tres.SecureChannel do # INIT state defp handle_INIT(:enter, _old_state, state_data) do - debug( - "Initiate HELLO handshake: " <> "#{state_data.ip_addr}:#{state_data.port}" - ) + debug("Initiate HELLO handshake: " <> "#{state_data.ip_addr}:#{state_data.port}") initiate_hello_handshake(state_data) end @@ -153,19 +151,14 @@ defmodule Tres.SecureChannel do end defp handle_INIT(:internal, message, _state_data) do - debug( - "Hello handshake in progress, " <> "dropping message: #{inspect(message)}" - ) + debug("Hello handshake in progress, " <> "dropping message: #{inspect(message)}") :keep_state_and_data end # CONNECTING state defp handle_CONNECTING(:enter, :INIT, state_data) do - debug( - "Initiate FEATURES handshake:" <> - " #{state_data.ip_addr}:#{state_data.port}" - ) + debug("Initiate FEATURES handshake:" <> " #{state_data.ip_addr}:#{state_data.port}") initiate_features_handshake(state_data) end @@ -195,8 +188,7 @@ defmodule Tres.SecureChannel do defp handle_CONNECTING(:internal, {:openflow, message}, _state_data) do debug( - "Features handshake in progress," <> - " dropping message: #{inspect(message.__struct__)}" + "Features handshake in progress," <> " dropping message: #{inspect(message.__struct__)}" ) :keep_state_and_data @@ -209,6 +201,7 @@ defmodule Tres.SecureChannel do # CONNECTED state defp handle_CONNECTED(:enter, :CONNECTING, state_data) do _tref = schedule_xactdb_ageout() + case init_handler(state_data) do %State{} = new_state_data -> start_periodic_idle_check() @@ -340,10 +333,12 @@ defmodule Tres.SecureChannel do action = {:next_event, :internal, {:openflow, message}} new_state_data = %{state_data | buffer: "", last_received: :os.timestamp()} handle_packet(leftovers, new_state_data, state, [action | actions]) + {:error, :binary_too_small} -> handle_packet("", %{state_data | buffer: binary}, state, actions) - {:error, :malformed_packet} -> - :ok = debug("malformed packet received from #{state_data.datapath_id}") + + {:error, {:malformed_packet, {_reason, st}}} -> + :ok = debug("malformed packet received from #{state_data.datapath_id} stack_trace: #{st}") handle_packet("", %{state_data | buffer: ""}, state, actions) end end @@ -375,8 +370,9 @@ defmodule Tres.SecureChannel do unless is_nil(message), do: send(state_data.handler_pid, message) XACT_KV.delete(state_data.xact_kv_ref, xid) end + defp process_xact_entry({:xact_entry, xid, message, _orig, from, _inserted_at}, state_data) - when is_tuple(from) do + when is_tuple(from) do reply = if is_nil(message), do: :noreply, else: message :ok = :gen_statem.reply(from, {:ok, reply}) XACT_KV.delete(state_data.xact_kv_ref, xid) @@ -558,8 +554,7 @@ defmodule Tres.SecureChannel do defp send_message(message, %State{socket: socket, transport: transport}) do if is_list(message) do for message <- message, - do: - debug("Sending: #{inspect(message.__struct__)}(xid: #{message.xid})") + do: debug("Sending: #{inspect(message.__struct__)}(xid: #{message.xid})") else debug("Sending: #{inspect(message.__struct__)}(xid: #{message.xid})") end diff --git a/lib/tres/switch_registry.ex b/lib/tres/switch_registry.ex index cad2612..30fb857 100644 --- a/lib/tres/switch_registry.ex +++ b/lib/tres/switch_registry.ex @@ -25,6 +25,7 @@ defmodule Tres.SwitchRegistry do def send_message(message, dpid, _blocking = true) do blocking_send_message(message, dpid) end + def send_message(message, dpid, _blocking) do send_message(message, dpid) end diff --git a/lib/tres/utils.ex b/lib/tres/utils.ex index 21ed74e..75af3b9 100644 --- a/lib/tres/utils.ex +++ b/lib/tres/utils.ex @@ -14,11 +14,11 @@ defmodule Tres.Utils do def start_openflow_listener do :ranch.start_listener( - _ref = Tres, - _trasport = :ranch_tcp, + _ref = Tres, + _trasport = :ranch_tcp, _transport_opts = transport_options(), - _protocol = @connection_manager, - _protocol_opts = [] + _protocol = @connection_manager, + _protocol_opts = [] ) end diff --git a/test/ofp_action_test.exs b/test/ofp_action_test.exs index 616cfc8..5580700 100644 --- a/test/ofp_action_test.exs +++ b/test/ofp_action_test.exs @@ -631,14 +631,14 @@ defmodule OfpActionTest do test_file = "test/packet_data/nx_clone.raw" packet = File.read!(test_file) actions = Openflow.Action.read(packet) + clone = - Openflow.Action.NxClone.new( - [ - Openflow.Action.PushVlan.new(), - Openflow.Action.SetField.new({:vlan_vid, 5}), - Openflow.Action.Output.new(10) - ] - ) + Openflow.Action.NxClone.new([ + Openflow.Action.PushVlan.new(), + Openflow.Action.SetField.new({:vlan_vid, 5}), + Openflow.Action.Output.new(10) + ]) + actions_bin = Openflow.Action.to_binary(clone) assert actions_bin == packet assert actions == [clone] diff --git a/test/ofp_packet_in2_test.exs b/test/ofp_packet_in2_test.exs index e9b7ef7..45eff9c 100644 --- a/test/ofp_packet_in2_test.exs +++ b/test/ofp_packet_in2_test.exs @@ -14,9 +14,9 @@ defmodule OfpPacketIn2Test do assert pktin.full_len == 64 assert pktin.table_id == 7 assert pktin.buffer_id == 0x114 - assert pktin.cookie == 0xfedcba9876543210 + assert pktin.cookie == 0xFEDCBA9876543210 assert pktin.reason == :action - assert pktin.metadata == [metadata: 0x5a5a5a5a5a5a5a5a] + assert pktin.metadata == [metadata: 0x5A5A5A5A5A5A5A5A] assert pktin.userdata == <<1, 2, 3, 4, 5>> end end