diff options
Diffstat (limited to 'lib/net')
-rw-r--r-- | lib/net/tcpconn.ex | 134 | ||||
-rw-r--r-- | lib/net/tcpserver.ex | 35 |
2 files changed, 169 insertions, 0 deletions
diff --git a/lib/net/tcpconn.ex b/lib/net/tcpconn.ex new file mode 100644 index 0000000..5d6c912 --- /dev/null +++ b/lib/net/tcpconn.ex @@ -0,0 +1,134 @@ +defmodule SNet.TCPConn do + use GenServer, restart: :temporary + require Salty.Box.Curve25519xchacha20poly1305, as: Box + require Salty.Sign.Ed25519, as: Sign + require Logger + + def start_link(state) do + GenServer.start_link(__MODULE__, state) + end + + def init(state) do + GenServer.cast(self(), :handshake) + {:ok, state} + end + + def handle_call(:get_host_str, _from, state) do + {:reply, "#{state.his_pkey|>Base.encode16|>String.downcase}@#{to_string(:inet_parse.ntoa(state.addr))}:#{state.port}", state} + end + + def handle_cast(:handshake, state) do + socket = state.socket + + {srv_pkey, srv_skey} = Shard.Identity.get_keypair + {:ok, sess_pkey, sess_skey} = Box.keypair + {:ok, challenge} = Salty.Random.buf 32 + + # Exchange public keys and challenge + :gen_tcp.send(socket, srv_pkey <> sess_pkey <> challenge) + {:ok, pkt} = :gen_tcp.recv(socket, 0) + cli_pkey = binary_part(pkt, 0, Sign.publickeybytes) + cli_sess_pkey = binary_part(pkt, Sign.publickeybytes, Box.publickeybytes) + cli_challenge = binary_part(pkt, Sign.publickeybytes + Box.publickeybytes, 32) + + # Do challenge and check their challenge + {:ok, cli_challenge_sign} = Sign.sign_detached(cli_challenge, srv_skey) + pkt = encode_pkt(cli_challenge_sign, cli_sess_pkey, sess_skey) + :gen_tcp.send(socket, pkt) + + {:ok, pkt} = :gen_tcp.recv(socket, 0) + challenge_sign = decode_pkt(pkt, cli_sess_pkey, sess_skey) + :ok = Sign.verify_detached(challenge_sign, challenge, cli_pkey) + + # Connected + :inet.setopts(socket, [active: true]) + + {:ok, {addr, port}} = :inet.peername socket + state =%{ socket: socket, + my_pkey: srv_pkey, + my_skey: srv_skey, + his_pkey: cli_pkey, + conn_my_pkey: sess_pkey, + conn_my_skey: sess_skey, + conn_his_pkey: cli_sess_pkey, + addr: addr, + port: port + } + Logger.info "New peer: #{print_id state} at #{inspect addr}:#{port}" + + GenServer.cast(self(), :init_push) + + {:noreply, state} + end + + def handle_cast({:send_msg, msg}, state) do + send_msg(state, msg) + {:noreply, state} + end + + def handle_cast(:init_push, state) do + push_messages(state, nil, 10) + {:noreply, state} + end + + defp encode_pkt(pkt, pk, sk) do + {:ok, n} = Salty.Random.buf Box.noncebytes + {:ok, msg} = Box.easy(pkt, n, pk, sk) + n <> msg + end + + defp decode_pkt(pkt, pk, sk) do + n = binary_part(pkt, 0, Box.noncebytes) + enc = binary_part(pkt, Box.noncebytes, (byte_size pkt) - Box.noncebytes) + {:ok, msg} = Box.open_easy(enc, n, pk, sk) + msg + end + + defp send_msg(state, msg) do + msgbin = :erlang.term_to_binary msg + enc = encode_pkt(msgbin, state.conn_his_pkey, state.conn_my_skey) + :gen_tcp.send(state.socket, enc) + end + + def handle_info({:tcp, _socket, raw_data}, state) do + msg = decode_pkt(raw_data, state.conn_his_pkey, state.conn_my_skey) + handle_packet(:erlang.binary_to_term(msg, [:safe]), state) + {:noreply, state} + end + + def handle_info({:tcp_closed, _socket}, state) do + Logger.info "Disconnected: #{print_id state} at #{inspect state.addr}:#{state.port}" + exit(:normal) + end + + defp push_messages(state, start, num) do + case GenServer.call(SApp.Chat.Log, {:read, start, num}) do + {:ok, list, rest} -> + send_msg(state, {:info, start, list, rest}) + _ -> nil + end + end + + defp handle_packet(msg, state) do + # Logger.info "Message: #{inspect msg}" + case msg do + :get_top -> push_messages(state, nil, 10) + {:get, start} -> push_messages(state, start, 20) + {:info, _start, list, rest} -> + if rest != nil and not GenServer.call(SApp.Chat.Log, {:has, rest}) do + send_msg(state, {:get, rest}) + end + spawn_link(fn -> + Process.sleep 1000 + GenServer.cast(SApp.Chat.Log, {:insert_many, list, &SApp.Chat.msg_callback/1}) + end) + end + end + + defp print_id(state) do + state.his_pkey + |> binary_part(0, 8) + |> Base.encode16 + |> String.downcase + end +end diff --git a/lib/net/tcpserver.ex b/lib/net/tcpserver.ex new file mode 100644 index 0000000..e5ee996 --- /dev/null +++ b/lib/net/tcpserver.ex @@ -0,0 +1,35 @@ +defmodule SNet.TCPServer do + require Logger + use Task, restart: :permanent + + def start_link(port) do + Task.start_link(__MODULE__, :accept, [port]) + end + + + @doc """ + Starts accepting connections on the given `port`. + """ + def accept(port) do + {:ok, socket} = :gen_tcp.listen(port, + [:binary, packet: 2, active: false, reuseaddr: true]) + Logger.info "Accepting connections on port #{port}" + loop_acceptor(socket) + end + + defp loop_acceptor(socket) do + {:ok, client} = :gen_tcp.accept(socket) + {:ok, pid} = DynamicSupervisor.start_child(SNet.ConnSupervisor, {SNet.TCPConn, %{socket: client}}) + :ok = :gen_tcp.controlling_process(client, pid) + loop_acceptor(socket) + end + + def add_peer(ip, port) do + {:ok, client} = :gen_tcp.connect(ip, port, [:binary, packet: 2, active: false]) + {:ok, pid} = DynamicSupervisor.start_child(SNet.ConnSupervisor, {SNet.TCPConn, %{socket: client}}) + :ok = :gen_tcp.controlling_process(client, pid) + pid + end + +end + |