Skip to content

Commit

Permalink
handle IP pool allocation failures gracefully
Browse files Browse the repository at this point in the history
A failure to allocate a IP address should result in a proper
GTP error cause (instead of crashing the context process).
  • Loading branch information
RoadRunnr committed May 6, 2019
1 parent c919d36 commit 49c6ee1
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 25 deletions.
31 changes: 21 additions & 10 deletions src/ggsn_gn.erl
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ pdp_alloc(#end_user_address{pdp_type_organization = 1,
<<_:4/bytes>> ->
ergw_inet:bin2ip(Address)
end,
{IP4, undefined};
{ipv4, IP4, undefined};

pdp_alloc(#end_user_address{pdp_type_organization = 1,
pdp_type_number = 16#57,
Expand All @@ -419,19 +419,19 @@ pdp_alloc(#end_user_address{pdp_type_organization = 1,
<<_:16/bytes>> ->
{ergw_inet:bin2ip(Address),128}
end,
{undefined, IP6};
{ipv6, undefined, IP6};
pdp_alloc(#end_user_address{pdp_type_organization = 1,
pdp_type_number = 16#8D,
pdp_address = Address}) ->
case Address of
<< IP4:4/bytes, IP6:16/bytes >> ->
{ergw_inet:bin2ip(IP4), {ergw_inet:bin2ip(IP6), 128}};
{ipv4v6, ergw_inet:bin2ip(IP4), {ergw_inet:bin2ip(IP6), 128}};
<< IP6:16/bytes >> ->
{{0,0,0,0}, {ergw_inet:bin2ip(IP6), 128}};
{ipv4v6, {0,0,0,0}, {ergw_inet:bin2ip(IP6), 128}};
<< IP4:4/bytes >> ->
{ergw_inet:bin2ip(IP4), {{0,0,0,0,0,0,0,0},64}};
<< >> ->
{{0,0,0,0}, {{0,0,0,0,0,0,0,0},64}}
{ipv4v6, ergw_inet:bin2ip(IP4), {{0,0,0,0,0,0,0,0},64}};
<< >> ->
{ipv4v6, {0,0,0,0}, {{0,0,0,0,0,0,0,0},64}}
end;

pdp_alloc(_) ->
Expand Down Expand Up @@ -969,10 +969,10 @@ session_ipv6_alloc(#{'Framed-IPv6-Prefix' := {{_,_,_,_,_,_,_,_}, _} = IPv6}, _Re
session_ipv6_alloc(_SessionOpts, ReqMSv6) ->
ReqMSv6.

session_ip_alloc(SessionOpts, {ReqMSv4, ReqMSv6}) ->
session_ip_alloc(SessionOpts, {PDPType, ReqMSv4, ReqMSv6}) ->
MSv4 = session_ipv4_alloc(SessionOpts, ReqMSv4),
MSv6 = session_ipv6_alloc(SessionOpts, ReqMSv6),
{MSv4, MSv6}.
{PDPType, MSv4, MSv6}.

maybe_ip(Key, {{_,_,_,_} = IPv4, _}, SessionIP) ->
SessionIP#{Key => IPv4};
Expand All @@ -982,8 +982,19 @@ maybe_ip(_, _, SessionIP) ->
SessionIP.

assign_ips(SessionOps, EUA, #context{vrf = VRF, local_control_tei = LocalTEI} = Context) ->
{ReqMSv4, ReqMSv6} = session_ip_alloc(SessionOps, pdp_alloc(EUA)),
{PDPType, ReqMSv4, ReqMSv6} = session_ip_alloc(SessionOps, pdp_alloc(EUA)),
{ok, MSv4, MSv6} = vrf:allocate_pdp_ip(VRF, LocalTEI, ReqMSv4, ReqMSv6),
case PDPType of
ipv4v6 when MSv4 /= undefined, MSv6 /= undefined ->
ok;
ipv4 when MSv4 /= undefined ->
ok;
ipv6 when MSv6 /= undefined ->
ok;
_ ->
throw(?CTX_ERR(?FATAL, all_dynamic_pdp_addresses_are_occupied, Context))
end,

SessionIP4 = maybe_ip('Framed-IP-Address', MSv4, #{}),
SessionIP = maybe_ip('Framed-IPv6-Prefix', MSv6, SessionIP4),
{SessionIP, Context#context{ms_v4 = MSv4, ms_v6 = MSv6}}.
Expand Down
2 changes: 2 additions & 0 deletions src/gtp_v1_c.erl
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,8 @@ map_reply_ie(no_resources_available) ->
#cause{value = no_resources_available};
map_reply_ie(rejected) ->
#cause{value = no_resources_available};
map_reply_ie(all_dynamic_pdp_addresses_are_occupied) ->
#cause{value = all_dynamic_pdp_addresses_are_occupied};
map_reply_ie(IE)
when is_tuple(IE) ->
IE.
2 changes: 2 additions & 0 deletions src/gtp_v2_c.erl
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,8 @@ map_reply_ie(no_resources_available) ->
#v2_cause{v2_cause = no_resources_available};
map_reply_ie(rejected) ->
#v2_cause{v2_cause = no_resources_available};
map_reply_ie(all_dynamic_addresses_are_occupied) ->
#v2_cause{v2_cause = all_dynamic_addresses_are_occupied};
map_reply_ie(IE)
when is_tuple(IE) ->
IE.
Expand Down
22 changes: 16 additions & 6 deletions src/pgw_s5s8.erl
Original file line number Diff line number Diff line change
Expand Up @@ -613,13 +613,13 @@ match_context(Type, Context, IE) ->

pdn_alloc(#v2_pdn_address_allocation{type = ipv4v6,
address = << IP6PrefixLen:8, IP6Prefix:16/binary, IP4:4/binary>>}) ->
{ergw_inet:bin2ip(IP4), {ergw_inet:bin2ip(IP6Prefix), IP6PrefixLen}};
{ipv4v6, ergw_inet:bin2ip(IP4), {ergw_inet:bin2ip(IP6Prefix), IP6PrefixLen}};
pdn_alloc(#v2_pdn_address_allocation{type = ipv4,
address = << IP4:4/binary>>}) ->
{ergw_inet:bin2ip(IP4), undefined};
{ipv4, ergw_inet:bin2ip(IP4), undefined};
pdn_alloc(#v2_pdn_address_allocation{type = ipv6,
address = << IP6PrefixLen:8, IP6Prefix:16/binary>>}) ->
{undefined, {ergw_inet:bin2ip(IP6Prefix), IP6PrefixLen}}.
{ipv6, undefined, {ergw_inet:bin2ip(IP6Prefix), IP6PrefixLen}}.

encode_paa({IPv4,_}, undefined) ->
encode_paa(ipv4, ergw_inet:ip2bin(IPv4), <<>>);
Expand Down Expand Up @@ -1071,10 +1071,10 @@ session_ipv6_alloc(#{'Framed-IPv6-Prefix' := {{_,_,_,_,_,_,_,_}, _} = IPv6}, _Re
session_ipv6_alloc(_SessionOpts, ReqMSv6) ->
ReqMSv6.

session_ip_alloc(SessionOpts, {ReqMSv4, ReqMSv6}) ->
session_ip_alloc(SessionOpts, {PDNType, ReqMSv4, ReqMSv6}) ->
MSv4 = session_ipv4_alloc(SessionOpts, ReqMSv4),
MSv6 = session_ipv6_alloc(SessionOpts, ReqMSv6),
{MSv4, MSv6}.
{PDNType, MSv4, MSv6}.

maybe_ip(Key, {{_,_,_,_} = IPv4, _}, SessionIP) ->
SessionIP#{Key => IPv4};
Expand All @@ -1084,8 +1084,18 @@ maybe_ip(_, _, SessionIP) ->
SessionIP.

assign_ips(SessionOps, PAA, #context{vrf = VRF, local_control_tei = LocalTEI} = Context) ->
{ReqMSv4, ReqMSv6} = session_ip_alloc(SessionOps, pdn_alloc(PAA)),
{PDNType, ReqMSv4, ReqMSv6} = session_ip_alloc(SessionOps, pdn_alloc(PAA)),
{ok, MSv4, MSv6} = vrf:allocate_pdp_ip(VRF, LocalTEI, ReqMSv4, ReqMSv6),
case PDNType of
ipv4v6 when MSv4 /= undefined, MSv6 /= undefined ->
ok;
ipv4 when MSv4 /= undefined ->
ok;
ipv6 when MSv6 /= undefined ->
ok;
_ ->
throw(?CTX_ERR(?FATAL, all_dynamic_addresses_are_occupied, Context))
end,
SessionIP4 = maybe_ip('Framed-IP-Address', MSv4, #{}),
SessionIP = maybe_ip('Framed-IPv6-Prefix', MSv6, SessionIP4),
{SessionIP, Context#context{ms_v4 = MSv4, ms_v6 = MSv6}}.
Expand Down
22 changes: 16 additions & 6 deletions src/saegw_s11.erl
Original file line number Diff line number Diff line change
Expand Up @@ -559,13 +559,13 @@ match_context(Type, Context, IE) ->

pdn_alloc(#v2_pdn_address_allocation{type = ipv4v6,
address = << IP6PrefixLen:8, IP6Prefix:16/binary, IP4:4/binary>>}) ->
{ergw_inet:bin2ip(IP4), {ergw_inet:bin2ip(IP6Prefix), IP6PrefixLen}};
{ipv4v6, ergw_inet:bin2ip(IP4), {ergw_inet:bin2ip(IP6Prefix), IP6PrefixLen}};
pdn_alloc(#v2_pdn_address_allocation{type = ipv4,
address = << IP4:4/binary>>}) ->
{ergw_inet:bin2ip(IP4), undefined};
{ipv4, ergw_inet:bin2ip(IP4), undefined};
pdn_alloc(#v2_pdn_address_allocation{type = ipv6,
address = << IP6PrefixLen:8, IP6Prefix:16/binary>>}) ->
{undefined, {ergw_inet:bin2ip(IP6Prefix), IP6PrefixLen}}.
{ipv6, undefined, {ergw_inet:bin2ip(IP6Prefix), IP6PrefixLen}}.

encode_paa({IPv4,_}, undefined) ->
encode_paa(ipv4, ergw_inet:ip2bin(IPv4), <<>>);
Expand Down Expand Up @@ -1018,10 +1018,10 @@ session_ipv6_alloc(#{'Framed-IPv6-Prefix' := {{_,_,_,_,_,_,_,_}, _} = IPv6}, _Re
session_ipv6_alloc(_SessionOpts, ReqMSv6) ->
ReqMSv6.

session_ip_alloc(SessionOpts, {ReqMSv4, ReqMSv6}) ->
session_ip_alloc(SessionOpts, {PDNType, ReqMSv4, ReqMSv6}) ->
MSv4 = session_ipv4_alloc(SessionOpts, ReqMSv4),
MSv6 = session_ipv6_alloc(SessionOpts, ReqMSv6),
{MSv4, MSv6}.
{PDNType, MSv4, MSv6}.

maybe_ip(Key, {{_,_,_,_} = IPv4, _}, SessionIP) ->
SessionIP#{Key => IPv4};
Expand All @@ -1031,8 +1031,18 @@ maybe_ip(_, _, SessionIP) ->
SessionIP.

assign_ips(SessionOps, PAA, #context{vrf = VRF, local_control_tei = LocalTEI} = Context) ->
{ReqMSv4, ReqMSv6} = session_ip_alloc(SessionOps, pdn_alloc(PAA)),
{PDNType, ReqMSv4, ReqMSv6} = session_ip_alloc(SessionOps, pdn_alloc(PAA)),
{ok, MSv4, MSv6} = vrf:allocate_pdp_ip(VRF, LocalTEI, ReqMSv4, ReqMSv6),
case PDNType of
ipv4v6 when MSv4 /= undefined, MSv6 /= undefined ->
ok;
ipv4 when MSv4 /= undefined ->
ok;
ipv6 when MSv6 /= undefined ->
ok;
_ ->
throw(?CTX_ERR(?FATAL, all_dynamic_addresses_are_occupied, Context))
end,
SessionIP4 = maybe_ip('Framed-IP-Address', MSv4, #{}),
SessionIP = maybe_ip('Framed-IPv6-Prefix', MSv6, SessionIP4),
{SessionIP, Context#context{ms_v4 = MSv4, ms_v6 = MSv6}}.
Expand Down
10 changes: 9 additions & 1 deletion test/ergw_ggsn_test_lib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ make_pdp_type(ipv6, IEs) ->
{?'PCO-DNS-Server-IPv6-Address',<<>>},
{?'PCO-IP-Address-Allocation-Via-NAS-Signalling',<<>>}]}}
| IEs];
make_pdp_type(ipv4v6, IEs) ->
make_pdp_type(SubType, IEs)
when SubType == ipv4v6;
SubType == pool_exhausted ->
[#end_user_address{pdp_type_organization = 1,
pdp_type_number = 16#8d,
pdp_address = <<>>},
Expand Down Expand Up @@ -342,6 +344,12 @@ validate_response(create_pdp_context_request, invalid_apn, Response, GtpC) ->
Response),
GtpC;

validate_response(create_pdp_context_request, pool_exhausted, Response, GtpC) ->
?match(#gtp{type = create_pdp_context_response,
ie = #{{cause,0} := #cause{value = all_dynamic_pdp_addresses_are_occupied}}},
Response),
GtpC;

validate_response(create_pdp_context_request, missing_ie, Response, GtpC) ->
?match(#gtp{type = create_pdp_context_response,
ie = #{{cause,0} := #cause{value = mandatory_ie_missing}}},
Expand Down
10 changes: 9 additions & 1 deletion test/ergw_pgw_test_lib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@ make_pdn_type(static_host_ipv6, IEs) ->
{?'PCO-DNS-Server-IPv6-Address', <<>>},
{?'PCO-IP-Address-Allocation-Via-NAS-Signalling',<<>>}]}}
| IEs];
make_pdn_type(ipv4v6, IEs) ->
make_pdn_type(SubType, IEs)
when SubType == ipv4v6;
SubType == pool_exhausted ->
PrefixLen = 0,
Prefix = ergw_inet:ip2bin({0,0,0,0,0,0,0,0}),
RequestedIP = ergw_inet:ip2bin({0,0,0,0}),
Expand Down Expand Up @@ -555,6 +557,12 @@ validate_response(create_session_request, invalid_apn, Response, GtpC) ->
Response),
GtpC;

