diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b5beef0a..c7b532f4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -42,6 +42,16 @@ build:otp-23.0: script: - sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6' - ip addr add fd96:dcd2:efdb:41c3::10/64 dev lo + - ip addr add fd96:dcd2:efdb:41c3::11/64 dev lo + - ip addr add fd96:dcd2:efdb:41c3::12/64 dev lo + - ip addr add fd96:dcd2:efdb:41c3::13/64 dev lo + - ip addr add fd96:dcd2:efdb:41c3::14/64 dev lo + - ip addr add fd96:dcd2:efdb:41c3::15/64 dev lo + - ip addr add fd96:dcd2:efdb:41c3::16/64 dev lo + - ip addr add fd96:dcd2:efdb:41c3::17/64 dev lo + - ip addr add fd96:dcd2:efdb:41c3::18/64 dev lo + - ip addr add fd96:dcd2:efdb:41c3::19/64 dev lo + - ip addr add fd96:dcd2:efdb:41c3::1a/64 dev lo - ip addr add fd96:dcd2:efdb:41c3::20/64 dev lo - ip addr add fd96:dcd2:efdb:41c3::30/64 dev lo - ip addr add fd96:dcd2:efdb:41c3::40/64 dev lo diff --git a/.travis.yml b/.travis.yml index 984c8d01..b89dac41 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,16 @@ before_script: - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6'; sudo ip addr add fd96:dcd2:efdb:41c3::10/64 dev lo; + sudo ip addr add fd96:dcd2:efdb:41c3::11/64 dev lo; + sudo ip addr add fd96:dcd2:efdb:41c3::12/64 dev lo; + sudo ip addr add fd96:dcd2:efdb:41c3::13/64 dev lo; + sudo ip addr add fd96:dcd2:efdb:41c3::14/64 dev lo; + sudo ip addr add fd96:dcd2:efdb:41c3::15/64 dev lo; + sudo ip addr add fd96:dcd2:efdb:41c3::16/64 dev lo; + sudo ip addr add fd96:dcd2:efdb:41c3::17/64 dev lo; + sudo ip addr add fd96:dcd2:efdb:41c3::18/64 dev lo; + sudo ip addr add fd96:dcd2:efdb:41c3::19/64 dev lo; + sudo ip addr add fd96:dcd2:efdb:41c3::1a/64 dev lo; sudo ip addr add fd96:dcd2:efdb:41c3::20/64 dev lo; sudo ip addr add fd96:dcd2:efdb:41c3::30/64 dev lo; sudo ip addr add fd96:dcd2:efdb:41c3::40/64 dev lo; diff --git a/src/ergw.erl b/src/ergw.erl index a6989f5b..0ac6c266 100644 --- a/src/ergw.erl +++ b/src/ergw.erl @@ -191,7 +191,7 @@ i(memory, socket) -> {socket, MemUsage}; i(memory, path) -> MemUsage = - lists:foldl(fun({_, Pid}, Mem) -> + lists:foldl(fun({_, Pid, _}, Mem) -> {memory, M} = erlang:process_info(Pid, memory), Mem + M end, 0, gtp_path_reg:all()), diff --git a/src/ergw_api.erl b/src/ergw_api.erl index 445abde0..8f63389d 100644 --- a/src/ergw_api.erl +++ b/src/ergw_api.erl @@ -18,7 +18,7 @@ peer(all) -> Peers = gtp_path_reg:all(), - lists:map(fun({_, Pid}) -> gtp_path:info(Pid) end, Peers); + lists:map(fun({_, Pid, _}) -> gtp_path:info(Pid) end, Peers); peer({_,_,_,_} = IP) -> collect_peer_info(gtp_path_reg:all(IP)); peer({_,_,_,_,_,_,_,_} = IP) -> @@ -55,9 +55,9 @@ memory(Limit0) -> %%%=================================================================== collect_peer_info(Peers) -> - lists:map(fun gtp_path:info/1, Peers). + lists:map(fun({Pid, _}) -> gtp_path:info(Pid) end, Peers). -collext_path_contexts(Path, Tunnels) -> +collext_path_contexts({Path, _State}, Tunnels) -> lists:foldl(fun(Pid, TunIn) -> collect_contexts(Pid, TunIn) end, Tunnels, gtp_path:all(Path)). diff --git a/src/ergw_proxy_lib.erl b/src/ergw_proxy_lib.erl index a3322848..c66fd2b3 100644 --- a/src/ergw_proxy_lib.erl +++ b/src/ergw_proxy_lib.erl @@ -12,7 +12,9 @@ -export([validate_options/3, validate_option/2, forward_request/3, forward_request/7, forward_request/9, get_seq_no/3, - select_gw/4, select_proxy_sockets/3]). + select_gw/5, + select_gtp_proxy_sockets/2, + select_sx_proxy_candidate/3]). -export([create_forward_session/3, modify_forward_session/5, delete_forward_session/4, @@ -48,16 +50,28 @@ get_seq_no(#context{control_port = GtpPort}, ReqKey, Request) -> ReqId = make_request_id(ReqKey, Request), ergw_gtp_c_socket:get_seq_no(GtpPort, ReqId). -select_gw(#{imsi := IMSI, gwSelectionAPN := APN}, Services, NodeSelect, Context) -> +choose_gw([], _NodeSelect, _Port, Context) -> + throw(?CTX_ERR(?FATAL, no_resources_available, Context)); +choose_gw(Nodes, NodeSelect, #gtp_port{name = Name} = Port, + #context{version = Version} = Context) -> + {Candidate, Next} = ergw_node_selection:snaptr_candidate(Nodes), + {Node, IP} = resolve_gw(Candidate, NodeSelect, Context), + case gtp_path_reg:state({Name, Version, IP}) of + down -> + choose_gw(Next, NodeSelect, Port, Context); + State when State =:= undefined; State =:= up -> + {Node, IP} + end. + +select_gw(#{imsi := IMSI, gwSelectionAPN := APN}, Services, NodeSelect, Port, Context) -> FQDN = ergw_node_selection:apn_to_fqdn(APN, IMSI), case ergw_node_selection:candidates(FQDN, Services, NodeSelect) of Nodes when is_list(Nodes), length(Nodes) /= 0 -> - {Node, _} = ergw_node_selection:snaptr_candidate(Nodes), - resolve_gw(Node, NodeSelect, Context); + choose_gw(Nodes, NodeSelect, Port, Context); _ -> throw(?CTX_ERR(?FATAL, system_failure, Context)) end; -select_gw(_ProxyInfo, _Services, _NodeSelect, Context) -> +select_gw(_ProxyInfo, _Services, _NodeSelect, _Port, Context) -> throw(?CTX_ERR(?FATAL, system_failure, Context)). lb(L) when is_list(L) -> @@ -83,9 +97,7 @@ select_gw_ip(_IP4, IP6) when length(IP6) /= 0 -> select_gw_ip(_IP4, _IP6) -> undefined. -select_proxy_sockets({GwNode, _}, #{upfSelectionAPN := APN} = ProxyInfo, - #{contexts := Contexts, proxy_ports := ProxyPorts, - node_selection := ProxyNodeSelect}) -> +select_gtp_proxy_sockets(ProxyInfo, #{contexts := Contexts, proxy_ports := ProxyPorts}) -> Context = maps:get(context, ProxyInfo, default), Ctx = maps:get(Context, Contexts, #{}), if Ctx =:= #{} -> @@ -93,23 +105,30 @@ select_proxy_sockets({GwNode, _}, #{upfSelectionAPN := APN} = ProxyInfo, true -> ok end, Cntl = maps:get(proxy_sockets, Ctx, ProxyPorts), - NodeSelect = maps:get(node_selection, Ctx, ProxyNodeSelect), + ergw_gtp_socket_reg:lookup(lb(Cntl)). +select_sx_proxy_candidate({GwNode, _}, #{upfSelectionAPN := APN} = ProxyInfo, + #{contexts := Contexts, node_selection := ProxyNodeSelect}) -> + Context = maps:get(context, ProxyInfo, default), + Ctx = maps:get(Context, Contexts, #{}), + if Ctx =:= #{} -> + ?LOG(warning, "proxy context ~p not found, using default", [Context]); + true -> ok + end, + NodeSelect = maps:get(node_selection, Ctx, ProxyNodeSelect), APN_FQDN = ergw_node_selection:apn_to_fqdn(APN), Services = [{"x-3gpp-upf", "x-sxa"}], Candidates0 = ergw_node_selection:candidates(APN_FQDN, Services, NodeSelect), PGWCandidate = [{GwNode, 0, Services, [], []}], - Candidates = - case ergw_node_selection:topology_match(Candidates0, PGWCandidate) of - {_, C} when is_list(C), length(C) /= 0 -> - C; - {C, _} when is_list(C), length(C) /= 0 -> - C; - _ -> - %% neither colocation, not topology matched - Candidates0 - end, - {ergw_gtp_socket_reg:lookup(lb(Cntl)), Candidates}. + case ergw_node_selection:topology_match(Candidates0, PGWCandidate) of + {_, C} when is_list(C), length(C) /= 0 -> + C; + {C, _} when is_list(C), length(C) /= 0 -> + C; + _ -> + %% neither colocation, not topology matched + Candidates0 + end. %%%=================================================================== %%% Options Validation diff --git a/src/ggsn_gn.erl b/src/ggsn_gn.erl index 83c0197e..bca6431d 100644 --- a/src/ggsn_gn.erl +++ b/src/ggsn_gn.erl @@ -338,7 +338,7 @@ handle_request(ReqKey, DAF = proplists:get_bool('Dual Address Bearer Flag', gtp_v1_c:get_common_flags(IEs)), Context1 = update_context_from_gtp_req(Request, Context0), - ContextPreAuth = gtp_path:bind(Request, Context1), + ContextPreAuth = gtp_path:bind(Request, false, Context1), gtp_context:terminate_colliding_context(ContextPreAuth), @@ -425,7 +425,7 @@ handle_request(ReqKey, 'Session' := Session} = Data0) -> Context0 = update_context_from_gtp_req(Request, OldContext), - Context = gtp_path:bind(Request, Context0), + Context = gtp_path:bind(Request, false, Context0), URRActions = update_session_from_gtp_req(IEs, Session, Context), Data1 = if Context /= OldContext -> @@ -489,7 +489,7 @@ handle_response({From, TermCause}, ie = #{?'Cause' := #cause{value = Cause}}} = Response, _Request, _State, #{context := Context0} = Data) -> - Context = gtp_path:bind(Response, Context0), + Context = gtp_path:bind(Response, false, Context0), close_pdp_context(TermCause, Data), if is_tuple(From) -> gen_statem:reply(From, {ok, Cause}); true -> ok @@ -674,7 +674,7 @@ defer_usage_report(URRActions, UsageReport) -> apply_context_change(NewContext0, OldContext, URRActions, #{pfcp := PCtx0, pcc := PCC} = Data) -> - NewContext = gtp_path:bind(NewContext0), + NewContext = gtp_path:bind(false, NewContext0), {PCtx, UsageReport} = ergw_gsn_lib:modify_sgi_session(PCC, URRActions, #{}, NewContext, PCtx0), diff --git a/src/ggsn_gn_proxy.erl b/src/ggsn_gn_proxy.erl index eaaa241f..76a20deb 100644 --- a/src/ggsn_gn_proxy.erl +++ b/src/ggsn_gn_proxy.erl @@ -276,7 +276,7 @@ handle_request(ReqKey, 'Session' := Session} = Data) -> Context1 = update_context_from_gtp_req(Request, Context0#context{state = #context_state{}}), - Context2 = gtp_path:bind(Request, Context1), + Context2 = gtp_path:bind(Request, false, Context1), gtp_context:terminate_colliding_context(Context2), gtp_context:remote_context_register_new(Context2), @@ -285,20 +285,21 @@ handle_request(ReqKey, SessionOpts = ggsn_gn:init_session_from_gtp_req(IEs, AAAopts, SessionOpts0), ProxyInfo = handle_proxy_info(Request, SessionOpts, Context2, Data), + ProxyGtpPort = ergw_proxy_lib:select_gtp_proxy_sockets(ProxyInfo, Data), %% GTP v1 services only, we don't do v1 to v2 conversion (yet) Services = [{"x-3gpp-ggsn", "x-gn"}, {"x-3gpp-ggsn", "x-gp"}, {"x-3gpp-pgw", "x-gn"}, {"x-3gpp-pgw", "x-gp"}], - ProxyGGSN = ergw_proxy_lib:select_gw(ProxyInfo, Services, NodeSelect, Context2), + ProxyGGSN = ergw_proxy_lib:select_gw(ProxyInfo, Services, NodeSelect, ProxyGtpPort, Context2), + + DPCandidates = ergw_proxy_lib:select_sx_proxy_candidate(ProxyGGSN, ProxyInfo, Data), - {ProxyGtpPort, DPCandidates} = - ergw_proxy_lib:select_proxy_sockets(ProxyGGSN, ProxyInfo, Data), SxConnectId = ergw_sx_node:request_connect(DPCandidates, NodeSelect, 1000), {ok, _} = ergw_aaa_session:invoke(Session, SessionOpts, start, #{async => true}), ProxyContext0 = init_proxy_context(ProxyGtpPort, Context2, ProxyInfo, ProxyGGSN), - ProxyContext1 = gtp_path:bind(ProxyContext0), + ProxyContext1 = gtp_path:bind(true, ProxyContext0), ergw_sx_node:wait_connect(SxConnectId), {Context, ProxyContext, PCtx} = @@ -317,7 +318,7 @@ handle_request(ReqKey, when ?IS_REQUEST_CONTEXT(ReqKey, Request, OldContext) -> Context0 = update_context_from_gtp_req(Request, OldContext), - Context1 = gtp_path:bind(Request, Context0), + Context1 = gtp_path:bind(Request, false, Context0), gtp_context:remote_context_update(OldContext, Context1), @@ -341,8 +342,8 @@ handle_request(ReqKey, proxy_context := ProxyContext0} = Data) when ?IS_REQUEST_CONTEXT(ReqKey, Request, ProxyContext0) -> - Context = gtp_path:bind(Context0), - ProxyContext = gtp_path:bind(Request, ProxyContext0), + Context = gtp_path:bind(false, Context0), + ProxyContext = gtp_path:bind(Request, true, ProxyContext0), DataNew = Data#{context => Context, proxy_context => ProxyContext}, forward_request(ggsn2sgsn, ReqKey, Request, DataNew, Data), @@ -355,8 +356,8 @@ handle_request(ReqKey, #{context := Context0, proxy_context := ProxyContext0} = Data) when ?IS_REQUEST_CONTEXT_OPTIONAL_TEI(ReqKey, Request, Context0) -> - Context = gtp_path:bind(Request, Context0), - ProxyContext = gtp_path:bind(ProxyContext0), + Context = gtp_path:bind(Request, false, Context0), + ProxyContext = gtp_path:bind(true, ProxyContext0), DataNew = Data#{context => Context, proxy_context => ProxyContext}, forward_request(sgsn2ggsn, ReqKey, Request, DataNew, Data), @@ -404,7 +405,7 @@ handle_response(#proxy_request{direction = sgsn2ggsn} = ProxyRequest, ?LOG(warning, "OK Proxy Response ~p", [Response]), ProxyContext1 = update_context_from_gtp_req(Response, PrevProxyCtx), - ProxyContext = gtp_path:bind(Response, ProxyContext1), + ProxyContext = gtp_path:bind(Response, true, ProxyContext1), gtp_context:remote_context_register(ProxyContext), Return = @@ -532,7 +533,7 @@ handle_sgsn_change(_, _, ProxyContext) -> update_path_bind(NewContext0, OldContext) when NewContext0 /= OldContext -> - NewContext = gtp_path:bind(NewContext0), + NewContext = gtp_path:bind(false, NewContext0), gtp_path:unbind(OldContext), NewContext; update_path_bind(NewContext, _OldContext) -> diff --git a/src/gtp_path.erl b/src/gtp_path.erl index 80a5f570..37d840fe 100644 --- a/src/gtp_path.erl +++ b/src/gtp_path.erl @@ -16,7 +16,7 @@ -export([start_link/4, all/1, maybe_new_path/3, handle_request/2, handle_response/4, - bind/1, bind/2, unbind/1, down/2, + bind/2, bind/3, unbind/1, down/2, get_handler/2, info/1]). %% Validate environment Variables @@ -27,7 +27,7 @@ terminate/3, code_change/4]). -ifdef(TEST). --export([ping/3, stop/1]). +-export([ping/1, ping/3, set/3, stop/1]). -endif. -include_lib("kernel/include/logger.hrl"). @@ -69,19 +69,19 @@ handle_request(#request{gtp_port = GtpPort, ip = IP} = ReqKey, #gtp{version = Ve handle_response(Path, Request, Ref, Response) -> gen_statem:cast(Path, {handle_response, Request, Ref, Response}). -bind(#context{remote_restart_counter = RestartCounter} = Context) -> - path_recovery(RestartCounter, bind_path(Context)). +bind(Active, #context{remote_restart_counter = RestartCounter} = Context) -> + bind_path_recovery(RestartCounter, Active, bind_path(Context)). bind(#gtp{ie = #{{recovery, 0} := #recovery{restart_counter = RestartCounter}} - } = Request, Context) -> - path_recovery(RestartCounter, bind_path(Request, Context)); + } = Request, Active, Context) -> + bind_path_recovery(RestartCounter, Active, bind_path(Request, Context)); bind(#gtp{ie = #{{v2_recovery, 0} := #v2_recovery{restart_counter = RestartCounter}} - } = Request, Context) -> - path_recovery(RestartCounter, bind_path(Request, Context)); -bind(Request, Context) -> - path_recovery(undefined, bind_path(Request, Context)). + } = Request, Active, Context) -> + bind_path_recovery(RestartCounter, Active, bind_path(Request, Context)); +bind(Request, Active, Context) -> + bind_path_recovery(undefined, Active, bind_path(Request, Context)). unbind(#context{version = Version, control_port = GtpPort, remote_control_teid = #fq_teid{ip = RemoteIP}}) -> @@ -121,14 +121,20 @@ get_handler(#gtp_port{type = 'gtp-c'}, v2) -> gtp_v2_c. -ifdef(TEST). +ping(Path) -> + gen_statem:call(Path, '$ping'). + ping(GtpPort, Version, IP) -> case get(GtpPort, Version, IP) of Path when is_pid(Path) -> - gen_statem:call(Path, '$ping'); + ping(Path); _ -> - ok + {error, no_found} end. +set(Path, Opt, Value) -> + gen_statem:call(Path, {'$set', Opt, Value}). + stop(Path) -> gen_statem:call(Path, '$stop'). @@ -140,22 +146,46 @@ stop(Path) -> %% Timer value: echo = echo interval when peer is up. --define(Defaults, [{t3, 10 * 1000}, % echo retry interval timeout - {n3, 5}, % echo retry count - {echo, 60 * 1000}]). % echo ping interval +-define(Defaults, [{active, false}, % keep probing path even when idle/down + {t3, 10 * 1000}, % echo retry interval + {n3, 5}, % echo retry count + {echo, 60 * 1000}, % echo ping interval + {idle_timeout, 1800 * 1000}, % time to keep the path entry when idle + {idle_echo, 600 * 1000}, % echo retry interval when idle + {down_timeout, 3600 * 1000}, % time to keep the path entry when down + {down_echo, 600 * 1000}]).% echo retry interval when down validate_options(Values) -> ergw_config:validate_options(fun validate_option/2, Values, ?Defaults, map). +validate_echo(_Opt, Value) when is_integer(Value), Value >= 60 * 1000 -> + Value; +validate_echo(_Opt, off = Value) -> + Value; +validate_echo(Opt, Value) -> + throw({error, {options, {Opt, Value}}}). + +validate_timeout(_Opt, Value) when is_integer(Value), Value >= 0 -> + Value; +validate_timeout(_Opt, infinity = Value) -> + Value; +validate_timeout(Opt, Value) -> + throw({error, {options, {Opt, Value}}}). + +validate_option(active, Value) when is_boolean(Value) -> + Value; validate_option(t3, Value) when is_integer(Value) andalso Value > 0 -> Value; validate_option(n3, Value) when is_integer(Value) andalso Value > 0 -> Value; -validate_option(echo, Value) - when is_integer(Value) andalso Value >= 60 * 1000 -> - Value; +validate_option(Opt, Value) + when Opt =:= echo; Opt =:= idle_echo; Opt =:= down_echo -> + validate_echo(Opt, Value); +validate_option(Opt, Value) + when Opt =:= idle_timeout; Opt =:= down_timeout -> + validate_timeout(Opt, Value); validate_option(Opt, Value) -> throw({error, {options, {Opt, Value}}}). @@ -171,12 +201,14 @@ callback_mode() -> [handle_event_function, state_enter]. init([#gtp_port{name = PortName} = GtpPort, Version, RemoteIP, Args]) -> RegKey = {PortName, Version, RemoteIP}, - gtp_path_reg:register(RegKey), + gtp_path_reg:register(RegKey, up), State = #state{peer = #peer{state = up, contexts = 0}, echo = stopped}, - Data0 = maps:with([t3, n3, echo], Args), + Data0 = maps:with([active, t3, n3, echo, + idle_timeout, idle_echo, + down_timeout, down_echo], Args), Data = Data0#{ %% Path Info Keys gtp_port => GtpPort, % #gtp_port{} @@ -193,6 +225,7 @@ init([#gtp_port{name = PortName} = GtpPort, Version, RemoteIP, Args]) -> handle_event(enter, #state{peer = Old}, #state{peer = Peer}, Data) when Old /= Peer -> + peer_state_change(Old, Peer, Data), State = peer_state(Peer), {keep_state_and_data, enter_peer_state_action(State, Data)}; @@ -215,22 +248,26 @@ handle_event({timeout, echo}, start_echo, #state{echo = EchoT} = State0, Data) handle_event({timeout, echo}, start_echo, _State, _Data) -> keep_state_and_data; +handle_event({timeout, peer}, stop, _State, #{reg_key := RegKey}) -> + gtp_path_reg:unregister(RegKey), + {stop, normal}; + handle_event({call, From}, all, _State, #{contexts := CtxS} = _Data) -> Reply = maps:keys(CtxS), {keep_state_and_data, [{reply, From, Reply}]}; -handle_event({call, From}, {bind, Pid}, #state{recovery = RstCnt} = State, Data) -> - register(Pid, State, Data, [{reply, From, {ok, RstCnt}}]); +handle_event({call, From}, {bind, Pid, Active}, #state{recovery = RstCnt} = State, Data) -> + register(Pid, Active, State, Data, [{reply, From, {ok, RstCnt}}]); -handle_event({call, From}, {bind, Pid, RstCnt}, State, Data) -> +handle_event({call, From}, {bind, Pid, Active, RstCnt}, State, Data) -> case update_restart_counter(RstCnt, State, Data) of initial -> - register(Pid, State#state{recovery = RstCnt}, Data, [{reply, From, ok}]); + register(Pid, Active, State#state{recovery = RstCnt}, Data, [{reply, From, ok}]); peer_restart -> %% try again after state change path_restart(RstCnt, State, Data, [postpone]); no -> - register(Pid, State, Data, [{reply, From, ok}]) + register(Pid, Active, State, Data, [{reply, From, ok}]) end; handle_event({call, From}, {unbind, Pid}, State, Data) -> @@ -290,6 +327,9 @@ handle_event({call, From}, '$ping', State0, Data) -> State = send_echo_request(State0, Data), {next_state, State, Data, [{{timeout, echo}, cancel}, {reply, From, ok}]}; +handle_event({call, From}, {'$set', Opt, Value}, _State, Data) -> + {keep_state, maps:put(Opt, Value, Data), {reply, From, maps:get(Opt, Data, undefined)}}; + handle_event({call, From}, '$stop', _State, #{reg_key := RegKey}) -> gtp_path_reg:unregister(RegKey), {stop_and_reply, normal, [{reply, From, ok}]}; @@ -321,6 +361,11 @@ peer_state(#peer{state = down}) -> down; peer_state(#peer{state = up, contexts = 0}) -> idle; peer_state(#peer{state = up}) -> busy. +peer_state_change(#peer{state = State}, #peer{state = State}, _) -> + ok; +peer_state_change(_, #peer{state = State}, #{reg_key := RegKey}) -> + gtp_path_reg:state(RegKey, State). + %%%=================================================================== %%% Internal functions %%%=================================================================== @@ -329,11 +374,21 @@ enter_peer_state_action(State, Data) -> [enter_state_timeout_action(State, Data), enter_state_echo_action(State, Data)]. +enter_state_timeout_action(idle, #{idle_timeout := Timeout}) when is_integer(Timeout) -> + {{timeout, peer}, Timeout, stop}; +enter_state_timeout_action(down, #{down_timeout := Timeout}) when is_integer(Timeout) -> + {{timeout, peer}, Timeout, stop}; enter_state_timeout_action(_State, _Data) -> {{timeout, peer}, cancel}. enter_state_echo_action(busy, #{echo := EchoInterval}) when is_integer(EchoInterval) -> {{timeout, echo}, EchoInterval, start_echo}; +enter_state_echo_action(idle, #{active := true, idle_echo := EchoInterval}) + when is_integer(EchoInterval) -> + {{timeout, echo}, EchoInterval, start_echo}; +enter_state_echo_action(down, #{active := true, down_echo := EchoInterval}) + when is_integer(EchoInterval) -> + {{timeout, echo}, EchoInterval, start_echo}; enter_state_echo_action(_, _) -> {{timeout, stop_echo}, 0, stop_echo}. @@ -420,9 +475,10 @@ update_contexts(State0, #{gtp_port := GtpPort, version := Version, ip := IP} = D Data = Data0#{contexts => CtxS}, {next_state, State, Data, Actions}. -register(Pid, State, #{contexts := CtxS} = Data, Actions) -> +register(Pid, Active, State, #{contexts := CtxS} = Data0, Actions) -> ?LOG(debug, "~s: register(~p)", [?MODULE, Pid]), MRef = erlang:monitor(process, Pid), + Data = maps:update_with(active, fun(V) -> V or Active end, Data0), update_contexts(State, Data, maps:put(Pid, MRef, CtxS), Actions). unregister(Pid, State, #{contexts := CtxS} = Data, Actions) @@ -441,12 +497,12 @@ bind_path(#context{version = Version, control_port = CntlGtpPort, Path = maybe_new_path(CntlGtpPort, Version, RemoteCntlIP), Context#context{path = Path}. -path_recovery(RestartCounter, #context{path = Path} = Context) +bind_path_recovery(RestartCounter, Active, #context{path = Path} = Context) when is_integer(RestartCounter) -> - ok = gen_statem:call(Path, {bind, self(), RestartCounter}), + ok = gen_statem:call(Path, {bind, self(), Active, RestartCounter}), Context#context{remote_restart_counter = RestartCounter}; -path_recovery(_RestartCounter, #context{path = Path} = Context) -> - {ok, PathRestartCounter} = gen_statem:call(Path, {bind, self()}), +bind_path_recovery(_RestartCounter, Active, #context{path = Path} = Context) -> + {ok, PathRestartCounter} = gen_statem:call(Path, {bind, self(), Active}), Context#context{remote_restart_counter = PathRestartCounter}. send_echo_request(State, #{gtp_port := GtpPort, handler := Handler, ip := DstIP, diff --git a/src/gtp_path_reg.erl b/src/gtp_path_reg.erl index 931c83dc..2eb68e08 100644 --- a/src/gtp_path_reg.erl +++ b/src/gtp_path_reg.erl @@ -11,11 +11,13 @@ %% API -export([start_link/0]). --export([register/1, unregister/1, lookup/1]). +-export([register/2, unregister/1, lookup/1]). -export([all/0, all/1]). +-export([state/1, state/2]). %% regine_server callbacks --export([init/1, handle_register/4, handle_unregister/3, handle_pid_remove/3, handle_death/3, terminate/2]). +-export([init/1, handle_register/4, handle_unregister/3, handle_pid_remove/3, + handle_death/3, terminate/2, handle_call/3]). %% -------------------------------------------------------------------- %% Include files @@ -33,19 +35,30 @@ start_link() -> regine_server:start_link({local, ?SERVER}, ?MODULE, []). -register(Key) -> - regine_server:register(?SERVER, self(), Key, undefined). +register(Key, State) -> + regine_server:register(?SERVER, self(), Key, State). unregister(Key) -> regine_server:unregister(?SERVER, Key, undefined). lookup(Key) -> case ets:lookup(?SERVER, Key) of - [{Key, Pid}] -> + [{Key, Pid, _}] -> Pid; _ -> undefined end. +state(Key, State) -> + regine_server:call(?SERVER, {state, Key, State}). + +state(Key) -> + case ets:lookup(?SERVER, Key) of + [{Key, _, State}] -> + State; + _ -> + undefined + end. + all() -> ets:tab2list(?SERVER). @@ -54,12 +67,12 @@ all({_,_,_,_} = IP) -> all({_,_,_,_,_,_,_,_} = IP) -> all_ip(IP); all(Port) when is_atom(Port) -> - Ms = ets:fun2ms(fun({{Name, _, _}, Pid}) when Name =:= Port -> Pid end), + Ms = ets:fun2ms(fun({{Name, _, _}, Pid, State}) when Name =:= Port -> {Pid, State} end), ets:select(?SERVER, Ms). all_ip(IP) -> %%ets:select(Tab,[{{'$1','$2','$3'},[],['$$']}]) - Ms = ets:fun2ms(fun({{_, _, PeerIP}, Pid}) when PeerIP =:= IP -> Pid end), + Ms = ets:fun2ms(fun({{_, _, PeerIP}, Pid, State}) when PeerIP =:= IP -> {Pid, State} end), ets:select(?SERVER, Ms). %%%=================================================================== @@ -70,8 +83,8 @@ init([]) -> ets:new(?SERVER, [ordered_set, named_table, public, {keypos, 1}]), {ok, #state{}}. -handle_register(Pid, Key, _Value, State) -> - ets:insert(?SERVER, {Key, Pid}), +handle_register(Pid, Key, Value, State) -> + ets:insert(?SERVER, {Key, Pid, Value}), {ok, [Key], State}. handle_unregister(Key, _Value, State) -> @@ -81,11 +94,15 @@ handle_pid_remove(_Pid, Keys, State) -> lists:foreach(fun(Key) -> ets:delete(?SERVER, Key) end, Keys), State. +handle_call({state, Key, PeerState}, _From, State) -> + Result = ets:update_element(?SERVER, Key, {3, PeerState}), + {reply, Result, State}. + handle_death(_Pid, _Reason, State) -> State. terminate(_Reason, _State) -> - ok. + ok. %%%=================================================================== %%% Internal functions diff --git a/src/pgw_s5s8.erl b/src/pgw_s5s8.erl index c57e9ed3..b2b6c28f 100644 --- a/src/pgw_s5s8.erl +++ b/src/pgw_s5s8.erl @@ -368,7 +368,7 @@ handle_request(ReqKey, Context1 = update_context_tunnel_ids(FqCntlTEID, FqDataTEID, Context0), Context2 = update_context_from_gtp_req(Request, Context1), - ContextPreAuth = gtp_path:bind(Request, Context2), + ContextPreAuth = gtp_path:bind(Request, false, Context2), gtp_context:terminate_colliding_context(ContextPreAuth), @@ -472,7 +472,7 @@ handle_request(ReqKey, Context0 = update_context_tunnel_ids(FqCntlTEID, FqDataTEID, OldContext), Context1 = update_context_from_gtp_req(Request, Context0), - Context = gtp_path:bind(Request, Context1), + Context = gtp_path:bind(Request, false, Context1), URRActions = update_session_from_gtp_req(IEs, Session, Context), Data1 = if Context /= OldContext -> @@ -641,7 +641,7 @@ handle_response(CommandReqKey, #{context := Context0, pfcp := PCtx, 'Session' := Session} = Data0) -> gtp_context:request_finished(CommandReqKey), - Context = gtp_path:bind(Response, Context0), + Context = gtp_path:bind(Response, false, Context0), Data = Data0#{context => Context}, if Cause =:= request_accepted andalso BearerCause =:= request_accepted -> @@ -671,7 +671,7 @@ handle_response({From, TermCause}, ie = #{?'Cause' := #v2_cause{v2_cause = RespCause}} = IEs} = Response, _Request, _State, #{context := Context0, 'Session' := Session} = Data) -> - Context = gtp_path:bind(Response, Context0), + Context = gtp_path:bind(Response, false, Context0), process_secondary_rat_usage_data_reports(IEs, Context, Session), close_pdn_context(TermCause, Data), if is_tuple(From) -> gen_statem:reply(From, {ok, RespCause}); @@ -864,7 +864,7 @@ apply_context_change(NewContext0, OldContext, URRActions, _ -> #{} end, - NewContext = gtp_path:bind(NewContext0), + NewContext = gtp_path:bind(false, NewContext0), {PCtx, UsageReport} = ergw_gsn_lib:modify_sgi_session(PCC, URRActions, ModifyOpts, NewContext, PCtx0), diff --git a/src/pgw_s5s8_proxy.erl b/src/pgw_s5s8_proxy.erl index 7672dff5..306a8f7d 100644 --- a/src/pgw_s5s8_proxy.erl +++ b/src/pgw_s5s8_proxy.erl @@ -284,7 +284,7 @@ handle_request(ReqKey, 'Session' := Session} = Data) -> Context1 = update_context_from_gtp_req(Request, Context0#context{state = #context_state{}}), - Context2 = gtp_path:bind(Request, Context1), + Context2 = gtp_path:bind(Request, false, Context1), gtp_context:terminate_colliding_context(Context2), gtp_context:remote_context_register_new(Context2), @@ -293,19 +293,20 @@ handle_request(ReqKey, SessionOpts = pgw_s5s8:init_session_from_gtp_req(IEs, AAAopts, Context2, SessionOpts0), ProxyInfo = handle_proxy_info(Request, SessionOpts, Context2, Data), + ProxyGtpPort = ergw_proxy_lib:select_gtp_proxy_sockets(ProxyInfo, Data), %% GTP v2 services only, we don't do v1 to v2 conversion (yet) Services = [{"x-3gpp-pgw", "x-s8-gtp"}, {"x-3gpp-pgw", "x-s5-gtp"}], - ProxyGGSN = ergw_proxy_lib:select_gw(ProxyInfo, Services, NodeSelect, Context2), + ProxyGGSN = ergw_proxy_lib:select_gw(ProxyInfo, Services, NodeSelect, ProxyGtpPort, Context2), + + DPCandidates = ergw_proxy_lib:select_sx_proxy_candidate(ProxyGGSN, ProxyInfo, Data), - {ProxyGtpPort, DPCandidates} = - ergw_proxy_lib:select_proxy_sockets(ProxyGGSN, ProxyInfo, Data), SxConnectId = ergw_sx_node:request_connect(DPCandidates, NodeSelect, 1000), {ok, _} = ergw_aaa_session:invoke(Session, SessionOpts, start, #{async =>true}), ProxyContext0 = init_proxy_context(ProxyGtpPort, Context2, ProxyInfo, ProxyGGSN), - ProxyContext1 = gtp_path:bind(ProxyContext0), + ProxyContext1 = gtp_path:bind(true, ProxyContext0), ergw_sx_node:wait_connect(SxConnectId), {Context, ProxyContext, PCtx} = @@ -324,7 +325,7 @@ handle_request(ReqKey, when ?IS_REQUEST_CONTEXT(ReqKey, Request, OldContext) -> Context0 = update_context_from_gtp_req(Request, OldContext), - Context1 = gtp_path:bind(Request, Context0), + Context1 = gtp_path:bind(Request, false, Context0), gtp_context:remote_context_update(OldContext, Context1), @@ -445,7 +446,7 @@ handle_response(#proxy_request{direction = sgw2pgw} = ProxyRequest, ?LOG(warning, "OK Proxy Response ~p", [Response]), ProxyContext1 = update_context_from_gtp_req(Response, PrevProxyCtx), - ProxyContext = gtp_path:bind(Response, ProxyContext1), + ProxyContext = gtp_path:bind(Response, true, ProxyContext1), gtp_context:remote_context_register(ProxyContext), Return = @@ -616,7 +617,7 @@ handle_sgw_change(_, _, ProxyContext) -> update_path_bind(NewContext0, OldContext) when NewContext0 /= OldContext -> - NewContext = gtp_path:bind(NewContext0), + NewContext = gtp_path:bind(false, NewContext0), gtp_path:unbind(OldContext), NewContext; update_path_bind(NewContext, _OldContext) -> @@ -801,14 +802,14 @@ initiate_session_teardown(pgw2sgw, bind_forward_path(sgw2pgw, Request, #{context := Context, proxy_context := ProxyContext} = Data) -> Data#{ - context => gtp_path:bind(Request, Context), - proxy_context => gtp_path:bind(ProxyContext) + context => gtp_path:bind(Request, false, Context), + proxy_context => gtp_path:bind(true, ProxyContext) }; bind_forward_path(pgw2sgw, Request, #{context := Context, proxy_context := ProxyContext} = Data) -> Data#{ - context => gtp_path:bind(Context), - proxy_context => gtp_path:bind(Request, ProxyContext) + context => gtp_path:bind(false, Context), + proxy_context => gtp_path:bind(Request, true, ProxyContext) }. fteid_forward_context(#f_teid{ipv4 = IPv4, ipv6 = IPv6, teid = TEID}, diff --git a/src/saegw_s11.erl b/src/saegw_s11.erl index 52e634de..26d8d19e 100644 --- a/src/saegw_s11.erl +++ b/src/saegw_s11.erl @@ -363,7 +363,7 @@ handle_request(ReqKey, Context1 = update_context_tunnel_ids(FqCntlTEID, FqDataTEID, Context0), Context2 = update_context_from_gtp_req(Request, Context1), - ContextPreAuth = gtp_path:bind(Request, Context2), + ContextPreAuth = gtp_path:bind(Request, false, Context2), gtp_context:terminate_colliding_context(ContextPreAuth), @@ -461,7 +461,7 @@ handle_request(ReqKey, Context0 = update_context_tunnel_ids(FqCntlTEID, FqDataTEID, OldContext), Context1 = update_context_from_gtp_req(Request, Context0), - Context = gtp_path:bind(Request, Context1), + Context = gtp_path:bind(Request, false, Context1), URRActions = update_session_from_gtp_req(IEs, Session, Context), Data1 = if Context /= OldContext -> @@ -580,7 +580,7 @@ handle_response(_, }} = IEs} = Response, _Request, run, #{context := Context0, pfcp := PCtx, 'Session' := Session} = Data0) -> - Context = gtp_path:bind(Response, Context0), + Context = gtp_path:bind(Response, false, Context0), Data = Data0#{context => Context}, if Cause =:= request_accepted andalso BearerCause =:= request_accepted -> @@ -610,7 +610,7 @@ handle_response({From, TermCause}, ie = #{?'Cause' := #v2_cause{v2_cause = Cause}}} = Response, _Request, _State, #{context := Context0} = Data) -> - Context = gtp_path:bind(Response, Context0), + Context = gtp_path:bind(Response, false, Context0), close_pdn_context(TermCause, Data), if is_tuple(From) -> gen_statem:reply(From, {ok, Cause}); true -> ok @@ -796,7 +796,7 @@ defer_usage_report(URRActions, UsageReport) -> apply_context_change(NewContext0, OldContext, URRActions, #{pfcp := PCtx0, pcc := PCC} = Data) -> ModifyOpts = #{send_end_marker => true}, - NewContext = gtp_path:bind(NewContext0), + NewContext = gtp_path:bind(false, NewContext0), {PCtx, UsageReport} = ergw_gsn_lib:modify_sgi_session(PCC, URRActions, ModifyOpts, NewContext, PCtx0), diff --git a/test/config_SUITE.erl b/test/config_SUITE.erl index 0ad0bb3a..c51e2f13 100644 --- a/test/config_SUITE.erl +++ b/test/config_SUITE.erl @@ -115,7 +115,11 @@ {'remote-irx', [{type, 'gtp-c'}, {ip, ?FINAL_GSN_IPv4}, {reuseaddr, true} - ]} + ]}, + {'remote-irx2', [{type, 'gtp-c'}, + {ip, ?FINAL_GSN2_IPv4}, + {reuseaddr, true} + ]} ]}, {handlers, @@ -130,7 +134,7 @@ ]}, %% remote GGSN handler {gn, [{handler, ggsn_gn}, - {sockets, ['remote-irx']}, + {sockets, ['remote-irx', 'remote-irx2']}, {node_selection, [static]}, {aaa, [{'Username', [{default, ['IMSI', <<"@">>, 'APN']}]}]} @@ -200,7 +204,11 @@ {path_management, [{t3, 10 * 1000}, {n3, 5}, - {echo, 60 * 1000}] + {echo, 60 * 1000}, + {idle_timeout, 1800 * 1000}, + {idle_echo, 600 * 1000}, + {down_timeout, 3600 * 1000}, + {down_echo, 600 * 1000}] } ]). @@ -304,7 +312,11 @@ {'remote-irx', [{type, 'gtp-c'}, {ip, ?FINAL_GSN_IPv4}, {reuseaddr, true} - ]} + ]}, + {'remote-irx2', [{type, 'gtp-c'}, + {ip, ?FINAL_GSN2_IPv4}, + {reuseaddr, true} + ]} ]}, {handlers, @@ -324,13 +336,13 @@ ]}, %% remote PGW handler {gn, [{handler, pgw_s5s8}, - {sockets, ['remote-irx']}, + {sockets, ['remote-irx', 'remote-irx2']}, {node_selection, [static]}, {aaa, [{'Username', [{default, ['IMSI', <<"@">>, 'APN']}]}]} ]}, {s5s8, [{handler, pgw_s5s8}, - {sockets, ['remote-irx']}, + {sockets, ['remote-irx', 'remote-irx2']}, {node_selection, [static]} ]} ]}, @@ -1046,6 +1058,12 @@ config(_Config) -> ?ok_option(set_cfg_value([path_management, t3], 10 * 1000, ?PGW_PROXY_CONFIG)), ?ok_option(set_cfg_value([path_management, n3], 5, ?PGW_PROXY_CONFIG)), ?ok_option(set_cfg_value([path_management, echo], 60 * 1000, ?PGW_PROXY_CONFIG)), + ?ok_option(set_cfg_value([path_management, idle_echo], 60 * 1000, ?PGW_PROXY_CONFIG)), + ?ok_option(set_cfg_value([path_management, down_echo], 60 * 1000, ?PGW_PROXY_CONFIG)), + ?ok_option(set_cfg_value([path_management, idle_timeout], 300 * 1000, ?PGW_PROXY_CONFIG)), + ?ok_option(set_cfg_value([path_management, down_timeout], 7200 * 1000, ?PGW_PROXY_CONFIG)), + ?ok_option(set_cfg_value([path_management, idle_timeout], 0, ?PGW_PROXY_CONFIG)), + ?ok_option(set_cfg_value([path_management, down_timeout], 0, ?PGW_PROXY_CONFIG)), ?error_option(set_cfg_value([path_management, t3], -1, ?PGW_PROXY_CONFIG)), ?error_option(set_cfg_value([path_management, n3], -1, ?PGW_PROXY_CONFIG)), @@ -1054,4 +1072,11 @@ config(_Config) -> ?error_option(set_cfg_value([path_management, echo], 59 * 1000, ?PGW_PROXY_CONFIG)), ?error_option(set_cfg_value([path_management, echo], invalid, ?PGW_PROXY_CONFIG)), + ?error_option(set_cfg_value([path_management, idle_echo], 59 * 1000, ?PGW_PROXY_CONFIG)), + ?error_option(set_cfg_value([path_management, down_echo], 59 * 1000, ?PGW_PROXY_CONFIG)), + + ?error_option(set_cfg_value([path_management, idle_timeout], -1, ?PGW_PROXY_CONFIG)), + ?error_option(set_cfg_value([path_management, down_timeout], -1, ?PGW_PROXY_CONFIG)), + ?error_option(set_cfg_value([path_management, idle_timeout], invalid, ?PGW_PROXY_CONFIG)), + ?error_option(set_cfg_value([path_management, down_timeout], invalid, ?PGW_PROXY_CONFIG)), ok. diff --git a/test/ergw_ggsn_test_lib.erl b/test/ergw_ggsn_test_lib.erl index d17903c3..a572e405 100644 --- a/test/ergw_ggsn_test_lib.erl +++ b/test/ergw_ggsn_test_lib.erl @@ -607,6 +607,8 @@ execute_request(MsgType, SubType, GtpC0) -> {validate_response(MsgType, SubType, Response, GtpC), Msg, Response}. +apn(M) when is_map(M) -> + apn(maps:get(apn, M, default)); apn(invalid_apn) -> [<<"IN", "VA", "LID">>]; apn(dotted_apn) -> ?'APN-EXA.MPLE'; apn(proxy_apn) -> ?'APN-PROXY'; @@ -616,8 +618,11 @@ apn({_, _, APN}) APN =:= v6only; APN =:= prefV6; APN =:= sgsnemu -> [atom_to_binary(APN, latin1)]; +apn([Label|_] = APN) when is_binary(Label) -> APN; apn(_) -> ?'APN-EXAMPLE'. +imsi(M, TEI) when is_map(M) -> + imsi(maps:get(imsi, M, random), TEI); imsi('2nd', _) -> <<"454545454545452">>; imsi(random, TEI) -> @@ -625,12 +630,14 @@ imsi(random, TEI) -> imsi(_, _) -> ?IMSI. +imei(M, TEI) when is_map(M) -> + imei(maps:get(imei, M, random), TEI); imei('2nd', _) -> <<"6543210987654321">>; imei(random, TEI) -> integer_to_binary(7000000000000000 + TEI); imei(_, _) -> - <<"1234567890123456">>. + <<"1234567890123456">>. %%%=================================================================== %%% GGSN injected functions diff --git a/test/ergw_pgw_test_lib.erl b/test/ergw_pgw_test_lib.erl index 97a474dc..14e6f5fd 100644 --- a/test/ergw_pgw_test_lib.erl +++ b/test/ergw_pgw_test_lib.erl @@ -1056,6 +1056,8 @@ execute_request(MsgType, SubType, GtpC0) -> {validate_response(MsgType, SubType, Response, GtpC), Msg, Response}. +apn(M) when is_map(M) -> + apn(maps:get(apn, M, default)); apn(invalid_apn) -> [<<"IN", "VA", "LID">>]; apn(dotted_apn) -> ?'APN-EXA.MPLE'; apn(proxy_apn) -> ?'APN-PROXY'; @@ -1064,8 +1066,11 @@ apn({_, _, APN}) when APN =:= v4only; APN =:= prefV4; APN =:= v6only; APN =:= prefV6 -> [atom_to_binary(APN, latin1)]; +apn([Label|_] = APN) when is_binary(Label) -> APN; apn(_) -> ?'APN-ExAmPlE'. +imsi(M, TEI) when is_map(M) -> + imsi(maps:get(imsi, M, random), TEI); imsi('2nd', _) -> <<"454545454545452">>; imsi(random, TEI) -> @@ -1073,6 +1078,8 @@ imsi(random, TEI) -> imsi(_, _) -> ?IMSI. +imei(M, TEI) when is_map(M) -> + imei(maps:get(imei, M, random), TEI); imei('2nd', _) -> <<"490154203237518">>; imei(random, TEI) -> diff --git a/test/ergw_test_lib.erl b/test/ergw_test_lib.erl index 08609e2a..624796a0 100644 --- a/test/ergw_test_lib.erl +++ b/test/ergw_test_lib.erl @@ -18,12 +18,13 @@ meck_unload/1, meck_validate/1]). -export([init_seq_no/2, - gtp_context/1, gtp_context/2, + gtp_context/1, gtp_context/2, gtp_context/3, gtp_context_inc_seq/1, gtp_context_inc_restart_counter/1, gtp_context_new_teids/1, gtp_context_new_teids/3, make_error_indication_report/1]). --export([start_gtpc_server/1, stop_gtpc_server/1, stop_gtpc_server/0, +-export([start_gtpc_server/1, start_gtpc_server/2, + stop_gtpc_server/1, stop_gtpc_server/0, wait_for_all_sx_nodes/0, reconnect_all_sx_nodes/0, stop_all_sx_nodes/0, make_gtp_socket/1, make_gtp_socket/2, send_pdu/2, send_pdu/3, @@ -132,6 +133,7 @@ group_config(ipv4, Config) -> {test_gsn, ?TEST_GSN_IPv4}, {proxy_gsn, ?PROXY_GSN_IPv4}, {final_gsn, ?FINAL_GSN_IPv4}, + {final_gsn_2, ?FINAL_GSN2_IPv4}, {sgw_u_sx, ?SGW_U_SX_IPv4}, {pgw_u01_sx, ?PGW_U01_SX_IPv4}, {pgw_u02_sx, ?PGW_U02_SX_IPv4}, @@ -144,6 +146,7 @@ group_config(ipv6, Config) -> {test_gsn, ?TEST_GSN_IPv6}, {proxy_gsn, ?PROXY_GSN_IPv6}, {final_gsn, ?FINAL_GSN_IPv6}, + {final_gsn_2, ?FINAL_GSN2_IPv6}, {sgw_u_sx, ?SGW_U_SX_IPv6}, {pgw_u01_sx, ?PGW_U01_SX_IPv6}, {pgw_u02_sx, ?PGW_U02_SX_IPv6}, @@ -247,6 +250,9 @@ gtp_context(Config) -> gtp_context(?MODULE, Config). gtp_context(Counter, Config) -> + gtp_context(Counter, proplists:get_value(client_ip, Config), Config). + +gtp_context(Counter, ClientIP, Config) -> GtpC = #gtpc{ counter = Counter, restart_counter = @@ -254,11 +260,11 @@ gtp_context(Counter, Config) -> seq_no = ets:update_counter(?MODULE, {Counter, seq_no}, 1) band 16#7fffff, - socket = make_gtp_socket(0, Config), + socket = make_gtp_socket(0, ClientIP), ue_ip = {undefined, undefined}, - local_ip = proplists:get_value(client_ip, Config), + local_ip = ClientIP, remote_ip = proplists:get_value(test_gsn, Config), rat_type = 1 @@ -314,10 +320,13 @@ make_error_indication_report(IP, TEI) -> %%%=================================================================== %% GTP-C default port (2123) handler -gtpc_server_init(Owner, Config) -> +gtpc_server_init(Owner, Config) when is_list(Config) -> + gtpc_server_init(Owner, proplists:get_value(client_ip, Config)); + +gtpc_server_init(Owner, IP) when is_tuple(IP) -> process_flag(trap_exit, true), - CntlS = make_gtp_socket(?GTP2c_PORT, Config), + CntlS = make_gtp_socket(?GTP2c_PORT, IP), proc_lib:init_ack(Owner, {ok, self()}), gtpc_server_loop(Owner, CntlS). @@ -339,8 +348,11 @@ gtpc_server_loop(Owner, CntlS) -> end. start_gtpc_server(Config) -> + start_gtpc_server(Config, gtpc_client_server). + +start_gtpc_server(Config, Name) -> {ok, Pid} = proc_lib:start_link(?MODULE, gtpc_server_init, [self(), Config]), - register(gtpc_client_server, Pid), + register(Name, Pid), Pid. wait_for_all_sx_nodes() -> @@ -366,11 +378,11 @@ stop_all_sx_nodes(_) -> timer:sleep(10), stop_all_sx_nodes(supervisor:which_children(ergw_sx_node_sup)). -stop_gtpc_server(_) -> - stop_gtpc_server(). - stop_gtpc_server() -> - case whereis(gtpc_client_server) of + stop_gtpc_server(gtp_client_server). + +stop_gtpc_server(Name) -> + case whereis(Name) of Pid when is_pid(Pid) -> unlink(Pid), exit(Pid, shutdown); @@ -395,10 +407,11 @@ sx_nodes_up(N, Cnt) -> make_gtp_socket(Config) -> make_gtp_socket(?GTP2c_PORT, Config). -make_gtp_socket(Port, Config) -> - {ok, S} = gen_udp:open(Port, [{ip, proplists:get_value(client_ip, Config)}, {active, false}, - binary, {reuseaddr, true}]), - S. +make_gtp_socket(Port, IP) when is_tuple(IP) -> + {ok, S} = gen_udp:open(Port, [{ip, IP}, {active, false}, binary, {reuseaddr, true}]), + S; +make_gtp_socket(Port, Config) when is_list(Config) -> + make_gtp_socket(Port, proplists:get_value(client_ip, Config)). send_pdu(#gtpc{socket = S, remote_ip = IP}, Msg) -> send_pdu(S, IP, Msg). diff --git a/test/ergw_test_lib.hrl b/test/ergw_test_lib.hrl index d6c082d8..7e784120 100644 --- a/test/ergw_test_lib.hrl +++ b/test/ergw_test_lib.hrl @@ -16,12 +16,13 @@ meck_unload/1, meck_validate/1]). -import('ergw_test_lib', [init_seq_no/2, - gtp_context/1, gtp_context/2, + gtp_context/1, gtp_context/2, gtp_context/3, gtp_context_inc_seq/1, gtp_context_inc_restart_counter/1, gtp_context_new_teids/1, make_error_indication_report/1]). --import('ergw_test_lib', [start_gtpc_server/1, stop_gtpc_server/1, stop_gtpc_server/0, +-import('ergw_test_lib', [start_gtpc_server/1, start_gtpc_server/2, + stop_gtpc_server/1, stop_gtpc_server/0, wait_for_all_sx_nodes/0, reconnect_all_sx_nodes/0, stop_all_sx_nodes/0, make_gtp_socket/1, make_gtp_socket/2, @@ -46,6 +47,7 @@ -define(TEST_GSN_IPv4, ?LOCALHOST_IPv4). -define(PROXY_GSN_IPv4, {127,0,100,1}). -define(FINAL_GSN_IPv4, {127,0,200,1}). +-define(FINAL_GSN2_IPv4, {127,0,200,2}). -define(SGW_U_SX_IPv4, {127,0,100,1}). -define(PGW_U01_SX_IPv4, {127,0,200,1}). @@ -57,6 +59,7 @@ -define(TEST_GSN_IPv6, ?LOCALHOST_IPv6). -define(PROXY_GSN_IPv6, {16#fd96, 16#dcd2, 16#efdb, 16#41c3, 0, 0, 0, 16#20}). -define(FINAL_GSN_IPv6, {16#fd96, 16#dcd2, 16#efdb, 16#41c3, 0, 0, 0, 16#30}). +-define(FINAL_GSN2_IPv6, {16#fd96, 16#dcd2, 16#efdb, 16#41c3, 0, 0, 0, 16#40}). -define(SGW_U_SX_IPv6, {16#fd96, 16#dcd2, 16#efdb, 16#41c3, 0, 0, 0, 16#20}). -define(PGW_U01_SX_IPv6, {16#fd96, 16#dcd2, 16#efdb, 16#41c3, 0, 0, 0, 16#30}). @@ -66,6 +69,7 @@ -define('APN-EXAMPLE', [<<"example">>, <<"net">>]). -define('APN-ExAmPlE', [<<"eXaMpLe">>, <<"net">>]). -define('APN-EXA.MPLE', [<<"exa.mple">>, <<"net">>]). +-define('APN-LB-1', [<<"lb-1">>]). -define('IMSI', <<"111111111111111">>). -define('MSISDN', <<"440000000000">>). diff --git a/test/ergw_test_sx_up.erl b/test/ergw_test_sx_up.erl index 7499ebce..b1bfc52c 100644 --- a/test/ergw_test_sx_up.erl +++ b/test/ergw_test_sx_up.erl @@ -318,7 +318,8 @@ handle_message(#pfcp{type = association_setup_request, user_plane_ip_resource_information([<<"cp">>], State0), user_plane_ip_resource_information([<<"irx">>], State0), user_plane_ip_resource_information([<<"proxy-irx">>], State0), - user_plane_ip_resource_information([<<"remote-irx">>], State0) + user_plane_ip_resource_information([<<"remote-irx">>], State0), + user_plane_ip_resource_information([<<"remote-irx2">>], State0) ], State = State0#state{cp_recovery_ts = CpRecoveryTS}, sx_reply(association_setup_response, RespIEs, State); diff --git a/test/ggsn_proxy_SUITE.erl b/test/ggsn_proxy_SUITE.erl index 9cc04ae7..9cdbf95e 100644 --- a/test/ggsn_proxy_SUITE.erl +++ b/test/ggsn_proxy_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright 2017, Travelping GmbH +%% Copyright 2017-2020, Travelping GmbH %% This program is free software; you can redistribute it and/or %% modify it under the terms of the GNU General Public License @@ -17,6 +17,8 @@ -include("ergw_ggsn_test_lib.hrl"). -define(TIMEOUT, 2000). +-define(NUM_OF_CLIENTS, 6). %% Num of IP clients for multi contexts + -define(HUT, ggsn_gn_proxy). %% Handler Under Test %%%=================================================================== @@ -39,7 +41,8 @@ ]}, {ergw, [{'$setup_vars', - [{"ORIGIN", {value, "epc.mnc001.mcc001.3gppnetwork.org"}}]}, + [{"ORIGIN", {value, "epc.mnc001.mcc001.3gppnetwork.org"}}, + {"HOMECC", {value, "epc.mnc000.mcc700.3gppnetwork.org"}}]}, {dp_handler, '$meck'}, {sockets, [{cp, [{type, 'gtp-u'}, @@ -57,7 +60,11 @@ {'remote-irx', [{type, 'gtp-c'}, {ip, ?MUST_BE_UPDATED}, {reuseaddr, true} - ]} + ]}, + {'remote-irx2', [{type, 'gtp-c'}, + {ip, ?MUST_BE_UPDATED}, + {reuseaddr, true} + ]} ]}, {ip_pools, @@ -86,7 +93,7 @@ ]}, %% remote GGSN handler {gn, [{handler, ggsn_gn}, - {sockets, ['remote-irx']}, + {sockets, ['remote-irx', 'remote-irx2']}, {node_selection, [default]}, {aaa, [{'Username', [{default, ['IMSI', <<"@">>, 'APN']}]}]} @@ -115,8 +122,16 @@ [{"x-3gpp-upf","x-sxb"}], "topon.pgw-1.nodes.$ORIGIN"}, + {"lb-1.apn.$HOMECC", {300,64536}, + [{"x-3gpp-ggsn","x-gn"},{"x-3gpp-ggsn","x-gp"}], + "topon.gtp.ggsn.$ORIGIN"}, + {"lb-1.apn.$HOMECC", {300,64536}, + [{"x-3gpp-ggsn","x-gn"},{"x-3gpp-ggsn","x-gp"}], + "topon.gtp.ggsn-2.$ORIGIN"}, + %% A/AAAA record alternatives {"topon.gtp.ggsn.$ORIGIN", ?MUST_BE_UPDATED, []}, + {"topon.gtp.ggsn-2.$ORIGIN", ?MUST_BE_UPDATED, []}, {"topon.sx.sgw-u01.$ORIGIN", ?MUST_BE_UPDATED, []}, {"topon.sx.pgw-u01.$ORIGIN", ?MUST_BE_UPDATED, []}, {"topon.pgw-1.nodes.$ORIGIN", ?MUST_BE_UPDATED, []}, @@ -137,7 +152,8 @@ {apns, [{?'APN-PROXY', [{vrf, example}, - {ip_pools, ['pool-A']}]} + {ip_pools, ['pool-A']}]}, + {?'APN-LB-1', [{vrf, example}, {ip_pools, ['pool-A']}]} ]}, {charging, @@ -174,6 +190,7 @@ {irx, [{features, ['Access']}]}, {'proxy-irx', [{features, ['Core']}]}, {'remote-irx', [{features, ['Access']}]}, + {'remote-irx2', [{features, ['Access']}]}, {example, [{features, ['SGi-LAN']}]}] }, {ip_pools, ['pool-A']}] @@ -182,7 +199,16 @@ {"topon.sx.pgw-u01.$ORIGIN", [connect]}, {"topon.upf-1.nodes.$ORIGIN", [connect]} ] - } + }, + + {path_management, [{active, false}, + {t3, 10 * 1000}, + {n3, 5}, + {echo, 60 * 1000}, + {idle_timeout, 1800 * 1000}, + {idle_echo, 600 * 1000}, + {down_timeout, 3600 * 1000}, + {down_echo, 600 * 1000}]} ]}, {ergw_aaa, @@ -245,7 +271,8 @@ ]}, {ergw, [{'$setup_vars', - [{"ORIGIN", {value, "epc.mnc001.mcc001.3gppnetwork.org"}}]}, + [{"ORIGIN", {value, "epc.mnc001.mcc001.3gppnetwork.org"}}, + {"HOMECC", {value, "epc.mnc000.mcc700.3gppnetwork.org"}}]}, {dp_handler, '$meck'}, {sockets, [{cp, [{type, 'gtp-u'}, @@ -259,7 +286,11 @@ {'remote-irx', [{type, 'gtp-c'}, {ip, ?FINAL_GSN_IPv4}, {reuseaddr, true} - ]} + ]}, + {'remote-irx2', [{type, 'gtp-c'}, + {ip, ?MUST_BE_UPDATED}, + {reuseaddr, true} + ]} ]}, {ip_pools, @@ -288,7 +319,7 @@ ]}, %% remote GGSN handler {gn, [{handler, ggsn_gn}, - {sockets, ['remote-irx']}, + {sockets, ['remote-irx', 'remote-irx2']}, {node_selection, [default]}, {aaa, [{'Username', [{default, ['IMSI', <<"@">>, 'APN']}]}]} @@ -317,8 +348,16 @@ [{"x-3gpp-upf","x-sxb"}], "topon.pgw-1.nodes.$ORIGIN"}, + {"lb-1.apn.$HOMECC", {300,64536}, + [{"x-3gpp-ggsn","x-gn"},{"x-3gpp-ggsn","x-gp"}], + "topon.gtp.ggsn.$ORIGIN"}, + {"lb-1.apn.$HOMECC", {300,64536}, + [{"x-3gpp-ggsn","x-gn"},{"x-3gpp-ggsn","x-gp"}], + "topon.gtp.ggsn-2.$ORIGIN"}, + %% A/AAAA record alternatives {"topon.gtp.ggsn.$ORIGIN", ?MUST_BE_UPDATED, []}, + {"topon.gtp.ggsn-2.$ORIGIN", ?MUST_BE_UPDATED, []}, {"topon.sx.sgw-u01.$ORIGIN", ?MUST_BE_UPDATED, []}, {"topon.sx.pgw-u01.$ORIGIN", ?MUST_BE_UPDATED, []}, {"topon.pgw-1.nodes.$ORIGIN", ?MUST_BE_UPDATED, []}, @@ -339,7 +378,8 @@ {apns, [{?'APN-PROXY', [{vrf, example}, - {ip_pools, ['pool-A']}]} + {ip_pools, ['pool-A']}]}, + {?'APN-LB-1', [{vrf, example}, {ip_pools, ['pool-A']}]} ]}, {charging, @@ -375,6 +415,7 @@ [{cp, [{features, ['CP-Function']}]}, {irx, [{features, ['Access', 'Core']}]}, {'remote-irx', [{features, ['Access']}]}, + {'remote-irx2', [{features, ['Access']}]}, {example, [{features, ['SGi-LAN']}]}] }, {ip_pools, ['pool-A']}] @@ -383,7 +424,16 @@ {"topon.sx.pgw-u01.$ORIGIN", [connect]}, {"topon.upf-1.nodes.$ORIGIN", [connect]} ] - } + }, + + {path_management, [{active, false}, + {t3, 10 * 1000}, + {n3, 5}, + {echo, 60 * 1000}, + {idle_timeout, 1800 * 1000}, + {idle_echo, 600 * 1000}, + {down_timeout, 3600 * 1000}, + {down_echo, 600 * 1000}]} ]}, {ergw_aaa, @@ -435,9 +485,12 @@ {[sockets, irx, ip], test_gsn}, {[sockets, 'proxy-irx', ip], proxy_gsn}, {[sockets, 'remote-irx', ip], final_gsn}, + {[sockets, 'remote-irx2', ip], final_gsn_2}, {[sx_socket, ip], localhost}, {[node_selection, {default, 2}, 2, "topon.gtp.ggsn.$ORIGIN"], {fun node_sel_update/2, final_gsn}}, + {[node_selection, {default, 2}, 2, "topon.gtp.ggsn-2.$ORIGIN"], + {fun node_sel_update/2, final_gsn_2}}, {[node_selection, {default, 2}, 2, "topon.sx.sgw-u01.$ORIGIN"], {fun node_sel_update/2, sgw_u_sx}}, {[node_selection, {default, 2}, 2, "topon.sx.pgw-u01.$ORIGIN"], @@ -452,9 +505,12 @@ [{[sockets, cp, ip], localhost}, {[sockets, irx, ip], test_gsn}, {[sockets, 'remote-irx', ip], final_gsn}, + {[sockets, 'remote-irx2', ip], final_gsn_2}, {[sx_socket, ip], localhost}, {[node_selection, {default, 2}, 2, "topon.gtp.ggsn.$ORIGIN"], {fun node_sel_update/2, final_gsn}}, + {[node_selection, {default, 2}, 2, "topon.gtp.ggsn-2.$ORIGIN"], + {fun node_sel_update/2, final_gsn_2}}, {[node_selection, {default, 2}, 2, "topon.sx.sgw-u01.$ORIGIN"], {fun node_sel_update/2, sgw_u_sx}}, {[node_selection, {default, 2}, 2, "topon.sx.pgw-u01.$ORIGIN"], @@ -527,11 +583,14 @@ common() -> create_pdp_context_request_accept_new, path_restart, path_restart_recovery, path_failure_to_ggsn, + path_failure_to_ggsn_and_restore, path_failure_to_sgsn, simple_pdp_context_request, simple_pdp_context_request_no_proxy_map, create_pdp_context_request_resend, create_pdp_context_proxy_request_resend, + create_lb_multi_context, + one_lb_node_down, delete_pdp_context_request_resend, delete_pdp_context_request_timeout, error_indication_sgsn2ggsn, @@ -646,10 +705,20 @@ init_per_testcase(request_fast_resend, Config) -> meck:passthrough([Request, Msg, Resent, State, Data]) end), Config; +init_per_testcase(path_failure_to_ggsn_and_restore, Config) -> + set_path_timers(#{'down_echo' => 1}), + setup_per_testcase(Config), + Config; init_per_testcase(simple_pdp_context_request, Config) -> setup_per_testcase(Config), ok = meck:new(ggsn_gn, [passthrough, no_link]), Config; +init_per_testcase(TestCase, Config) + when TestCase == create_lb_multi_context; + TestCase == one_lb_node_down -> + setup_per_testcase(Config), + ok = meck:new(ggsn_gn, [passthrough, no_link]), + Config; init_per_testcase(ggsn_update_pdp_context_request, Config) -> %% our GGSN does not send update_bearer_request, so we have to fake them setup_per_testcase(Config), @@ -693,6 +762,12 @@ end_per_testcase(path_restart, Config) -> meck:unload(gtp_path), end_per_testcase(Config), Config; +end_per_testcase(TestCase, Config) + when TestCase == create_lb_multi_context; + TestCase == one_lb_node_down-> + ok = meck:unload(ggsn_gn), + end_per_testcase(Config), + Config; end_per_testcase(create_pdp_context_proxy_request_resend, Config) -> ok = meck:unload(ggsn_gn), end_per_testcase(Config), @@ -712,6 +787,9 @@ end_per_testcase(request_fast_resend, Config) -> ok = meck:unload(ggsn_gn), end_per_testcase(Config), Config; +end_per_testcase(path_failure_to_ggsn_and_restore, Config) -> + set_path_timers(#{'down_echo' => 600 * 1000}), + end_per_testcase(Config); end_per_testcase(simple_pdp_context_request, Config) -> meck:unload(ggsn_gn), end_per_testcase(Config), @@ -844,11 +922,96 @@ path_failure_to_ggsn(Config) -> ok = meck:wait(?HUT, terminate, '_', ?TIMEOUT), wait4tunnels(?TIMEOUT), + + DownPeers = lists:filter( + fun({_, State}) -> State =:= down end, gtp_path_reg:all(FinalGSN)), + ?equal(1, length(DownPeers)), + [{PeerPid, _}] = DownPeers, + gtp_path:stop(PeerPid), + meck_validate(Config), ok = meck:delete(ergw_gtp_c_socket, send_request, 7), ok. +%%-------------------------------------------------------------------- +path_failure_to_ggsn_and_restore() -> + [{doc, "Check that Create Session Request works and " + "that a path failure (Echo timeout) terminates the session " + "and is later restored with a valid echo"}]. +path_failure_to_ggsn_and_restore(Config) -> + Cntl = whereis(gtpc_client_server), + + ok = meck:expect(ergw_proxy_lib, select_gw, + fun (ProxyInfo, Services, NodeSelect, Port, Context) -> + try + meck:passthrough([ProxyInfo, Services, NodeSelect, Port, Context]) + catch + throw:#ctx_err{} = CtxErr -> + meck:exception(throw, CtxErr) + end + end), + + lists:foreach(fun({_, Pid, _}) -> gtp_path:stop(Pid) end, gtp_path_reg:all()), + + {GtpC, _, _} = create_pdp_context(Config), + + {_Handler, CtxPid} = gtp_context_reg:lookup({'irx', {imsi, ?'IMSI', 5}}), + #{proxy_context := Ctx1} = gtp_context:info(CtxPid), + #context{control_port = CPort} = Ctx1, + + FinalGSN = proplists:get_value(final_gsn, Config), + ok = meck:expect(ergw_gtp_c_socket, send_request, + fun (_, IP, _, _, _, #gtp{type = echo_request}, CbInfo) + when IP =:= FinalGSN -> + %% simulate a Echo timeout + ergw_gtp_c_socket:send_reply(CbInfo, timeout); + (GtpPort, IP, Port, T3, N3, Msg, CbInfo) -> + meck:passthrough([GtpPort, IP, Port, T3, N3, Msg, CbInfo]) + end), + + gtp_path:ping(CPort, v1, FinalGSN), + + %% echo timeout should trigger a Delete PDP Context Request to SGSN. + Request = recv_pdu(Cntl, 5000), + ?match(#gtp{type = delete_pdp_context_request}, Request), + Response = make_response(Request, simple, GtpC), + send_pdu(Cntl, GtpC, Response), + + %% wait for session cleanup + ct:sleep(100), + delete_pdp_context(not_found, GtpC), + + % Check that IP is marked down + ?match([_], lists:filter( + fun({_, State}) -> State =:= down end, gtp_path_reg:all(FinalGSN))), + + create_pdp_context(overload, Config), + ct:sleep(100), + + ok = meck:delete(ergw_gtp_c_socket, send_request, 7), + + %% Successful echo, clears down marked IP. + gtp_path:ping(CPort, v1, FinalGSN), + + %% wait for 100ms + ?equal(timeout, recv_pdu(GtpC, undefined, 100, fun(Why) -> Why end)), + + ?match([], lists:filter( + fun({_, _, State}) -> State =:= down end, gtp_path_reg:all())), + + %% Check that new session now successfully created + {GtpC1, _, _} = create_pdp_context(Config), + delete_pdp_context(GtpC1), + + ok = meck:wait(?HUT, terminate, '_', ?TIMEOUT), + wait4tunnels(?TIMEOUT), + lists:foreach(fun({_, Pid, _}) -> gtp_path:stop(Pid) end, gtp_path_reg:all()), + + meck_validate(Config), + ok = meck:delete(ergw_proxy_lib, select_gw, 5), + ok. + %%-------------------------------------------------------------------- path_failure_to_sgsn() -> [{doc, "Check that Create PDP Context works and " @@ -943,6 +1106,88 @@ simple_pdp_context_request_no_proxy_map(Config) -> ?equal([], outstanding_requests()), ok. +%%-------------------------------------------------------------------- +create_lb_multi_context() -> + [{doc, "Create multi contexts across the 2 Load Balancers"}]. +create_lb_multi_context(Config) -> + init_seq_no(?MODULE, 16#8000), + + %% for 6 clients cumulative nCr for at least 1 hit on both lb = 0.984 + %% for 10 clients it is = 0.999. 1 < No of clients =< 10 + + GtpCs0 = make_gtp_contexts(?NUM_OF_CLIENTS, Config), + GtpCs1 = lists:map(fun(GtpC0) -> create_pdp_context(#{apn => ?'APN-LB-1'}, GtpC0) end, GtpCs0), + + GSNs = [proplists:get_value(K, Config) || K <- [final_gsn, final_gsn_2]], + CntS0 = lists:foldl(fun(K, M) -> + maps:put(proplists:get_value(K, Config), 0, M) + end, #{}, GSNs), + CntS = lists:foldl( + fun({{_,{teid,'gtp-c',{fq_teid, PeerIP,_}}},_}, M) -> + maps:update_with(PeerIP, fun(C) -> C + 1 end, 1, M); + (_, M) -> M + end, CntS0, gtp_context_reg:all()), + + [?match(X when X > 0, maps:get(K, CntS)) || K <- GSNs], + + lists:foreach(fun({GtpC1,_,_}) -> delete_pdp_context(GtpC1) end, GtpCs1), + + ok = meck:wait(?NUM_OF_CLIENTS, ?HUT, terminate, '_', ?TIMEOUT), + wait4tunnels(?TIMEOUT), + meck_validate(Config), + ok. + +%%---------------------------------------------------------------------- +one_lb_node_down() -> + [{doc, "One lb PGW peer node is down"}]. +one_lb_node_down(Config) -> + %% set one peer node as down gtp_path_req and ensure that it is not chosen + init_seq_no(?MODULE, 16#8000), + + %% stop all existing paths + lists:foreach(fun({_, Pid, _}) -> gtp_path:stop(Pid) end, gtp_path_reg:all()), + + DownGSN = proplists:get_value(final_gsn_2, Config), + CPort = ergw_gtp_socket_reg:lookup('irx'), + + ok = meck:expect(ergw_gtp_c_socket, send_request, + fun (_, IP, _, _, _, #gtp{type = echo_request}, CbInfo) + when IP =:= DownGSN -> + %% simulate a Echo timeout + ergw_gtp_c_socket:send_reply(CbInfo, timeout); + (GtpPort, IP, Port, T3, N3, Msg, CbInfo) -> + meck:passthrough([GtpPort, IP, Port, T3, N3, Msg, CbInfo]) + end), + + %% create the path + CPid = gtp_path:maybe_new_path(CPort, v1, DownGSN), + gtp_path:set(CPid, active, true), + + %% down the path by forcing a echo + ok = gtp_path:ping(CPid), + ct:sleep(100), + + % make sure that worked + DownPeers = lists:filter( + fun({_, State}) -> State =:= down end, gtp_path_reg:all(DownGSN)), + ?equal(1, length(DownPeers)), + + GtpCs0 = make_gtp_contexts(?NUM_OF_CLIENTS, Config), + GtpCs1 = lists:map(fun(GtpC0) -> create_pdp_context(random, GtpC0) end, GtpCs0), + + PgwFqTeids = [X || {{_,{teid,'gtp-c',{fq_teid, PeerIP,_}}},_} = + X <- gtp_context_reg:all(), PeerIP == DownGSN], + ?match(0, length(PgwFqTeids)), % Check no connection to down peer + + lists:foreach(fun({GtpC1,_,_}) -> delete_pdp_context(GtpC1) end, GtpCs1), + + ok = meck:wait(?NUM_OF_CLIENTS, ?HUT, terminate, '_', ?TIMEOUT), + wait4tunnels(?TIMEOUT), + lists:foreach(fun({_, Pid, _}) -> gtp_path:stop(Pid) end, gtp_path_reg:all()), + + meck_validate(Config), + ok. + %%-------------------------------------------------------------------- duplicate_pdp_context_request() -> [{doc, "Check the a new incomming request for the same IMSI terminates the first"}]. @@ -1725,3 +1970,24 @@ proxy_context_selection_map(ProxyInfo, Context) -> Other -> Other end. + +% Set Timers for Path management +set_path_timers(SetTimers) -> + {ok, Timers} = application:get_env(ergw, path_management), + NewTimers = maps:merge(Timers, SetTimers), + application:set_env(ergw, path_management, NewTimers). + +make_gtp_contexts(Cnt, Config) -> + BaseIP = proplists:get_value(client_ip, Config), + make_gtp_contexts(Cnt, BaseIP, Config). + +make_gtp_context_ip(Id, {A,B,C,D}) -> + {A,B,C,D + Id}; +make_gtp_context_ip(Id, {A,B,C,D,E,F,G,H}) -> + {A,B,C,D,E,F,G,H + Id}. + +make_gtp_contexts(0, _BaseIP, _Config) -> + []; +make_gtp_contexts(Id, BaseIP, Config) -> + IP = make_gtp_context_ip(Id, BaseIP), + [gtp_context(?MODULE, IP, Config) | make_gtp_contexts(Id - 1, BaseIP, Config)]. diff --git a/test/pgw_SUITE.erl b/test/pgw_SUITE.erl index cafa513c..73848ec5 100644 --- a/test/pgw_SUITE.erl +++ b/test/pgw_SUITE.erl @@ -1242,7 +1242,7 @@ path_maintenance(Config) -> end), %% kill all path to ensure the meck override is used - [gtp_path:stop(Pid) || {_, Pid} <- gtp_path_reg:all()], + [gtp_path:stop(Pid) || {_, Pid, _} <- gtp_path_reg:all()], {GtpC, _, _} = create_session(Config), @@ -1263,7 +1263,7 @@ path_maintenance(Config) -> meck_validate(Config), - [gtp_path:stop(Pid) || {_, Pid} <- gtp_path_reg:all()], + [gtp_path:stop(Pid) || {_, Pid, _} <- gtp_path_reg:all()], ok. %%-------------------------------------------------------------------- diff --git a/test/pgw_proxy_SUITE.erl b/test/pgw_proxy_SUITE.erl index 92a855b2..821e1e41 100644 --- a/test/pgw_proxy_SUITE.erl +++ b/test/pgw_proxy_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright 2017, Travelping GmbH +%% Copyright 2017-2020, Travelping GmbH %% This program is free software; you can redistribute it and/or %% modify it under the terms of the GNU General Public License @@ -18,6 +18,8 @@ -include("ergw_pgw_test_lib.hrl"). -define(TIMEOUT, 2000). +-define(NUM_OF_CLIENTS, 6). %% Num of IP clients for multi contexts max = 10 + -define(HUT, pgw_s5s8_proxy). %% Handler Under Test %%%=================================================================== @@ -40,7 +42,8 @@ ]}, {ergw, [{'$setup_vars', - [{"ORIGIN", {value, "epc.mnc001.mcc001.3gppnetwork.org"}}]}, + [{"ORIGIN", {value, "epc.mnc001.mcc001.3gppnetwork.org"}}, + {"HOMECC", {value, "epc.mnc000.mcc700.3gppnetwork.org"}}]}, {sockets, [{cp, [{type, 'gtp-u'}, {ip, ?MUST_BE_UPDATED}, @@ -57,7 +60,11 @@ {'remote-irx', [{type, 'gtp-c'}, {ip, ?MUST_BE_UPDATED}, {reuseaddr, true} - ]} + ]}, + {'remote-irx2', [{type, 'gtp-c'}, + {ip, ?MUST_BE_UPDATED}, + {reuseaddr, true} + ]} ]}, {ip_pools, @@ -91,13 +98,13 @@ ]}, %% remote PGW handler {gn, [{handler, pgw_s5s8}, - {sockets, ['remote-irx']}, + {sockets, ['remote-irx', 'remote-irx2']}, {node_selection, [default]}, {aaa, [{'Username', [{default, ['IMSI', <<"@">>, 'APN']}]}]} ]}, {s5s8, [{handler, pgw_s5s8}, - {sockets, ['remote-irx']}, + {sockets, ['remote-irx', 'remote-irx2']}, {node_selection, [default]} ]} ]}, @@ -125,8 +132,18 @@ [{"x-3gpp-upf","x-sxb"}], "topon.pgw-1.nodes.$ORIGIN"}, + {"lb-1.apn.$HOMECC", {300,64536}, + [{"x-3gpp-pgw","x-s5-gtp"},{"x-3gpp-pgw","x-s8-gtp"}, + {"x-3gpp-pgw","x-gn"},{"x-3gpp-pgw","x-gp"}], + "topon.s5s8.pgw.$ORIGIN"}, + {"lb-1.apn.$HOMECC", {300,64536}, + [{"x-3gpp-pgw","x-s5-gtp"},{"x-3gpp-pgw","x-s8-gtp"}, + {"x-3gpp-pgw","x-gn"},{"x-3gpp-pgw","x-gp"}], + "topon.s5s8.pgw-2.$ORIGIN"}, + %% A/AAAA record alternatives {"topon.s5s8.pgw.$ORIGIN", ?MUST_BE_UPDATED, []}, + {"topon.s5s8.pgw-2.$ORIGIN", ?MUST_BE_UPDATED, []}, {"topon.sx.sgw-u01.$ORIGIN", ?MUST_BE_UPDATED, []}, {"topon.sx.pgw-u01.$ORIGIN", ?MUST_BE_UPDATED, []}, {"topon.pgw-1.nodes.$ORIGIN", ?MUST_BE_UPDATED, []}, @@ -149,7 +166,8 @@ {apns, [{?'APN-PROXY', [{vrf, example}, - {ip_pools, ['pool-A']}]} + {ip_pools, ['pool-A']}]}, + {?'APN-LB-1', [{vrf, example}, {ip_pools, ['pool-A']}]} ]}, {charging, @@ -186,6 +204,7 @@ {irx, [{features, ['Access']}]}, {'proxy-irx', [{features, ['Core']}]}, {'remote-irx', [{features, ['Access']}]}, + {'remote-irx2', [{features, ['Access']}]}, {example, [{features, ['SGi-LAN']}]}] }, {ip_pools, ['pool-A']}] @@ -194,7 +213,16 @@ {"topon.sx.pgw-u01.$ORIGIN", [connect]}, {"topon.upf-1.nodes.$ORIGIN", [connect]} ] - } + }, + + {path_management, [{active, false}, + {t3, 10 * 1000}, + {n3, 5}, + {echo, 60 * 1000}, + {idle_timeout, 1800 * 1000}, + {idle_echo, 600 * 1000}, + {down_timeout, 3600 * 1000}, + {down_echo, 600 * 1000}]} ]}, {ergw_aaa, @@ -257,7 +285,8 @@ ]}, {ergw, [{'$setup_vars', - [{"ORIGIN", {value, "epc.mnc001.mcc001.3gppnetwork.org"}}]}, + [{"ORIGIN", {value, "epc.mnc001.mcc001.3gppnetwork.org"}}, + {"HOMECC", {value, "epc.mnc000.mcc700.3gppnetwork.org"}}]}, {sockets, [{cp, [{type, 'gtp-u'}, {ip, ?MUST_BE_UPDATED}, @@ -270,7 +299,11 @@ {'remote-irx', [{type, 'gtp-c'}, {ip, ?MUST_BE_UPDATED}, {reuseaddr, true} - ]} + ]}, + {'remote-irx2', [{type, 'gtp-c'}, + {ip, ?MUST_BE_UPDATED}, + {reuseaddr, true} + ]} ]}, {ip_pools, @@ -304,13 +337,13 @@ ]}, %% remote PGW handler {gn, [{handler, pgw_s5s8}, - {sockets, ['remote-irx']}, + {sockets, ['remote-irx', 'remote-irx2']}, {node_selection, [default]}, {aaa, [{'Username', [{default, ['IMSI', <<"@">>, 'APN']}]}]} ]}, {s5s8, [{handler, pgw_s5s8}, - {sockets, ['remote-irx']}, + {sockets, ['remote-irx', 'remote-irx2']}, {node_selection, [default]} ]} ]}, @@ -338,8 +371,18 @@ [{"x-3gpp-upf","x-sxb"}], "topon.pgw-1.nodes.$ORIGIN"}, + {"lb-1.apn.$HOMECC", {300,64536}, + [{"x-3gpp-pgw","x-s5-gtp"},{"x-3gpp-pgw","x-s8-gtp"}, + {"x-3gpp-pgw","x-gn"},{"x-3gpp-pgw","x-gp"}], + "topon.s5s8.pgw.$ORIGIN"}, + {"lb-1.apn.$HOMECC", {300,64536}, + [{"x-3gpp-pgw","x-s5-gtp"},{"x-3gpp-pgw","x-s8-gtp"}, + {"x-3gpp-pgw","x-gn"},{"x-3gpp-pgw","x-gp"}], + "topon.s5s8.pgw-2.$ORIGIN"}, + %% A/AAAA record alternatives {"topon.s5s8.pgw.$ORIGIN", ?MUST_BE_UPDATED, []}, + {"topon.s5s8.pgw-2.$ORIGIN", ?MUST_BE_UPDATED, []}, {"topon.sx.sgw-u01.$ORIGIN", ?MUST_BE_UPDATED, []}, {"topon.sx.pgw-u01.$ORIGIN", ?MUST_BE_UPDATED, []}, {"topon.pgw-1.nodes.$ORIGIN", ?MUST_BE_UPDATED, []}, @@ -362,7 +405,8 @@ {apns, [{?'APN-PROXY', [{vrf, example}, - {ip_pools, ['pool-A']}]} + {ip_pools, ['pool-A']}]}, + {?'APN-LB-1', [{vrf, example}, {ip_pools, ['pool-A']}]} ]}, {charging, @@ -399,6 +443,7 @@ {irx, [{features, ['Access']}]}, {'proxy-irx', [{features, ['Core']}]}, {'remote-irx', [{features, ['Access']}]}, + {'remote-irx2', [{features, ['Access']}]}, {example, [{features, ['SGi-LAN']}]}] }, {ip_pools, ['pool-A']}] @@ -407,7 +452,16 @@ {"topon.sx.pgw-u01.$ORIGIN", [connect]}, {"topon.upf-1.nodes.$ORIGIN", [connect]} ] - } + }, + + {path_management, [{active, false}, + {t3, 10 * 1000}, + {n3, 5}, + {echo, 60 * 1000}, + {idle_timeout, 1800 * 1000}, + {idle_echo, 600 * 1000}, + {down_timeout, 3600 * 1000}, + {down_echo, 600 * 1000}]} ]}, {ergw_aaa, @@ -459,9 +513,12 @@ {[sockets, irx, ip], test_gsn}, {[sockets, 'proxy-irx', ip], proxy_gsn}, {[sockets, 'remote-irx', ip], final_gsn}, + {[sockets, 'remote-irx2', ip], final_gsn_2}, {[sx_socket, ip], localhost}, {[node_selection, {default, 2}, 2, "topon.s5s8.pgw.$ORIGIN"], {fun node_sel_update/2, final_gsn}}, + {[node_selection, {default, 2}, 2, "topon.s5s8.pgw-2.$ORIGIN"], + {fun node_sel_update/2, final_gsn_2}}, {[node_selection, {default, 2}, 2, "topon.sx.sgw-u01.$ORIGIN"], {fun node_sel_update/2, sgw_u_sx}}, {[node_selection, {default, 2}, 2, "topon.sx.pgw-u01.$ORIGIN"], @@ -476,9 +533,12 @@ [{[sockets, cp, ip], localhost}, {[sockets, irx, ip], test_gsn}, {[sockets, 'remote-irx', ip], final_gsn}, + {[sockets, 'remote-irx2', ip], final_gsn_2}, {[sx_socket, ip], localhost}, {[node_selection, {default, 2}, 2, "topon.s5s8.pgw.$ORIGIN"], {fun node_sel_update/2, final_gsn}}, + {[node_selection, {default, 2}, 2, "topon.s5s8.pgw-2.$ORIGIN"], + {fun node_sel_update/2, final_gsn_2}}, {[node_selection, {default, 2}, 2, "topon.sx.sgw-u01.$ORIGIN"], {fun node_sel_update/2, sgw_u_sx}}, {[node_selection, {default, 2}, 2, "topon.sx.pgw-u01.$ORIGIN"], @@ -551,6 +611,7 @@ common() -> create_session_request_accept_new, path_restart, path_restart_recovery, path_failure_to_pgw, + path_failure_to_pgw_and_restore, path_failure_to_sgw, simple_session, simple_session_random_port, @@ -558,6 +619,8 @@ common() -> create_session_overload_response, create_session_request_resend, create_session_proxy_request_resend, + create_lb_multi_session, + one_lb_node_down, delete_session_request_resend, delete_session_request_timeout, error_indication_sgw2pgw, @@ -676,10 +739,20 @@ init_per_testcase(TestCase, Config) meck:passthrough([GtpPort, DstIP, DstPort, T3, N3, Msg, CbInfo]) end), Config; +init_per_testcase(path_failure_to_pgw_and_restore, Config) -> + set_path_timers(#{'down_echo' => 1}), + setup_per_testcase(Config), + Config; init_per_testcase(simple_session, Config) -> setup_per_testcase(Config), ok = meck:new(pgw_s5s8, [passthrough, no_link]), Config; +init_per_testcase(TestCase, Config) + when TestCase == create_lb_multi_session; + TestCase == one_lb_node_down -> + setup_per_testcase(Config), + ok = meck:new(pgw_s5s8, [passthrough, no_link]), + Config; init_per_testcase(request_fast_resend, Config) -> setup_per_testcase(Config), ok = meck:new(pgw_s5s8, [passthrough, no_link]), @@ -768,10 +841,19 @@ end_per_testcase(TestCase, Config) ok = meck:delete(ergw_gtp_c_socket, send_request, 7), end_per_testcase(Config), Config; +end_per_testcase(path_failure_to_pgw_and_restore, Config) -> + set_path_timers(#{'down_echo' => 600 * 1000}), + end_per_testcase(Config); end_per_testcase(simple_session, Config) -> ok = meck:unload(pgw_s5s8), end_per_testcase(Config), Config; +end_per_testcase(TestCase, Config) + when TestCase == create_lb_multi_session; + TestCase == one_lb_node_down -> + ok = meck:unload(pgw_s5s8), + end_per_testcase(Config), + Config; end_per_testcase(request_fast_resend, Config) -> ok = meck:unload(pgw_s5s8), end_per_testcase(Config), @@ -910,7 +992,7 @@ path_failure_to_pgw(Config) -> meck:passthrough([GtpPort, IP, Port, T3, N3, Msg, CbInfo]) end), - gtp_path:ping(CPort, v2, FinalGSN), + ok = gtp_path:ping(CPort, v2, FinalGSN), %% echo timeout should trigger a DBR to SGW... Request = recv_pdu(Cntl, 5000), @@ -929,11 +1011,97 @@ path_failure_to_pgw(Config) -> ok = meck:wait(?HUT, terminate, '_', ?TIMEOUT), wait4tunnels(?TIMEOUT), + + DownPeers = lists:filter( + fun({_, State}) -> State =:= down end, gtp_path_reg:all(FinalGSN)), + ?equal(1, length(DownPeers)), + [{PeerPid, _}] = DownPeers, + gtp_path:stop(PeerPid), + meck_validate(Config), ok = meck:delete(ergw_gtp_c_socket, send_request, 7), ok. +%%-------------------------------------------------------------------- +path_failure_to_pgw_and_restore() -> + [{doc, "Check that Create Session Request works and " + "that a path failure (Echo timeout) terminates the session " + "and is later restored with a valid echo"}]. +path_failure_to_pgw_and_restore(Config) -> + Cntl = whereis(gtpc_client_server), + + ok = meck:expect(ergw_proxy_lib, select_gw, + fun (ProxyInfo, Services, NodeSelect, Port, Context) -> + try + meck:passthrough([ProxyInfo, Services, NodeSelect, Port, Context]) + catch + throw:#ctx_err{} = CtxErr -> + meck:exception(throw, CtxErr) + end + end), + + lists:foreach(fun({_, Pid, _}) -> gtp_path:stop(Pid) end, gtp_path_reg:all()), + + {GtpC, _, _} = create_session(Config), + + {_Handler, CtxPid} = gtp_context_reg:lookup({'irx', {imsi, ?'IMSI', 5}}), + #{proxy_context := Ctx1} = gtp_context:info(CtxPid), + #context{control_port = CPort} = Ctx1, + + FinalGSN = proplists:get_value(final_gsn, Config), + ok = meck:expect(ergw_gtp_c_socket, send_request, + fun (_, IP, _, _, _, #gtp{type = echo_request}, CbInfo) + when IP =:= FinalGSN -> + %% simulate a Echo timeout + ergw_gtp_c_socket:send_reply(CbInfo, timeout); + (GtpPort, IP, Port, T3, N3, Msg, CbInfo) -> + meck:passthrough([GtpPort, IP, Port, T3, N3, Msg, CbInfo]) + end), + + ok = gtp_path:ping(CPort, v2, FinalGSN), + + %% echo timeout should trigger a DBR to SGW. + Request = recv_pdu(Cntl, 5000), + ?match(#gtp{type = delete_bearer_request}, Request), + Response = make_response(Request, simple, GtpC), + send_pdu(Cntl, GtpC, Response), + + %% wait for session cleanup + ct:sleep(100), + delete_session(not_found, GtpC), + + % Check that IP is marked down + ?match([_], lists:filter( + fun({_, State}) -> State =:= down end, gtp_path_reg:all(FinalGSN))), + + % confirm that a new session will now fail as the PGW is marked as down + create_session(overload, Config), + ct:sleep(100), + + ok = meck:delete(ergw_gtp_c_socket, send_request, 7), + + %% Successful echo, clears down marked IP. + gtp_path:ping(CPort, v2, FinalGSN), + + %% wait for 100ms + ?equal(timeout, recv_pdu(GtpC, undefined, 100, fun(Why) -> Why end)), + + ?match([], lists:filter( + fun({_, _, State}) -> State =:= down end, gtp_path_reg:all())), + + %% Check that new session now successfully created + {GtpC1, _, _} = create_session(Config), + delete_session(GtpC1), + + ok = meck:wait(?HUT, terminate, '_', ?TIMEOUT), + wait4tunnels(?TIMEOUT), + lists:foreach(fun({_, Pid, _}) -> gtp_path:stop(Pid) end, gtp_path_reg:all()), + + meck_validate(Config), + ok = meck:delete(ergw_proxy_lib, select_gw, 5), + ok. + %%-------------------------------------------------------------------- path_failure_to_sgw() -> [{doc, "Check that Create Session Request works and " @@ -955,7 +1123,7 @@ path_failure_to_sgw(Config) -> meck:passthrough([GtpPort, IP, Port, T3, N3, Msg, CbInfo]) end), - gtp_path:ping(CPort, v2, ClientIP), + ok = gtp_path:ping(CPort, v2, ClientIP), %% wait for session cleanup ct:sleep(100), @@ -1213,6 +1381,88 @@ simple_session_no_proxy_map(Config) -> ?equal([], outstanding_requests()), ok. +%%-------------------------------------------------------------------- +create_lb_multi_session() -> + [{doc, "Create multi sessions across the 2 Load Balancers"}]. +create_lb_multi_session(Config) -> + init_seq_no(?MODULE, 16#80000), + + %% for 6 clients cumulative nCr for at least 1 hit on both lb = 0.984 + %% for 10 clients it is = 0.999. 1 < No of clients =< 10 + + GtpCs0 = make_gtp_contexts(?NUM_OF_CLIENTS, Config), + GtpCs1 = lists:map(fun(GtpC0) -> create_session(#{apn => ?'APN-LB-1'}, GtpC0) end, GtpCs0), + + GSNs = [proplists:get_value(K, Config) || K <- [final_gsn, final_gsn_2]], + CntS0 = lists:foldl(fun(K, M) -> + maps:put(proplists:get_value(K, Config), 0, M) + end, #{}, GSNs), + CntS = lists:foldl( + fun({{_,{teid,'gtp-c',{fq_teid, PeerIP,_}}},_}, M) -> + maps:update_with(PeerIP, fun(C) -> C + 1 end, 1, M); + (_, M) -> M + end, CntS0, gtp_context_reg:all()), + + [?match(X when X > 0, maps:get(K, CntS)) || K <- GSNs], + + lists:foreach(fun({GtpC1,_,_}) -> delete_session(GtpC1) end, GtpCs1), + + ok = meck:wait(?NUM_OF_CLIENTS, ?HUT, terminate, '_', ?TIMEOUT), + wait4tunnels(?TIMEOUT), + meck_validate(Config), + ok. + +%%---------------------------------------------------------------------- +one_lb_node_down() -> + [{doc, "One lb PGW peer node is down"}]. +one_lb_node_down(Config) -> + %% set one peer node as down gtp_path_req and ensure that it is not chosen + init_seq_no(?MODULE, 16#80000), + + %% stop all existing paths + lists:foreach(fun({_, Pid, _}) -> gtp_path:stop(Pid) end, gtp_path_reg:all()), + + DownGSN = proplists:get_value(final_gsn_2, Config), + CPort = ergw_gtp_socket_reg:lookup('irx'), + + ok = meck:expect(ergw_gtp_c_socket, send_request, + fun (_, IP, _, _, _, #gtp{type = echo_request}, CbInfo) + when IP =:= DownGSN -> + %% simulate a Echo timeout + ergw_gtp_c_socket:send_reply(CbInfo, timeout); + (GtpPort, IP, Port, T3, N3, Msg, CbInfo) -> + meck:passthrough([GtpPort, IP, Port, T3, N3, Msg, CbInfo]) + end), + + %% create the path + CPid = gtp_path:maybe_new_path(CPort, v2, DownGSN), + gtp_path:set(CPid, active, true), + + %% down the path by forcing a echo + ok = gtp_path:ping(CPid), + ct:sleep(100), + + % make sure that worked + DownPeers = lists:filter( + fun({_, State}) -> State =:= down end, gtp_path_reg:all(DownGSN)), + ?equal(1, length(DownPeers)), + + GtpCs0 = make_gtp_contexts(?NUM_OF_CLIENTS, Config), + GtpCs1 = lists:map(fun(GtpC0) -> create_session(random, GtpC0) end, GtpCs0), + + PgwFqTeids = [X || {{_,{teid,'gtp-c',{fq_teid, PeerIP,_}}},_} = + X <- gtp_context_reg:all(), PeerIP == DownGSN], + ?match(0, length(PgwFqTeids)), % Check no connection to down peer + + lists:foreach(fun({GtpC1,_,_}) -> delete_session(GtpC1) end, GtpCs1), + + ok = meck:wait(?NUM_OF_CLIENTS, ?HUT, terminate, '_', ?TIMEOUT), + wait4tunnels(?TIMEOUT), + lists:foreach(fun({_, Pid, _}) -> gtp_path:stop(Pid) end, gtp_path_reg:all()), + + meck_validate(Config), + ok. + %%-------------------------------------------------------------------- duplicate_session_request() -> [{doc, "Check the a new incomming request for the same IMSI terminates the first"}]. @@ -2304,3 +2554,24 @@ check_contexts_metric(Version, Cnt, Expect) -> pfcp_seids() -> lists:flatten(ets:match(gtp_context_reg, {{seid, '$1'},{ergw_sx_node, '_'}})). + +% Set Timers for Path management +set_path_timers(SetTimers) -> + {ok, Timers} = application:get_env(ergw, path_management), + NewTimers = maps:merge(Timers, SetTimers), + application:set_env(ergw, path_management, NewTimers). + +make_gtp_contexts(Cnt, Config) -> + BaseIP = proplists:get_value(client_ip, Config), + make_gtp_contexts(Cnt, BaseIP, Config). + +make_gtp_context_ip(Id, {A,B,C,D}) -> + {A,B,C,D + Id}; +make_gtp_context_ip(Id, {A,B,C,D,E,F,G,H}) -> + {A,B,C,D,E,F,G,H + Id}. + +make_gtp_contexts(0, _BaseIP, _Config) -> + []; +make_gtp_contexts(Id, BaseIP, Config) -> + IP = make_gtp_context_ip(Id, BaseIP), + [gtp_context(?MODULE, IP, Config) | make_gtp_contexts(Id - 1, BaseIP, Config)]. diff --git a/test/proxy_lib_SUITE.erl b/test/proxy_lib_SUITE.erl index 5495e8c6..d2effcc0 100644 --- a/test/proxy_lib_SUITE.erl +++ b/test/proxy_lib_SUITE.erl @@ -94,7 +94,8 @@ proxy_lookup() -> [{doc, "lookup from config"}]. proxy_lookup(_Config) -> NodeSelect = [default], - Context = "TEST", + Port = #gtp_port{name = <<"TEST">>}, + Context = #context{version = v1}, PI = #{imsi => <<"001010000000002">>, msisdn => <<"444444400008502">>, @@ -102,31 +103,32 @@ proxy_lookup(_Config) -> context => <<"GRX2">> }, + gtp_path_reg:start_link(), PI1 = PI#{gwSelectionAPN => apn(<<"web.apn.epc.mnc001.mcc001.3gppnetwork.org">>)}, - Proxy1 = (catch ergw_proxy_lib:select_gw(PI1, ?SERVICES, NodeSelect, Context)), + Proxy1 = (catch ergw_proxy_lib:select_gw(PI1, ?SERVICES, NodeSelect, Port, Context)), ?match({?'CP-Node', ?'CP-IP'}, Proxy1), PI2 = PI#{gwSelectionAPN => apn(<<"web.apn.epc.mnc123.mcc001.3gppnetwork.org">>)}, - Proxy2 = (catch ergw_proxy_lib:select_gw(PI2, ?SERVICES, NodeSelect, Context)), + Proxy2 = (catch ergw_proxy_lib:select_gw(PI2, ?SERVICES, NodeSelect, Port, Context)), ?match({?'CP-Node', ?'CP-IP'}, Proxy2), PI4 = PI#{gwSelectionAPN => apn(<<"web">>)}, - Proxy4 = (catch ergw_proxy_lib:select_gw(PI4, ?SERVICES, NodeSelect, Context)), + Proxy4 = (catch ergw_proxy_lib:select_gw(PI4, ?SERVICES, NodeSelect, Port, Context)), ?match({?'CP-Node', ?'CP-IP'}, Proxy4), PI5 = PI#{gwSelectionAPN => apn(<<"web.mnc001.mcc001.gprs">>)}, - Proxy5 = (catch ergw_proxy_lib:select_gw(PI5, ?SERVICES, NodeSelect, Context)), + Proxy5 = (catch ergw_proxy_lib:select_gw(PI5, ?SERVICES, NodeSelect, Port, Context)), ?match({?'CP-Node', ?'CP-IP'}, Proxy5), PI6 = PI#{gwSelectionAPN => apn(<<"web.mnc123.mcc001.gprs">>)}, - Proxy6 = (catch ergw_proxy_lib:select_gw(PI6, ?SERVICES, NodeSelect, Context)), + Proxy6 = (catch ergw_proxy_lib:select_gw(PI6, ?SERVICES, NodeSelect, Port, Context)), ?match({?'CP-Node', ?'CP-IP'}, Proxy6), PI7 = PI#{gwSelectionAPN => apn(<<"web.mnc567.mcc001.gprs">>)}, - Proxy7 = (catch ergw_proxy_lib:select_gw(PI7, ?SERVICES, NodeSelect, Context)), - ?match(#ctx_err{level = ?FATAL, reply = system_failure, context = "TEST"}, Proxy7), + Proxy7 = (catch ergw_proxy_lib:select_gw(PI7, ?SERVICES, NodeSelect, Port, Context)), + ?match(#ctx_err{level = ?FATAL, reply = system_failure, context = Context}, Proxy7), ok. apn(Bin) ->