Elixir
netfilter
落書き
More than 1 year has passed since last update.

Elixir == Webみたいな感あるけど
個人的にはパケットガチャガチャできる言語といえばこちら、
という印象です。

sudo iptables -A INPUT -j NFQUEUE --queue-num 0

で、packetをプリントする。

defmodule NfQueue do
  use GenServer

  defmodule State do
    defstruct [
      socket: nil,
      queue:  nil
    ]
  end

  require Logger

  @netlink_netfilter 12
  @nfqnl_copy_packet 2
  @nf_accept 1

  def start_link(queue) do
    GenServer.start_link(__MODULE__, [queue], name: __MODULE__)
  end

  def init([queue]) do
    {:ok, socket} = :gen_socket.socket(:netlink, :raw, @netlink_netfilter)
    :ok = :gen_socket.bind(socket, netlink_sockaddr_nl())
    :ok = :gen_socket.setsockopt(socket, :sol_socket, :rcvbuf, 57_108_864)
    :ok = :gen_socket.setsockopt(socket, :sol_socket, :sndbuf, 57_108_864)
    :ok = nfq_create_queue(socket, queue)
    :ok = nfq_set_mode(socket, queue, @nfqnl_copy_packet, 0xffff)
    :ok = nfq_set_flags(socket, queue, [:conntrack], [:conntrack])
    :ok = :gen_socket.input_event(socket, true)
    {:ok, %State{socket: socket, queue: queue}}
  end

  def handle_info({socket, :input_ready}, %State{socket: socket} = state) do
    case :gen_socket.recv(socket, 8192) do
      {:ok, data} ->
        data
        |> :netlink_codec.nl_ct_dec
        |> Enum.each(&process_nfq_message(&1, state))
      other ->
        :ok = Logger.debug("[#{__MODULE__}] other: #{inspect(other)}")
    end
    :ok = :gen_socket.input_event(socket, true)
    {:noreply, state}
  end
  def handle_info(_info, state) do
    {:noreply, state}
  end

  # private functions

  defp nfq_create_queue(socket, queue),
    do: build_send_cfg_msg(socket, :bind, queue, :unspec)

  defp nfq_set_mode(socket, queue, copy_mode, length) do
    command = {:params, length, copy_mode}
    message = {:queue, :config, [:ack, :request], 0, 0, {:unspec, 0, queue, [command]}}
    nfnl_query(socket, message)
  end

  defp nfq_set_flags(socket, queue, flags, mask) do
    command = [mask: mask, flags: flags]
    message = {:queue, :config, [:ack, :request], 0, 0, {:unspec, 0, queue, command}}
    nfnl_query(socket, message)
  end

  defp build_send_cfg_msg(socket, command, queue, pf) do
    command = {:cmd, command, pf}
    message = {:queue, :config, [:ack, :request], 0, 0, {:unspec, 0, queue, [command]}}
    nfnl_query(socket, message)
  end

  defp process_nfq_message({:queue, :packet, _flags, _seq, _pid, packet}, state),
    do: process_nfq_packet(packet, state)

  defp process_nfq_packet({family, _version, _queue, info}, %State{socket: socket, queue: queue})
    when family == :inet or family == :inet6 do
    IO.inspect({queue, info})
    {_, id, _, _} = :lists.keyfind(:packet_hdr, 1, info)
    nla = [{:verdict_hdr, @nf_accept, id}]
    message = {:queue, :verdict, [:request], 0, 0, {:unspec, 0, queue, nla}}
    request = :netlink_codec.nl_ct_enc(message)
    :gen_socket.sendto(socket, netlink_sockaddr_nl(), request)
  end

  defp nfnl_query(socket, query) do
    request = :netlink_codec.nl_ct_enc(query)
    :gen_socket.sendto(socket, netlink_sockaddr_nl(), request)
    case :gen_socket.recv(socket, 8192) do
      {:ok, reply} ->
        case :netlink_codec.nl_ct_dec(reply) do
          [{:netlink, :error, _, _, _, {0, _}}|_]     -> :ok
          [{:netlink, :error, _, _, _, {errno, _}}|_] -> {:error, errno}
          [msg|_] -> {:error, msg}
          other   -> other
        end
      other ->
        other
    end
  end

  defp netlink_sockaddr_nl do
    sockaddr_nl(:netlink, 0, 0)
  end

  defp sockaddr_nl(family, pid, groups) do
    sockaddr_nl({family, pid, groups})
  end

  defp sockaddr_nl({family, pid, groups}) when is_atom(family) do
    sockaddr_nl({:gen_socket.family(family), pid, groups})
  end
  defp sockaddr_nl({family, pid, groups}) do
    <<family::16-native-integer, 0::16, pid::32-native-integer, groups::32-native-integer>>
  end
  defp sockaddr_nl(<< family::16-native-integer, _pad::16, pid::32-native-integer, groups::32-native-integer>>) do
    {:gen_socket.family(family), pid, groups}
  end
end

楽しい。