defmodule Openflow.Match do @moduledoc false use Bitwise defstruct(type: :oxm, fields: []) @type t :: %__MODULE__{type: :oxm, fields: Keyword.t()} # Match Classes @type u8 :: 0..0xFF @type u16 :: 0..0xFFFF @type u24 :: 0..0xFFFFFF @type u32 :: 0..0xFFFFFFFF @type u64 :: 0..0xFFFFFFFFFFFFFFFF @type u128 :: 0..0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF @type eth_addr :: <<_::48>> @type in_addr :: :inet.ip4_address() @type in6_addr :: :inet.ip6_address() @spec new(Keyword.t()) :: t() def new(fields \\ []) do %__MODULE__{type: :oxm, fields: fields} end @spec to_binary(t()) :: <<_::_*8>> def to_binary(%__MODULE__{fields: fields} = match) do fields_bin = encode_fields(fields, <<>>) length = byte_size(fields_bin) + 4 type_int = Openflow.Enums.to_int(match.type, :match_type) padding = Openflow.Utils.padding(length, 8) <> end def read(<<_::16, no_pad_len::16, tail::bytes>>) do pad_len = Openflow.Utils.pad_length(no_pad_len, 8) fields_len = no_pad_len - 4 <> = tail fields = decode_fields([], fields_bin) {fields, rest} end def codec_header(<<0xFFFF::16, oxm_field::7, has_mask::1, _len::8, exp_int::32>>) do oxm_field = match_field({0xFFFF, exp_int}, oxm_field) if has_mask == 1, do: :"masked_#{oxm_field}", else: oxm_field end def codec_header(<>) do oxm_field = match_field(oxm_class, oxm_field) if has_mask == 1, do: :"masked_#{oxm_field}", else: oxm_field end def codec_header(oxm_field) when is_atom(oxm_field) do field_str = "#{oxm_field}" has_mask = if String.match?(field_str, ~r/^masked_/), do: 1, else: 0 oxm_field = if has_mask == 1, do: :"#{String.replace(field_str, ~r/^masked_/, "")}", else: oxm_field case match_class(oxm_field) do {oxm_class_int, vendor_int} -> oxm_class = Openflow.Enums.oxm_class_to_atom(oxm_class_int) oxm_field_int = Openflow.Enums.to_int(oxm_field, oxm_class) oxm_length = div(n_bits_of(oxm_field) + 6, 8) <> oxm_class_int -> oxm_class = Openflow.Enums.oxm_class_to_atom(oxm_class_int) oxm_field_int = Openflow.Enums.to_int(oxm_field, oxm_class) oxm_length = div(n_bits_of(oxm_field), 8) <> end end def n_bits_of(f) do f |> format_type() |> bit_len() end @spec decode_value(binary(), atom()) :: term() def decode_value(v0, f) do v = trim_heading_pad(v0, f) f |> format_type() |> _decode_value(v) end @spec encode_value(term(), atom()) :: binary() def encode_value(v, f) do f |> format_type() |> _encode_value(v) end def header_size(<>) do case oxm_header do <<0xFFFF::16, _::16>> -> 8 <<_oxm_class::16, _::16>> -> 4 end end # private functions ## Encode defp encode_fields([field | rest], <>), do: encode_fields(rest, <>) defp encode_fields([], <>), do: acc defp encode_field({f, {v, m}}) do t = format_type(f) class = match_class(f) field = match_field(class, f) value = _encode_value(t, v) mask = _encode_value(t, m) oxm_header = oxm_header_w(class, field, byte_size(value)) <> end defp encode_field({f, v}) do t = format_type(f) class = match_class(f) field = match_field(class, f) value = _encode_value(t, v) oxm_header = oxm_header(class, field, byte_size(value)) <> end ## Decode defp decode_fields(acc, <<>>), do: Enum.reverse(acc) defp decode_fields( acc, << 0xFFFF::16, _::7, has_mask::1, length::8, body_bin::size(length)-bytes, rest::bytes >> ) do value_len = length - 6 <> = body_bin oxm_field = match_field({0xFFFF, exp_int}, field_int) field = decode_field(has_mask, oxm_field, value_bin) decode_fields([field | acc], rest) end defp decode_fields(acc, <>) do <> = oxm_header <> = tail oxm_field = match_field(oxm_class, oxm_field) field = decode_field(has_mask, oxm_field, body_bin) decode_fields([field | acc], rest) end defp decode_field(1, oxm_field, binary) do type = format_type(oxm_field) length = div(byte_size(binary), 2) <> = binary value = _decode_value(type, value_bin) mask = _decode_value(type, mask_bin) {oxm_field, {value, mask}} end defp decode_field(0, oxm_field, binary) do type = format_type(oxm_field) value = _decode_value(type, binary) {oxm_field, value} end ## Encode Types defp _encode_value(:ofp13_port_no, v), do: <> defp _encode_value(:ofp10_port_no, v), do: <> defp _encode_value(:tcp_flags, v), do: <> defp _encode_value(:ipv6exthdr_flags, v), do: <> defp _encode_value(:tun_gbp_flags, v), do: <> defp _encode_value(:ct_state_flags, v), do: <> defp _encode_value(:mac, <>), do: mac defp _encode_value(:in_addr, {a1, a2, a3, a4}), do: <> defp _encode_value(:in6_addr, {a1, a2, a3, a4, a5, a6, a7, a8}), do: << a1::16, a2::16, a3::16, a4::16, a5::16, a6::16, a7::16, a8::16 >> defp _encode_value(:u8, v), do: <> defp _encode_value(:u16, v), do: <> defp _encode_value(:u24, v), do: <> defp _encode_value(:u32, v), do: <> defp _encode_value(:u64, v), do: <> defp _encode_value(:u128, v), do: <> defp _encode_value(:arbitrary, v), do: <> ## Decode types defp _decode_value(:ofp13_port_no, <>), do: Openflow.Utils.get_enum(v, :openflow13_port_no) defp _decode_value(:ofp10_port_no, <>), do: Openflow.Utils.get_enum(v, :openflow10_port_no) defp _decode_value(:tcp_flags, <>), do: Openflow.Enums.int_to_flags(v, :tcp_flags) defp _decode_value(:ipv6exthdr_flags, <>), do: Openflow.Enums.int_to_flags(v, :ipv6exthdr_flags) defp _decode_value(:tun_gbp_flags, <>), do: Openflow.Enums.int_to_flags(v, :tun_gbp_flags) defp _decode_value(:ct_state_flags, <>), do: Openflow.Enums.int_to_flags(v, :ct_state_flags) defp _decode_value(:mac, <>), do: v defp _decode_value(:in_addr, <>), do: {a1, a2, a3, a4} defp _decode_value(:in6_addr, << a1::16, a2::16, a3::16, a4::16, a5::16, a6::16, a7::16, a8::16 >>), do: {a1, a2, a3, a4, a5, a6, a7, a8} defp _decode_value(:u8, <>), do: v defp _decode_value(:u16, <>), do: v defp _decode_value(:u24, <>), do: v defp _decode_value(:u32, <>), do: v defp _decode_value(:u64, <>), do: v defp _decode_value(:u128, <>), do: v defp _decode_value(:arbitrary, <>), do: v ## Bit size defp bit_len(:ofp13_port_no), do: 32 defp bit_len(:ofp10_port_no), do: 16 defp bit_len(:tcp_flags), do: 16 defp bit_len(:ipv6exthdr_flags), do: 16 defp bit_len(:tun_gbp_flags), do: 8 defp bit_len(:ct_state_flags), do: 32 defp bit_len(:mac), do: 48 defp bit_len(:in_addr), do: 32 defp bit_len(:in6_addr), do: 128 defp bit_len(:u8), do: 8 defp bit_len(:u16), do: 16 defp bit_len(:u24), do: 24 defp bit_len(:u32), do: 32 defp bit_len(:u64), do: 64 defp bit_len(:u128), do: 128 defp bit_len(:arbitrary), do: 0 ## OXM Match classes @spec match_field(integer() | {integer(), integer()}, atom()) :: integer() defp match_field({0xFFFF, exp}, f) do class = Openflow.Utils.get_enum(exp, :experimenter_oxm_vendors) Openflow.Utils.get_enum(f, class) end defp match_field(c, f) do class = Openflow.Utils.get_enum(c, :oxm_class) Openflow.Utils.get_enum(f, class) end # OpenFlow Basic defp match_class(f) when f in [ :in_port, :in_phy_port, :metadata, :eth_dst, :eth_src, :eth_type, :vlan_vid, :vlan_pcp, :ip_dscp, :ip_ecn, :ip_proto, :ipv4_src, :ipv4_dst, :tcp_src, :tcp_dst, :udp_src, :udp_dst, :sctp_src, :sctp_dst, :icmpv4_type, :icmpv4_code, :arp_op, :arp_spa, :arp_tpa, :arp_sha, :arp_tha, :ipv6_src, :ipv6_dst, :ipv6_flabel, :icmpv6_type, :icmpv6_code, :ipv6_nd_target, :ipv6_nd_sll, :ipv6_nd_tll, :mpls_label, :mpls_tc, :mpls_bos, :pbb_isid, :tunnel_id, :ipv6_exthdr, :pbb_uca ], do: 0x8000 # PacketRegisters defp match_class(f) when f in [ :xreg0, :xreg1, :xreg2, :xreg3, :xreg4, :xreg5, :xreg6, :xreg7 ], do: 0x8001 # NXM_0 defp match_class(f) when f in [ :nx_in_port, :nx_eth_dst, :nx_eth_src, :nx_eth_type, :nx_vlan_tci, :nx_ip_tos, :nx_ip_proto, :nx_ipv4_src, :nx_ipv4_dst, :nx_tcp_src, :nx_tcp_dst, :nx_udp_src, :nx_udp_dst, :nx_icmpv4_type, :nx_icmpv4_code, :nx_arp_op, :nx_arp_spa, :nx_arp_tpa, :nx_tcp_flags ], do: 0x0000 # NXM_1 defp match_class(f) when f in [ :reg0, :reg1, :reg2, :reg3, :reg4, :reg5, :reg6, :reg7, :reg8, :reg9, :reg10, :reg11, :reg12, :reg13, :reg14, :reg15, :tun_id, :nx_arp_sha, :nx_arp_tha, :nx_ipv6_src, :nx_ipv6_dst, :nx_icmpv6_type, :nx_icmpv6_code, :nx_ipv6_nd_target, :nx_ipv6_nd_sll, :nx_ipv6_nd_tll, :nx_ip_frag, :nx_ipv6_label, :nx_ip_ecn, :nx_ip_ttl, :nx_mpls_ttl, :tun_src, :tun_dst, :pkt_mark, :dp_hash, :recirc_id, :conj_id, :tun_gbp_id, :tun_gbp_flags, :tun_metadata0, :tun_metadata1, :tun_metadata2, :tun_metadata3, :tun_metadata4, :tun_metadata5, :tun_metadata6, :tun_metadata7, :tun_metadata8, :tun_metadata9, :tun_metadata10, :tun_metadata11, :tun_metadata12, :tun_metadata13, :tun_metadata14, :tun_metadata15, :tun_metadata16, :tun_metadata17, :tun_metadata18, :tun_metadata19, :tun_metadata20, :tun_metadata21, :tun_metadata22, :tun_metadata23, :tun_metadata24, :tun_metadata25, :tun_metadata26, :tun_metadata27, :tun_metadata28, :tun_metadata29, :tun_metadata30, :tun_metadata31, :tun_metadata32, :tun_metadata33, :tun_metadata34, :tun_metadata35, :tun_metadata36, :tun_metadata37, :tun_metadata38, :tun_metadata39, :tun_metadata40, :tun_metadata41, :tun_metadata42, :tun_metadata43, :tun_metadata44, :tun_metadata45, :tun_metadata46, :tun_metadata47, :tun_metadata48, :tun_metadata49, :tun_metadata50, :tun_metadata51, :tun_metadata52, :tun_metadata53, :tun_metadata54, :tun_metadata55, :tun_metadata56, :tun_metadata57, :tun_metadata58, :tun_metadata59, :tun_metadata60, :tun_metadata61, :tun_metadata62, :tun_metadata63, :tun_flags, :ct_state, :ct_zone, :ct_mark, :ct_label, :tun_ipv6_src, :tun_ipv6_dst, :xxreg0, :xxreg1, :xxreg2, :xxreg3, :xxreg4, :xxreg5, :xxreg6, :xxreg7, :ct_nw_proto, :ct_nw_src, :ct_nw_dst, :ct_ipv6_src, :ct_ipv6_dst, :ct_tp_src, :ct_tp_dst ], do: 0x0001 # Nicira Ext defp match_class(f) when f in [ :nsh_flags, :nsh_mdtype, :nsh_np, :nsh_spi, :nsh_si, :nsh_c1, :nsh_c2, :nsh_c3, :nsh_c4, :nsh_ttl ], do: {0xFFFF, 0x00002320} # ONF Ext defp match_class(f) when f in [ :onf_tcp_flags, :onf_actset_output, :onf_pbb_uca ], do: {0xFFFF, 0x4F4E4600} # Special types defp format_type(t) when t in [ :in_port, :in_phy_port, :onf_actset_output ], do: :ofp13_port_no defp format_type(t) when t in [ :nx_in_port ], do: :ofp10_port_no # Composite types defp format_type(t) when t in [ :nx_tcp_flags, :onf_tcp_flags ], do: :tcp_flags defp format_type(t) when t in [ :ipv6_exthdr ], do: :ipv6exthdr_flags defp format_type(t) when t in [ :tun_gbp_flags ], do: :tun_gbp_flags defp format_type(t) when t in [ :ct_state ], do: :ct_state_flags defp format_type(t) when t in [ :eth_dst, :eth_src, :arp_sha, :arp_tha, :ipv6_nd_sll, :ipv6_nd_tll, :nx_eth_dst, :nx_eth_src, :nx_arp_sha, :nx_arp_tha, :nx_ipv6_nd_sll, :nx_ipv6_nd_tll ], do: :mac defp format_type(t) when t in [ :ipv4_src, :ipv4_dst, :arp_spa, :arp_tpa, :nx_ipv4_src, :nx_ipv4_dst, :nx_arp_spa, :nx_arp_tpa, :tun_src, :tun_dst, :ct_nw_src, :ct_nw_dst ], do: :in_addr defp format_type(t) when t in [ :ipv6_src, :ipv6_dst, :ipv6_nd_target, :nx_ipv6_src, :nx_ipv6_dst, :nx_ipv6_nd_target, :tun_ipv6_src, :tun_ipv6_dst, :ct_ipv6_src, :ct_ipv6_dst ], do: :in6_addr # Scalar types defp format_type(t) when t in [ :vlan_pcp, :ip_dscp, :ip_ecn, :ip_proto, :icmpv4_code, :icmpv4_type, :icmpv6_code, :icmpv6_type, :mpls_tc, :mpls_bos, :nx_ip_tos, :nx_ip_proto, :nx_icmpv4_code, :nx_icmpv4_type, :nx_icmpv6_code, :nx_icmpv6_type, :nx_ip_frag, :nx_ip_ecn, :nx_ip_ttl, :nx_mpls_ttl, :ct_nw_proto, :nsh_flags, :nsh_mdtype, :nsh_np, :nsh_si, :nsh_ttl, :pbb_uca, :onf_pbb_uca ], do: :u8 defp format_type(t) when t in [ :eth_type, :vlan_vid, :nx_vlan_tci, :tcp_src, :tcp_dst, :udp_src, :udp_dst, :sctp_src, :sctp_dst, :arp_op, :nx_eth_type, :nx_tcp_src, :nx_tcp_dst, :nx_udp_src, :nx_udp_dst, :nx_arp_op, :tun_gbp_id, :tun_flags, :ct_zone, :ct_tp_src, :ct_tp_dst ], do: :u16 defp format_type(t) when t in [ :pbb_isid ], do: :u24 defp format_type(t) when t in [ :ipv6_flabel, :mpls_label, :reg0, :reg1, :reg2, :reg3, :reg4, :reg5, :reg6, :reg7, :reg8, :reg9, :reg10, :reg11, :reg12, :reg13, :reg14, :reg15, :nx_ipv6_flabel, :pkt_mark, :dp_hash, :recirc_id, :conj_id, :ct_mark, :nsh_spi, :nsh_c1, :nsh_c2, :nsh_c3, :nsh_c4 ], do: :u32 defp format_type(t) when t in [ :metadata, :tunnel_id, :tun_id, :xreg0, :xreg1, :xreg2, :xreg3, :xreg4, :xreg5, :xreg6, :xreg7 ], do: :u64 defp format_type(t) when t in [ :ct_label, :xxreg0, :xxreg1, :xxreg2, :xxreg3, :xxreg4, :xxreg5, :xxreg6, :xxreg7 ], do: :u128 defp format_type(t) when t in [ :tun_metadata0, :tun_metadata1, :tun_metadata2, :tun_metadata3, :tun_metadata4, :tun_metadata5, :tun_metadata6, :tun_metadata7, :tun_metadata8, :tun_metadata9, :tun_metadata10, :tun_metadata11, :tun_metadata12, :tun_metadata13, :tun_metadata14, :tun_metadata15, :tun_metadata16, :tun_metadata17, :tun_metadata18, :tun_metadata19, :tun_metadata20, :tun_metadata21, :tun_metadata22, :tun_metadata23, :tun_metadata24, :tun_metadata25, :tun_metadata26, :tun_metadata27, :tun_metadata28, :tun_metadata29, :tun_metadata30, :tun_metadata31, :tun_metadata32, :tun_metadata33, :tun_metadata34, :tun_metadata35, :tun_metadata36, :tun_metadata37, :tun_metadata38, :tun_metadata39, :tun_metadata40, :tun_metadata41, :tun_metadata42, :tun_metadata43, :tun_metadata44, :tun_metadata45, :tun_metadata46, :tun_metadata47, :tun_metadata48, :tun_metadata49, :tun_metadata50, :tun_metadata51, :tun_metadata52, :tun_metadata53, :tun_metadata54, :tun_metadata55, :tun_metadata56, :tun_metadata57, :tun_metadata58, :tun_metadata59, :tun_metadata60, :tun_metadata61, :tun_metadata62, :tun_metadata63 ], do: :arbitrary defp oxm_header__({class, exp}, field, has_mask, length), do: <> defp oxm_header__(class, field, has_mask, length), do: <> defp oxm_header(class, field, length), do: oxm_header__(class, field, 0, length) defp oxm_header_w(class, field, length), do: oxm_header__(class, field, 1, length * 2) defp trim_heading_pad(v0, f) do case n_bits_of(f) do n_bits when bit_size(v0) < n_bits -> head_pad_len = n_bits - bit_size(v0) <<0::size(head_pad_len), v0::bytes>> n_bits when bit_size(v0) > n_bits -> head_pad_len = bit_size(v0) - n_bits <<_::size(head_pad_len), value::size(n_bits)-bits>> = v0 value _ -> v0 end end end