This commit is contained in:
anlicheng 2026-03-27 11:29:22 +08:00
parent e0afccfced
commit bf9be7ebc5
5 changed files with 928 additions and 1239 deletions

View File

@ -35,101 +35,89 @@
}). }).
-endif. -endif.
-ifndef('SDL_EMPTY_PB_H').
-define('SDL_EMPTY_PB_H', true).
-record(sdl_empty,
{pkt_id = 0 :: non_neg_integer() | undefined % = 1, optional, 32 bits
}).
-endif.
-ifndef('SDL_REGISTER_SUPER_PB_H'). -ifndef('SDL_REGISTER_SUPER_PB_H').
-define('SDL_REGISTER_SUPER_PB_H', true). -define('SDL_REGISTER_SUPER_PB_H', true).
-record(sdl_register_super, -record(sdl_register_super,
{pkt_id = 0 :: non_neg_integer() | undefined, % = 1, optional, 32 bits {client_id = <<>> :: unicode:chardata() | undefined, % = 1, optional
client_id = <<>> :: unicode:chardata() | undefined, % = 2, optional network_id = 0 :: non_neg_integer() | undefined, % = 2, optional, 32 bits
network_id = 0 :: non_neg_integer() | undefined, % = 3, optional, 32 bits mac = <<>> :: iodata() | undefined, % = 3, optional
mac = <<>> :: iodata() | undefined, % = 4, optional ip = 0 :: non_neg_integer() | undefined, % = 4, optional, 32 bits
ip = 0 :: non_neg_integer() | undefined, % = 5, optional, 32 bits mask_len = 0 :: non_neg_integer() | undefined, % = 5, optional, 32 bits
mask_len = 0 :: non_neg_integer() | undefined, % = 6, optional, 32 bits hostname = <<>> :: unicode:chardata() | undefined, % = 6, optional
hostname = <<>> :: unicode:chardata() | undefined, % = 7, optional pub_key = <<>> :: unicode:chardata() | undefined, % = 7, optional
pub_key = <<>> :: unicode:chardata() | undefined, % = 8, optional access_token = <<>> :: unicode:chardata() | undefined % = 8, optional
access_token = <<>> :: unicode:chardata() | undefined % = 9, optional
}). }).
-endif. -endif.
-ifndef('SDL_REGISTER_SUPER_ACK_PB_H'). -ifndef('SDL_REGISTER_SUPER_ACK_PB_H').
-define('SDL_REGISTER_SUPER_ACK_PB_H', true). -define('SDL_REGISTER_SUPER_ACK_PB_H', true).
-record(sdl_register_super_ack, -record(sdl_register_super_ack,
{pkt_id = 0 :: non_neg_integer() | undefined, % = 1, optional, 32 bits {algorithm = <<>> :: unicode:chardata() | undefined, % = 1, optional
algorithm = <<>> :: unicode:chardata() | undefined, % = 2, optional key = <<>> :: iodata() | undefined, % = 2, optional
key = <<>> :: iodata() | undefined, % = 3, optional region_id = 0 :: non_neg_integer() | undefined, % = 3, optional, 32 bits
region_id = 0 :: non_neg_integer() | undefined, % = 4, optional, 32 bits session_token = <<>> :: iodata() | undefined % = 4, optional
session_token = <<>> :: iodata() | undefined % = 5, optional
}). }).
-endif. -endif.
-ifndef('SDL_REGISTER_SUPER_NAK_PB_H'). -ifndef('SDL_REGISTER_SUPER_NAK_PB_H').
-define('SDL_REGISTER_SUPER_NAK_PB_H', true). -define('SDL_REGISTER_SUPER_NAK_PB_H', true).
-record(sdl_register_super_nak, -record(sdl_register_super_nak,
{pkt_id = 0 :: non_neg_integer() | undefined, % = 1, optional, 32 bits {error_code = 0 :: non_neg_integer() | undefined, % = 1, optional, 32 bits
error_code = 0 :: non_neg_integer() | undefined, % = 2, optional, 32 bits error_message = <<>> :: unicode:chardata() | undefined % = 2, optional
error_message = <<>> :: unicode:chardata() | undefined % = 3, optional
}). }).
-endif. -endif.
-ifndef('SDL_QUERY_INFO_PB_H'). -ifndef('SDL_QUERY_INFO_PB_H').
-define('SDL_QUERY_INFO_PB_H', true). -define('SDL_QUERY_INFO_PB_H', true).
-record(sdl_query_info, -record(sdl_query_info,
{pkt_id = 0 :: non_neg_integer() | undefined, % = 1, optional, 32 bits {dst_mac = <<>> :: iodata() | undefined % = 1, optional
dst_mac = <<>> :: iodata() | undefined % = 2, optional
}). }).
-endif. -endif.
-ifndef('SDL_PEER_INFO_PB_H'). -ifndef('SDL_PEER_INFO_PB_H').
-define('SDL_PEER_INFO_PB_H', true). -define('SDL_PEER_INFO_PB_H', true).
-record(sdl_peer_info, -record(sdl_peer_info,
{pkt_id = 0 :: non_neg_integer() | undefined, % = 1, optional, 32 bits {dst_mac = <<>> :: iodata() | undefined, % = 1, optional
dst_mac = <<>> :: iodata() | undefined, % = 2, optional v4_info :: sdlan_pb:sdl_v4_info() | undefined, % = 2, optional
v4_info = undefined :: sdlan_pb:sdl_v4_info() | undefined, % = 3, optional v6_info :: sdlan_pb:sdl_v6_info() | undefined % = 3, optional
v6_info :: sdlan_pb:sdl_v6_info() | undefined % = 4, optional
}). }).
-endif. -endif.
-ifndef('SDL_ARP_REQUEST_PB_H'). -ifndef('SDL_ARP_REQUEST_PB_H').
-define('SDL_ARP_REQUEST_PB_H', true). -define('SDL_ARP_REQUEST_PB_H', true).
-record(sdl_arp_request, -record(sdl_arp_request,
{pkt_id = 0 :: non_neg_integer() | undefined, % = 1, optional, 32 bits {target_ip = 0 :: non_neg_integer() | undefined, % = 1, optional, 32 bits
target_ip = 0 :: non_neg_integer() | undefined % = 2, optional, 32 bits origin_ip = 0 :: non_neg_integer() | undefined, % = 2, optional, 32 bits
context = <<>> :: iodata() | undefined % = 3, optional
}). }).
-endif. -endif.
-ifndef('SDL_ARP_RESPONSE_PB_H'). -ifndef('SDL_ARP_RESPONSE_PB_H').
-define('SDL_ARP_RESPONSE_PB_H', true). -define('SDL_ARP_RESPONSE_PB_H', true).
-record(sdl_arp_response, -record(sdl_arp_response,
{pkt_id = 0 :: non_neg_integer() | undefined, % = 1, optional, 32 bits {target_ip = 0 :: non_neg_integer() | undefined, % = 1, optional, 32 bits
target_ip = 0 :: non_neg_integer() | undefined, % = 2, optional, 32 bits target_mac = <<>> :: iodata() | undefined, % = 2, optional
target_mac = <<>> :: iodata() | undefined % = 3, optional origin_ip = 0 :: non_neg_integer() | undefined, % = 3, optional, 32 bits
context = <<>> :: iodata() | undefined % = 4, optional
}). }).
-endif. -endif.
-ifndef('SDL_POLICY_REQUEST_PB_H'). -ifndef('SDL_POLICY_REQUEST_PB_H').
-define('SDL_POLICY_REQUEST_PB_H', true). -define('SDL_POLICY_REQUEST_PB_H', true).
-record(sdl_policy_request, -record(sdl_policy_request,
{pkt_id = 0 :: non_neg_integer() | undefined, % = 1, optional, 32 bits {src_identity_id = 0 :: non_neg_integer() | undefined, % = 1, optional, 32 bits
src_identity_id = 0 :: non_neg_integer() | undefined, % = 2, optional, 32 bits dst_identity_id = 0 :: non_neg_integer() | undefined, % = 2, optional, 32 bits
dst_identity_id = 0 :: non_neg_integer() | undefined, % = 3, optional, 32 bits version = 0 :: non_neg_integer() | undefined % = 3, optional, 32 bits
version = 0 :: non_neg_integer() | undefined % = 4, optional, 32 bits
}). }).
-endif. -endif.
-ifndef('SDL_POLICY_RESPONSE_PB_H'). -ifndef('SDL_POLICY_RESPONSE_PB_H').
-define('SDL_POLICY_RESPONSE_PB_H', true). -define('SDL_POLICY_RESPONSE_PB_H', true).
-record(sdl_policy_response, -record(sdl_policy_response,
{pkt_id = 0 :: non_neg_integer() | undefined, % = 1, optional, 32 bits {src_identity_id = 0 :: non_neg_integer() | undefined, % = 1, optional, 32 bits
src_identity_id = 0 :: non_neg_integer() | undefined, % = 2, optional, 32 bits dst_identity_id = 0 :: non_neg_integer() | undefined, % = 2, optional, 32 bits
dst_identity_id = 0 :: non_neg_integer() | undefined, % = 3, optional, 32 bits version = 0 :: non_neg_integer() | undefined, % = 3, optional, 32 bits
version = 0 :: non_neg_integer() | undefined, % = 4, optional, 32 bits rules = <<>> :: iodata() | undefined % = 4, optional
rules = <<>> :: iodata() | undefined % = 5, optional
}). }).
-endif. -endif.
@ -162,21 +150,20 @@
-ifndef('SDL_STUN_REQUEST_PB_H'). -ifndef('SDL_STUN_REQUEST_PB_H').
-define('SDL_STUN_REQUEST_PB_H', true). -define('SDL_STUN_REQUEST_PB_H', true).
-record(sdl_stun_request, -record(sdl_stun_request,
{cookie = 0 :: non_neg_integer() | undefined, % = 1, optional, 32 bits {client_id = <<>> :: unicode:chardata() | undefined, % = 1, optional
client_id = <<>> :: unicode:chardata() | undefined, % = 2, optional network_id = 0 :: non_neg_integer() | undefined, % = 2, optional, 32 bits
network_id = 0 :: non_neg_integer() | undefined, % = 3, optional, 32 bits mac = <<>> :: iodata() | undefined, % = 3, optional
mac = <<>> :: iodata() | undefined, % = 4, optional ip = 0 :: non_neg_integer() | undefined, % = 4, optional, 32 bits
ip = 0 :: non_neg_integer() | undefined, % = 5, optional, 32 bits nat_type = 0 :: non_neg_integer() | undefined, % = 5, optional, 32 bits
nat_type = 0 :: non_neg_integer() | undefined, % = 6, optional, 32 bits v6_info :: sdlan_pb:sdl_v6_info() | undefined, % = 6, optional
v6_info :: sdlan_pb:sdl_v6_info() | undefined, % = 7, optional session_token = <<>> :: iodata() | undefined % = 7, optional
session_token = <<>> :: iodata() | undefined % = 8, optional
}). }).
-endif. -endif.
-ifndef('SDL_STUN_REPLY_PB_H'). -ifndef('SDL_STUN_REPLY_PB_H').
-define('SDL_STUN_REPLY_PB_H', true). -define('SDL_STUN_REPLY_PB_H', true).
-record(sdl_stun_reply, -record(sdl_stun_reply,
{cookie = 0 :: non_neg_integer() | undefined % = 1, optional, 32 bits {
}). }).
-endif. -endif.

