From 9d5bc114eda76f4c90bbafffee9d9c9152c314ee Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Sat, 27 Jun 2026 15:32:23 +0800 Subject: [PATCH] fix --- README.md | 20 ++++- .../src/relay_server_udp_handler.erl | 76 +++++++++++++++++-- .../src/relay_server_udp_listener.erl | 3 +- .../src/relay_server_udp_protocol.erl | 64 +++++++++------- config/sys.config | 4 + 5 files changed, 129 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 12f4816..5ba6207 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,20 @@ Each UDP datagram is one `RKP1` frame: stream_id(8), payload_len(4), payload(payload_len) Frame types are `open = 1`, `data = 2`, `close = 3`, and `error = 4`. -`open` payload is RelayKit JSON: `host`, `port`, `useTLS`, and `method`. -The server is a transparent TCP relay: `useTLS` is parsed for client -compatibility, but the upstream connection is always raw TCP. +`open` payload is binary encoded as: + + host_len(2), host(host_len), port(2), + username_len(2), username(username_len), + password_len(2), password(password_len) + +Integer fields are unsigned big-endian values. `host`, `username`, and +`password` are binary strings. The server is a transparent TCP relay, so TLS +negotiation, HTTP methods, and request bytes are carried inside subsequent +`data` frames without being interpreted by the server. + +`username` and `password` are checked against the `relay_server` application +`users` config: + + {users, [ + {admin, "password"} + ]} diff --git a/apps/relay_server/src/relay_server_udp_handler.erl b/apps/relay_server/src/relay_server_udp_handler.erl index fb9a431..0055dbf 100644 --- a/apps/relay_server/src/relay_server_udp_handler.erl +++ b/apps/relay_server/src/relay_server_udp_handler.erl @@ -7,7 +7,7 @@ -behaviour(gen_server). --export([start_link/4]). +-export([start_link/4, start_link/5]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). @@ -27,20 +27,25 @@ peer :: {inet:ip_address(), inet:port_number()}, idle_timeout :: timeout(), connect_timeout :: timeout(), + users = #{} :: #{binary() => binary()}, streams = #{} :: #{non_neg_integer() => stream()}, sockets = #{} :: #{inet:socket() => non_neg_integer()} }). start_link(Transport, Peer, IdleTimeout, ConnectTimeout) -> - gen_server:start_link(?MODULE, [Transport, Peer, IdleTimeout, ConnectTimeout], []). + start_link(Transport, Peer, IdleTimeout, ConnectTimeout, load_users()). -init([{udp, Server, _Sock}, Peer, IdleTimeout, ConnectTimeout]) -> +start_link(Transport, Peer, IdleTimeout, ConnectTimeout, Users) -> + gen_server:start_link(?MODULE, [Transport, Peer, IdleTimeout, ConnectTimeout, Users], []). + +init([{udp, Server, _Sock}, Peer, IdleTimeout, ConnectTimeout, Users]) -> logger:debug("UDP peer connected: ~s", [esockd:format(Peer)]), {ok, #state{ server = Server, peer = Peer, idle_timeout = IdleTimeout, - connect_timeout = ConnectTimeout + connect_timeout = ConnectTimeout, + users = normalize_users(Users) }, IdleTimeout}. handle_call(_Request, _From, State = #state{idle_timeout = IdleTimeout}) -> @@ -54,12 +59,14 @@ handle_info({datagram, Server, <<"stop">>}, State = #state{server = Server, peer {stop, normal, State}; handle_info({datagram, Server, Packet}, State = #state{server = Server}) -> handle_datagram(Packet, State); + handle_info({tcp, Socket, Data}, State) -> handle_remote_data(Socket, Data, State); handle_info({tcp_closed, Socket}, State) -> handle_remote_closed(Socket, State); handle_info({tcp_error, Socket, Reason}, State) -> handle_remote_error(Socket, Reason, State); + handle_info(timeout, State = #state{peer = Peer}) -> logger:debug("UDP peer idle timeout: ~s", [esockd:format(Peer)]), {stop, normal, State}; @@ -97,7 +104,15 @@ handle_open(StreamId, Payload, State = #state{streams = Streams}) -> false -> case relay_server_udp_protocol:decode_open_request(Payload) of {ok, Request} -> - open_stream(StreamId, Request, State); + case authenticate(Request, State#state.users) of + ok -> + open_stream(StreamId, Request, State); + error -> + logger:warning("UDP relay stream ~p authentication failed from ~s", + [StreamId, esockd:format(State#state.peer)]), + send_error(StreamId, <<"authentication failed">>, State), + State + end; {error, Reason} -> send_error(StreamId, format_error(Reason), State), State @@ -225,3 +240,54 @@ format_error(Reason) when is_binary(Reason) -> Reason; format_error(Reason) -> iolist_to_binary(io_lib:format("~p", [Reason])). + +load_users() -> + application:get_env(relay_server, users, []). + +normalize_users(Users) when is_list(Users) -> + lists:foldl(fun normalize_user/2, #{}, Users); +normalize_users(_Users) -> + #{}. + +normalize_user({Username0, Password0}, Users) -> + case {to_credential_binary(Username0), to_credential_binary(Password0)} of + {{ok, Username}, {ok, Password}} when byte_size(Username) > 0 -> + maps:put(Username, Password, Users); + _ -> + Users + end; +normalize_user(_User, Users) -> + Users. + +to_credential_binary(Value) when is_binary(Value) -> + {ok, Value}; +to_credential_binary(Value) when is_atom(Value) -> + {ok, atom_to_binary(Value, utf8)}; +to_credential_binary(Value) when is_list(Value) -> + try unicode:characters_to_binary(Value) of + Binary when is_binary(Binary) -> {ok, Binary}; + _Other -> error + catch + _Class:_Reason -> error + end; +to_credential_binary(_Value) -> + error. + +authenticate(#{username := Username, password := Password}, Users) -> + case maps:get(Username, Users, undefined) of + undefined -> + error; + ExpectedPassword -> + case secure_equal(Password, ExpectedPassword) of + true -> ok; + false -> error + end + end. + +secure_equal(Left, Right) when is_binary(Left), is_binary(Right) -> + (byte_size(Left) =:= byte_size(Right)) andalso secure_equal(Left, Right, 0) =:= 0. + +secure_equal(<>, <>, Diff) -> + secure_equal(LRest, RRest, Diff bor (L bxor R)); +secure_equal(<<>>, <<>>, Diff) -> + Diff. diff --git a/apps/relay_server/src/relay_server_udp_listener.erl b/apps/relay_server/src/relay_server_udp_listener.erl index 8e8b088..9488b1f 100644 --- a/apps/relay_server/src/relay_server_udp_listener.erl +++ b/apps/relay_server/src/relay_server_udp_listener.erl @@ -43,6 +43,7 @@ init([]) -> IdleTimeout = proplists:get_value(idle_timeout, Props, ?DEFAULT_IDLE_TIMEOUT), ConnectTimeout = proplists:get_value(connect_timeout, Props, ?DEFAULT_CONNECT_TIMEOUT), + Users = application:get_env(relay_server, users, []), UdpOptions = proplists:get_value(udp_options, Props, ?DEFAULT_UDP_OPTIONS), MaxConnections = proplists:get_value(max_connections, Props, ?DEFAULT_MAX_CONNECTIONS), AccessRules = proplists:get_value(access_rules, Props, [{allow, all}]), @@ -54,7 +55,7 @@ init([]) -> ], Opts = maybe_add(max_conn_rate, Props, BaseOpts), - MFA = {relay_server_udp_handler, start_link, [IdleTimeout, ConnectTimeout]}, + MFA = {relay_server_udp_handler, start_link, [IdleTimeout, ConnectTimeout, Users]}, case esockd:open_udp(Listener, ListenOn, Opts, MFA) of {ok, Pid} -> logger:info("UDP listener ~p started on ~s", diff --git a/apps/relay_server/src/relay_server_udp_protocol.erl b/apps/relay_server/src/relay_server_udp_protocol.erl index 826c981..442f47d 100644 --- a/apps/relay_server/src/relay_server_udp_protocol.erl +++ b/apps/relay_server/src/relay_server_udp_protocol.erl @@ -5,7 +5,8 @@ -module(relay_server_udp_protocol). --export([decode/1, encode/3, decode_open_request/1]). +-export([decode/1, encode/3, decode_open_request/1, encode_open_request/2, + encode_open_request/4]). -define(MAGIC, <<"RKP1">>). -define(VERSION, 1). @@ -31,34 +32,39 @@ encode(Type, StreamId, Payload) when is_binary(Payload) -> PayloadLen = byte_size(Payload), <<"RKP1", ?VERSION:8, TypeNo:8, 0:16, StreamId:64, PayloadLen:32, Payload/binary>>. -decode_open_request(Payload) -> - try json:decode(Payload) of - #{ - <<"host">> := Host, - <<"port">> := Port, - <<"useTLS">> := UseTLS, - <<"method">> := Method - } when - is_binary(Host), - byte_size(Host) > 0, - is_integer(Port), - Port > 0, - Port =< 65535, - is_boolean(UseTLS), - is_binary(Method) - -> - {ok, #{ - host => Host, - port => Port, - use_tls => UseTLS, - method => Method - }}; - _Other -> - {error, invalid_open_request} - catch - _Class:_Reason -> - {error, invalid_open_request} - end. +decode_open_request(<>) + when HostLen > 0, Port > 0 -> + {ok, #{ + host => Host, + port => Port, + username => Username, + password => Password + }}; +decode_open_request(_Payload) -> + {error, invalid_open_request}. + +encode_open_request(Host, Port) -> + encode_open_request(Host, Port, <<>>, <<>>). + +encode_open_request(Host, Port, Username, Password) when + is_binary(Host), + byte_size(Host) > 0, + byte_size(Host) =< 65535, + is_integer(Port), + Port > 0, + Port =< 65535, + is_binary(Username), + byte_size(Username) =< 65535, + is_binary(Password), + byte_size(Password) =< 65535 -> + HostLen = byte_size(Host), + UsernameLen = byte_size(Username), + PasswordLen = byte_size(Password), + <>. type(1) -> open; type(2) -> data; diff --git a/config/sys.config b/config/sys.config index af7d0d2..1340700 100644 --- a/config/sys.config +++ b/config/sys.config @@ -8,6 +8,10 @@ {connect_timeout, 5000}, {max_connections, 1024}, {udp_options, [binary, {reuseaddr, true}]} + ]}, + + {users, [ + {admin, "v7@Qm!2z#R8$pL4^xT?K"} ]} ]},