validate_response(create_session_request, pool_exhausted, Response, GtpC) ->
?match(#gtp{type = create_session_response,
ie = #{{v2_cause,0} := #v2_cause{v2_cause = all_dynamic_addresses_are_occupied}}},
Response),
GtpC;

validate_response(create_session_request, invalid_mapping, Response, GtpC) ->
?match(#gtp{type = create_session_response,
ie = #{{v2_cause,0} := #v2_cause{v2_cause = user_authentication_failed}}},
Expand Down
10 changes: 9 additions & 1 deletion test/ergw_saegw_test_lib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ make_pdn_type(ipv6, IEs) ->
#v2_protocol_configuration_options{
config = {0, [{1,<<>>}, {3,<<>>}, {10,<<>>}]}}
| IEs];
make_pdn_type(ipv4v6, IEs) ->
make_pdn_type(SubType, IEs)
when SubType == ipv4v6;
SubType == pool_exhausted ->
PrefixLen = 64,
Prefix = ergw_inet:ip2bin({0,0,0,0,0,0,0,0}),
RequestedIP = ergw_inet:ip2bin({0,0,0,0}),
Expand Down Expand Up @@ -416,6 +418,12 @@ validate_response(create_session_request, invalid_apn, Response, GtpC) ->
Response),
GtpC;