View File

@ -151,7 +151,7 @@ handle_event(info, {quic, Data, Stream, _Props}, _StateName, State = #state{stre
%% %%
handle_event(internal, {frame, <<?PACKET_REGISTER_SUPER, Body/binary>>}, initialized, State=#state{stream = Stream}) -> handle_event(internal, {frame, <<?PACKET_REGISTER_SUPER, Body/binary>>}, initialized, State=#state{stream = Stream}) ->
#sdl_register_super{ #sdl_register_super{
pkt_id = PktId, client_id = ClientId, network_id = NetworkId, mac = Mac, ip = Ip, mask_len = MaskLen, client_id = ClientId, network_id = NetworkId, mac = Mac, ip = Ip, mask_len = MaskLen,
hostname = HostName, pub_key = PubKey, access_token = AccessToken} = sdlan_pb:decode_msg(Body, sdl_register_super), hostname = HostName, pub_key = PubKey, access_token = AccessToken} = sdlan_pb:decode_msg(Body, sdl_register_super),
true = (Mac =/= <<>> andalso PubKey =/= <<>> andalso ClientId =/= <<>>), true = (Mac =/= <<>> andalso PubKey =/= <<>> andalso ClientId =/= <<>>),
@ -181,7 +181,6 @@ handle_event(internal, {frame, <<?PACKET_REGISTER_SUPER, Body/binary>>}, initial
{ok, Algorithm, Key, RegionId, SessionToken} = sdlan_network:attach(NetworkPid, self(), ClientId, Mac, Ip, HostName), {ok, Algorithm, Key, RegionId, SessionToken} = sdlan_network:attach(NetworkPid, self(), ClientId, Mac, Ip, HostName),
RsaPubKey = sdlan_cipher:rsa_pem_decode(PubKey), RsaPubKey = sdlan_cipher:rsa_pem_decode(PubKey),
RegisterSuperAck = sdlan_pb:encode_msg(#sdl_register_super_ack { RegisterSuperAck = sdlan_pb:encode_msg(#sdl_register_super_ack {
pkt_id = PktId,
algorithm = Algorithm, algorithm = Algorithm,
key = rsa_encode(Key, RsaPubKey), key = rsa_encode(Key, RsaPubKey),
region_id = RegionId, region_id = RegionId,
@ -210,28 +209,30 @@ handle_event(internal, {frame, <<?PACKET_REGISTER_SUPER, Body/binary>>}, initial
{next_state, registered, State#state{network_id = NetworkId, network_pid = NetworkPid, client_id = ClientId, mac = Mac, ip = Ip, offline_cb = OfflineCb}}; {next_state, registered, State#state{network_id = NetworkId, network_pid = NetworkPid, client_id = ClientId, mac = Mac, ip = Ip, offline_cb = OfflineCb}};
undefined -> undefined ->
logger:warning("[sdlan_quic_channel] client_id: ~p, register get error: network not found", [ClientId]), logger:warning("[sdlan_quic_channel] client_id: ~p, register get error: network not found", [ClientId]),
quic_send(Stream, register_nak_reply(PktId, ?NAK_INTERNAL_FAULT, <<"Internal Error">>)), quic_send(Stream, register_nak_reply(?NAK_INTERNAL_FAULT, <<"Internal Error">>)),
{stop, normal, State} {stop, normal, State}
end; end;
{ok, #{<<"error">> := #{<<"code">> := Code, <<"message">> := Message}}} -> {ok, #{<<"error">> := #{<<"code">> := Code, <<"message">> := Message}}} ->
logger:warning("[sdlan_quic_channel] network_id: ~p, client_id: ~p, register get error: ~ts, error_code: ~p", [NetworkId, ClientId, Message, Code]), logger:warning("[sdlan_quic_channel] network_id: ~p, client_id: ~p, register get error: ~ts, error_code: ~p", [NetworkId, ClientId, Message, Code]),
quic_send(Stream, register_nak_reply(PktId, Code, Message)), quic_send(Stream, register_nak_reply(Code, Message)),
{stop, normal, State}; {stop, normal, State};
{error, Reason} -> {error, Reason} ->
logger:warning("[sdlan_quic_channel] network_id: ~p, client_id: ~p, register get error: ~p", [NetworkId, ClientId, Reason]), logger:warning("[sdlan_quic_channel] network_id: ~p, client_id: ~p, register get error: ~p", [NetworkId, ClientId, Reason]),
quic_send(Stream, register_nak_reply(PktId, ?NAK_NETWORK_FAULT, <<"Network Error">>)), quic_send(Stream, register_nak_reply(?NAK_NETWORK_FAULT, <<"Network Error">>)),
{stop, normal, State} {stop, normal, State}
end; end;
handle_event(internal, {frame, <<?PACKET_QUERY_INFO, Body/binary>>}, registered, #state{stream = Stream, network_pid = NetworkPid, mac = SrcMac}) when is_pid(NetworkPid) -> handle_event(internal, {frame, <<?PACKET_QUERY_INFO, Body/binary>>}, registered, #state{stream = Stream, network_pid = NetworkPid, mac = SrcMac}) when is_pid(NetworkPid) ->
#sdl_query_info{pkt_id = PktId, dst_mac = DstMac} = sdlan_pb:decode_msg(Body, sdl_query_info), #sdl_query_info{dst_mac = DstMac} = sdlan_pb:decode_msg(Body, sdl_query_info),
case sdlan_network:peer_info(NetworkPid, SrcMac, DstMac) of case sdlan_network:peer_info(NetworkPid, SrcMac, DstMac) of
error -> error ->
logger:debug("[sdlan_channel] query_info src_mac is: ~p, dst_mac: ~p, nat_peer not found", logger:debug("[sdlan_channel] query_info src_mac is: ~p, dst_mac: ~p, nat_peer not found",
[sdlan_util:format_mac(SrcMac), sdlan_util:format_mac(DstMac)]), [sdlan_util:format_mac(SrcMac), sdlan_util:format_mac(DstMac)]),
EmptyResponse = sdlan_pb:encode_msg(#sdl_empty{ EmptyResponse = sdlan_pb:encode_msg(#sdl_peer_info{
pkt_id = PktId dst_mac = DstMac,
v4_info = undefined,
v6_info = undefined
}), }),
quic_send(Stream, <<?PACKET_PEER_INFO, EmptyResponse/binary>>), quic_send(Stream, <<?PACKET_PEER_INFO, EmptyResponse/binary>>),
keep_state_and_data; keep_state_and_data;
@ -240,7 +241,6 @@ handle_event(internal, {frame, <<?PACKET_QUERY_INFO, Body/binary>>}, registered,
[sdlan_util:format_mac(SrcMac), sdlan_util:format_mac(DstMac), NatPeer]), [sdlan_util:format_mac(SrcMac), sdlan_util:format_mac(DstMac), NatPeer]),
PeerInfo = sdlan_pb:encode_msg(#sdl_peer_info{ PeerInfo = sdlan_pb:encode_msg(#sdl_peer_info{
pkt_id = PktId,
dst_mac = DstMac, dst_mac = DstMac,
v4_info = #sdl_v4_info { v4_info = #sdl_v4_info {
port = NatPort, port = NatPort,
@ -255,23 +255,25 @@ handle_event(internal, {frame, <<?PACKET_QUERY_INFO, Body/binary>>}, registered,
%% arp查询 %% arp查询
handle_event(internal, {frame, <<?PACKET_ARP_REQUEST, Body/binary>>}, registered, #state{stream = Stream, network_id = NetworkId, network_pid = NetworkPid}) when is_pid(NetworkPid) -> handle_event(internal, {frame, <<?PACKET_ARP_REQUEST, Body/binary>>}, registered, #state{stream = Stream, network_id = NetworkId, network_pid = NetworkPid}) when is_pid(NetworkPid) ->
#sdl_arp_request{pkt_id = PktId, target_ip = TargetIp} = sdlan_pb:decode_msg(Body, sdl_arp_request), #sdl_arp_request{target_ip = TargetIp, origin_ip = OriginIp, context = Context} = sdlan_pb:decode_msg(Body, sdl_arp_request),
case sdlan_network:arp_request(NetworkPid, TargetIp) of case sdlan_network:arp_request(NetworkPid, TargetIp) of
error -> error ->
logger:debug("[sdlan_channel] network: ~p, arp_request target_ip: ~p, mac not found", [NetworkId, sdlan_util:int_to_ipv4(TargetIp)]), logger:debug("[sdlan_channel] network: ~p, arp_request target_ip: ~p, mac not found", [NetworkId, sdlan_util:int_to_ipv4(TargetIp)]),
EmptyArpResponsePkt = sdlan_pb:encode_msg(#sdl_arp_response{ EmptyArpResponsePkt = sdlan_pb:encode_msg(#sdl_arp_response{
pkt_id = PktId,
target_ip = TargetIp, target_ip = TargetIp,
target_mac = <<>> target_mac = <<>>,
origin_ip = OriginIp,
context = Context
}), }),
quic_send(Stream, <<?PACKET_ARP_RESPONSE, EmptyArpResponsePkt/binary>>), quic_send(Stream, <<?PACKET_ARP_RESPONSE, EmptyArpResponsePkt/binary>>),
keep_state_and_data; keep_state_and_data;
{ok, Mac} -> {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)]), 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)]),
ArpResponsePkt = sdlan_pb:encode_msg(#sdl_arp_response{ ArpResponsePkt = sdlan_pb:encode_msg(#sdl_arp_response{
pkt_id = PktId,
target_ip = TargetIp, target_ip = TargetIp,
target_mac = Mac target_mac = Mac,
origin_ip = OriginIp,
context = Context
}), }),
quic_send(Stream, <<?PACKET_ARP_RESPONSE, ArpResponsePkt/binary>>), quic_send(Stream, <<?PACKET_ARP_RESPONSE, ArpResponsePkt/binary>>),
keep_state_and_data keep_state_and_data
@ -365,10 +367,9 @@ decode_frames0(<<Len:16, Frame:Len/binary, Rest/binary>>, MaxPacketSize, Frames)
decode_frames0(Rest, _MaxPacketSize, Frames) -> decode_frames0(Rest, _MaxPacketSize, Frames) ->
{ok, Rest, lists:reverse(Frames)}. {ok, Rest, lists:reverse(Frames)}.
-spec register_nak_reply(PacketId :: integer(), ErrorCode :: integer(), ErrorMsg :: binary()) -> binary(). -spec register_nak_reply(ErrorCode :: integer(), ErrorMsg :: binary()) -> binary().
register_nak_reply(PacketId, ErrorCode, ErrorMsg) when is_integer(PacketId), is_integer(ErrorCode), is_binary(ErrorMsg) -> register_nak_reply(ErrorCode, ErrorMsg) when is_integer(ErrorCode), is_binary(ErrorMsg) ->
RegisterNakReply = sdlan_pb:encode_msg(#sdl_register_super_nak { RegisterNakReply = sdlan_pb:encode_msg(#sdl_register_super_nak {
pkt_id = PacketId,
error_code = ErrorCode, error_code = ErrorCode,
error_message = ErrorMsg error_message = ErrorMsg
}), }),

