调整协议,只加密头部分

This commit is contained in:
anlicheng 2026-07-01 16:48:45 +08:00
parent 94dc39805e
commit 22db3e1f46
4 changed files with 65 additions and 87 deletions

View File

@ -13,8 +13,9 @@ Run
$ rebar3 shell
The UDP listener is configured in `config/sys.config` and listens on port `16380`
by default. It accepts RelayKit UDP relay frames.
The UDP listener is configured in `config/sys.config`; the checked-in config
listens on port `1443`, while the code fallback default is `16380`. It accepts
RelayKit UDP relay frames.
Protocol
--------
@ -29,7 +30,8 @@ The decrypted plaintext is one `RKP1` frame:
magic(4) = "RKP1", version(1) = 1, type(1), reserved(2),
stream_id(8), payload_len(4), payload(payload_len)
Frame types are `open = 1`, `data = 2`, `close = 3`, and `error = 4`.
Frame types are `open = 1`, `open_ack = 2`, `data = 3`, `close = 4`, and
`error = 5`.
`open` payload is binary encoded as:
host_len(2), host(host_len), port(2),

View File

@ -1,45 +1,33 @@
%%%-------------------------------------------------------------------
%% @doc ChaCha20-Poly1305 helpers compatible with CryptoKit ChaChaPoly.
%% 1.
%% @end
%%%-------------------------------------------------------------------
-module(chacha20_cipher).
-export([encrypt/2, decrypt/2]).
-export([encrypt/2, decrypt/4]).
-define(KEY_BYTES, 32).
-define(NONCE_BYTES, 12).
-define(TAG_BYTES, 16).
-define(AAD, <<>>).
encrypt(PlainText, Key) when is_binary(PlainText) ->
case valid_key(Key) of
true ->
encrypt(PlainText, Key) when is_binary(PlainText), byte_size(Key) =:= ?KEY_BYTES ->
Nonce = crypto:strong_rand_bytes(?NONCE_BYTES),
try crypto:crypto_one_time_aead(
chacha20_poly1305, Key, Nonce, PlainText, ?AAD, true
) of
try crypto:crypto_one_time_aead(chacha20_poly1305, Key, Nonce, PlainText, ?AAD, true) of
{CipherText, Tag} ->
{ok, <<Nonce/binary, CipherText/binary, Tag/binary>>}
catch
_Class:_Reason ->
{error, crypto_failed}
end;
false ->
{error, invalid_key}
end;
encrypt(_PlainText, _Key) ->
{error, invalid_plaintext}.
decrypt(Combined, Key) when is_binary(Combined), byte_size(Combined) >= ?NONCE_BYTES + ?TAG_BYTES ->
case valid_key(Key) of
true ->
<<Nonce:?NONCE_BYTES/binary, Rest/binary>> = Combined,
CipherTextBytes = byte_size(Rest) - ?TAG_BYTES,
<<CipherText:CipherTextBytes/binary, Tag:?TAG_BYTES/binary>> = Rest,
try crypto:crypto_one_time_aead(
chacha20_poly1305, Key, Nonce, CipherText, ?AAD, Tag, false
) of
decrypt(Nonce, CipherText, Tag, Key) when is_binary(Nonce), is_binary(CipherText), is_binary(Tag), is_binary(Key) ->
try crypto:crypto_one_time_aead(chacha20_poly1305, Key, Nonce, CipherText, ?AAD, Tag, false) of
error ->
{error, authentication_failed};
PlainText ->
@ -47,16 +35,4 @@ decrypt(Combined, Key) when is_binary(Combined), byte_size(Combined) >= ?NONCE_B
catch
_Class:_Reason ->
{error, crypto_failed}
end;
false ->
{error, invalid_key}
end;
decrypt(Combined, _Key) when is_binary(Combined) ->
{error, ciphertext_too_short};
decrypt(_Combined, _Key) ->
{error, invalid_ciphertext}.
valid_key(Key) when is_binary(Key), byte_size(Key) =:= ?KEY_BYTES ->
true;
valid_key(_Key) ->
false.
end.

View File