validate_response(create_session_request, pool_exhausted, Response, GtpC) ->
?match(#gtp{type = create_session_response,
ie = #{{v2_cause,0} := #v2_cause{v2_cause = all_dynamic_addresses_are_occupied}}},
Response),
GtpC;

validate_response(create_session_request, invalid_mapping, Response, GtpC) ->
?match(#gtp{type = create_session_response,
ie = #{{v2_cause,0} := #v2_cause{v2_cause = user_authentication_failed}}},
Expand Down
49 changes: 49 additions & 0 deletions test/ggsn_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ common() ->
create_pdp_context_request_gy_fail,
create_pdp_context_request_rf_fail,
create_pdp_context_request_invalid_apn,
create_pdp_context_request_pool_exhausted,
create_pdp_context_request_dotted_apn,
create_pdp_context_request_accept_new,
path_restart, path_restart_recovery, path_restart_multi,
Expand Down Expand Up @@ -383,6 +384,10 @@ init_per_testcase(create_pdp_context_request_rf_fail, Config) ->
meck:passthrough([Session, SessionOpts, Procedure, Opts])
end),
Config;
init_per_testcase(create_pdp_context_request_pool_exhausted, Config) ->
init_per_testcase(Config),
ok = meck:new(vrf, [passthrough, no_link]),
Config;
init_per_testcase(path_restart, Config) ->
init_per_testcase(Config),
ok = meck:new(gtp_path, [passthrough, no_link]),
Expand Down Expand Up @@ -448,6 +453,10 @@ end_per_testcase(TestCase, Config)
ok = meck:delete(ergw_aaa_session, invoke, 4),
end_per_testcase(Config),
Config;
end_per_testcase(create_pdp_context_request_pool_exhausted, Config) ->
meck:unload(vrf),
end_per_testcase(Config),
Config;
end_per_testcase(path_restart, Config) ->
meck:unload(gtp_path),
end_per_testcase(Config),
Expand Down Expand Up @@ -585,6 +594,46 @@ create_pdp_context_request_invalid_apn(Config) ->
socket_counter_metrics_ok( MetricsBefore, MetricsAfter, missing_or_unknown_apn ), % In response
ok.

