feat(erlang): add dual-stack IP parsing utilities

This commit is contained in:
Alice39s 2026-06-27 23:52:40 +09:00
parent c1db56c369
commit 2f0dc5f33f
No known key found for this signature in database
GPG Key ID: 0E0AA11C3939DDDD
2 changed files with 96 additions and 14 deletions

View File

@ -4,13 +4,53 @@
%% license that can be found in the LICENSE file. %% license that can be found in the LICENSE file.
%% %%
%% @doc %% @doc
%% ip2region utils %% ip2region utils: IPv4/IPv6 parsing and version detection.
%% @end %% @end
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(ip2region_util). -module(ip2region_util).
-export([ipv4_to_n/1]).
-export([ipv4_to_n/1, ip_version/1, ip_to_bytes/1]).
-spec ip_version(Ip :: tuple() | list() | binary() | integer()) ->
ipv4 | ipv6 | {error, atom()}.
ip_version(Ip) when is_integer(Ip) -> ipv4;
ip_version({_, _, _, _}) -> ipv4;
ip_version({_, _, _, _, _, _, _, _}) -> ipv6;
ip_version(Ip) when is_binary(Ip) ->
ip_version(binary_to_list(Ip));
ip_version(Ip) when is_list(Ip) ->
case inet_parse:address(Ip) of
{ok, {_, _, _, _}} -> ipv4;
{ok, {_, _, _, _, _, _, _, _}} -> ipv6;
_ -> {error, bad_ip_format}
end;
ip_version(_) ->
{error, bad_ip_format}.
-spec ip_to_bytes(Ip :: tuple() | list() | binary() | integer()) ->
{ok, ipv4 | ipv6, binary()} | {error, atom()}.
ip_to_bytes(Ip) when is_integer(Ip) ->
{ok, ipv4, <<Ip:32>>};
ip_to_bytes({A, B, C, D}) ->
{ok, ipv4, <<A, B, C, D>>};
ip_to_bytes({A, B, C, D, E, F, G, H}) ->
{ok, ipv6, <<A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>};
ip_to_bytes(Ip) when is_binary(Ip) ->
ip_to_bytes(binary_to_list(Ip));
ip_to_bytes(Ip) when is_list(Ip) ->
case inet_parse:address(Ip) of
{ok, {A, B, C, D}} ->
{ok, ipv4, <<A, B, C, D>>};
{ok, {A, B, C, D, E, F, G, H}} ->
{ok, ipv6, <<A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>};
_ ->
{error, bad_ip_format}
end;
ip_to_bytes(_) ->
{error, bad_ip_format}.
-spec ipv4_to_n(Ip :: tuple() | list() | binary() | integer()) ->
non_neg_integer() | {error, atom()}.
ipv4_to_n(IntIp) when is_integer(IntIp) -> IntIp; ipv4_to_n(IntIp) when is_integer(IntIp) -> IntIp;
ipv4_to_n({A, B, C, D}) -> ipv4_to_n({A, B, C, D}) ->
<<N:32>> = <<A, B, C, D>>, <<N:32>> = <<A, B, C, D>>,
@ -18,9 +58,9 @@ ipv4_to_n({A, B, C, D}) ->
ipv4_to_n(Ip) when is_binary(Ip) -> ipv4_to_n(Ip) when is_binary(Ip) ->
ipv4_to_n(binary_to_list(Ip)); ipv4_to_n(binary_to_list(Ip));
ipv4_to_n(Ip) when is_list(Ip) -> ipv4_to_n(Ip) when is_list(Ip) ->
case inet_parse:address(Ip) of case ip_to_bytes(Ip) of
{ok, Addr} -> {ok, ipv4, <<N:32>>} -> N;
ipv4_to_n(Addr); _ -> {error, bad_ip_format}
_ -> end;
{error, bad_ip_format} ipv4_to_n(_) ->
end. {error, bad_ip_format}.

View File

@ -0,0 +1,42 @@
-module(ip2region_util_test).
-include_lib("eunit/include/eunit.hrl").
ipv4_inputs_test_() ->
[
?_assertEqual({ok, ipv4, <<1,0,8,0>>}, ip2region_util:ip_to_bytes("1.0.8.0")),
?_assertEqual({ok, ipv4, <<1,0,8,0>>}, ip2region_util:ip_to_bytes(<<"1.0.8.0">>)),
?_assertEqual({ok, ipv4, <<1,0,8,0>>}, ip2region_util:ip_to_bytes({1,0,8,0})),
?_assertEqual({ok, ipv4, <<1,0,8,0>>}, ip2region_util:ip_to_bytes(16779264)),
?_assertEqual(ipv4, ip2region_util:ip_version("1.0.8.0")),
?_assertEqual(ipv4, ip2region_util:ip_version({1,0,8,0}))
].
ipv6_inputs_test_() ->
[
?_assertEqual({ok, ipv6, <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1>>},
ip2region_util:ip_to_bytes("::1")),
?_assertEqual({ok, ipv6, <<0,0,0,0,0,0,0,0,0,0,255,255,1,0,8,0>>},
ip2region_util:ip_to_bytes("::ffff:1.0.8.0")),
?_assertEqual({ok, ipv6, <<0,0,0,0,0,0,0,0,0,0,255,255,1,0,8,0>>},
ip2region_util:ip_to_bytes(<<"::ffff:1.0.8.0">>)),
?_assertEqual({ok, ipv6, <<0,0,0,0,0,0,0,0,0,0,255,255,1,0,8,0>>},
ip2region_util:ip_to_bytes({0,0,0,0,0,65535,256,2048})),
?_assertEqual(ipv6, ip2region_util:ip_version("::1")),
?_assertEqual(ipv6, ip2region_util:ip_version({0,0,0,0,0,0,0,1}))
].
invalid_inputs_test_() ->
[
?_assertEqual({error, bad_ip_format}, ip2region_util:ip_to_bytes("xxx.0.8.0")),
?_assertEqual({error, bad_ip_format}, ip2region_util:ip_to_bytes("::ggg")),
?_assertEqual({error, bad_ip_format}, ip2region_util:ip_to_bytes({1,2,3})),
?_assertEqual({error, bad_ip_format}, ip2region_util:ip_to_bytes(atom))
].
legacy_ipv4_to_n_test_() ->
[
?_assertEqual(16779264, ip2region_util:ipv4_to_n("1.0.8.0")),
?_assertEqual(16779264, ip2region_util:ipv4_to_n(<<"1.0.8.0">>)),
?_assertEqual(16779264, ip2region_util:ipv4_to_n({1,0,8,0})),
?_assertEqual(16779264, ip2region_util:ipv4_to_n(16779264))
].