File diff suppressed because it is too large Load Diff

View File

@ -99,12 +99,10 @@ handle_info({udp, Sock, Ip, Port, <<?PACKET_STUN_REQUEST:8, Body/binary>>}, Stat
StunRequest = catch sdlan_pb:decode_msg(Body, sdl_stun_request), StunRequest = catch sdlan_pb:decode_msg(Body, sdl_stun_request),
%% ip对应的nat的映射关系 %% ip对应的nat的映射关系
maybe maybe
#sdl_stun_request{cookie = Cookie, session_token = SessionToken, client_id = ClientId, network_id = NetworkId, mac = Mac, nat_type = NatType, v6_info = V6Info} ?= StunRequest, #sdl_stun_request{session_token = SessionToken, client_id = ClientId, network_id = NetworkId, mac = Mac, nat_type = NatType, v6_info = V6Info} ?= StunRequest,
{ok, NetworkPid} ?= sdlan_network:lookup_pid(NetworkId), {ok, NetworkPid} ?= sdlan_network:lookup_pid(NetworkId),
sdlan_network:update_hole(NetworkPid, SessionToken, ClientId, Mac, {Ip, Port}, NatType, V6Info), sdlan_network:update_hole(NetworkPid, SessionToken, ClientId, Mac, {Ip, Port}, NatType, V6Info),
StunReply = sdlan_pb:encode_msg(#sdl_stun_reply{ StunReply = sdlan_pb:encode_msg(#sdl_stun_reply{}),
cookie = Cookie
}),
%logger:debug("[sdlan_stun] stun_request network_id: ~p, client_id: ~p, hole: ~p", [NetworkId, ClientId, {Ip, Port}]) %logger:debug("[sdlan_stun] stun_request network_id: ~p, client_id: ~p, hole: ~p", [NetworkId, ClientId, {Ip, Port}])
ok = gen_udp:send(Sock, Ip, Port, <<?PACKET_STUN_REPLY:8, StunReply/binary>>) ok = gen_udp:send(Sock, Ip, Port, <<?PACKET_STUN_REPLY:8, StunReply/binary>>)
end, end,

View File

@ -66,7 +66,7 @@ message SDLQueryInfo {
message SDLPeerInfo { message SDLPeerInfo {
bytes dst_mac = 1; bytes dst_mac = 1;
SDLV4Info v4_info = 2; optional SDLV4Info v4_info = 2;
optional SDLV6Info v6_info = 3; optional SDLV6Info v6_info = 3;
} }