%%--------------------------------------------------------------------

create_pdp_context_request_pool_exhausted() ->
[{doc, "Dynamic IP pool exhausted"}].
create_pdp_context_request_pool_exhausted(Config) ->
ok = meck:expect(vrf, allocate_pdp_ip,
fun(VRF, TEI, ReqIPv4, ReqIPv6) ->
{ok, _IPv4, IPv6} =
meck:passthrough([VRF, TEI, ReqIPv4, ReqIPv6]),
{ok, undefined, IPv6}
end),

MetricsBefore = socket_counter_metrics(),

create_pdp_context(pool_exhausted, Config),

MetricsAfter = socket_counter_metrics(),
socket_counter_metrics_ok( MetricsBefore, MetricsAfter, create_pdp_context_request ),
socket_counter_metrics_ok( MetricsBefore, MetricsAfter, all_dynamic_pdp_addresses_are_occupied ), % In response

ok = meck:expect(vrf, allocate_pdp_ip,
fun(VRF, TEI, ReqIPv4, ReqIPv6) ->
{ok, IPv4, _IPv6} =
meck:passthrough([VRF, TEI, ReqIPv4, ReqIPv6]),
{ok, IPv4, undefined}
end),
create_pdp_context(pool_exhausted, Config),

ok = meck:expect(vrf, allocate_pdp_ip,
fun(VRF, TEI, ReqIPv4, ReqIPv6) ->
{ok, _IPv4, _IPv6} =
meck:passthrough([VRF, TEI, ReqIPv4, ReqIPv6]),
{ok, undefined, undefined}
end),
create_pdp_context(pool_exhausted, Config),

?equal([], outstanding_requests()),
meck_validate(Config),
ok.

%%--------------------------------------------------------------------
create_pdp_context_request_dotted_apn() ->
[{doc, "Check dotted APN return on Create PDP Context Request"}].
Expand Down

0 comments on commit 49c6ee1

Please sign in to comment.