@ -13,17 +13,6 @@
-define(TCP_OPTIONS, [binary, {packet, raw}, {active, once}, {nodelay, true}]).
-define(CHACHA20_KEY, <<
16#9F, 16#4A, 16#6C, 16#0D,
16#8B, 16#21, 16#7E, 16#3F,
16#42, 16#D9, 16#AA, 16#78,
16#13, 16#C5, 16#E2, 16#B6,
16#F0, 16#A9, 16#27, 16#BC,
16#6D, 16#31, 16#E8, 16#4C,
16#55, 16#FA, 16#10, 16#2A,
16#7E, 16#9D, 16#3C, 16#BB
>>).
-record(stream, {
id :: non_neg_integer(),
socket :: inet:socket(),
@ -38,7 +27,6 @@
peer :: {inet:ip_address(), inet:port_number()},
idle_timeout :: timeout(),
connect_timeout :: timeout(),
cipher_key :: binary(),
users = #{} :: #{binary() => binary()},
streams = #{} :: #{non_neg_integer() => stream()},
sockets = #{} :: #{inet:socket() => non_neg_integer()}
@ -57,7 +45,6 @@ init([{udp, Server, _Sock}, Peer, IdleTimeout, ConnectTimeout, Users]) ->
peer = Peer,
idle_timeout = IdleTimeout,
connect_timeout = ConnectTimeout,
cipher_key = ?CHACHA20_KEY,
users = normalize_users(Users)
}, IdleTimeout}.
@ -90,16 +77,7 @@ terminate(_Reason, #state{streams = Streams}) ->
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
handle_encrypted_datagram(Packet, State = #state{idle_timeout = IdleTimeout, cipher_key = Key}) ->
case chacha20_cipher:decrypt(Packet, Key) of
{ok, PlainPacket} ->
handle_datagram(PlainPacket, State);
{error, Reason} ->
logger:error("UDP relay ignored undecryptable datagram: ~p", [Reason]),
{noreply, State, IdleTimeout}
end.
handle_datagram(Packet, State = #state{idle_timeout = IdleTimeout}) ->
handle_encrypted_datagram(Packet, State = #state{idle_timeout = IdleTimeout}) ->
case relay_server_udp_protocol:decode(Packet) of
{ok, connection_close} ->
{stop, normal, State};
@ -246,21 +224,19 @@ remove_stream(StreamId, State = #state{streams = Streams, sockets = Sockets}) ->
close_remote(#stream{socket = Socket}) ->
gen_tcp:close(Socket).
send_frame(_Type, _StreamId, <<>>, _State) ->
ok;
send_frame(Type, StreamId, <<Part:1200/binary, Rest/binary>>, State) ->
send_frame(Type, StreamId, <<Part:1200/binary, Rest/binary>>, State)
when byte_size(Rest) > 0 ->
case send_frame0(Type, StreamId, Part, State) of
ok ->
send_frame(Type, StreamId, Rest, State);
Error ->
Error
end;
send_frame(Type, StreamId, Rest, State) ->
send_frame0(Type, StreamId, Rest, State).
send_frame(Type, StreamId, Payload, State) ->
send_frame0(Type, StreamId, Payload, State).
send_frame0(Type, StreamId, Part, #state{server = Server, peer = Peer, cipher_key = Key}) ->
PlainPacket = relay_server_udp_protocol:encode(Type, StreamId, Part),
case chacha20_cipher:encrypt(PlainPacket, Key) of
send_frame0(Type, StreamId, Part, #state{server = Server, peer = Peer}) ->
case relay_server_udp_protocol:encode(Type, StreamId, Part) of
{ok, Packet} ->
logger:error("UDP relay encrypt packet size: ~p", [byte_size(Packet)]),
Server ! {datagram, Peer, Packet},

View File

@ -14,28 +14,52 @@
-define(CONNECTION_CLOSE, 16#FF).
-define(CHACHA20_KEY, <<
16#9F, 16#4A, 16#6C, 16#0D,
16#8B, 16#21, 16#7E, 16#3F,
16#42, 16#D9, 16#AA, 16#78,
16#13, 16#C5, 16#E2, 16#B6,
16#F0, 16#A9, 16#27, 16#BC,
16#6D, 16#31, 16#E8, 16#4C,
16#55, 16#FA, 16#10, 16#2A,
16#7E, 16#9D, 16#3C, 16#BB
>>).
decode(<<Nonce:12/binary, CipherHead:20/binary, Tag:16/binary, Body/binary>>) ->
case chacha20_cipher:decrypt(Nonce, CipherHead, Tag, ?CHACHA20_KEY) of
{ok, PlainText} ->
decode0(PlainText, Body);
Error ->
Error
end.
%%
decode(<<"RKP1", ?VERSION:8, ?CONNECTION_CLOSE:8, 0:16, 0:64, 0:32, _Rest/binary>>) ->
decode0(<<"RKP1", ?VERSION:8, ?CONNECTION_CLOSE:8, 0:16, 0:64, 0:32>>, _Body) ->
{ok, connection_close};
decode(<<"RKP1", ?VERSION:8, TypeNo:8, 0:16, StreamId:64, PayloadLen:32, Rest/binary>>) ->
case {type(TypeNo), byte_size(Rest) >= PayloadLen} of
decode0(<<"RKP1", ?VERSION:8, TypeNo:8, 0:16, StreamId:64, PayloadLen:32>>, Body) when is_binary(Body) ->
case {type(TypeNo), byte_size(Body) >= PayloadLen} of
{undefined, _} ->
{error, unknown_frame_type};
{_, false} ->
{error, truncated_frame};
{Type, true} ->
<<Payload:PayloadLen/binary, _Trailing/binary>> = Rest,
<<Payload:PayloadLen/binary, _Trailing/binary>> = Body,
{ok, {Type, StreamId, Payload}}
end;
decode(Packet) when byte_size(Packet) < ?HEADER_LENGTH ->
decode0(Header, _Body) when byte_size(Header) < ?HEADER_LENGTH ->
{error, frame_too_short};
decode(_Packet) ->
decode0(_Header, _Body) ->
{error, bad_frame_header}.
encode(Type, StreamId, Payload) when is_binary(Payload) ->
encode(Type, StreamId, Payload) when is_atom(Type), is_integer(StreamId), is_binary(Payload) ->
TypeNo = type_no(Type),
PayloadLen = byte_size(Payload),
<<"RKP1", ?VERSION:8, TypeNo:8, 0:16, StreamId:64, PayloadLen:32, Payload/binary>>.
Head = <<"RKP1", ?VERSION:8, TypeNo:8, 0:16, StreamId:64, PayloadLen:32>>,
case chacha20_cipher:encrypt(Head, ?CHACHA20_KEY) of
{ok, EncryptHead} ->
<<EncryptHead/binary, Payload/binary>>;
Error ->
Error
end.
decode_open_request(<<HostLen:16, Host:HostLen/binary, Port:16,
UsernameLen:16, Username:UsernameLen/binary,