From 22db3e1f46662657c786690f9f516ce929c6d8f5 Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Wed, 1 Jul 2026 16:48:45 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=8D=8F=E8=AE=AE=EF=BC=8C?= =?UTF-8?q?=E5=8F=AA=E5=8A=A0=E5=AF=86=E5=A4=B4=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++- apps/relay_server/src/chacha20_cipher.erl | 66 ++++++------------- .../src/relay_server_udp_handler.erl | 38 ++--------- .../src/relay_server_udp_protocol.erl | 40 ++++++++--- 4 files changed, 65 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index 6eaf9f5..e5945b1 100644 --- a/README.md +++ b/README.md @@ -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), diff --git a/apps/relay_server/src/chacha20_cipher.erl b/apps/relay_server/src/chacha20_cipher.erl index 1cb50a0..d39f312 100644 --- a/apps/relay_server/src/chacha20_cipher.erl +++ b/apps/relay_server/src/chacha20_cipher.erl @@ -1,62 +1,38 @@ %%%------------------------------------------------------------------- %% @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 -> - Nonce = crypto:strong_rand_bytes(?NONCE_BYTES), - try crypto:crypto_one_time_aead( - chacha20_poly1305, Key, Nonce, PlainText, ?AAD, true - ) of - {CipherText, Tag} -> - {ok, <>} - catch - _Class:_Reason -> - {error, crypto_failed} - end; - false -> - {error, invalid_key} +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 + {CipherText, Tag} -> + {ok, <>} + catch + _Class:_Reason -> + {error, crypto_failed} 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 -> - <> = Combined, - CipherTextBytes = byte_size(Rest) - ?TAG_BYTES, - <> = Rest, - try crypto:crypto_one_time_aead( - chacha20_poly1305, Key, Nonce, CipherText, ?AAD, Tag, false - ) of - error -> - {error, authentication_failed}; - PlainText -> - {ok, PlainText} - 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. +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 -> + {ok, PlainText} + catch + _Class:_Reason -> + {error, crypto_failed} + end. \ No newline at end of file diff --git a/apps/relay_server/src/relay_server_udp_handler.erl b/apps/relay_server/src/relay_server_udp_handler.erl index bef5261..89835af 100644 --- a/apps/relay_server/src/relay_server_udp_handler.erl +++ b/apps/relay_server/src/relay_server_udp_handler.erl @@ -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, <>, State) -> +send_frame(Type, StreamId, <>, 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}, diff --git a/apps/relay_server/src/relay_server_udp_protocol.erl b/apps/relay_server/src/relay_server_udp_protocol.erl index 75c6c45..2a13630 100644 --- a/apps/relay_server/src/relay_server_udp_protocol.erl +++ b/apps/relay_server/src/relay_server_udp_protocol.erl @@ -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(<>) -> + 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} -> - <> = Rest, + <> = 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} -> + <>; + Error -> + Error + end. decode_open_request(<