diff options
Diffstat (limited to 'shard/lib/net')
-rw-r--r-- | shard/lib/net/group.ex | 17 | ||||
-rw-r--r-- | shard/lib/net/manager.ex | 119 | ||||
-rw-r--r-- | shard/lib/net/tcpconn.ex | 25 | ||||
-rw-r--r-- | shard/lib/net/tcpserver.ex | 3 |
4 files changed, 142 insertions, 22 deletions
diff --git a/shard/lib/net/group.ex b/shard/lib/net/group.ex index d2c2537..692438a 100644 --- a/shard/lib/net/group.ex +++ b/shard/lib/net/group.ex @@ -36,12 +36,12 @@ defmodule SNet.PubShardGroup do def init_lookup(%SNet.PubShardGroup{id: id}, _notify_to) do # For now: ask all currently connected peers and connect to new peers we know of spawn fn -> - for {_, pid, _} <- Shard.Manager.list_connections do + for {_, pid, _} <- SNet.Manager.list_connections do GenServer.cast(pid, {:send_msg, {:interested, [id]}}) end for peer_info <- Shard.Manager.get_shard_peers id do - if Shard.Manager.get_connections_to peer_info == [] do - Shard.Manager.add_peer(peer_info) # TODO callback when connected + if SNet.Manager.get_connections_to peer_info == [] do + SNet.Manager.add_peer(peer_info) # TODO callback when connected end end end @@ -49,9 +49,10 @@ defmodule SNet.PubShardGroup do end def get_connections(%SNet.PubShardGroup{id: id}) do - for peer_info <- Shard.Manager.get_shard_peers(id), - [{pid, _auth}|_] = Shard.Manager.get_connections_to(peer_info), - do: pid + Shard.Manager.get_shard_peers(id) + |> Enum.map(&(SNet.Manager.get_connections_to(&1))) + |> Enum.filter(&(&1 != [])) + |> Enum.map(fn [{pid, _auth}|_] -> pid end) end def broadcast(group, msg, nmax) do @@ -63,10 +64,10 @@ defmodule SNet.PubShardGroup do |> Enum.count if nmax - nsent > 0 do Shard.Manager.get_shard_peers(id) - |> Enum.filter(&(Shard.Manager.get_connections_to(&1) == [])) + |> Enum.filter(&(SNet.Manager.get_connections_to(&1) == [])) |> Enum.shuffle |> Enum.take(nmax - nsent) - |> Enum.map(&(Shard.Manager.send(&1, msg))) + |> Enum.map(&(SNet.Manager.send(&1, msg))) end end diff --git a/shard/lib/net/manager.ex b/shard/lib/net/manager.ex new file mode 100644 index 0000000..17d6e06 --- /dev/null +++ b/shard/lib/net/manager.ex @@ -0,0 +1,119 @@ +defmodule SNet.Manager do + @moduledoc""" + - :connections (not persistent) + + List of + { peer_info, pid, nil | {my_pk, his_pk} } + """ + + use GenServer + + require Logger + + def start_link(_) do + GenServer.start_link(__MODULE__, nil, name: __MODULE__) + end + + def init(_) do + Process.flag(:trap_exit, true) + + :ets.new(:connections, [:bag, :protected, :named_table]) + + {:ok, nil} + end + + def handle_call({:add_peer, peer_info}, _from, state) do + pid = add_peer_internal(peer_info) + {:reply, pid, state} + end + + def handle_call({:accept, client}, _from, state) do + my_port = Application.get_env(:shard, :port) + {:ok, pid} = SNet.TCPConn.start_link(%{socket: client, my_port: my_port}) + {:reply, pid, state} + end + + def handle_call({:peer_up, pid, peer_info, auth}, _from, state) do + case :ets.match(:connections, {peer_info, :_, auth}) do + [{_, pid2, _}] when pid2 != pid -> + {:reply, :redundant, state} + _ -> + :ets.insert(:connections, {peer_info, pid, auth}) + + # Send interested message for all our shards + id_list = (for {id, _, _} <- Shard.Manager.list_shards(), do: id) + GenServer.cast(pid, {:send_msg, {:interested, id_list}}) + + {:reply, :ok, state} + end + end + + def handle_cast({:connect_and_send, peer_info, msg}, state) do + pid = add_peer_internal(peer_info) + GenServer.cast(pid, {:send_msg, msg}) + {:noreply, state} + end + + def handle_info({:EXIT, pid, _reason}, state) do + :ets.match_delete(:connections, {:_, pid, :_}) + {:noreply, state} + end + + defp add_peer_internal(peer_info) do + case :ets.lookup(:connections, peer_info) do + [{_, pid, _}|_] -> + pid + [] -> + my_port = Application.get_env(:shard, :port) + {:ok, pid} = SNet.TCPConn.start_link(%{connect_to: peer_info, my_port: my_port, auth: nil}) + :ets.insert(:connections, {peer_info, pid, nil}) + pid + end + end + + # ========= + # INTERFACE + # ========= + + @doc""" + Connect to a peer specified by ip address and port + """ + def add_peer(peer_info) do + GenServer.call(__MODULE__, {:add_peer, peer_info}) + end + + @doc""" + Return the list of all connected peers + """ + def list_connections() do + for [x] <- :ets.match(:connections, :"$1"), do: x + end + + @doc""" + Return the list of connections to a given peer, possibly with different auth + """ + def get_connections_to(peer_info) do + for {^peer_info, pid, auth} <- :ets.lookup(:connections, peer_info), do: {pid, auth} + end + + @doc""" + Send message to a peer specified by peer info. + Opens a connection if necessary. + """ + def send(peer_info, msg) do + case :ets.lookup(:connections, peer_info) do + [{^peer_info, pid, _auth}|_] -> + GenServer.cast(pid, {:send_msg, msg}) + [] -> + GenServer.cast(__MODULE__, {:connect_and_send, peer_info, msg}) + end + end + + @doc""" + Send message to a peer specified by peer id + """ + def send_pid(pid, msg) do + GenServer.cast(pid, {:send_msg, msg}) + end +end + diff --git a/shard/lib/net/tcpconn.ex b/shard/lib/net/tcpconn.ex index 67d7f4c..476c426 100644 --- a/shard/lib/net/tcpconn.ex +++ b/shard/lib/net/tcpconn.ex @@ -33,23 +33,21 @@ defmodule SNet.TCPConn do end def init(state) do - if state.is_client do - GenServer.cast(self(), :client_handshake) - else + if Map.has_key?(state, :socket) do GenServer.cast(self(), :server_handshake) + else + GenServer.cast(self(), :client_handshake) end - {:ok, state} end - def handle_call(:get_peer_info, _from, state) do {:reply, state.peer_info, state} end - def handle_cast(:client_handshake, state) do - socket = state.socket + {:inet, ip, port} = state.connect_to + {:ok, socket} = :gen_tcp.connect(ip, port, [:binary, packet: 2, active: false]) net_key = Application.get_env(:shard, :network_key) %{public: cli_eph_pk, secret: cli_eph_sk} = :enacl.box_keypair @@ -150,9 +148,11 @@ defmodule SNet.TCPConn do |> Map.put(:peer_info, {:inet, addr, port}) |> Map.put(:my_port, state.my_port) - GenServer.cast(Shard.Manager, {:peer_up, self(), state.peer_info, state.auth}) - Logger.info "New peer: #{print_id state} at #{inspect addr}:#{port}" + if GenServer.call(SNet.Manager, {:peer_up, self(), state.peer_info, state.auth}) == :redundant do + exit :redundant + end + Logger.info "New peer: #{print_id state} at #{inspect addr}:#{port}" {:noreply, state} end @@ -262,9 +262,11 @@ defmodule SNet.TCPConn do |> Map.put(:peer_info, {:inet, addr, his_port}) |> Map.put(:my_port, state.my_port) - GenServer.cast(Shard.Manager, {:peer_up, self(), state.peer_info, state.auth}) - Logger.info "New peer: #{print_id state} at #{inspect state.peer_info} (#{port})" + if GenServer.call(SNet.Manager, {:peer_up, self(), state.peer_info, state.auth}) == :redundant do + exit :redundant + end + Logger.info "New peer: #{print_id state} at #{inspect state.peer_info} (#{port})" {:noreply, state} end @@ -284,7 +286,6 @@ defmodule SNet.TCPConn do def handle_info({:tcp_closed, _socket}, state) do Logger.info "Disconnected: #{print_id state} at #{inspect state.peer_info}" - GenServer.cast(Shard.Manager, {:peer_down, self(), state.peer_info, state.auth}) exit(:normal) end diff --git a/shard/lib/net/tcpserver.ex b/shard/lib/net/tcpserver.ex index 6cc3473..d7326ad 100644 --- a/shard/lib/net/tcpserver.ex +++ b/shard/lib/net/tcpserver.ex @@ -19,8 +19,7 @@ defmodule SNet.TCPServer do defp loop_acceptor(socket, my_port) do {:ok, client} = :gen_tcp.accept(socket) - {:ok, pid} = DynamicSupervisor.start_child(Shard.DynamicSupervisor, - {SNet.TCPConn, %{socket: client, my_port: my_port, is_client: false}}) + pid = GenServer.call(SNet.Manager, {:accept, client}) :ok = :gen_tcp.controlling_process(client, pid) loop_acceptor(socket, my_port) end |