From 92a5998868fdf38ad79f0ae0ea9c1bcd2c3c453d Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Wed, 4 Mar 2026 21:23:01 +0800 Subject: [PATCH] support arp request --- apps/sdlan/include/sdlan.hrl | 4 ++ apps/sdlan/src/quic/sdlan_quic_channel.erl | 24 +++++++++- apps/sdlan/src/sdlan_ipaddr.erl | 55 ---------------------- apps/sdlan/src/sdlan_network.erl | 21 +++++++-- apps/sdlan/src/sdlan_util.erl | 46 +++++++++++++++++- 5 files changed, 89 insertions(+), 61 deletions(-) delete mode 100644 apps/sdlan/src/sdlan_ipaddr.erl diff --git a/apps/sdlan/include/sdlan.hrl b/apps/sdlan/include/sdlan.hrl index 5b93a19..ddc76f6 100644 --- a/apps/sdlan/include/sdlan.hrl +++ b/apps/sdlan/include/sdlan.hrl @@ -60,6 +60,10 @@ %% 欢迎消息 -define(PACKET_WELCOME, 16#4F). +%% ARP查询 +-define(PACKET_ARP_REQUEST, 16#50). +-define(PACKET_ARP_RESPONSE, 16#51). + %% 数据转发 -define(PACKET_STUN_DATA, 16#FF). diff --git a/apps/sdlan/src/quic/sdlan_quic_channel.erl b/apps/sdlan/src/quic/sdlan_quic_channel.erl index 177d067..df5f073 100644 --- a/apps/sdlan/src/quic/sdlan_quic_channel.erl +++ b/apps/sdlan/src/quic/sdlan_quic_channel.erl @@ -159,7 +159,7 @@ handle_event(internal, {frame, <>}, initial true = not (sdlan_util:is_multicast_mac(Mac) orelse sdlan_util:is_broadcast_mac(Mac)), MacBinStr = sdlan_util:format_mac(Mac), - IpAddr = sdlan_ipaddr:int_to_ipv4(Ip), + IpAddr = sdlan_util:int_to_ipv4(Ip), Params = #{ <<"network_id">> => NetworkId, <<"client_id">> => ClientId, @@ -251,6 +251,28 @@ handle_event(internal, {frame, <>}, registered, keep_state_and_data end; +%% arp查询 +handle_event(internal, {frame, <>}, registered, #state{stream = Stream, network_id = NetworkId, network_pid = NetworkPid, mac = SrcMac}) when is_pid(NetworkPid) -> + #sdl_arp_request{pkt_id = PktId, target_ip = TargetIp} = sdlan_pb:decode_msg(Body, sdl_arp_request), + case sdlan_network:arp_request(NetworkPid, TargetIp) of + error -> + logger:debug("[sdlan_channel] network: ~p, arp_request target_ip: ~p, mac not found", [NetworkId, TargetIp]), + EmptyResponse = sdlan_pb:encode_msg(#sdl_empty{ + pkt_id = PktId + }), + quic_send(Stream, <>), + keep_state_and_data; + {ok, Mac} -> + logger:debug("[sdlan_channel] network: ~p, arp_request target_ip: ~p, mac: ~p", [NetworkId, sdlan_util:int_to_ipv4(TargetIp), sdlan_util:format_mac(Mac)]), + PeerInfo = sdlan_pb:encode_msg(#sdl_arp_response{ + pkt_id = PktId, + target_ip = TargetIp, + target_mac = Mac + }), + quic_send(Stream, <>), + keep_state_and_data + end; + handle_event(internal, {frame, <>}, registered, #state{stream = Stream, network_pid = NetworkPid}) when is_pid(NetworkPid) -> maybe PolicyRequest = catch sdlan_pb:decode_msg(Body, sdl_policy_request), diff --git a/apps/sdlan/src/sdlan_ipaddr.erl b/apps/sdlan/src/sdlan_ipaddr.erl deleted file mode 100644 index 019f54d..0000000 --- a/apps/sdlan/src/sdlan_ipaddr.erl +++ /dev/null @@ -1,55 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @author anlicheng -%%% @copyright (C) 2024, -%%% @doc -%%% -%%% @end -%%% Created : 27. 3月 2024 17:43 -%%%------------------------------------------------------------------- --module(sdlan_ipaddr). --author("anlicheng"). - -%% API --export([ipv4_to_int/1, int_to_ipv4/1, ips/2, format_ip/1]). --export([ipv6_bytes_to_binary/1]). - -format_ip(Ip) when is_integer(Ip) -> - int_to_ipv4(Ip); -format_ip(Ip) -> - Ip. - --spec ipv4_to_int(Ip :: integer() | binary() | inet:ip4_address()) -> integer(). -ipv4_to_int(Ip) when is_integer(Ip) -> - Ip; -ipv4_to_int({Ip0, Ip1, Ip2, Ip3}) -> - <> = <>, - Ip; -ipv4_to_int(Ip) when is_binary(Ip) -> - Parts0 = binary:split(Ip, <<".">>, [global]), - Parts = lists:map(fun binary_to_integer/1, Parts0), - <> = iolist_to_binary(Parts), - IpInt. - --spec int_to_ipv4(Ip :: integer()) -> binary(). -int_to_ipv4(Ip) when is_integer(Ip) -> - <> = <>, - <<(integer_to_binary(Ip0))/binary, $., (integer_to_binary(Ip1))/binary, $., (integer_to_binary(Ip2))/binary, $., (integer_to_binary(Ip3))/binary>>. - --spec ips(NetAddr :: binary(), MaskLen :: integer()) -> [Ip :: integer()]. -ips(NetAddr, MaskLen) when is_binary(NetAddr), is_integer(MaskLen) -> - Mask = 16#FFFFFFFF bsr MaskLen, - Net0 = ipv4_to_int(NetAddr), - %% 防止网络地址给得不对,比如: "192.168.1.101", - L = 32 - MaskLen, - Net = (Net0 bsr L) bsl L, - lists:map(fun(V) -> Net + V end, lists:seq(1, Mask - 1)). - --spec ipv6_bytes_to_binary(Bytes :: binary()) -> Bin :: binary(). -ipv6_bytes_to_binary(<>) -> - Segments = [integer_to_list(X, 16) || X <- [A, B, C, D, E, F, G, H]], - % 填充每个段以确保是4位 - Padded = [string:pad(S, 4, leading, $0) || S <- Segments], - % 合并成IPv6地址格式,这里没有处理最简化形式的缩写 - iolist_to_binary(lists:flatten(string:join(Padded, ":"))); -ipv6_bytes_to_binary(_) -> - <<"">>. diff --git a/apps/sdlan/src/sdlan_network.erl b/apps/sdlan/src/sdlan_network.erl index d4e801c..53767fe 100644 --- a/apps/sdlan/src/sdlan_network.erl +++ b/apps/sdlan/src/sdlan_network.erl @@ -20,7 +20,7 @@ %% API -export([start_link/2]). --export([get_name/1, get_pid/1, lookup_pid/1, peer_info/3, unregister/3, debug_info/1, get_network_id/1, attach/6]). +-export([get_name/1, get_pid/1, lookup_pid/1, peer_info/3, unregister/3, debug_info/1, get_network_id/1, attach/6, arp_request/2]). -export([forward/5, update_hole/7, disable_client/2, get_channel/2]). -export([test_event/1]). @@ -109,6 +109,10 @@ unregister(Pid, ClientId, Mac) when is_pid(Pid), is_binary(ClientId), is_binary( peer_info(Pid, SrcMac, DstMac) when is_pid(Pid), is_binary(SrcMac), is_binary(DstMac) -> gen_server:call(Pid, {peer_info, SrcMac, DstMac}). +-spec arp_request(Pid :: pid(), TargetIp :: integer()) -> error | {ok, Mac :: binary()}. +arp_request(Pid, TargetIp) when is_pid(Pid), is_integer(TargetIp) -> + gen_server:call(Pid, {arp_request, TargetIp}). + -spec forward(pid(), Sock :: any(), SrcMac :: binary(), DstMac :: binary(), Packet :: binary()) -> no_return(). forward(Pid, Sock, SrcMac, DstMac, Packet) when is_pid(Pid), is_binary(SrcMac), is_binary(DstMac), is_binary(Packet) -> gen_server:cast(Pid, {forward, Sock, SrcMac, DstMac, Packet}). @@ -183,7 +187,7 @@ handle_call({attach, ChannelPid, ClientId, Mac, Ip, Hostname}, _From, State = #state{network_id = NetworkId, domain = Domain, endpoints = Endpoints, aes_key = AesKey}) -> %% 分配ip地址的时候,以mac地址为唯一基准 logger:debug("[sdlan_network] alloc_ip, network_id: ~p, client_id: ~p, mac: ~p, ip_addr: ~p", - [NetworkId, ClientId, sdlan_util:format_mac(Mac), sdlan_ipaddr:int_to_ipv4(Ip)]), + [NetworkId, ClientId, sdlan_util:format_mac(Mac), sdlan_util:int_to_ipv4(Ip)]), %% 添加域名->ip的映射关系 sdlan_hostname_regedit:insert(Hostname, Domain, Ip), @@ -246,7 +250,7 @@ handle_call({peer_info, SrcMac, DstMac}, _From, State = #state{endpoints = Endpo {ok, #endpoint{hole = #hole{peer = {SrcNatIp, SrcNatPort}, nat_type = SrcNatType}, v6_info = SrcV6Info}} ?= maps:find(SrcMac, Endpoints), RegisterEvent = sdlan_pb:encode_msg(#sdl_send_register_event { dst_mac = SrcMac, - nat_ip = sdlan_ipaddr:ipv4_to_int(SrcNatIp), + nat_ip = sdlan_util:ipv4_to_int(SrcNatIp), nat_type = SrcNatType, nat_port = SrcNatPort, v6_info = SrcV6Info @@ -258,6 +262,15 @@ handle_call({peer_info, SrcMac, DstMac}, _From, State = #state{endpoints = Endpo {reply, error, State} end; +%% arp查询 +handle_call({arp_request, TargetIp}, _From, State = #state{endpoints = Endpoints}) -> + case search_endpoint(fun(_, #endpoint{ip = Ip0}) -> Ip0 =:= TargetIp end, Endpoints) of + error -> + {reply, error, State}; + {ok, Mac, _} -> + {reply, {ok, Mac}, State} + end; + handle_call(debug_info, _From, State = #state{network_id = NetworkId, ipaddr = IpAddr, mask_len = MaskLen, owner_id = OwnerId, endpoints = Endpoints}) -> Reply = #{ <<"network_id">> => NetworkId, @@ -467,7 +480,7 @@ format_endpoint({Mac, #endpoint{client_id = ClientId, ip = Ip, hole = #hole{peer #{ client_id => ClientId, mac => sdlan_util:format_mac(Mac), - ip => sdlan_ipaddr:int_to_ipv4(Ip), + ip => sdlan_util:int_to_ipv4(Ip), hole_map => HoleMap, v6_info => V6InfoMap }. diff --git a/apps/sdlan/src/sdlan_util.erl b/apps/sdlan/src/sdlan_util.erl index 874c3d9..2d42bda 100644 --- a/apps/sdlan/src/sdlan_util.erl +++ b/apps/sdlan/src/sdlan_util.erl @@ -13,6 +13,8 @@ -export([rand_byte/1, md5/1, format_mac/1, assert_call/2]). -export([json_data/1, json_error/2]). -export([is_broadcast_mac/1, is_multicast_mac/1]). +-export([ipv4_to_int/1, int_to_ipv4/1, ips/2, format_ip/1]). +-export([ipv6_bytes_to_binary/1]). -spec format_mac(Mac :: binary()) -> binary(). format_mac(Mac) when is_binary(Mac) -> @@ -71,4 +73,46 @@ is_broadcast_mac(Mac) when is_binary(Mac) -> -spec is_multicast_mac(Mac :: binary()) -> boolean(). is_multicast_mac(Mac) when is_binary(Mac) -> - binary:part(Mac, 0, 3) =:= <<16#01,16#00,16#5E>>. \ No newline at end of file + binary:part(Mac, 0, 3) =:= <<16#01,16#00,16#5E>>. + + +format_ip(Ip) when is_integer(Ip) -> + int_to_ipv4(Ip); +format_ip(Ip) -> + Ip. + +-spec ipv4_to_int(Ip :: integer() | binary() | inet:ip4_address()) -> integer(). +ipv4_to_int(Ip) when is_integer(Ip) -> + Ip; +ipv4_to_int({Ip0, Ip1, Ip2, Ip3}) -> + <> = <>, + Ip; +ipv4_to_int(Ip) when is_binary(Ip) -> + Parts0 = binary:split(Ip, <<".">>, [global]), + Parts = lists:map(fun binary_to_integer/1, Parts0), + <> = iolist_to_binary(Parts), + IpInt. + +-spec int_to_ipv4(Ip :: integer()) -> binary(). +int_to_ipv4(Ip) when is_integer(Ip) -> + <> = <>, + <<(integer_to_binary(Ip0))/binary, $., (integer_to_binary(Ip1))/binary, $., (integer_to_binary(Ip2))/binary, $., (integer_to_binary(Ip3))/binary>>. + +-spec ips(NetAddr :: binary(), MaskLen :: integer()) -> [Ip :: integer()]. +ips(NetAddr, MaskLen) when is_binary(NetAddr), is_integer(MaskLen) -> + Mask = 16#FFFFFFFF bsr MaskLen, + Net0 = ipv4_to_int(NetAddr), + %% 防止网络地址给得不对,比如: "192.168.1.101", + L = 32 - MaskLen, + Net = (Net0 bsr L) bsl L, + lists:map(fun(V) -> Net + V end, lists:seq(1, Mask - 1)). + +-spec ipv6_bytes_to_binary(Bytes :: binary()) -> Bin :: binary(). +ipv6_bytes_to_binary(<>) -> + Segments = [integer_to_list(X, 16) || X <- [A, B, C, D, E, F, G, H]], + % 填充每个段以确保是4位 + Padded = [string:pad(S, 4, leading, $0) || S <- Segments], + % 合并成IPv6地址格式,这里没有处理最简化形式的缩写 + iolist_to_binary(lists:flatten(string:join(Padded, ":"))); +ipv6_bytes_to_binary(_) -> + <<"">>. \ No newline at end of file