From 87aeb8ef2cebc7551088b8606f6fef869e1525b7 Mon Sep 17 00:00:00 2001 From: benoitc Date: Wed, 21 Feb 2024 05:04:29 +0100 Subject: [PATCH] rbreaking change: prefer IPV6 This change implements the happy eyeballs strategy to bnring better support of IPv6 for hackney. It follows the recommendations iof the RFC 8305. https://datatracker.ietf.org/doc/html/rfc8305 --- NOTICE | 3 + rebar.lock | 3 + src/hackney_connection.erl | 20 +- src/hackney_happy.erl | 72 +++++++ src/hackney_http_connect.erl | 2 +- src/hackney_pool.erl | 8 +- src/hackney_socks5.erl | 2 +- src/hackney_ssl.erl | 30 ++- src/hackney_tcp.erl | 2 +- src/libs/hackney_cidr.erl | 243 ++++++++++++++++++++++ test/hackney_cidr_tests.erl | 377 +++++++++++++++++++++++++++++++++++ 11 files changed, 720 insertions(+), 42 deletions(-) create mode 100644 src/hackney_happy.erl create mode 100644 src/libs/hackney_cidr.erl create mode 100644 test/hackney_cidr_tests.erl diff --git a/NOTICE b/NOTICE index be905996..ef394ed6 100644 --- a/NOTICE +++ b/NOTICE @@ -21,3 +21,6 @@ Copyright (c) 2009, Erlang Training and Consulting Ltd. Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. *) hackney_trace (C) 2015 under the Erlang Public LicensE + +*) hackney_cidr is based on inet_cidr 1.2.1. vendored for customer purpose. +Copyright (c) 2024, Enki Multimedia , MIT License diff --git a/rebar.lock b/rebar.lock index ee72ef3e..29b16ba8 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,6 +1,7 @@ {"1.2.0", [{<<"certifi">>,{pkg,<<"certifi">>,<<"2.12.0">>},0}, {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},0}, + {<<"inet_cidr">>,{pkg,<<"erl_cidr">>,<<"1.2.1">>},0}, {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},0}, {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.2.0">>},0}, {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.4.1">>},0}, @@ -10,6 +11,7 @@ {pkg_hash,[ {<<"certifi">>, <<"2D1CCA2EC95F59643862AF91F001478C9863C2AC9CB6E2F89780BFD8DE987329">>}, {<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>}, + {<<"inet_cidr">>, <<"921BB463BCF10EAD937AE491E5BCAC2823E550B339A9893AC6574518553CC067">>}, {<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>}, {<<"mimerl">>, <<"67E2D3F571088D5CFD3E550C383094B47159F3EEE8FFA08E64106CDF5E981BE3">>}, {<<"parse_trans">>, <<"6E6AA8167CB44CC8F39441D05193BE6E6F4E7C2946CB2759F015F8C56B76E5FF">>}, @@ -18,6 +20,7 @@ {pkg_hash_ext,[ {<<"certifi">>, <<"EE68D85DF22E554040CDB4BE100F33873AC6051387BAF6A8F6CE82272340FF1C">>}, {<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>}, + {<<"inet_cidr">>, <<"FCF5D51B1CC1B26D1748AB39F9B614AB445AD17172A9396D48C6B5C15EA9EFCF">>}, {<<"metrics">>, <<"69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16">>}, {<<"mimerl">>, <<"F278585650AA581986264638EBF698F8BB19DF297F66AD91B18910DFC6E19323">>}, {<<"parse_trans">>, <<"620A406CE75DADA827B82E453C19CF06776BE266F5A67CFF34E1EF2CBB60E49A">>}, diff --git a/src/hackney_connection.erl b/src/hackney_connection.erl index 3dc95a60..ceac66c4 100644 --- a/src/hackney_connection.erl +++ b/src/hackney_connection.erl @@ -102,27 +102,13 @@ connect_options(hackney_local_tcp, _Host, ClientOptions) -> proplists:get_value(connect_options, ClientOptions, []); connect_options(Transport, Host, ClientOptions) -> - ConnectOpts0 = proplists:get_value(connect_options, ClientOptions, []), - - %% handle ipv6 - ConnectOpts1 = case lists:member(inet, ConnectOpts0) orelse - lists:member(inet6, ConnectOpts0) of - true -> - ConnectOpts0; - false -> - case hackney_util:is_ipv6(Host) of - true -> - [inet6 | ConnectOpts0]; - false -> - ConnectOpts0 - end - end, + ConnectOpts = proplists:get_value(connect_options, ClientOptions, []), case Transport of hackney_ssl -> - ConnectOpts1 ++ ssl_opts(Host, ClientOptions); + [{ssl_options, ssl_opts(Host, ClientOptions)} | ConnectOpts]; _ -> - ConnectOpts1 + ConnectOpts end. diff --git a/src/hackney_happy.erl b/src/hackney_happy.erl new file mode 100644 index 00000000..0a90dc01 --- /dev/null +++ b/src/hackney_happy.erl @@ -0,0 +1,72 @@ +-module(hackney_happy). + +-export([connect/3, connect/4]). + +-include("hackney_internal.hrl"). +-include_lib("kernel/include/inet.hrl"). + +-define(TIMEOUT, 250). +-define(CONNECT_TIMEOUT, 5000). + +connect(Hostname, Port, Opts) -> + connect(Hostname, Port, Opts, ?CONNECT_TIMEOUT). + +connect(Hostname, Port, Opts, Timeout) -> + case hackney_cidr:is_ipv6(Hostname) of + true -> + ?report_debug("connect using IPv6", [{hostname, Hostname}, {port, Port}]), + gen_tcp:connect(Hostname, Port, [inet6 | Opts], Timeout); + false -> + case hackney_cidr:is_ipv4(Hostname) of + true -> + ?report_debug("connect using IPv4", [{hostname, Hostname}, {port, Port}]), + gen_tcp:connect(Hostname, Port, [inet | Opts], Timeout); + false -> + ?report_debug("happy eyeballs, try to connect using IPv6", [{hostname, Hostname}, {port, Port}]), + Self = self(), + Addrs = getaddrs(Hostname), + Pid = spawn_link( fun() -> try_connect(Addrs, Port, Opts, Self, {error, nxdomain}) end), + MRef = erlang:monitor(process, Pid), + receive + {happy_connect, OK} -> + erlang:demonitor(MRef, [flush]), + OK; + {'DOWN', MRef, _Type, _Pid, Info} -> + {'error', {'connect_error', Info}} + after Timeout -> + erlang:demonitor(MRef, [flush]), + {error, connect_timeout} + end + end + end. + +getaddrs(Hostname) -> + IP6Addrs = [{Addr, 'inet6'} || Addr <- getbyname(Hostname, 'aaaa')], + IP4Addrs = [{Addr, 'inet'} || Addr <- getbyname(Hostname, 'a')], + IP6Addrs ++ IP4Addrs. + +getbyname(Hostname, Type) -> + case (catch inet_res:getbyname(Hostname, Type)) of + {'ok', #hostent{h_addr_list=AddrList}} -> lists:usort(AddrList); + {error, _Reason} -> []; + Else -> + %% ERLANG 22 has an issue when g matching somee DNS server messages + ?report_debug("DNS error", [{hostname, Hostname} + ,{type, Type} + ,{error, Else}]), + [] + end. + +try_connect([], _Port, _Opts, ServerPid, LastError) -> + ?report_trace("happy eyeball: failed to connect", [{error, LastError}]), + ServerPid ! {hackney_happy, LastError}; +try_connect([{IP, Type} | Rest], Port, Opts, ServerPid, _LastError) -> + ?report_trace("try to connect", [{ip, IP}, {type, Type}]), + case gen_tcp:connect(IP, Port, [Type | Opts], ?TIMEOUT) of + {ok, Socket} = OK -> + ?report_trace("success to connect", [{ip, IP}, {type, Type}]), + ok = gen_tcp:controlling_process(Socket, ServerPid), + ServerPid ! {happy_connect, OK}; + Error -> + try_connect(Rest, Port, Opts, ServerPid, Error) + end. diff --git a/src/hackney_http_connect.erl b/src/hackney_http_connect.erl index 60861cf5..03299313 100644 --- a/src/hackney_http_connect.erl +++ b/src/hackney_http_connect.erl @@ -63,7 +63,7 @@ connect(ProxyHost, ProxyPort, Opts, Timeout) ConnectOpts = hackney_util:filter_options(Opts, AcceptedOpts, BaseOpts), %% connect to the proxy, and upgrade the socket if needed. - case gen_tcp:connect(ProxyHost, ProxyPort, ConnectOpts) of + case hackney_happy:connect(ProxyHost, ProxyPort, ConnectOpts) of {ok, Socket} -> case do_handshake(Socket, Host, Port, Opts) of ok -> diff --git a/src/hackney_pool.erl b/src/hackney_pool.erl index b51feaa6..852ad867 100644 --- a/src/hackney_pool.erl +++ b/src/hackney_pool.erl @@ -63,12 +63,13 @@ checkout(Host, Port, Transport, Client) -> Requester = self(), try do_checkout(Requester, Host, Port, Transport, Client) - catch _:_ -> + catch _:Error -> + ?report_trace("pool: checkout failure", [{error, Error}]), {error, checkout_failure} end. do_checkout(Requester, Host, _Port, Transport, #client{options=Opts, - mod_metrics=Metrics}=Client) -> + mod_metrics=Metrics}=Client) -> ConnectTimeout = proplists:get_value(connect_timeout, Opts, 8000), %% Fall back to using connect_timeout if checkout_timeout is not set CheckoutTimeout = proplists:get_value(checkout_timeout, Opts, ConnectTimeout), @@ -78,7 +79,6 @@ do_checkout(Requester, Host, _Port, Transport, #client{options=Opts, Pool = find_pool(PoolName, Opts), case catch gen_server:call(Pool, {checkout, Connection, Requester, RequestRef}, CheckoutTimeout) of {ok, Socket, Owner} -> - %% stats ?report_debug("reuse a connection", [{pool, PoolName}]), _ = metrics:update_meter(Metrics, [hackney_pool, PoolName, take_rate], 1), @@ -105,7 +105,7 @@ do_checkout(Requester, Host, _Port, Transport, #client{options=Opts, _ = metrics:increment_counter(Metrics, [hackney, Host, connect_timeout]), {error, timeout}; Error -> - ?report_trace("connect error", []), + ?report_trace("connect error", [{pool, PoolName}, {error, Error}]), _ = metrics:increment_counter(Metrics, [hackney, Host, connect_error]), Error end; diff --git a/src/hackney_socks5.erl b/src/hackney_socks5.erl index 4be6e77d..c7dcac26 100644 --- a/src/hackney_socks5.erl +++ b/src/hackney_socks5.erl @@ -62,7 +62,7 @@ connect(Host, Port, Opts, Timeout) when is_list(Host), is_integer(Port), ConnectOpts = hackney_util:filter_options(Opts, AcceptedOpts, BaseOpts), %% connect to the socks 5 proxy - case gen_tcp:connect(ProxyHost, ProxyPort, ConnectOpts, Timeout) of + case hackney_happy:connect(ProxyHost, ProxyPort, ConnectOpts, Timeout) of {ok, Socket} -> case do_handshake(Socket, Host, Port, Opts) of ok -> diff --git a/src/hackney_ssl.erl b/src/hackney_ssl.erl index 5b65e4a6..79e1015b 100644 --- a/src/hackney_ssl.erl +++ b/src/hackney_ssl.erl @@ -25,16 +25,7 @@ %% @doc Atoms used to identify messages in {active, once | true} mode. messages(_) -> {ssl, ssl_closed, ssl_error}. -%% @doc The ssl:connect/4 (and related) doesn't work with textual representation -%% of IP addresses. It accepts either a string with a DNS-resolvable name or a -%% tuple with parts of an IP as numbers. This function attempts to parse given -%% string and either returns such tuple, or the string if it's impossible to -%% parse. -parse_address(Host) when is_list(Host) -> - case inet:parse_address(Host) of - {ok, Address} -> Address; - {error, _} -> Host - end. + check_hostname_opts(Host0) -> @@ -134,14 +125,17 @@ find(_Fun, []) -> connect(Host, Port, Opts) -> connect(Host, Port, Opts, infinity). -connect(Host, Port, Opts, Timeout) when is_list(Host), is_integer(Port), - (Timeout =:= infinity orelse is_integer(Timeout)) -> - BaseOpts = [binary, {active, false}, {packet, raw}, - {secure_renegotiate, true}, - {reuse_sessions, true}], - Opts1 = hackney_util:merge_opts(BaseOpts, Opts), - %% connect - ssl:connect(parse_address(Host), Port, Opts1, Timeout). +connect(Host, Port, Opts0, Timeout) when is_list(Host), is_integer(Port), + (Timeout =:= 5000 orelse is_integer(Timeout)) -> + SSLOpts = [{secure_renegotiate, true}, proplists:get_value(ssl_options, Opts0)], + BaseOpts = [binary, {active, false}, {packet, raw}], + Opts1 = hackney_util:merge_opts(BaseOpts, proplists:delete(ssl_options, Opts0)), + case hackney_happy:connect(Host, Port, Opts1, Timeout) of + {ok, Sock} -> + ssl:connect(Sock, SSLOpts); + Error -> + Error + end. diff --git a/src/hackney_tcp.erl b/src/hackney_tcp.erl index 506d9c37..cb21cd9f 100644 --- a/src/hackney_tcp.erl +++ b/src/hackney_tcp.erl @@ -28,7 +28,7 @@ connect(Host, Port, Opts, Timeout) when is_list(Host), is_integer(Port), BaseOpts = [binary, {active, false}, {packet, raw}], Opts1 = hackney_util:merge_opts(BaseOpts, Opts), %% connect - gen_tcp:connect(Host, Port, Opts1, Timeout). + hackney_happy:connect(Host, Port, Opts1, Timeout). recv(Socket, Length) -> recv(Socket, Length, infinity). diff --git a/src/libs/hackney_cidr.erl b/src/libs/hackney_cidr.erl new file mode 100644 index 00000000..b308f79d --- /dev/null +++ b/src/libs/hackney_cidr.erl @@ -0,0 +1,243 @@ +%%% -*- erlang -*- +%%% This file is part of inet_cidr eleased under the MIT license. +%%% See the NOTICE for more information. +%%% +%%% Copyright (c) 2016-2024 Benoît Chesneau + +-module(hackney_cidr). + +-export([parse/1, parse/2]). +-export([address_count/2]). +-export([contains/2]). +-export([usort_cidrs/1]). +-export([merge_cidrs/1]). +-export([to_string/1]). +-export([to_binary/1]). +-export([is_ipv4/1]). +-export([is_ipv6/1]). + +-type cidr() :: {Start :: inet:ip4_address(), End :: inet:ip4_address(), MaskLen :: 0..32} + | {Start :: inet:ip6_address(), End :: inet:ip6_address(), MaskLen :: 0..128}. + +-export_type([cidr/0]). + +-spec parse(string() | binary()) -> cidr(). +%% @doc parses S as a CIDR notation IP address and mask +parse(S) -> + parse(S, false). + +-spec parse(string() | binary(), Adjust :: boolean()) -> cidr(). +%% @doc parses S as a CIDR notation IP address and mask. +%% If Adjust = `true', allow the IP to contain values beyond the mask and +%% silently ignore them. Otherwise, enforce that the IP address is fully inside +%% the specified mask (the default behavior of `parse/1'). +parse(B, Adjust) when is_binary(B) -> + parse(binary_to_list(B), Adjust); +parse(S, Adjust) -> + {StartAddr, PrefixLen} = parse_cidr(S, Adjust), + EndAddr = calc_end_address(StartAddr, PrefixLen), + {StartAddr, EndAddr, PrefixLen}. + +-spec address_count(inet:ip4_address(), MaskLen :: 0..32) -> pos_integer(); + (inet:ip6_address(), MaskLen :: 0..128) -> pos_integer(). +%% @doc return the number of IP addresses included in the CIDR block +address_count(IP, Len) -> + 1 bsl (bit_count(IP) - Len). + +-spec contains(cidr(), inet:ip_address() | cidr()) -> boolean(). +%% @doc return true if the CIDR block contains the IP address or CIDR block, false otherwise. +contains({StartAddr, EndAddr, _L}, Addr) when tuple_size(StartAddr) == tuple_size(EndAddr), + tuple_size(StartAddr) == tuple_size(Addr) -> + ip_gte(Addr, StartAddr) andalso ip_lte(Addr, EndAddr); + +contains({StartAddr1, EndAddr1, _L1}, + {StartAddr2, EndAddr2, _L2}) when tuple_size(StartAddr1) == tuple_size(EndAddr1), + tuple_size(EndAddr1) == tuple_size(StartAddr2), + tuple_size(StartAddr2) == tuple_size(EndAddr2) -> + ip_gte(StartAddr2, StartAddr1) andalso ip_lte(StartAddr2, EndAddr1) andalso + ip_gte(EndAddr2, StartAddr1) andalso ip_lte(EndAddr2, EndAddr1); + +contains(_, _) -> + false. + +-spec usort_cidrs([cidr()]) -> [cidr()]. +%% @doc Unique sort a list of CIDR blocks, ordering IPv4 ranges before IPv6 ranges +usort_cidrs(CIDRs) -> + lists:usort(fun cidr_lte/2, CIDRs). + +-spec merge_cidrs([cidr()]) -> [cidr()]. +%% @doc Unique sort and merge a list of CIDR blocks, ordering IPv4 ranges before IPv6 ranges. +%% For merging, CIDR blocks that are contained by other CIDR blocks are removed and +%% adjacent CIDR blocks are merged into larger ones. +merge_cidrs(CIDRs) -> + merge_sorted_cidrs(usort_cidrs(CIDRs)). + +-spec to_string(cidr()) -> string(). +%% @doc return a CIDR block as a string. +to_string({StartAddr, _EndAddr, Len}) -> + inet:ntoa(StartAddr) ++ "/" ++ integer_to_list(Len). + +-spec to_binary(cidr()) -> binary(). +%% @doc return a CIDR block as a binary string. +to_binary({StartAddr, _EndAddr, Len}) -> + <<(list_to_binary(inet:ntoa(StartAddr)))/binary, "/", (integer_to_binary(Len))/binary>>. + +-spec is_ipv4(inet:ip_address()) -> boolean(). +%% @doc return true if the value is an ipv4 address +is_ipv4({A, B, C, D}) -> + (((A >= 0) andalso (A =< 255)) andalso + ((B >= 0) andalso (B =< 255)) andalso + ((C >= 0) andalso (C =< 255)) andalso + ((D >= 0) andalso (D =< 255))); +is_ipv4(_) -> + false. + +-spec is_ipv6(inet:ip_address()) -> boolean(). +%% @doc return true if the value is an ipv6 address +is_ipv6({A, B, C, D, E, F, G, H}) -> + (((A >= 0) andalso (A =< 65535)) andalso + ((B >= 0) andalso (B =< 65535)) andalso + ((C >= 0) andalso (C =< 65535)) andalso + ((D >= 0) andalso (D =< 65535)) andalso + ((E >= 0) andalso (E =< 65535)) andalso + ((F >= 0) andalso (F =< 65535)) andalso + ((G >= 0) andalso (G =< 65535)) andalso + ((H >= 0) andalso (H =< 65535))); +is_ipv6(_) -> + false. + +%% internals + +bit_count({_, _, _, _}) -> 32; +bit_count({_, _, _, _, _, _, _, _}) -> 128. + +parse_cidr(S, Adjust) -> + {StartAddr, Masked, PrefixLen} = + case re:split(S, "/", [{return, list}, {parts, 2}]) of + [Prefix, LenStr] -> + {ok, Addr} = inet:parse_address(Prefix), + {PLen, _} = string:to_integer(LenStr), + {Addr, band_with_mask(Addr, start_mask(Addr, PLen)), PLen}; + [Prefix] -> + {ok, Addr} = inet:parse_address(Prefix), + PLen = case is_ipv6(Addr) of + true -> 128; + false -> 32 + end, + {Addr, band_with_mask(Addr, start_mask(Addr, PLen)), PLen} + end, + if + Adjust /= true, Masked /= StartAddr -> error(invalid_cidr); + true -> ok + end, + {Masked, PrefixLen}. + +start_mask({_, _, _, _}=Addr, Len) when Len >= 0, Len =< 32 -> + {A, B, C, D} = end_mask(Addr, Len), + {bnot A, bnot B, bnot C, bnot D}; + +start_mask({_, _, _, _, _, _, _, _}=Addr, Len) when Len >= 0, Len =< 128 -> + {A, B, C, D, E, F, G, H} = end_mask(Addr, Len), + {bnot A, bnot B, bnot C, bnot D, bnot E, bnot F, bnot G, bnot H}. + +end_mask({_, _, _, _}, Len) when Len >= 0, Len =< 32 -> + if + Len == 32 -> {0, 0, 0, 0}; + Len >= 24 -> {0, 0, 0, bmask(Len, 8)}; + Len >= 16 -> {0, 0, bmask(Len, 8), 16#FF}; + Len >= 8 -> {0, bmask(Len, 8), 16#FF, 16#FF}; + Len >= 0 -> {bmask(Len, 8), 16#FF, 16#FF, 16#FF} + end; + +end_mask({_, _, _, _, _, _, _, _}, Len) when Len >= 0, Len =< 128 -> + if + Len == 128 -> {0, 0, 0, 0, 0, 0, 0, 0}; + Len >= 112 -> {0, 0, 0, 0, 0, 0, 0, bmask(Len, 16)}; + Len >= 96 -> {0, 0, 0, 0, 0, 0, bmask(Len, 16), 16#FFFF}; + Len >= 80 -> {0, 0, 0, 0, 0, bmask(Len, 16), 16#FFFF, 16#FFFF}; + Len >= 64 -> {0, 0, 0, 0, bmask(Len, 16), 16#FFFF, 16#FFFF, 16#FFFF}; + Len >= 48 -> {0, 0, 0, bmask(Len, 16), 16#FFFF, 16#FFFF, 16#FFFF, + 16#FFFF}; + Len >= 32 -> {0, 0, bmask(Len, 16), 16#FFFF, 16#FFFF, 16#FFFF, 16#FFFF, + 16#FFFF}; + Len >= 16 -> {0, bmask(Len, 16), 16#FFFF, 16#FFFF, 16#FFFF, 16#FFFF, + 16#FFFF, 16#FFFF}; + Len >= 0 -> {bmask(Len, 16), 16#FFFF, 16#FFFF, 16#FFFF, 16#FFFF, + 16#FFFF, 16#FFFF, 16#FFFF} + end. + +bmask(I, 8) when I >= 0, I =< 32 -> + 16#FF bsr (I rem 8); +bmask(I, 16) when I >= 0, I =< 128 -> + 16#FFFF bsr (I rem 16). + +calc_end_address(Addr, Len) -> + bor_with_mask(Addr, end_mask(Addr, Len)). + +bor_with_mask({A, B, C, D}, {E, F, G, H}) -> + {A bor E, B bor F, C bor G, D bor H}; +bor_with_mask({A, B, C, D, E, F, G, H}, {I, J, K, L, M, N, O, P}) -> + {A bor I, B bor J, C bor K, D bor L, E bor M, F bor N, G bor O, H bor P}. + +band_with_mask({A, B, C, D}, {E, F, G, H}) -> + {A band E, B band F, C band G, D band H}; +band_with_mask({A, B, C, D, E, F, G, H}, {I, J, K, L, M, N, O, P}) -> + {A band I, B band J, C band K, D band L, E band M, F band N, G band O, + H band P}. + +ip_lte({A, B, C, D1}, {A, B, C, D2}) -> D1 =< D2; +ip_lte({A, B, C1, _}, {A, B, C2, _}) -> C1 =< C2; +ip_lte({A, B1, _, _}, {A, B2, _, _}) -> B1 =< B2; +ip_lte({A1, _, _, _}, {A2, _, _, _}) -> A1 =< A2; +ip_lte({A, B, C, D, E, F, G, H1}, {A, B, C, D, E, F, G, H2}) -> H1 =< H2; +ip_lte({A, B, C, D, E, F, G1, _}, {A, B, C, D, E, F, G2, _}) -> G1 =< G2; +ip_lte({A, B, C, D, E, F1, _, _}, {A, B, C, D, E, F2, _, _}) -> F1 =< F2; +ip_lte({A, B, C, D, E1, _, _, _}, {A, B, C, D, E2, _, _, _}) -> E1 =< E2; +ip_lte({A, B, C, D1, _, _, _, _}, {A, B, C, D2, _, _, _, _}) -> D1 =< D2; +ip_lte({A, B, C1, _, _, _, _, _}, {A, B, C2, _, _, _, _, _}) -> C1 =< C2; +ip_lte({A, B1, _, _, _, _, _, _}, {A, B2, _, _, _, _, _, _}) -> B1 =< B2; +ip_lte({A1, _, _, _, _, _, _, _}, {A2, _, _, _, _, _, _, _}) -> A1 =< A2. + +ip_gte({A, B, C, D1}, {A, B, C, D2}) -> D1 >= D2; +ip_gte({A, B, C1, _}, {A, B, C2, _}) -> C1 >= C2; +ip_gte({A, B1, _, _}, {A, B2, _, _}) -> B1 >= B2; +ip_gte({A1, _, _, _}, {A2, _, _, _}) -> A1 >= A2; +ip_gte({A, B, C, D, E, F, G, H1}, {A, B, C, D, E, F, G, H2}) -> H1 >= H2; +ip_gte({A, B, C, D, E, F, G1, _}, {A, B, C, D, E, F, G2, _}) -> G1 >= G2; +ip_gte({A, B, C, D, E, F1, _, _}, {A, B, C, D, E, F2, _, _}) -> F1 >= F2; +ip_gte({A, B, C, D, E1, _, _, _}, {A, B, C, D, E2, _, _, _}) -> E1 >= E2; +ip_gte({A, B, C, D1, _, _, _, _}, {A, B, C, D2, _, _, _, _}) -> D1 >= D2; +ip_gte({A, B, C1, _, _, _, _, _}, {A, B, C2, _, _, _, _, _}) -> C1 >= C2; +ip_gte({A, B1, _, _, _, _, _, _}, {A, B2, _, _, _, _, _, _}) -> B1 >= B2; +ip_gte({A1, _, _, _, _, _, _, _}, {A2, _, _, _, _, _, _, _}) -> A1 >= A2. + +% @private Compare 2 CIDR specifications based on the following criteria: +% * IPv4 < IPv6 +% * If start range matches, sort on mask length +% * Otherwise, sort on start IP +cidr_lte({StartAddr, _, L1}, + {StartAddr, _, L2}) -> + L1 =< L2; +cidr_lte({StartAddr1, _, _L1}, + {StartAddr2, _, _L2}) when tuple_size(StartAddr1) =/= tuple_size(StartAddr2) -> + tuple_size(StartAddr1) =< tuple_size(StartAddr2); +cidr_lte({StartAddr1, _, _L1}, + {StartAddr2, _, _L2}) when tuple_size(StartAddr1) == tuple_size(StartAddr2) -> + ip_lte(StartAddr1, StartAddr2). + +%% @private merge a list of uniquely sorted CIDR blocks to their minimal +%% representation. +merge_sorted_cidrs(SortedCIDRs) -> + merge_sorted_cidrs(SortedCIDRs, []). + +merge_sorted_cidrs([], Acc) -> + lists:reverse(Acc); +merge_sorted_cidrs([CIDR], Acc) -> + lists:reverse([CIDR | Acc]); +merge_sorted_cidrs([CIDR1, CIDR2 | SortedCIDRs], Acc) -> + case contains(CIDR1, CIDR2) of + true -> + merge_sorted_cidrs([CIDR1 | SortedCIDRs], Acc); + false -> + merge_sorted_cidrs([CIDR2 | SortedCIDRs], [CIDR1 | Acc]) + end. diff --git a/test/hackney_cidr_tests.erl b/test/hackney_cidr_tests.erl new file mode 100644 index 00000000..dd9aa079 --- /dev/null +++ b/test/hackney_cidr_tests.erl @@ -0,0 +1,377 @@ +-module(hackney_cidr_tests). + +-include_lib("eunit/include/eunit.hrl"). + +parse_ipv4_test_() -> + [?_assertEqual({{0, 0, 0, 0}, {255, 255, 255, 255}, 0}, + hackney_cidr:parse("192.168.0.0/0", true)), + ?_assertEqual({{192, 0, 0, 0}, {192, 255, 255, 255}, 8}, + hackney_cidr:parse("192.168.0.0/8", true)), + ?_assertEqual({{192, 168, 0, 0}, {192, 169, 255, 255}, 15}, + hackney_cidr:parse("192.168.0.0/15", true)), + ?_assertEqual({{192, 168, 0, 0}, {192, 168, 255, 255}, 16}, + hackney_cidr:parse("192.168.0.0/16")), + ?_assertEqual({{192, 168, 0, 0}, {192, 168, 127, 255}, 17}, + hackney_cidr:parse("192.168.0.0/17")), + ?_assertEqual({{192, 168, 0, 0}, {192, 168, 63, 255}, 18}, + hackney_cidr:parse("192.168.0.0/18")), + ?_assertEqual({{192, 168, 0, 0}, {192, 168, 31, 255}, 19}, + hackney_cidr:parse("192.168.0.0/19")), + ?_assertEqual({{192, 168, 0, 0}, {192, 168, 15, 255}, 20}, + hackney_cidr:parse("192.168.0.0/20")), + ?_assertEqual({{192, 168, 0, 0}, {192, 168, 7, 255}, 21}, + hackney_cidr:parse("192.168.0.0/21")), + ?_assertEqual({{192, 168, 0, 0}, {192, 168, 3, 255}, 22}, + hackney_cidr:parse("192.168.0.0/22")), + ?_assertEqual({{192, 168, 0, 0}, {192, 168, 1, 255}, 23}, + hackney_cidr:parse("192.168.0.0/23")), + ?_assertEqual({{192, 168, 0, 0}, {192, 168, 0, 255}, 24}, + hackney_cidr:parse("192.168.0.0/24")), + ?_assertEqual({{192, 168, 0, 0}, {192, 168, 0, 1}, 31}, + hackney_cidr:parse("192.168.0.0/31")), + ?_assertEqual({{192, 168, 0, 0}, {192, 168, 0, 0}, 32}, + hackney_cidr:parse("192.168.0.0/32")), + ?_assertEqual({{192, 168, 0, 0}, {192, 168, 0, 0}, 32}, + hackney_cidr:parse(<<"192.168.0.0/32">>)), + ?_assertEqual({{192, 168, 0, 0}, {192, 168, 0, 0}, 32}, + hackney_cidr:parse(<<"192.168.0.0">>))]. + +parse_ipv6_test_() -> + [?_assertEqual({{0, 0, 0, 0, 0, 0, 0, 0}, + {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535}, + 0}, + hackney_cidr:parse("2001:abcd::/0", true)), + ?_assertEqual({{8193, 43981, 0, 0, 0, 0, 0, 0}, + {8193, 43981, 65535, 65535, 65535, 65535, 65535, 65535}, + 32}, + hackney_cidr:parse("2001:abcd::/32")), + ?_assertEqual({{8193, 43981, 0, 0, 0, 0, 0, 0}, + {8193, 43981, 32767, 65535, 65535, 65535, 65535, 65535}, + 33}, + hackney_cidr:parse("2001:abcd::/33")), + ?_assertEqual({{8193, 43981, 0, 0, 0, 0, 0, 0}, + {8193, 43981, 16383, 65535, 65535, 65535, 65535, 65535}, + 34}, + hackney_cidr:parse("2001:abcd::/34")), + ?_assertEqual({{8193, 43981, 0, 0, 0, 0, 0, 0}, + {8193, 43981, 8191, 65535, 65535, 65535, 65535, 65535}, + 35}, + hackney_cidr:parse("2001:abcd::/35")), + ?_assertEqual({{8193, 43981, 0, 0, 0, 0, 0, 0}, + {8193, 43981, 4095, 65535, 65535, 65535, 65535, 65535}, + 36}, + hackney_cidr:parse("2001:abcd::/36")), + ?_assertEqual({{8193, 43981, 0, 0, 0, 0, 0, 0}, + {8193, 43981, 0, 0, 0, 0, 0, 0}, + 128}, + hackney_cidr:parse("2001:abcd::/128")), + ?_assertEqual({{8193, 3512, 0, 0, 0, 0, 0, 0}, + {8193, 3512, 0, 65535, 65535, 65535, 65535, 65535}, + 48}, + hackney_cidr:parse("2001:db8::/48")), + ?_assertEqual({{8193, 3512, 0, 0, 0, 0, 0, 0}, + {8193, 3512, 0, 65535, 65535, 65535, 65535, 65535}, + 48}, + hackney_cidr:parse(<<"2001:db8::/48">>)), + ?_assertEqual({{8193,3512,0,0,0,0,0,1},{8193,3512,0,0,0,0,0,1},128}, + hackney_cidr:parse(<<"2001:0db8::1/128">>)), + ?_assertEqual({{8193,3512,0,0,0,0,0,1},{8193,3512,0,0,0,0,0,1},128}, + hackney_cidr:parse(<<"2001:0db8::1">>))] + . + + +to_string_test_() -> + [?_assertEqual("192.168.0.0/16", + hackney_cidr:to_string({{192, 168, 0, 0}, {192, 168, 255, 255}, 16})), + ?_assertEqual("2001:abcd::/32", + hackney_cidr:to_string({{8193, 43981, 0, 0, 0, 0, 0, 0}, + {8193, 43981, 65535, 65535, 65535, 65535, 65535, 65535}, + 32}))]. + +to_binary_test_() -> + [?_assertEqual(<<"192.168.0.0/16">>, + hackney_cidr:to_binary({{192, 168, 0, 0}, {192, 168, 255, 255}, 16})), + ?_assertEqual(<<"2001:abcd::/32">>, + hackney_cidr:to_binary({{8193, 43981, 0, 0, 0, 0, 0, 0}, + {8193, 43981, 65535, 65535, 65535, 65535, 65535, 65535}, + 32}))]. + +ipv4_address_count_test_() -> + {ok, Addr} = inet:parse_address("192.168.0.0"), + [?_assertEqual(4294967296, hackney_cidr:address_count(Addr, 0)), + ?_assertEqual( 65536, hackney_cidr:address_count(Addr, 16)), + ?_assertEqual( 32768, hackney_cidr:address_count(Addr, 17)), + ?_assertEqual( 256, hackney_cidr:address_count(Addr, 24)), + ?_assertEqual( 1, hackney_cidr:address_count(Addr, 32))]. + +ipv6_address_count_test_() -> + {ok, Addr} = inet:parse_address("2001::abcd"), + [?_assertEqual(1 bsl 128, hackney_cidr:address_count(Addr, 0)), + ?_assertEqual(1 bsl 64, hackney_cidr:address_count(Addr, 64)), + ?_assertEqual(1, hackney_cidr:address_count(Addr, 128))]. + +contains_test_() -> + Block = hackney_cidr:parse("192.168.1.0/24"), + [?_assertNot(hackney_cidr:contains(Block, {}))]. + +ipv4_contains_test_() -> + Block = {{192, 168, 0, 0}, {192, 168, 255, 255}, 16}, + [?_assert(hackney_cidr:contains(Block, {192, 168, 0, 0})), + ?_assert(hackney_cidr:contains(Block, {192, 168, 0, 1})), + ?_assert(hackney_cidr:contains(Block, {192, 168, 1, 0})), + ?_assert(hackney_cidr:contains(Block, {192, 168, 0, 255})), + ?_assert(hackney_cidr:contains(Block, {192, 168, 255, 0})), + ?_assert(hackney_cidr:contains(Block, {192, 168, 255, 255})), + ?_assertNot(hackney_cidr:contains(Block, {192, 168, 255, 256})), + ?_assertNot(hackney_cidr:contains(Block, {192, 169, 0, 0})), + ?_assertNot(hackney_cidr:contains(Block, {192, 167, 255, 255}))]. + +ipv4_contains_cidr_test_() -> + Block = {{192, 168, 0, 0}, {192, 168, 255, 255}, 16}, + [?_assert(hackney_cidr:contains(Block, Block)), + ?_assert(hackney_cidr:contains(Block, hackney_cidr:parse("192.168.1.0/24"))), + ?_assert(hackney_cidr:contains(Block, hackney_cidr:parse("192.168.254.0/24"))), + ?_assert(hackney_cidr:contains(Block, hackney_cidr:parse("192.168.1.2/31"))), + ?_assert(hackney_cidr:contains(Block, hackney_cidr:parse("192.168.1.1/32"))), + ?_assertNot(hackney_cidr:contains(Block, hackney_cidr:parse("10.0.0.0/16"))), + ?_assertNot(hackney_cidr:contains(Block, hackney_cidr:parse("10.0.0.1/32"))), + ?_assertNot(hackney_cidr:contains(Block, hackney_cidr:parse("192.169.0.0/16"))), + ?_assertNot(hackney_cidr:contains(Block, hackney_cidr:parse("192.168.0.0/15")))]. + +ipv6_contains_test_() -> + Block = {{8193, 43981, 0, 0, 0, 0, 0, 0}, + {8193, 43981, 8191, 65535, 65535, 65535, 65535, 65535}, + 35}, + [?_assert(hackney_cidr:contains(Block, {8193, 43981, 0, 0, 0, 0, 0, 0})), + ?_assert(hackney_cidr:contains(Block, {8193, 43981, 0, 0, 0, 0, 0, 1})), + ?_assert(hackney_cidr:contains(Block, {8193, 43981, 8191, 65535, 65535, 65535, 65535, 65534})), + ?_assert(hackney_cidr:contains(Block, {8193, 43981, 8191, 65535, 65535, 65535, 65535, 65535})), + ?_assertNot(hackney_cidr:contains(Block, {8193, 43981, 8192, 65535, 65535, 65535, 65535, 65535})), + ?_assertNot(hackney_cidr:contains(Block, {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535}))]. + +ipv6_contains_cidr_test_() -> + Block = hackney_cidr:parse("2001:abcd::/32"), + [?_assert(hackney_cidr:contains(Block, Block)), + ?_assert(hackney_cidr:contains(Block, hackney_cidr:parse("2001:abcd:1:1:1:1:1:1/128"))), + ?_assert(hackney_cidr:contains(Block, hackney_cidr:parse("2001:abcd:2000::/35"))), + ?_assert(hackney_cidr:contains(Block, hackney_cidr:parse("2001:abcd:2000:1:1:1::/96"))), + ?_assertNot(hackney_cidr:contains(Block, hackney_cidr:parse("2002:abcd::/35"))), + ?_assertNot(hackney_cidr:contains(Block, hackney_cidr:parse("2002:abcd:1:1:1:1:1:1/128"))), + ?_assertNot(hackney_cidr:contains(Block, hackney_cidr:parse("2001:ffff::/35")))]. + +usort_cidrs_test_() -> + [?_assertEqual([], hackney_cidr:usort_cidrs([])), + ?_assertEqual([hackney_cidr:parse("10.0.0.0/8")], + hackney_cidr:usort_cidrs([hackney_cidr:parse("10.0.0.0/8")])), + ?_assertEqual([hackney_cidr:parse("10.0.0.0/8")], + hackney_cidr:usort_cidrs([hackney_cidr:parse("10.0.0.0/8"), + hackney_cidr:parse("10.0.0.0/8")])), + ?_assertEqual([hackney_cidr:parse("2001:abcd::/32")], + hackney_cidr:usort_cidrs([hackney_cidr:parse("2001:abcd::/32")])), + ?_assertEqual([hackney_cidr:parse("2001:abcd::/32")], + hackney_cidr:usort_cidrs([hackney_cidr:parse("2001:abcd::/32"), + hackney_cidr:parse("2001:abcd::/32")])), + ?_assertEqual([hackney_cidr:parse("10.0.0.0/8"), + hackney_cidr:parse("2001:abcd::/32")], + hackney_cidr:usort_cidrs([hackney_cidr:parse("2001:abcd::/32"), + hackney_cidr:parse("10.0.0.0/8"), + hackney_cidr:parse("2001:abcd::/32"), + hackney_cidr:parse("10.0.0.0/8")])), + ?_assertEqual([hackney_cidr:parse("10.0.0.0/8"), + hackney_cidr:parse("10.0.0.0/16"), + hackney_cidr:parse("10.1.0.0/16"), + hackney_cidr:parse("2001:abcd::/32")], + hackney_cidr:usort_cidrs([hackney_cidr:parse("2001:abcd::/32"), + hackney_cidr:parse("10.1.0.0/16"), + hackney_cidr:parse("10.0.0.0/8"), + hackney_cidr:parse("10.0.0.0/16"), + hackney_cidr:parse("10.0.0.0/8")])), + ?_assertEqual([hackney_cidr:parse("10.0.0.0/8"), + hackney_cidr:parse("2001:abcd::/32"), + hackney_cidr:parse("2001:abcd:1::/48"), + hackney_cidr:parse("2001:abcd:1::/64"), + hackney_cidr:parse("2001:abcd:1::1/128")], + hackney_cidr:usort_cidrs([hackney_cidr:parse("2001:abcd::/32"), + hackney_cidr:parse("2001:abcd:1::1/128"), + hackney_cidr:parse("2001:abcd:1::/64"), + hackney_cidr:parse("10.0.0.0/8"), + hackney_cidr:parse("2001:abcd:1::1/128"), + hackney_cidr:parse("2001:abcd:1::/48"), + hackney_cidr:parse("10.0.0.0/8"), + hackney_cidr:parse("2001:abcd:1::/64")]))]. + +merge_cidrs_test_() -> + [?_assertEqual([], hackney_cidr:merge_cidrs([])), + ?_assertEqual([hackney_cidr:parse("10.0.0.0/16"), + hackney_cidr:parse("10.10.0.0/16")], + hackney_cidr:merge_cidrs([hackney_cidr:parse("10.10.0.0/16"), + hackney_cidr:parse("10.0.0.1/32"), + hackney_cidr:parse("10.10.99.0/24"), + hackney_cidr:parse("10.0.99.0/24"), + hackney_cidr:parse("10.0.0.0/16")])), + ?_assertEqual([hackney_cidr:parse("10.0.1.1/32"), + hackney_cidr:parse("10.0.1.2/31"), + hackney_cidr:parse("10.0.1.4/30")], + hackney_cidr:merge_cidrs([hackney_cidr:parse("10.0.1.4/30"), + hackney_cidr:parse("10.0.1.4/32"), + hackney_cidr:parse("10.0.1.2/31"), + hackney_cidr:parse("10.0.1.1/32"), + hackney_cidr:parse("10.0.1.3/32"), + hackney_cidr:parse("10.0.1.4/30")])), + ?_assertEqual([hackney_cidr:parse("10.0.0.0/16")], + hackney_cidr:merge_cidrs([hackney_cidr:parse("10.0.1.0/24"), + hackney_cidr:parse("10.0.254.0/24"), + hackney_cidr:parse("10.0.0.0/16"), + hackney_cidr:parse("10.0.100.99/32"), + hackney_cidr:parse("10.0.0.0/16")])), + ?_assertEqual([hackney_cidr:parse("10.0.0.0/8"), + hackney_cidr:parse("2001:abcd::/32")], + hackney_cidr:merge_cidrs([hackney_cidr:parse("2001:abcd::/32"), + hackney_cidr:parse("2001:abcd:1::1/128"), + hackney_cidr:parse("2001:abcd:1::/64"), + hackney_cidr:parse("10.0.0.0/8"), + hackney_cidr:parse("2001:abcd:1::1/128"), + hackney_cidr:parse("2001:abcd:1::/48"), + hackney_cidr:parse("10.0.0.0/8"), + hackney_cidr:parse("2001:abcd:1::/64")])), + ?_assertEqual([hackney_cidr:parse("2001:abcd::/32")], + hackney_cidr:merge_cidrs([hackney_cidr:parse("2001:abcd:abcd::/48"), + hackney_cidr:parse("2001:abcd:1234::/48"), + hackney_cidr:parse("2001:abcd:9999::/48"), + hackney_cidr:parse("2001:abcd::/32"), + hackney_cidr:parse("2001:abcd:abcd::1/128"), + hackney_cidr:parse("2001:abcd:abcd::4/126"), + hackney_cidr:parse("2001:abcd:abcd:0:0:abcd::/96")]))]. + +is_ipv4_test_() -> + {ok, Addr} = inet:parse_address("2001::abcd"), + [?_assert(hackney_cidr:is_ipv4({192, 168, 0, 0})), + ?_assertNot(hackney_cidr:is_ipv4({192, 168, 0, 256})), + ?_assertNot(hackney_cidr:is_ipv4({192, 168, 0})), + ?_assertNot(hackney_cidr:is_ipv4({192, 168, 0, 0, 0})), + ?_assertNot(hackney_cidr:is_ipv4(Addr))]. + +is_ipv6_test_() -> + [?_assert(hackney_cidr:is_ipv6({8193, 43981, 0, 0, 0, 0, 0, 0})), + ?_assertNot(hackney_cidr:is_ipv6({192, 168, 0, 0})), + ?_assertNot(hackney_cidr:is_ipv6({8193, 43981, 0, 0, 0, 0, 0, 70000})), + ?_assertNot(hackney_cidr:is_ipv6({8193, 43981, 0, 0, 0, 0, 0})), + ?_assertNot(hackney_cidr:is_ipv6({8193, 43981, 0, 0, 0, 0, 0, 0, 0}))]. + +ipv4_ip_lte_test_() -> + [?_assert(hackney_cidr:ip_lte({0, 0, 0, 0}, {0, 0, 0, 0})), + ?_assert(hackney_cidr:ip_lte({255, 255, 255, 255}, {255, 255, 255, 255})), + ?_assert(hackney_cidr:ip_lte({192, 168, 1, 1}, {192, 168, 1, 1})), + ?_assert(hackney_cidr:ip_lte({192, 168, 1, 1}, {192, 168, 1, 2})), + ?_assert(hackney_cidr:ip_lte({192, 168, 1, 1}, {192, 168, 2, 1})), + ?_assert(hackney_cidr:ip_lte({192, 168, 1, 1}, {192, 169, 1, 1})), + ?_assert(hackney_cidr:ip_lte({192, 168, 1, 1}, {193, 168, 1, 1})), + ?_assertNot(hackney_cidr:ip_lte({192, 168, 1, 1}, {192, 168, 1, 0})), + ?_assertNot(hackney_cidr:ip_lte({192, 168, 1, 1}, {192, 168, 0, 1})), + ?_assertNot(hackney_cidr:ip_lte({192, 168, 1, 1}, {192, 167, 1, 1})), + ?_assertNot(hackney_cidr:ip_lte({192, 168, 1, 1}, {191, 168, 1, 1}))]. + +ipv6_ip_lte_test_() -> + [?_assert(hackney_cidr:ip_lte({0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0})), + ?_assert(hackney_cidr:ip_lte({65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535}, + {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535})), + ?_assert(hackney_cidr:ip_lte({0, 0, 0, 0, 0, 0, 0, 0}, + {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535})), + ?_assert(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 4, 3, 2, 1})), + ?_assert(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535})), + ?_assert(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 4, 3, 2, 2})), + ?_assert(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 4, 3, 3, 1})), + ?_assert(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 4, 4, 2, 1})), + ?_assert(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 5, 3, 2, 1})), + ?_assert(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 6, 4, 3, 2, 1})), + ?_assert(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 7, 5, 4, 3, 2, 1})), + ?_assert(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43982, 6, 5, 4, 3, 2, 1})), + ?_assert(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8194, 43981, 6, 5, 4, 3, 2, 1})), + ?_assertNot(hackney_cidr:ip_lte({65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535}, + {8193, 43981, 6, 5, 4, 3, 2, 1})), + ?_assertNot(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 2}, + {8193, 43981, 6, 5, 4, 3, 2, 1})), + ?_assertNot(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 4, 3, 1, 1})), + ?_assertNot(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 4, 2, 2, 1})), + ?_assertNot(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 3, 3, 2, 1})), + ?_assertNot(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 4, 4, 3, 2, 1})), + ?_assertNot(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 5, 5, 4, 3, 2, 1})), + ?_assertNot(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43980, 6, 5, 4, 3, 2, 1})), + ?_assertNot(hackney_cidr:ip_lte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8192, 43981, 6, 5, 4, 3, 2, 1}))]. + +ipv4_ip_gte_test_() -> + [?_assert(hackney_cidr:ip_gte({0, 0, 0, 0}, {0, 0, 0, 0})), + ?_assert(hackney_cidr:ip_gte({255, 255, 255, 255}, {255, 255, 255, 255})), + ?_assert(hackney_cidr:ip_gte({192, 168, 1, 1}, {192, 168, 1, 1})), + ?_assert(hackney_cidr:ip_gte({192, 168, 1, 1}, {192, 168, 1, 0})), + ?_assert(hackney_cidr:ip_gte({192, 168, 1, 1}, {192, 168, 0, 1})), + ?_assert(hackney_cidr:ip_gte({192, 168, 1, 1}, {192, 167, 1, 1})), + ?_assert(hackney_cidr:ip_gte({192, 168, 1, 1}, {191, 168, 1, 1})), + ?_assertNot(hackney_cidr:ip_gte({192, 168, 1, 1}, {192, 168, 1, 2})), + ?_assertNot(hackney_cidr:ip_gte({192, 168, 1, 1}, {192, 168, 2, 1})), + ?_assertNot(hackney_cidr:ip_gte({192, 168, 1, 1}, {192, 169, 1, 1})), + ?_assertNot(hackney_cidr:ip_gte({192, 168, 1, 1}, {193, 168, 1, 1}))]. + +ipv6_ip_gte_test_() -> + [?_assert(hackney_cidr:ip_gte({0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0})), + ?_assert(hackney_cidr:ip_gte({65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535}, + {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535})), + ?_assert(hackney_cidr:ip_gte({65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535}, + {8193, 43981, 6, 5, 4, 3, 2, 1})), + ?_assert(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 4, 3, 2, 1})), + ?_assert(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 2}, + {8193, 43981, 6, 5, 4, 3, 2, 1})), + ?_assert(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 4, 3, 1, 1})), + ?_assert(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 4, 2, 2, 1})), + ?_assert(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 3, 3, 2, 1})), + ?_assert(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 4, 4, 3, 2, 1})), + ?_assert(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 5, 5, 4, 3, 2, 1})), + ?_assert(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43980, 6, 5, 4, 3, 2, 1})), + ?_assert(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8192, 43981, 6, 5, 4, 3, 2, 1})), + ?_assertNot(hackney_cidr:ip_gte({0, 0, 0, 0, 0, 0, 0, 0}, + {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535})), + ?_assertNot(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535})), + ?_assertNot(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 4, 3, 2, 2})), + ?_assertNot(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 4, 3, 3, 1})), + ?_assertNot(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 4, 4, 2, 1})), + ?_assertNot(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 5, 5, 3, 2, 1})), + ?_assertNot(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 6, 6, 4, 3, 2, 1})), + ?_assertNot(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43981, 7, 5, 4, 3, 2, 1})), + ?_assertNot(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8193, 43982, 6, 5, 4, 3, 2, 1})), + ?_assertNot(hackney_cidr:ip_gte({8193, 43981, 6, 5, 4, 3, 2, 1}, + {8194, 43981, 6, 5, 4, 3, 2, 1}))].