diff --git a/.gitignore b/.gitignore index c1ef028f..d962fd67 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,4 @@ rebar3 ergw.config data.ergw* log.ergw* -apps/ergw_core/priv/*.so +apps/ergw_core/priv* diff --git a/apps/ergw/priv/schemas/ergw_config.yaml b/apps/ergw/priv/schemas/ergw_config.yaml index b28c44e4..5e205458 100644 --- a/apps/ergw/priv/schemas/ergw_config.yaml +++ b/apps/ergw/priv/schemas/ergw_config.yaml @@ -169,6 +169,10 @@ components: - "*" ipv6_ue_interface_id: $ref: TS29571_CommonData.yaml#/components/schemas/Ipv6Addr + upf_selection: + type: array + items: + type: string prefered_bearer_type: type: string enum: @@ -530,6 +534,20 @@ properties: prefix: type: integer default: 0 + + sbi_client: + type: object + properties: + upf_selection: + type: object + properties: + endpoint: + $ref: 'TS29571_CommonData.yaml#/components/schemas/Uri' + timeout: + $ref: "#/components/schemas/TimeoutInf" + default: + type: string + required: ['endpoint', 'timeout', 'default'] accept_new: default: true diff --git a/apps/ergw/src/ergw.app.src b/apps/ergw/src/ergw.app.src index dc3d170a..f048ea69 100644 --- a/apps/ergw/src/ergw.app.src +++ b/apps/ergw/src/ergw.app.src @@ -9,7 +9,7 @@ prometheus_diameter_collector, jsx, compiler, os_mon, jobs, ra, riak_core, riak_core_lite_util, - ergw_cluster, ergw_core, ergw_aaa]}, + ergw_cluster, ergw_core, ergw_aaa, ergw_sbi_client]}, {included_applications, [k8s_dist]}, {mod, {ergw_app, []}}, {registered, []} diff --git a/apps/ergw/src/ergw_config.erl b/apps/ergw/src/ergw_config.erl index 156e2ff6..2cb99d6c 100644 --- a/apps/ergw/src/ergw_config.erl +++ b/apps/ergw/src/ergw_config.erl @@ -132,16 +132,21 @@ ergw_aaa_init(apps, #{apps := Apps0}) -> ergw_aaa_init(_, _) -> ok. +ergw_sbi_client_init(Opts) -> + ergw_sbi_client_config:validate_options(fun ergw_sbi_client_config:validate_option/2, Opts). + ergw_core_init(Config) -> Init = [node, aaa, wait_till_running, path_management, node_selection, sockets, upf_nodes, handlers, ip_pools, apns, charging, proxy_map, - http_api], + http_api, sbi_client], lists:foreach(ergw_core_init(_, Config), Init). ergw_core_init(node, #{node := Node}) -> ergw_core:start_node(Node); ergw_core_init(aaa, #{aaa := AAA}) -> ergw_aaa_init(AAA); +ergw_core_init(sbi_client, #{sbi_client := SbiClient}) -> + ergw_sbi_client_init(SbiClient); ergw_core_init(wait_till_running, _) -> ergw_core:wait_till_running(); ergw_core_init(path_management, #{path_management := NodeSel}) -> @@ -208,7 +213,8 @@ config_raw_meta() -> proxy_map => config_meta_proxy_map(), sockets => config_meta_socket(), teid => config_meta_tei_mngr(), - aaa => config_meta_aaa()}. + aaa => config_meta_aaa(), + sbi_client => config_meta_sbi_client()}. config_meta() -> load_typespecs(), @@ -229,6 +235,7 @@ config_meta_apns() -> prefered_bearer_type => atom, ipv6_ue_interface_id => ip6_ifid, inactivity_timeout => timeout, + upf_selection => {list, atom}, 'MS-Primary-DNS-Server' => ip4_address, 'MS-Secondary-DNS-Server' => ip4_address, 'MS-Primary-NBNS-Server' => ip4_address, @@ -543,6 +550,14 @@ config_meta_tei_mngr() -> #{prefix => integer, len => integer}. +config_meta_sbi_client() -> + #{upf_selection => config_meta_sbi_client_upf_selection()}. + +config_meta_sbi_client_upf_selection() -> + #{endpoint => string, + timeout => timeout, + default => string}. + config_meta_aaa() -> #{product_name => config_meta_aaa_product_name(), rate_limits => config_meta_aaa_rate_limits(), diff --git a/apps/ergw/test/config_SUITE_data/ggsn.json b/apps/ergw/test/config_SUITE_data/ggsn.json index ea0a791d..96d982ee 100644 --- a/apps/ergw/test/config_SUITE_data/ggsn.json +++ b/apps/ergw/test/config_SUITE_data/ggsn.json @@ -383,5 +383,15 @@ "teid": { "len": 0, "prefix": 0 + }, + "sbi_client": { + "upf_selection": { + "endpoint": "https://example.com/nf-selection-api/v1", + "timeout": { + "timeout": 500, + "unit": "millisecond" + }, + "default": "fallback" + } } } diff --git a/apps/ergw/test/config_SUITE_data/ggsn_proxy.json b/apps/ergw/test/config_SUITE_data/ggsn_proxy.json index c45deaa9..d74ec3dc 100644 --- a/apps/ergw/test/config_SUITE_data/ggsn_proxy.json +++ b/apps/ergw/test/config_SUITE_data/ggsn_proxy.json @@ -392,5 +392,15 @@ "teid": { "len": 0, "prefix": 0 + }, + "sbi_client": { + "upf_selection": { + "endpoint": "https://example.com/nf-selection-api/v1", + "timeout": { + "timeout": 500, + "unit": "millisecond" + }, + "default": "fallback" + } } } diff --git a/apps/ergw/test/config_SUITE_data/pgw.json b/apps/ergw/test/config_SUITE_data/pgw.json index affe2ece..452c0fbc 100644 --- a/apps/ergw/test/config_SUITE_data/pgw.json +++ b/apps/ergw/test/config_SUITE_data/pgw.json @@ -360,5 +360,15 @@ "teid": { "len": 0, "prefix": 0 + }, + "sbi_client": { + "upf_selection": { + "endpoint": "https://example.com/nf-selection-api/v1", + "timeout": { + "timeout": 500, + "unit": "millisecond" + }, + "default": "fallback" + } } } diff --git a/apps/ergw/test/config_SUITE_data/pgw_proxy.json b/apps/ergw/test/config_SUITE_data/pgw_proxy.json index 4bd90159..79cb84c7 100644 --- a/apps/ergw/test/config_SUITE_data/pgw_proxy.json +++ b/apps/ergw/test/config_SUITE_data/pgw_proxy.json @@ -439,5 +439,15 @@ "teid": { "len": 0, "prefix": 0 + }, + "sbi_client": { + "upf_selection": { + "endpoint": "https://example.com/nf-selection-api/v1", + "timeout": { + "timeout": 500, + "unit": "millisecond" + }, + "default": "fallback" + } } } diff --git a/apps/ergw/test/config_SUITE_data/saegw_s11.json b/apps/ergw/test/config_SUITE_data/saegw_s11.json index 88d95f50..53faeedd 100644 --- a/apps/ergw/test/config_SUITE_data/saegw_s11.json +++ b/apps/ergw/test/config_SUITE_data/saegw_s11.json @@ -319,5 +319,15 @@ "teid": { "len": 0, "prefix": 0 + }, + "sbi_client": { + "upf_selection": { + "endpoint": "https://example.com/nf-selection-api/v1", + "timeout": { + "timeout": 500, + "unit": "millisecond" + }, + "default": "fallback" + } } } diff --git a/apps/ergw/test/config_SUITE_data/tdf.json b/apps/ergw/test/config_SUITE_data/tdf.json index 06703eb4..5f4512b8 100644 --- a/apps/ergw/test/config_SUITE_data/tdf.json +++ b/apps/ergw/test/config_SUITE_data/tdf.json @@ -303,5 +303,15 @@ "teid": { "len": 0, "prefix": 0 + }, + "sbi_client": { + "upf_selection": { + "endpoint": "https://example.com/nf-selection-api/v1", + "timeout": { + "timeout": 500, + "unit": "millisecond" + }, + "default": "fallback" + } } } diff --git a/apps/ergw/test/ergw_http_api_SUITE_data/ggsn.json b/apps/ergw/test/ergw_http_api_SUITE_data/ggsn.json index 2361e974..c2697ad1 100644 --- a/apps/ergw/test/ergw_http_api_SUITE_data/ggsn.json +++ b/apps/ergw/test/ergw_http_api_SUITE_data/ggsn.json @@ -367,6 +367,16 @@ "len": 0, "prefix": 0 }, + "sbi_client": { + "upf_selection": { + "endpoint": "https://example.com/nf-selection-api/v1", + "timeout": { + "timeout": 500, + "unit": "millisecond" + }, + "default": "fallback" + } + }, "aaa": { "apps": [ { diff --git a/apps/ergw/test/sbi_nbsf_SUITE_data/ipv4.json b/apps/ergw/test/sbi_nbsf_SUITE_data/ipv4.json index 8ca0d2a7..e9af9fe5 100644 --- a/apps/ergw/test/sbi_nbsf_SUITE_data/ipv4.json +++ b/apps/ergw/test/sbi_nbsf_SUITE_data/ipv4.json @@ -723,6 +723,16 @@ "len":0, "prefix":0 }, + "sbi_client": { + "upf_selection": { + "endpoint": "https://example.com/nf-selection-api/v1", + "timeout": { + "timeout": 500, + "unit": "millisecond" + }, + "default": "fallback" + } + }, "upf_nodes":{ "default":{ "heartbeat":{ diff --git a/apps/ergw/test/sbi_nbsf_SUITE_data/ipv6.json b/apps/ergw/test/sbi_nbsf_SUITE_data/ipv6.json index 93798e27..351842b3 100644 --- a/apps/ergw/test/sbi_nbsf_SUITE_data/ipv6.json +++ b/apps/ergw/test/sbi_nbsf_SUITE_data/ipv6.json @@ -723,6 +723,16 @@ "len":0, "prefix":0 }, + "sbi_client": { + "upf_selection": { + "endpoint": "https://example.com/nf-selection-api/v1", + "timeout": { + "timeout": 500, + "unit": "millisecond" + }, + "default": "fallback" + } + }, "upf_nodes":{ "default":{ "heartbeat":{ diff --git a/apps/ergw/test/smc_SUITE_data/ggsn.json b/apps/ergw/test/smc_SUITE_data/ggsn.json index e8bb8bd9..f7776ab8 100644 --- a/apps/ergw/test/smc_SUITE_data/ggsn.json +++ b/apps/ergw/test/smc_SUITE_data/ggsn.json @@ -400,6 +400,16 @@ "len": 0, "prefix": 0 }, + "sbi_client": { + "upf_selection": { + "endpoint": "https://example.com/nf-selection-api/v1", + "timeout": { + "timeout": 500, + "unit": "millisecond" + }, + "default": "fallback" + } + }, "aaa": { "apps": [ { diff --git a/apps/ergw/test/smc_SUITE_data/ggsn_proxy.json b/apps/ergw/test/smc_SUITE_data/ggsn_proxy.json index 363f5ed2..6acb5a30 100644 --- a/apps/ergw/test/smc_SUITE_data/ggsn_proxy.json +++ b/apps/ergw/test/smc_SUITE_data/ggsn_proxy.json @@ -410,6 +410,16 @@ "len": 0, "prefix": 0 }, + "sbi_client": { + "upf_selection": { + "endpoint": "https://example.com/nf-selection-api/v1", + "timeout": { + "timeout": 500, + "unit": "millisecond" + }, + "default": "fallback" + } + }, "aaa": { "apps": [ { diff --git a/apps/ergw/test/smc_SUITE_data/pgw.json b/apps/ergw/test/smc_SUITE_data/pgw.json index 7ace9f91..89bb29bf 100644 --- a/apps/ergw/test/smc_SUITE_data/pgw.json +++ b/apps/ergw/test/smc_SUITE_data/pgw.json @@ -369,6 +369,16 @@ "len": 0, "prefix": 0 }, + "sbi_client": { + "upf_selection": { + "endpoint": "https://example.com/nf-selection-api/v1", + "timeout": { + "timeout": 500, + "unit": "millisecond" + }, + "default": "fallback" + } + }, "aaa": { "apps": [ { diff --git a/apps/ergw/test/smc_SUITE_data/pgw_proxy.json b/apps/ergw/test/smc_SUITE_data/pgw_proxy.json index a744b9a6..260eb453 100644 --- a/apps/ergw/test/smc_SUITE_data/pgw_proxy.json +++ b/apps/ergw/test/smc_SUITE_data/pgw_proxy.json @@ -457,6 +457,16 @@ "len": 0, "prefix": 0 }, + "sbi_client": { + "upf_selection": { + "endpoint": "https://example.com/nf-selection-api/v1", + "timeout": { + "timeout": 500, + "unit": "millisecond" + }, + "default": "fallback" + } + }, "aaa": { "apps": [ { diff --git a/apps/ergw/test/smc_SUITE_data/saegw_s11.json b/apps/ergw/test/smc_SUITE_data/saegw_s11.json index 007a07f5..d7399902 100644 --- a/apps/ergw/test/smc_SUITE_data/saegw_s11.json +++ b/apps/ergw/test/smc_SUITE_data/saegw_s11.json @@ -329,6 +329,16 @@ "len": 0, "prefix": 0 }, + "sbi_client": { + "upf_selection": { + "endpoint": "https://example.com/nf-selection-api/v1", + "timeout": { + "timeout": 500, + "unit": "millisecond" + }, + "default": "fallback" + } + }, "aaa": { "apps": [ { diff --git a/apps/ergw/test/smc_SUITE_data/tdf.json b/apps/ergw/test/smc_SUITE_data/tdf.json index 2eb3fdd6..3ddf61d6 100644 --- a/apps/ergw/test/smc_SUITE_data/tdf.json +++ b/apps/ergw/test/smc_SUITE_data/tdf.json @@ -323,6 +323,16 @@ "len": 0, "prefix": 0 }, + "sbi_client": { + "upf_selection": { + "endpoint": "https://example.com/nf-selection-api/v1", + "timeout": { + "timeout": 500, + "unit": "millisecond" + }, + "default": "fallback" + } + }, "aaa": { "apps": [ { diff --git a/apps/ergw_core/src/ergw_apn.erl b/apps/ergw_core/src/ergw_apn.erl index c9471f0c..246becd1 100644 --- a/apps/ergw_core/src/ergw_apn.erl +++ b/apps/ergw_core/src/ergw_apn.erl @@ -50,7 +50,8 @@ get(_, _) -> {bearer_type, 'IPv4v6'}, {prefered_bearer_type, 'IPv6'}, {ipv6_ue_interface_id, default}, - {inactivity_timeout, 48 * 3600 * 1000} %% 48hrs timer in msecs + {inactivity_timeout, 48 * 3600 * 1000}, %% 48hrs timer in msecs + {upf_selection, ['3gpp']} ]). validate_options({APN0, Value}) when ?is_opts(Value) -> @@ -113,6 +114,9 @@ validate_apn_option({ipv6_ue_interface_id, {0,0,0,0,E,F,G,H}} = Opt) G >= 0, G < 65536, H >= 0, H < 65536, (E + F + G + H) =/= 0 -> Opt; +validate_apn_option({upf_selection, List} = Opt) -> + lists:foreach(fun validate_upf_selection/1, List), + Opt; validate_apn_option({Opt, Value}) when Opt == 'MS-Primary-DNS-Server'; Opt == 'MS-Secondary-DNS-Server'; Opt == 'MS-Primary-NBNS-Server'; Opt == 'MS-Secondary-NBNS-Server'; @@ -124,3 +128,21 @@ validate_apn_option({Opt = inactivity_timeout, Timer}) {Opt, Timer}; validate_apn_option({Opt, Value}) -> erlang:error(badarg, [Opt, Value]). + +validate_upf_selection('3gpp') -> + ok; +validate_upf_selection(M) when is_atom(M) -> + case code:ensure_loaded(M) of + {module, _} -> + ok; + _ -> + erlang:error(badarg, [apn, upf_selection, M]) + end, + case erlang:function_exported(M, upf_selection, 1) of + true -> + ok; + false -> + erlang:error(badarg, [apn, upf_selection, M]) + end; +validate_upf_selection(Opt) -> + erlang:error(badarg, [apn, upf_selection, Opt]). diff --git a/apps/ergw_core/src/ergw_core.app.src b/apps/ergw_core/src/ergw_core.app.src index b4e7fcf6..df238563 100644 --- a/apps/ergw_core/src/ergw_core.app.src +++ b/apps/ergw_core/src/ergw_core.app.src @@ -8,7 +8,7 @@ prometheus, cowboy, prometheus_cowboy, prometheus_diameter_collector, compiler, os_mon, jobs, ra, - riak_core, riak_core_lite_util]}, + riak_core, riak_core_lite_util, ergw_sbi_client]}, {mod, {ergw_core_app, []}}, {registered, []} ]}. diff --git a/apps/ergw_core/src/ergw_pfcp_context.erl b/apps/ergw_core/src/ergw_pfcp_context.erl index ddeed9d5..8ceed7b4 100644 --- a/apps/ergw_core/src/ergw_pfcp_context.erl +++ b/apps/ergw_core/src/ergw_pfcp_context.erl @@ -903,7 +903,7 @@ select_upf(Candidates) -> ergw_sx_node:attach(Pid) ]). -%% select_upf/2 +%% select_upf_with/3 select_upf_with(_, [], _) -> {error, ?CTX_ERR(?FATAL, no_resources_available)}; select_upf_with(Fun, Candidates, Available) -> @@ -936,6 +936,62 @@ apn_filter(_, _, F) -> %% select_upf/3 select_upf(Candidates, Session0, APNOpts) -> + Select = maps:get(upf_selection, APNOpts, ['3gpp']), + select_upf(Candidates, Session0, APNOpts, Select). + +%% select_upf/4 +select_upf(_Candidates, _Session, _APNOpts, []) -> + {error, ?CTX_ERR(?FATAL, no_resources_available)}; +select_upf(Candidates, Session, APNOpts, ['3gpp'|_]) -> + select_upf_3gpp(Candidates, Session, APNOpts); +select_upf(Candidates, Session, APNOpts, [API|Next]) -> + case (catch API:upf_selection(Session)) of + {ok, UPF} -> + select_upf_api_result(UPF, Session, APNOpts); + {error, Error} -> + {error, ?CTX_ERR(?FATAL, Error)}; + skip -> + select_upf(Candidates, Session, APNOpts, Next); + Other -> + ?LOG(error, #{what => "UPF selection", api => API, error => Other}), + {error, ?CTX_ERR(?FATAL, no_resources_available)} + end. + +select_upf_api_result(UPF, Session0, APNOpts) when is_binary(UPF) -> + Available = ergw_sx_node_reg:available(), + case Available of + %% TODO: this NodePools/NodeCaps thing can be cleaned, + %% NodePools is contained within NodeCaps + #{UPF := {Pid, {_, NodePools} = NodeCaps}} -> + Wanted = maps:fold(fun apn_filter/3, #{}, APNOpts), + case common_caps_n(Wanted, NodePools) of + [] -> + {error, ?CTX_ERR(?FATAL, no_resources_available)}; + Pools -> + #{ip_pools := IPpools, ip_versions := IPvs, + nat_port_blocks := NATblocks, vrf := VRF} = select(random, Pools), + PoolV4 = select_ip_pool(v4, IPvs, IPpools), + PoolV6 = select_ip_pool(v6, IPvs, IPpools), + NAT = select(random, NATblocks), + + Session1 = Session0#{'Framed-Pool' => PoolV4, 'Framed-IPv6-Pool' => PoolV6}, + Session2 = case maps:is_key(nat_port_blocks, Wanted) of + true -> Session1#{'NAT-Pool-Id' => NAT}; + false -> Session1 + end, + Session = init_session_ue_ifid(APNOpts, Session2), + Node = {UPF, Pid, NodeCaps, NodePools}, + UPinfo = {api, Node, VRF, PoolV4, NAT, PoolV6}, + {ok, {UPinfo, Session}} + end; + _ -> + {error, ?CTX_ERR(?FATAL, no_resources_available)} + end; +select_upf_api_result(_UPF, _Session, _APNOpts) -> + {error, ?CTX_ERR(?FATAL, no_resources_available)}. + +%% select_upf_3gpp/3 +select_upf_3gpp(Candidates, Session0, APNOpts) -> Wanted = maps:fold(fun apn_filter/3, #{}, APNOpts), do([error_m || {_, _, _, Pools} = Node <- select_by_caps(Wanted, undefined, Candidates), @@ -966,6 +1022,29 @@ filter([{nat, NAT}|T]) -> maps:put(nat_port_blocks, [[NAT]], filter(T)). %% reselect_upf/4 +reselect_upf(_Candidates, Session, _APNOpts, {api, {_, Pid, NodeCaps, NodePools}, VRF0, PoolV4, NATBlock, PoolV6}) -> + NAT = maps:get('NAT-Pool-Id', Session, undefined), + IP4 = maps:get('Framed-Pool', Session, undefined), + IP6 = maps:get('Framed-IPv6-Pool', Session, undefined), + + do([error_m || + VRF <- + if (IP4 /= PoolV4 orelse IP6 /= PoolV6 orelse + NATBlock /= NAT) -> + Wanted = filter([{v4, IP4}, {v6, IP6}, {nat, NAT}]), + case common_caps_n(Wanted, NodePools) of + [] -> + fail(?CTX_ERR(?FATAL, no_resources_available)); + Pools -> + #{vrf := VRF1} = select(random, Pools), + return(VRF1) + end; + true -> + return(VRF0) + end, + {PCtx, _} <- ergw_sx_node:attach(Pid), + return({PCtx, NodeCaps, #bearer{interface = 'SGi-LAN', vrf = VRF}}) + ]); reselect_upf(Candidates, Session, _APNOpts, {{NodeName, _, _, _} = Node0, VRF0, PoolV4, NATBlock, PoolV6}) -> NAT = maps:get('NAT-Pool-Id', Session, undefined), IP4 = maps:get('Framed-Pool', Session, undefined), diff --git a/apps/ergw_core/src/ggsn_gn.erl b/apps/ergw_core/src/ggsn_gn.erl index 4fcb21e1..9bfc4773 100644 --- a/apps/ergw_core/src/ggsn_gn.erl +++ b/apps/ergw_core/src/ggsn_gn.erl @@ -24,7 +24,7 @@ %% shared API's -export([init_session/4, - init_session_from_gtp_req/4, + init_session_from_gtp_req/5, update_tunnel_from_gtp_req/3, update_context_from_gtp_req/2 ]). @@ -171,7 +171,7 @@ handle_request(ReqKey, gtp_context:terminate_colliding_context(LeftTunnel, Context1), SessionOpts0 = init_session(IEs, LeftTunnel, Context1, AAAopts), - SessionOpts1 = init_session_from_gtp_req(IEs, AAAopts, LeftTunnel, SessionOpts0), + SessionOpts1 = init_session_from_gtp_req(IEs, AAAopts, LeftTunnel, LeftBearer1, SessionOpts0), SessionOpts2 = init_session_qos(IEs, SessionOpts1), @@ -215,7 +215,7 @@ handle_request(ReqKey, Bearer = Bearer0#{left => LeftBearer}, LeftTunnel = ergw_gtp_gsn_lib:update_tunnel_endpoint(Request, LeftTunnelOld, LeftTunnel0), - URRActions = update_session_from_gtp_req(IEs, Session, LeftTunnel), + URRActions = update_session_from_gtp_req(IEs, Session, LeftTunnel, LeftBearer), PCtx = if LeftBearer /= LeftBearerOld -> case ergw_gtp_gsn_lib:apply_bearer_change( @@ -245,8 +245,9 @@ handle_request(ReqKey, handle_request(ReqKey, #gtp{type = ms_info_change_notification_request, ie = IEs} = Request, _Resent, _State, - #{context := Context, pfcp := PCtx, left_tunnel := LeftTunnel, 'Session' := Session}) -> - URRActions = update_session_from_gtp_req(IEs, Session, LeftTunnel), + #{context := Context, pfcp := PCtx, left_tunnel := LeftTunnel, + bearer := #{left := LeftBearer}, 'Session' := Session}) -> + URRActions = update_session_from_gtp_req(IEs, Session, LeftTunnel, LeftBearer), gtp_context:trigger_usage_report(self(), URRActions, PCtx), ResponseIEs0 = [#cause{value = request_accepted}], @@ -453,7 +454,8 @@ copy_to_session(_, #protocol_configuration_options{config = {0, Options}}, lists:foldr(fun copy_ppp_to_session/2, Session, Options); copy_to_session(_, #access_point_name{apn = APN}, _AAAopts, Session) -> {NI, _OI} = ergw_node_selection:split_apn(APN), - Session#{'Called-Station-Id' => + Session#{'APN' => APN, + 'Called-Station-Id' => iolist_to_binary(lists:join($., NI))}; copy_to_session(_, #ms_international_pstn_isdn_number{ msisdn = {isdn_address, _, _, 1, MSISDN}}, _AAAopts, Session) -> @@ -554,15 +556,27 @@ copy_to_session(_, #ms_time_zone{timezone = TZ, dst = DST}, _AAAopts, Session) - copy_to_session(_, _, _AAAopts, Session) -> Session. -copy_tunnel_to_session(#tunnel{remote = #fq_teid{ip = {_,_,_,_} = IP}}, Session) -> - Session#{'3GPP-SGSN-Address' => IP}; -copy_tunnel_to_session(#tunnel{remote = #fq_teid{ip = {_,_,_,_,_,_,_,_} = IP}}, Session) -> - Session#{'3GPP-SGSN-IPv6-Address' => IP}; +ip_to_session({_,_,_,_} = IP, #{ip4 := Key}, Session) -> + Session#{Key => IP}; +ip_to_session({_,_,_,_,_,_,_,_} = IP, #{ip6 := Key}, Session) -> + Session#{Key => IP}. + +copy_tunnel_to_session(#tunnel{version = Version, remote = #fq_teid{ip = IP}}, Session) -> + ip_to_session(IP, #{ip4 => '3GPP-SGSN-Address', + ip6 => '3GPP-SGSN-IPv6-Address'}, + Session#{'GTP-Version' => Version}); copy_tunnel_to_session(_, Session) -> Session. -init_session_from_gtp_req(IEs, AAAopts, Tunnel, Session0) -> - Session = copy_tunnel_to_session(Tunnel, Session0), +copy_bearer_to_session(#bearer{remote = #fq_teid{ip = IP}}, Session) -> + ip_to_session(IP, #{ip4 => '3GPP-SGSN-UP-Address', + ip6 => '3GPP-SGSN-UP-IPv6-Address'}, Session); +copy_bearer_to_session(_, Session) -> + Session. + +init_session_from_gtp_req(IEs, AAAopts, Tunnel, Bearer, Session0) -> + Session1 = copy_tunnel_to_session(Tunnel, Session0), + Session = copy_bearer_to_session(Bearer, Session1), maps:fold(copy_to_session(_, _, AAAopts, _), Session, IEs). init_session_qos(#{?'Quality of Service Profile' := @@ -578,12 +592,13 @@ init_session_qos(#{?'Quality of Service Profile' := init_session_qos(_IEs, Session) -> Session. -update_session_from_gtp_req(IEs, Session, _Context) -> +update_session_from_gtp_req(IEs, Session, Tunnel, Bearer) -> OldSOpts = ergw_aaa_session:get(Session), - NewSOpts0 = - maps:fold(copy_to_session(_, _, undefined, _), OldSOpts, IEs), + NewSOpts0 = init_session_qos(IEs, OldSOpts), + NewSOpts1 = copy_tunnel_to_session(Tunnel, NewSOpts0), + NewSOpts2 = copy_bearer_to_session(Bearer, NewSOpts1), NewSOpts = - init_session_qos(IEs, NewSOpts0), + maps:fold(copy_to_session(_, _, undefined, _), NewSOpts2, IEs), ergw_aaa_session:set(Session, NewSOpts), gtp_context:collect_charging_events(OldSOpts, NewSOpts). diff --git a/apps/ergw_core/src/ggsn_gn_proxy.erl b/apps/ergw_core/src/ggsn_gn_proxy.erl index d17a02ce..5606cce5 100644 --- a/apps/ergw_core/src/ggsn_gn_proxy.erl +++ b/apps/ergw_core/src/ggsn_gn_proxy.erl @@ -247,7 +247,7 @@ handle_request(ReqKey, gtp_context:terminate_colliding_context(LeftTunnel, Context), SessionOpts0 = ggsn_gn:init_session(IEs, LeftTunnel, Context, AAAopts), - SessionOpts = ggsn_gn:init_session_from_gtp_req(IEs, AAAopts, LeftTunnel, SessionOpts0), + SessionOpts = ggsn_gn:init_session_from_gtp_req(IEs, AAAopts, LeftTunnel, LeftBearer1, SessionOpts0), ProxyInfo = case handle_proxy_info(SessionOpts, LeftTunnel, LeftBearer1, Context, Data) of diff --git a/apps/ergw_core/src/pgw_s5s8.erl b/apps/ergw_core/src/pgw_s5s8.erl index 68bb7f8c..8340d035 100644 --- a/apps/ergw_core/src/pgw_s5s8.erl +++ b/apps/ergw_core/src/pgw_s5s8.erl @@ -21,7 +21,7 @@ %% shared API's -export([init_session/4, - init_session_from_gtp_req/4, + init_session_from_gtp_req/5, update_tunnel_from_gtp_req/3, update_context_from_gtp_req/2 ]). @@ -206,7 +206,7 @@ handle_request(ReqKey, gtp_context:terminate_colliding_context(LeftTunnel, Context1), SessionOpts0 = init_session(IEs, LeftTunnel, Context0, AAAopts), - SessionOpts1 = init_session_from_gtp_req(IEs, AAAopts, LeftTunnel, SessionOpts0), + SessionOpts1 = init_session_from_gtp_req(IEs, AAAopts, LeftTunnel, LeftBearer1, SessionOpts0), %% SessionOpts = init_session_qos(ReqQoSProfile, SessionOpts1), {Verdict, Cause, SessionOpts, Context, Bearer, PCC4, PCtx} = @@ -257,7 +257,7 @@ handle_request(ReqKey, Bearer = Bearer0#{left => LeftBearer}, LeftTunnel = ergw_gtp_gsn_lib:update_tunnel_endpoint(Request, LeftTunnelOld, LeftTunnel0), - {OldSOpts, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel), + {OldSOpts, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel, LeftBearer), URRActions = gtp_context:collect_charging_events(OldSOpts, NewSOpts), PCtx = if LeftBearer /= LeftBearerOld -> @@ -313,7 +313,7 @@ handle_request(ReqKey, when not is_map_key(?'Bearer Contexts to be modified', IEs) -> process_secondary_rat_usage_data_reports(IEs, Context, Session), - {LeftTunnel0, _LeftBearer} = + {LeftTunnel0, LeftBearer} = case update_tunnel_from_gtp_req( Request, LeftTunnelOld#tunnel{version = v2}, LeftBearerOld) of {ok, Result1} -> Result1; @@ -321,7 +321,7 @@ handle_request(ReqKey, end, LeftTunnel = ergw_gtp_gsn_lib:update_tunnel_endpoint(Request, LeftTunnelOld, LeftTunnel0), - {OldSOpts, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel), + {OldSOpts, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel, LeftBearer), URRActions = gtp_context:collect_charging_events(OldSOpts, NewSOpts), gtp_context:trigger_usage_report(self(), URRActions, PCtx), @@ -342,8 +342,8 @@ handle_request(#request{src = Src, ip = IP, port = Port} = ReqKey, group = #{?'EPS Bearer ID' := EBI} = Bearer}} = IEs}, _Resent, _State, #{context := Context, left_tunnel := LeftTunnel, - 'Session' := Session}) -> - {OldSOpts, _} = update_session_from_gtp_req(IEs, Session, LeftTunnel), + bearer := #{left := LeftBearer}, 'Session' := Session}) -> + {OldSOpts, _} = update_session_from_gtp_req(IEs, Session, LeftTunnel, LeftBearer), Type = update_bearer_request, RequestIEs0 = @@ -362,10 +362,10 @@ handle_request(ReqKey, #gtp{type = change_notification_request, ie = IEs} = Request, _Resent, _State, #{context := Context, pfcp := PCtx, left_tunnel := LeftTunnel, - 'Session' := Session}) -> + bearer := #{left := LeftBearer}, 'Session' := Session}) -> process_secondary_rat_usage_data_reports(IEs, Context, Session), - {OldSOpts, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel), + {OldSOpts, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel, LeftBearer), URRActions = gtp_context:collect_charging_events(OldSOpts, NewSOpts), gtp_context:trigger_usage_report(self(), URRActions, PCtx), @@ -432,14 +432,15 @@ handle_response({CommandReqKey, OldSOpts}, group = #{?'Cause' := #v2_cause{v2_cause = BearerCause}} }} = IEs} = Response, _Request, connected = State, - #{pfcp := PCtx, left_tunnel := LeftTunnel0, 'Session' := Session} = Data) -> + #{pfcp := PCtx, left_tunnel := LeftTunnel0, bearer := #{left := LeftBearer}, + 'Session' := Session} = Data) -> gtp_context:request_finished(CommandReqKey), LeftTunnel = gtp_path:bind(Response, LeftTunnel0), DataNew = Data#{left_tunnel => LeftTunnel}, if Cause =:= request_accepted andalso BearerCause =:= request_accepted -> - {_, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel), + {_, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel, LeftBearer), URRActions = gtp_context:collect_charging_events(OldSOpts, NewSOpts), gtp_context:trigger_usage_report(self(), URRActions, PCtx), {keep_state, DataNew}; @@ -626,7 +627,8 @@ copy_to_session(_, #v2_protocol_configuration_options{config = {0, Options}}, lists:foldr(fun copy_ppp_to_session/2, Session, Options); copy_to_session(_, #v2_access_point_name{apn = APN}, _AAAopts, Session) -> {NI, _OI} = ergw_node_selection:split_apn(APN), - Session#{'Called-Station-Id' => + Session#{'APN' => APN, + 'Called-Station-Id' => iolist_to_binary(lists:join($., NI))}; copy_to_session(_, #v2_msisdn{msisdn = MSISDN}, _AAAopts, Session) -> Session#{'Calling-Station-Id' => MSISDN, '3GPP-MSISDN' => MSISDN}; @@ -746,13 +748,24 @@ copy_qos_to_session(#{?'Bearer Contexts to be created' := copy_qos_to_session(_, Session) -> Session. -copy_tunnel_to_session(#tunnel{remote = #fq_teid{ip = {_,_,_,_} = IP}}, Session) -> - Session#{'3GPP-SGSN-Address' => IP}; -copy_tunnel_to_session(#tunnel{remote = #fq_teid{ip = {_,_,_,_,_,_,_,_} = IP}}, Session) -> - Session#{'3GPP-SGSN-IPv6-Address' => IP}; +ip_to_session({_,_,_,_} = IP, #{ip4 := Key}, Session) -> + Session#{Key => IP}; +ip_to_session({_,_,_,_,_,_,_,_} = IP, #{ip6 := Key}, Session) -> + Session#{Key => IP}. + +copy_tunnel_to_session(#tunnel{version = Version, remote = #fq_teid{ip = IP}}, Session) -> + ip_to_session(IP, #{ip4 => '3GPP-SGSN-Address', + ip6 => '3GPP-SGSN-IPv6-Address'}, + Session#{'GTP-Version' => Version}); copy_tunnel_to_session(_, Session) -> Session. +copy_bearer_to_session(#bearer{remote = #fq_teid{ip = IP}}, Session) -> + ip_to_session(IP, #{ip4 => '3GPP-SGSN-UP-Address', + ip6 => '3GPP-SGSN-UP-IPv6-Address'}, Session); +copy_bearer_to_session(_, Session) -> + Session. + sec_rat_udr_to_report([], _, Reports) -> Reports; sec_rat_udr_to_report(#v2_secondary_rat_usage_data_report{irpgw = false}, _, Reports) -> @@ -773,19 +786,21 @@ process_secondary_rat_usage_data_reports(#{?'Secondary RAT Usage Data Report' := process_secondary_rat_usage_data_reports(_, _, _) -> ok. -init_session_from_gtp_req(IEs, AAAopts, Tunnel, Session0) - when is_record(Tunnel, tunnel) -> +init_session_from_gtp_req(IEs, AAAopts, Tunnel, Bearer, Session0) + when is_record(Tunnel, tunnel), is_record(Bearer, bearer) -> Session1 = copy_qos_to_session(IEs, Session0), - Session = copy_tunnel_to_session(Tunnel, Session1), + Session2 = copy_tunnel_to_session(Tunnel, Session1), + Session = copy_bearer_to_session(Bearer, Session2), maps:fold(copy_to_session(_, _, AAAopts, _), Session, IEs). -update_session_from_gtp_req(IEs, Session, Tunnel) +update_session_from_gtp_req(IEs, Session, Tunnel, Bearer) when is_record(Tunnel, tunnel) -> OldSOpts = ergw_aaa_session:get(Session), NewSOpts0 = copy_qos_to_session(IEs, OldSOpts), NewSOpts1 = copy_tunnel_to_session(Tunnel, NewSOpts0), + NewSOpts2 = copy_bearer_to_session(Bearer, NewSOpts1), NewSOpts = - maps:fold(copy_to_session(_, _, undefined, _), NewSOpts1, IEs), + maps:fold(copy_to_session(_, _, undefined, _), NewSOpts2, IEs), ergw_aaa_session:set(Session, NewSOpts), {OldSOpts, NewSOpts}. diff --git a/apps/ergw_core/src/pgw_s5s8_proxy.erl b/apps/ergw_core/src/pgw_s5s8_proxy.erl index e7303a3c..20e9ae76 100644 --- a/apps/ergw_core/src/pgw_s5s8_proxy.erl +++ b/apps/ergw_core/src/pgw_s5s8_proxy.erl @@ -256,7 +256,7 @@ handle_request(ReqKey, gtp_context:terminate_colliding_context(LeftTunnel, Context), SessionOpts0 = pgw_s5s8:init_session(IEs, LeftTunnel, Context, AAAopts), - SessionOpts = pgw_s5s8:init_session_from_gtp_req(IEs, AAAopts, LeftTunnel, SessionOpts0), + SessionOpts = pgw_s5s8:init_session_from_gtp_req(IEs, AAAopts, LeftTunnel, LeftBearer1, SessionOpts0), ProxyInfo = case handle_proxy_info(SessionOpts, LeftTunnel, LeftBearer1, Context, Data) of diff --git a/apps/ergw_core/src/saegw_s11.erl b/apps/ergw_core/src/saegw_s11.erl index 3d921b97..c4e369ca 100644 --- a/apps/ergw_core/src/saegw_s11.erl +++ b/apps/ergw_core/src/saegw_s11.erl @@ -172,7 +172,7 @@ handle_request(ReqKey, gtp_context:terminate_colliding_context(LeftTunnel, Context1), SessionOpts0 = pgw_s5s8:init_session(IEs, LeftTunnel, Context1, AAAopts), - SessionOpts1 = pgw_s5s8:init_session_from_gtp_req(IEs, AAAopts, LeftTunnel, SessionOpts0), + SessionOpts1 = pgw_s5s8:init_session_from_gtp_req(IEs, AAAopts, LeftTunnel, LeftBearer1, SessionOpts0), %% SessionOpts = init_session_qos(ReqQoSProfile, SessionOpts1), {Verdict, Cause, SessionOpts, Context, Bearer, PCC4, PCtx} = @@ -218,7 +218,7 @@ handle_request(ReqKey, Bearer = Bearer0#{left => LeftBearer}, LeftTunnel = ergw_gtp_gsn_lib:update_tunnel_endpoint(Request, LeftTunnelOld, LeftTunnel0), - {OldSOpts, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel), + {OldSOpts, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel, LeftBearer), URRActions = gtp_context:collect_charging_events(OldSOpts, NewSOpts), PCtx = if LeftBearer /= LeftBearerOld -> @@ -252,7 +252,7 @@ handle_request(ReqKey, left_tunnel := LeftTunnelOld, bearer := #{left := LeftBearerOld}, 'Session' := Session} = Data) when not is_map_key(?'Bearer Contexts to be modified', IEs) -> - {LeftTunnel0, _LeftBearer} = + {LeftTunnel0, LeftBearer} = case update_tunnel_from_gtp_req( Request, LeftTunnelOld#tunnel{version = v2}, LeftBearerOld) of {ok, Result1} -> Result1; @@ -260,7 +260,7 @@ handle_request(ReqKey, end, LeftTunnel = ergw_gtp_gsn_lib:update_tunnel_endpoint(Request, LeftTunnelOld, LeftTunnel0), - {OldSOpts, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel), + {OldSOpts, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel, LeftBearer), URRActions = gtp_context:collect_charging_events(OldSOpts, NewSOpts), gtp_context:trigger_usage_report(self(), URRActions, PCtx), @@ -282,8 +282,9 @@ handle_request(#request{src = Src, ip = IP, port = Port} = ReqKey, #v2_bearer_context{ group = #{?'EPS Bearer ID' := EBI} = Bearer}} = IEs}, _Resent, _State, - #{context := Context, left_tunnel := LeftTunnel, 'Session' := Session}) -> - {OldSOpts, _} = update_session_from_gtp_req(IEs, Session, LeftTunnel), + #{context := Context, left_tunnel := LeftTunnel, + bearer := #{left := LeftBearer}, 'Session' := Session}) -> + {OldSOpts, _} = update_session_from_gtp_req(IEs, Session, LeftTunnel, LeftBearer), Type = update_bearer_request, RequestIEs0 = [AMBR, @@ -350,16 +351,17 @@ handle_response({CommandReqKey, OldSOpts}, group = #{?'Cause' := #v2_cause{v2_cause = BearerCause}} }} = IEs} = Response, _Request, connected = State, - #{pfcp := PCtx, left_tunnel := LeftTunnel0, 'Session' := Session} = Data) -> + #{pfcp := PCtx, left_tunnel := LeftTunnel0,bearer := #{left := LeftBearer}, + 'Session' := Session} = Data) -> gtp_context:request_finished(CommandReqKey), LeftTunnel = gtp_path:bind(Response, LeftTunnel0), DataNew = Data#{left_tunnel => LeftTunnel}, - {_, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel), + {_, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel, LeftBearer), if Cause =:= request_accepted andalso BearerCause =:= request_accepted -> - {_, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel), + {_, NewSOpts} = update_session_from_gtp_req(IEs, Session, LeftTunnel, LeftBearer), URRActions = gtp_context:collect_charging_events(OldSOpts, NewSOpts), gtp_context:trigger_usage_report(self(), URRActions, PCtx), {keep_state, DataNew}; @@ -497,7 +499,8 @@ copy_to_session(_, #v2_protocol_configuration_options{config = {0, Options}}, lists:foldr(fun copy_ppp_to_session/2, Session, Options); copy_to_session(_, #v2_access_point_name{apn = APN}, _AAAopts, Session) -> {NI, _OI} = ergw_node_selection:split_apn(APN), - Session#{'Called-Station-Id' => + Session#{'APN' => APN, + 'Called-Station-Id' => iolist_to_binary(lists:join($., NI))}; copy_to_session(_, #v2_msisdn{msisdn = MSISDN}, _AAAopts, Session) -> Session#{'Calling-Station-Id' => MSISDN, '3GPP-MSISDN' => MSISDN}; @@ -616,20 +619,32 @@ copy_qos_to_session(#{?'Bearer Contexts to be created' := copy_qos_to_session(_, Session) -> Session. -copy_tunnel_to_session(#tunnel{remote = #fq_teid{ip = {_,_,_,_} = IP}}, Session) -> - Session#{'3GPP-SGSN-Address' => IP}; -copy_tunnel_to_session(#tunnel{remote = #fq_teid{ip = {_,_,_,_,_,_,_,_} = IP}}, Session) -> - Session#{'3GPP-SGSN-IPv6-Address' => IP}; +ip_to_session({_,_,_,_} = IP, #{ip4 := Key}, Session) -> + Session#{Key => IP}; +ip_to_session({_,_,_,_,_,_,_,_} = IP, #{ip6 := Key}, Session) -> + Session#{Key => IP}. + +copy_tunnel_to_session(#tunnel{version = Version, remote = #fq_teid{ip = IP}}, Session) -> + ip_to_session(IP, #{ip4 => '3GPP-SGSN-Address', + ip6 => '3GPP-SGSN-IPv6-Address'}, + Session#{'GTP-Version' => Version}); copy_tunnel_to_session(_, Session) -> Session. -update_session_from_gtp_req(IEs, Session, Tunnel) +copy_bearer_to_session(#bearer{remote = #fq_teid{ip = IP}}, Session) -> + ip_to_session(IP, #{ip4 => '3GPP-SGSN-UP-Address', + ip6 => '3GPP-SGSN-UP-IPv6-Address'}, Session); +copy_bearer_to_session(_, Session) -> + Session. + +update_session_from_gtp_req(IEs, Session, Tunnel, Bearer) when is_record(Tunnel, tunnel) -> OldSOpts = ergw_aaa_session:get(Session), NewSOpts0 = copy_qos_to_session(IEs, OldSOpts), NewSOpts1 = copy_tunnel_to_session(Tunnel, NewSOpts0), + NewSOpts2 = copy_bearer_to_session(Bearer, NewSOpts1), NewSOpts = - maps:fold(copy_to_session(_, _, undefined, _), NewSOpts1, IEs), + maps:fold(copy_to_session(_, _, undefined, _), NewSOpts2, IEs), ergw_aaa_session:set(Session, NewSOpts), {OldSOpts, NewSOpts}. diff --git a/apps/ergw_core/test/ergw_core_sbi_h.erl b/apps/ergw_core/test/ergw_core_sbi_h.erl new file mode 100644 index 00000000..573e2c31 --- /dev/null +++ b/apps/ergw_core/test/ergw_core_sbi_h.erl @@ -0,0 +1,55 @@ +-module(ergw_core_sbi_h). + +-export([init/2, allowed_methods/2, content_types_accepted/2, to_json/2]). + +init(Req, State) -> + {cowboy_rest, Req, State}. + +allowed_methods(Req, State) -> + {[<<"POST">>], Req, State}. + +content_types_accepted(Req, State) -> + {[{{<<"application">>, <<"json">>, '*'}, to_json}], + Req, State}. + +read_body(Req0, Acc) -> + case cowboy_req:read_body(Req0) of + {ok, Data, Req} -> {ok, <>, Req}; + {more, Data, Req} -> read_body(Req, <>) + end. + +to_json(Req0, State) -> + {ok, Data, Req1} = read_body(Req0, <<>>), + JSON = jsx:decode(Data, [{labels, binary}, return_maps]), + case JSON of + %% #{<<"gpsi">> := <<"msisdn-440000000000">>} -> + %% Headers = #{<<"content-type">> => <<"application/json">>}, + %% Body = jsx:encode(#{<<"ipv4Addr">> => <<"127.0.0.1">>}), + %% Resp = cowboy_req:reply(200, Headers, Body, Req1), + %% {stop, Resp, State}; + %% #{<<"gpsi">> := <<"msisdn-440000000001">>} -> + %% Headers = #{<<"content-type">> => <<"application/json">>}, + %% Body = jsx:encode(#{<<"ipv6Addr">> => <<"::1">>}), + %% Resp = cowboy_req:reply(200, Headers, Body, Req1), + %% {stop, Resp, State}; + #{<<"gpsi">> := <<"msisdn-440000000000">>} -> + Headers = #{<<"content-type">> => <<"application/json">>}, + Body = jsx:encode(#{<<"fqdn">> => <<"topon.sx.prox03.epc.mnc001.mcc001.3gppnetwork.org">>}), + Resp = cowboy_req:reply(200, Headers, Body, Req1), + {stop, Resp, State}; + _ -> + Headers = #{<<"content-type">> => <<"application/problem+json">>}, + Error = + #{<<"type">> => <<"type">>, + <<"titel">> => <<"title">>, + <<"status">> => 404, + <<"detail">> => <<"detail">>, + <<"instance">> => <<"instance">>, + <<"cause">> => <<"cause">>, + <<"invalidParams">> => + [] + }, + Body = jsx:encode(Error), + Resp = cowboy_req:reply(404, Headers, Body, Req1), + {stop, Resp, State} + end. diff --git a/apps/ergw_core/test/ergw_core_sbi_lib.erl b/apps/ergw_core/test/ergw_core_sbi_lib.erl new file mode 100644 index 00000000..0ac651ef --- /dev/null +++ b/apps/ergw_core/test/ergw_core_sbi_lib.erl @@ -0,0 +1,38 @@ +%% Copyright 2021, Travelping GmbH + +%% This program is free software; you can redistribute it and/or +%% modify it under the terms of the GNU General Public License +%% as published by the Free Software Foundation; either version +%% 2 of the License, or (at your option) any later version. + +-module(ergw_core_sbi_lib). + +-export([init_per_group/1, end_per_group/1]). + +init_per_group(Config) -> + [{ok, _} = application:ensure_all_started(App) || + App <- [ranch, ergw_sbi_client]], + + ProtoOpts = + #{env => + #{dispatch => + cowboy_router:compile( + [{'_', [{"/nf-selection", ergw_core_sbi_h, []}]}]) + }}, + {ok, _O} = cowboy:start_clear(?MODULE, [], ProtoOpts), + Port = ranch:get_port(?MODULE), + ct:pal("CowBoy: ~p, Port: ~p", [_O, Port]), + + SbiCfg = + #{upf_selection => + #{timeout => 1000, + default => "fallback", + endpoint => + uri_string:recompose( + #{host => "localhost", path => "/nf-selection", port => Port, scheme => "http"}) + } + }, + [{sbi_config, SbiCfg}|Config]. + +end_per_group(_) -> + (catch cowboy:stop_listener(?MODULE)). diff --git a/apps/ergw_core/test/pgw_SUITE.erl b/apps/ergw_core/test/pgw_SUITE.erl index 06c4e06f..22415a44 100644 --- a/apps/ergw_core/test/pgw_SUITE.erl +++ b/apps/ergw_core/test/pgw_SUITE.erl @@ -616,9 +616,9 @@ node_sel_update(Node, {_,_,_,_,_,_,_,_} = IP) -> suite() -> [{timetrap,{seconds,30}}]. -init_per_suite(Config0) -> +init_per_suite(Config) -> [{handler_under_test, ?HUT}, - {app_cfg, ?TEST_CONFIG} | Config0]. + {app_cfg, ?TEST_CONFIG} | Config]. end_per_suite(_Config) -> ok. @@ -632,18 +632,21 @@ init_per_group(single_socket, Config0) -> AppCfg = set_cfg_value([ergw_core, sockets, 'irx-socket', send_port], false, AppCfg0), Config = lists:keystore(app_cfg, 1, Config0, {app_cfg, AppCfg}), lib_init_per_group(Config); -init_per_group(ipv6, Config) -> +init_per_group(ipv6, Config0) -> case ergw_test_lib:has_ipv6_test_config() of true -> - update_app_config(ipv6, ?CONFIG_UPDATE, Config); + Config1 = ergw_core_sbi_lib:init_per_group(Config0), + update_app_config(ipv6, ?CONFIG_UPDATE, Config1); _ -> {skip, "IPv6 test IPs not configured"} end; -init_per_group(ipv4, Config) -> - update_app_config(ipv4, ?CONFIG_UPDATE, Config). +init_per_group(ipv4, Config0) -> + Config1 = ergw_core_sbi_lib:init_per_group(Config0), + update_app_config(ipv4, ?CONFIG_UPDATE, Config1). -end_per_group(Group, _Config) +end_per_group(Group, Config) when Group == ipv4; Group == ipv6 -> + ergw_core_sbi_lib:end_per_group(Config), ok; end_per_group(Group, Config) when Group == common; @@ -672,6 +675,7 @@ common() -> path_maintenance, simple_session_request, simple_session_request_cp_teid, + simple_session_request_nf_sel, change_reporting_indication, duplicate_session_request, duplicate_session_slow, @@ -838,6 +842,11 @@ init_per_testcase(simple_session_request_cp_teid, Config) -> {ok, _} = ergw_test_sx_up:feature('pgw-u01', ftup, 0), setup_per_testcase(Config), Config; +init_per_testcase(simple_session_request_nf_sel, Config) -> + ergw_test_lib:set_apn_key(upf_selection, [ergw_sbi_client]), + ergw_sbi_client:setup(proplists:get_value(sbi_config, Config, #{})), + setup_per_testcase(Config), + Config; init_per_testcase(duplicate_session_slow, Config) -> setup_per_testcase(Config), ok = meck:expect(gtp_context, handle_event, @@ -1053,6 +1062,10 @@ end_per_testcase(path_maintenance, Config) -> end_per_testcase(simple_session_request_cp_teid, Config) -> {ok, _} = ergw_test_sx_up:feature('pgw-u01', ftup, 1), end_per_testcase(Config); +end_per_testcase(simple_session_request_nf_sel, Config) -> + ergw_test_lib:set_apn_key(upf_selection, ['3gpp']), + ergw_sbi_client:setup(#{}), + end_per_testcase(Config); end_per_testcase(duplicate_session_slow, Config) -> ok = meck:delete(gtp_context, handle_event, 4), end_per_testcase(Config); @@ -1691,6 +1704,39 @@ simple_session_request_cp_teid(Config) -> meck_validate(Config), ok. +%%-------------------------------------------------------------------- +simple_session_request_nf_sel() -> + [{doc, "Check simple Create Session, Delete Session sequence with NF selection API"}]. +simple_session_request_nf_sel(Config) -> + PoolId = [<<"pool-A">>, ipv4, "10.180.0.1"], + + ?match_metric(prometheus_gauge, ergw_local_pool_free, PoolId, ?IPv4PoolSize), + ?match_metric(prometheus_gauge, ergw_local_pool_used, PoolId, 0), + + {GtpC, _, _} = create_session(ipv4, Config), + + ?match_metric(prometheus_gauge, ergw_local_pool_free, PoolId, ?IPv4PoolSize - 1), + ?match_metric(prometheus_gauge, ergw_local_pool_used, PoolId, 1), + + delete_session(GtpC), + + ?equal([], outstanding_requests()), + ok = meck:wait(?HUT, terminate, '_', ?TIMEOUT), + + ?match_metric(prometheus_gauge, ergw_local_pool_free, PoolId, ?IPv4PoolSize), + ?match_metric(prometheus_gauge, ergw_local_pool_used, PoolId, 0), + + SER = lists:last( + lists:filter( + fun(#pfcp{type = session_establishment_request}) -> true; + (_) ->false + end, ergw_test_sx_up:history('pgw-u02'))), + + ?match_map(#{create_pdr => '_', create_far => '_', create_urr => '_'}, SER#pfcp.ie), + + meck_validate(Config), + ok. + %%-------------------------------------------------------------------- change_reporting_indication() -> [{doc, "Check CRSI flag in Create Session"}]. diff --git a/apps/ergw_sbi_client/.gitignore b/apps/ergw_sbi_client/.gitignore new file mode 100644 index 00000000..48774e8f --- /dev/null +++ b/apps/ergw_sbi_client/.gitignore @@ -0,0 +1,21 @@ +.rebar3 +_* +.eunit +*.o +*.beam +*.plt +*.swp +*.swo +.erlang.cookie +ebin +log +erl_crash.dump +.rebar +logs +_build +.idea +*.iml +rebar3.crashdump +*~ +rebar3 +rebar.lock diff --git a/apps/ergw_sbi_client/rebar.config b/apps/ergw_sbi_client/rebar.config new file mode 100644 index 00000000..53c5c312 --- /dev/null +++ b/apps/ergw_sbi_client/rebar.config @@ -0,0 +1,22 @@ +%-*-Erlang-*- +{erl_opts, [debug_info]}. + +{deps, [ + {gun, "2.0.0-rc.2"}, + {jsx, "3.0.0"} +]}. + +{profiles, + [{test, [{erl_opts, [nowarn_export_all]}, + {deps, [{cowboy, "2.9.0"}]} + ]} + ]}. + +{shell, [ + % {config, "config/sys.config"}, + {apps, [ergw_sbi_client]} +]}. + +{xref_checks, [ + undefined_functions +]}. diff --git a/apps/ergw_sbi_client/src/ergw_sbi_client.app.src b/apps/ergw_sbi_client/src/ergw_sbi_client.app.src new file mode 100644 index 00000000..ac84f3a8 --- /dev/null +++ b/apps/ergw_sbi_client/src/ergw_sbi_client.app.src @@ -0,0 +1,17 @@ +{application, ergw_sbi_client, + [{description, "erGW SBI client"}, + {vsn, semver}, + {registered, []}, + {mod, {ergw_sbi_client_app, []}}, + {applications, + [kernel, + stdlib, + gun, + jsx + ]}, + {env,[]}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} + ]}. diff --git a/apps/ergw_sbi_client/src/ergw_sbi_client.erl b/apps/ergw_sbi_client/src/ergw_sbi_client.erl new file mode 100644 index 00000000..9d47bd4d --- /dev/null +++ b/apps/ergw_sbi_client/src/ergw_sbi_client.erl @@ -0,0 +1,251 @@ +-module(ergw_sbi_client). + +-export([ + start_link/0 +]). + +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2 +]). + +-export([ + setup/1, + upf_selection/1 +]). + +-ifdef(TEST). +-export([from_session/1]). +-endif. + +-ignore_xref([?MODULE]). + +-include_lib("gtplib/include/gtp_packet.hrl"). + +%%% ============================================================================ +%%% API functions +%%% ============================================================================ + +start_link() -> + Opts = [{hibernate_after, 5000}, {spawn_opt, [{fullsweep_after, 0}]}], + gen_server:start_link({local, ?MODULE}, ?MODULE, [], Opts). + +upf_selection(Session) -> + Query = from_session(Session), + gen_server:call(?MODULE, {upf_selection, Query}). + +setup(Config) -> + gen_server:call(?MODULE, {setup, Config}). + +%%% ============================================================================ +%%% gen_server callbacks functions +%%% ============================================================================ + +init([]) -> + {ok, #{}}. + +handle_call({setup, Config}, _From, _State) -> + _ = ergw_sbi_client_config:validate_options( + fun ergw_sbi_client_config:validate_option/2, Config), + {reply, ok, #{}}; +handle_call({upf_selection, Data}, _From, State) -> + {Result, NewState} = upf_selection(Data, State), + {reply, Result, NewState}; +handle_call(_Request, _From, State) -> + {reply, ignored, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +%%% ============================================================================ +%%% Internal Functions +%%% ============================================================================ + +put_kv([K], V, M) -> + M#{K => V}; +put_kv([H|T], V, M) -> + maps:put(H, put_kv(T, V, maps:get(H, M, #{})), M). + +from_ip({_, _, _, _} = IP) -> + #{<<"ipv4Addr">> => iolist_to_binary(inet:ntoa(IP))}; +from_ip({_, _, _, _,_, _, _, _} = IP) -> + #{<<"ipv6Addr">> => iolist_to_binary(inet:ntoa(IP))}. + +from_session(Session) -> + maps:fold(fun from_session/3, #{}, Session). + +from_plmnid({MCC, MNC}) -> + #{<<"mcc">> => MCC, <<"mnc">> => MNC}. + +from_uli('CGI', #cgi{plmn_id = PlmnId, lac = LAC, ci = CI}, Req) -> + Id = #{<<"plmnId">> => from_plmnid(PlmnId), + <<"lac">> => LAC, + <<"cellId">> => CI}, + put_kv([<<"userLocationInfo">>, <<"cgi">>], Id, Req); +from_uli('SAI', #sai{plmn_id = PlmnId, lac = LAC, sac = SAC}, Req) -> + Id = #{<<"plmnId">> => from_plmnid(PlmnId), + <<"lac">> => LAC, + <<"sac">> => SAC}, + put_kv([<<"userLocationInfo">>, <<"sai">>], Id, Req); +from_uli('RAI', #rai{plmn_id = PlmnId, lac = LAC, rac = RAC}, Req) -> + Id = #{<<"plmnId">> => from_plmnid(PlmnId), + <<"lac">> => LAC, + <<"rac">> => RAC}, + put_kv([<<"userLocationInfo">>, <<"rai">>], Id, Req); +from_uli('LAI', #lai{plmn_id = PlmnId, lac = LAC}, Req) -> + Id = #{<<"plmnId">> => from_plmnid(PlmnId), + <<"lac">> => LAC}, + put_kv([<<"userLocationInfo">>, <<"lai">>], Id, Req); +from_uli(_, _, Req) -> + Req. + +from_session('APN', [H|_] = V, Req) when is_binary(H) -> + Req#{<<"accessPointName">> => iolist_to_binary(lists:join($., V))}; +from_session('3GPP-IMSI', V, Req) -> + Req#{<<"supi">> => <<"imsi-", V/binary>>}; +from_session('3GPP-SGSN-MCC-MNC', PlmnId, Req) -> + put_kv([<<"vPlmn">>, <<"plmnId">>], from_plmnid(PlmnId), Req); +from_session('3GPP-SGSN-Address', V, Req) -> + put_kv([<<"vPlmn">>, <<"cpAddress">>], from_ip(V), Req); +from_session('3GPP-SGSN-IPv6-Address', V, Req) -> + put_kv([<<"vPlmn">>, <<"cpAddress">>], from_ip(V), Req); +from_session('3GPP-SGSN-UP-Address', V, Req) -> + put_kv([<<"vPlmn">>, <<"upAddress">>], from_ip(V), Req); +from_session('3GPP-SGSN-UP-IPv6-Address', V, Req) -> + put_kv([<<"vPlmn">>, <<"upAddress">>], from_ip(V), Req); +from_session('GTP-Version', v1, Req) -> + Req#{<<"protocolType">> => <<"GTPv1">>}; +from_session('GTP-Version', v2, Req) -> + Req#{<<"protocolType">> => <<"GTPv2">>}; +from_session('3GPP-MSISDN', V, Req) -> + Req#{<<"gpsi">> => <<"msisdn-", V/binary>>}; +from_session('3GPP-IMEISV' , V, Req) -> + Req#{<<"pei">> => <<"imeisv-", V/binary>>}; + +from_session('User-Location-Info', V, Req) when is_map(V) -> + maps:fold(fun from_uli/3, Req, V); + +from_session('QoS-Information' = K, V, Req) -> + Req#{K => V}; +from_session(_, _, Req) -> + Req. + +upf_selection(Data, #{upf_selection := + #{timeout := Timeout,uri := #{path := Path}} = Config} = State) -> + case gun_open(Config) of + {ok, Pid} -> + Headers = [{<<"content-type">>, <<"application/json">>}], + Body = jsx:encode(Data), + StreamRef = gun:post(Pid, Path, Headers, Body), + Resp = get_reponse(#{timeout => Timeout, + stream_ref => StreamRef, + pid => Pid, + acc => <<>>}), + {Resp, State#{upf_selection => Config#{pid => Pid}}}; + {error, timeout} = Resp -> + {Resp, State}; + {error, _Reason} = Resp -> + {Resp, State} + end; +upf_selection(Data, State) -> + case application:get_env(ergw_sbi_client, upf_selection, #{}) of + #{endpoint := Endpoint} = Config -> + case parse_uri(Endpoint) of + #{} = URI -> + upf_selection(Data, State#{upf_selection => Config#{uri => URI}}); + Error -> + {Error, State} + end; + _ -> + {{error, not_configured}, State} + end. + +gun_open(#{pid := Pid} = Opts) when is_pid(Pid) -> + case is_process_alive(Pid) of + true -> + {ok, Pid}; + false -> + gun_open(maps:remove(pid, Opts)) + end; +gun_open(#{uri := #{host := Host, port := Port}, timeout := Timeout}) -> + GunOpts = #{http2_opts => #{keepalive => infinity}, + protocols => [http2]}, + case gun:open(Host, Port, GunOpts) of + {ok, Pid} -> + case gun:await_up(Pid, Timeout) of + {ok, _} -> + {ok, Pid}; + {error, timeout} = Resp -> + Resp; + {error, _Reason} = Resp -> + Resp + end; + Error -> + Error + end; +gun_open(_) -> + {error, badarg}. + +get_reponse(#{pid := Pid, stream_ref := StreamRef, timeout := Timeout, acc := Acc} = Opts) -> + %% @TODO: fix correct 'Timeout' calculation issue and add time of request finished + case gun:await(Pid, StreamRef, Timeout) of + {response, fin, Status, Headers} -> + handle_response(#{status => Status, headers => Headers}, Acc); + {response, nofin, Status, Headers} -> + get_reponse(Opts#{status => Status, headers => Headers}); + {data, nofin, Data} -> + get_reponse(Opts#{acc => <>}); + {data, fin, Data} -> + handle_response(Opts, <>); + {error, timeout} = Response -> + Response; + {error, _Reason} = Response-> + Response + end. + +handle_response(#{status := 200, headers := Headers}, Data) -> + case lists:keyfind(<<"content-type">>, 1, Headers) of + {<<"content-type">>, ContentType} -> + case cow_http_hd:parse_content_type(ContentType) of + {<<"application">>, <<"json">>, _Param} -> + case jsx:decode(Data, [{labels, binary}, return_maps]) of + #{<<"ipv4Addr">> := IP} when is_binary(IP) -> + inet:parse_ipv4_address(binary_to_list(IP)); + #{<<"ipv6Addr">> := IP} when is_binary(IP) -> + inet:parse_ipv6_address(binary_to_list(IP)); + #{<<"fqdn">> := FQDN} when is_binary(FQDN) -> + {ok, FQDN}; + _ -> + {error, invalid_payload} + end; + _ -> + {error, invalid_content_type} + end; + _ -> + {error, no_content_type} + end; +handle_response(_, _) -> + {error, invalid_response}. + +parse_uri(URI) when is_list(URI) -> + case uri_string:parse(URI) of + #{host := _, port := _, path := _, scheme := _} = ParsedUri -> + ParsedUri; + #{} = ParsedUri -> + ParsedUri#{port => 443}; + Error -> + Error + end; +parse_uri(URI) when is_binary(URI) -> + parse_uri(binary_to_list(URI)); +parse_uri(_) -> + {error, parse}. diff --git a/apps/ergw_sbi_client/src/ergw_sbi_client_app.erl b/apps/ergw_sbi_client/src/ergw_sbi_client_app.erl new file mode 100644 index 00000000..fd6af873 --- /dev/null +++ b/apps/ergw_sbi_client/src/ergw_sbi_client_app.erl @@ -0,0 +1,18 @@ +%%%------------------------------------------------------------------- +%% @doc ergw_sbi_client public API +%% @end +%%%------------------------------------------------------------------- + +-module(ergw_sbi_client_app). + +-behaviour(application). + +-export([start/2, stop/1]). + +start(_StartType, _StartArgs) -> + ergw_sbi_client_sup:start_link(). + +stop(_State) -> + ok. + +%% internal functions diff --git a/apps/ergw_sbi_client/src/ergw_sbi_client_config.erl b/apps/ergw_sbi_client/src/ergw_sbi_client_config.erl new file mode 100644 index 00000000..3765ba60 --- /dev/null +++ b/apps/ergw_sbi_client/src/ergw_sbi_client_config.erl @@ -0,0 +1,36 @@ +-module(ergw_sbi_client_config). + +-export([ + validate_options/2, + validate_option/2 +]). + +validate_options(Fun, M) when is_map(M) -> + maps:fold( + fun(K0, V0, A) -> + {K, V} = validate_option(Fun, K0, V0), + A#{K => V} + end, #{}, M); +validate_options(_Fun, []) -> + []; +validate_options(Fun, [{Opt, Value} | Tail]) -> + [validate_option(Fun, Opt, Value) | validate_options(Fun, Tail)]. + +validate_option(Fun, Opt, Value) when is_function(Fun, 2) -> + {Opt, Fun(Opt, Value)}; +validate_option(Fun, Opt, Value) when is_function(Fun, 1) -> + Fun({Opt, Value}). + +validate_option(upf_selection = Opt, #{default := [_|_], + endpoint := [_|_] = URI, + timeout := T} = Value) + when is_integer(T), T > 0-> + case uri_string:parse(URI) of + #{host := _, path := _, scheme := _} -> + ok = application:set_env(ergw_sbi_client, Opt, Value), + Value; + _ -> + erlang:error(badarg, [Opt, Value]) + end; +validate_option(Opt, Value) -> + erlang:error(badarg, [Opt, Value]). diff --git a/apps/ergw_sbi_client/src/ergw_sbi_client_sup.erl b/apps/ergw_sbi_client/src/ergw_sbi_client_sup.erl new file mode 100644 index 00000000..934d1700 --- /dev/null +++ b/apps/ergw_sbi_client/src/ergw_sbi_client_sup.erl @@ -0,0 +1,43 @@ +%%%------------------------------------------------------------------- +%% @doc ergw_sbi_client top level supervisor. +%% @end +%%%------------------------------------------------------------------- + +-module(ergw_sbi_client_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +%% sup_flags() = #{strategy => strategy(), % optional +%% intensity => non_neg_integer(), % optional +%% period => pos_integer()} % optional +%% child_spec() = #{id => child_id(), % mandatory +%% start => mfargs(), % mandatory +%% restart => restart(), % optional +%% shutdown => shutdown(), % optional +%% type => worker(), % optional +%% modules => modules()} % optional +init([]) -> + SupFlags = #{ + strategy => one_for_all, + intensity => 0, + period => 1 + }, + ChildSpec = #{ + id => ergw_sbi_client, + start => {ergw_sbi_client, start_link, []}, + type => worker, + restart => permanent, + shutdown => 5000 + }, + {ok, {SupFlags, [ChildSpec]}}. + +%% internal functions diff --git a/apps/ergw_sbi_client/test/ergw_sbi_client_SUITE.erl b/apps/ergw_sbi_client/test/ergw_sbi_client_SUITE.erl new file mode 100644 index 00000000..f8c0b026 --- /dev/null +++ b/apps/ergw_sbi_client/test/ergw_sbi_client_SUITE.erl @@ -0,0 +1,171 @@ +-module(ergw_sbi_client_SUITE). + +-compile([export_all, nowarn_export_all]). + +-include_lib("gtplib/include/gtp_packet.hrl"). + +-define(match(Guard, Expr), + ((fun () -> + case (Expr) of + Guard -> ok; + V -> ct:pal("MISMATCH(~s:~b, ~s)~nExpected: ~p~nActual: ~p~n", + [?FILE, ?LINE, ??Expr, ??Guard, V]), + error(badmatch) + end + end)())). + +all() -> + [from_session, + upf_selection + ]. + +init_per_suite(Config) -> + [{ok, _} = application:ensure_all_started(App) || + App <- [ranch, gun, ergw_sbi_client]], + + ProtoOpts = + #{env => + #{dispatch => + cowboy_router:compile( + [{'_', [{"/ok", sbi_h, []}]}]) + }}, + {ok, _} = cowboy:start_clear(?MODULE, [], ProtoOpts), + Port = ranch:get_port(?MODULE), + + TestCfg = + #{upf_selection => + #{timeout => 1000, + default => "fallback", + endpoint => + uri_string:recompose( + #{host => "localhost", path => "/ok", port => Port, scheme => "http"}) + } + }, + _ = ergw_sbi_client_config:validate_options(fun ergw_sbi_client_config:validate_option/2, TestCfg), + ok = ergw_sbi_client:setup(TestCfg), + Config. + +end_per_suite(_) -> + ok = cowboy:stop_listener(?MODULE), + ok. + +%%-------------------------------------------------------------------- +session() -> + %% copied from ergw_core/pgw_SUITE + #{'Username' => <<"111111111111111/3520990017614823/440000000000/ATOM/TEXT/12345@example.net">>, + 'Framed-Protocol' => 'GPRS-PDP-Context', + 'Multi-Session-Id' => 15958412130338583375391261628607396908, + '3GPP-SGSN-Address' => {127,127,127,127}, + '3GPP-RAT-Type' => 1, + 'Framed-IPv6-Pool' => <<"pool-A">>, + '3GPP-Charging-Id' => 1981737279, + '3GPP-Selection-Mode' => 0, + '3GPP-IMSI' => <<"111111111111111">>, + '3GPP-MSISDN' => <<"440000000000">>, + 'Password' => <<"ergw">>, + '3GPP-GGSN-Address' => {127,0,0,1}, + 'Session-Start' => -576460456578330165, + 'Calling-Station-Id' => <<"440000000000">>, + 'Service-Type' => 'Framed-User', + '3GPP-GGSN-MCC-MNC' => <<"00101">>, + '3GPP-IMEISV' => <<"3520990017614823">>, + 'Charging-Rule-Base-Name' => <<"m2m0001">>, + '3GPP-MS-TimeZone' => {10,0}, + 'Session-Id' => 15958412130338583375391261628607396909, + 'Framed-Pool' => <<"pool-A">>, + 'Diameter-Session-Id' => <<"zeus;201423479;105063168;207890578476">>, + 'User-Location-Info' => + #{'ext-macro-eNB' => + #ext_macro_enb{plmn_id = {<<"001">>, <<"001">>}, + id = rand:uniform(16#1fffff)}, + 'SAI' => + #sai{plmn_id = {<<"001">>, <<"001">>}, + lac = rand:uniform(16#ffff), + sac = rand:uniform(16#ffff)}, + 'RAI' => + #rai{plmn_id = {<<"001">>, <<"001">>}, + lac = rand:uniform(16#ffff), + rac = rand:uniform(16#ffff)}, + 'TAI' => + #tai{plmn_id = {<<"001">>, <<"001">>}, + tac = rand:uniform(16#ffff)}}, + 'Called-Station-Id' => <<"example.net">>, + 'Node-Id' => <<"PGW-001">>, + 'QoS-Information' => + #{'APN-Aggregate-Max-Bitrate-DL' => 1704125000, + 'APN-Aggregate-Max-Bitrate-UL' => 48128000, + 'Allocation-Retention-Priority' => + #{'Pre-emption-Capability' => 1, + 'Pre-emption-Vulnerability' => 0, + 'Priority-Level' => 10}, + 'Guaranteed-Bitrate-DL' => 0, + 'Guaranteed-Bitrate-UL' => 0, + 'Max-Requested-Bandwidth-DL' => 0, + 'Max-Requested-Bandwidth-UL' => 0, + 'QoS-Class-Identifier' => 8}, + 'NAS-Identifier' => <<"NAS-Identifier">>, + '3GPP-NSAPI' => 5, + '3GPP-PDP-Type' => 'IPv4', + '3GPP-SGSN-MCC-MNC' => {<<"001">>,<<"001">>}, + '3GPP-IMSI-MCC-MNC' => <<"11111">>, + + %% TBD: + 'APN' => [<<"example">>, <<"net">>], + '3GPP-SGSN-UP-Address' => {127,127,127,127}, + 'GTP-Version' => v2 + }. + +%%-------------------------------------------------------------------- +from_session() -> + [{doc, "Check mapping from session to request"}]. +from_session(_Config) -> + Req = ergw_sbi_client:from_session(session()), + ct:pal("Req: ~p~n", [Req]), + + ?match( + #{'QoS-Information' := + #{'APN-Aggregate-Max-Bitrate-DL' := 1704125000, + 'APN-Aggregate-Max-Bitrate-UL' := 48128000, + 'Allocation-Retention-Priority' := + #{'Pre-emption-Capability' := 1, + 'Pre-emption-Vulnerability' := 0,'Priority-Level' := 10}, + 'Guaranteed-Bitrate-DL' := 0,'Guaranteed-Bitrate-UL' := 0, + 'Max-Requested-Bandwidth-DL' := 0, + 'Max-Requested-Bandwidth-UL' := 0,'QoS-Class-Identifier' := 8}, + <<"accessPointName">> := <<"example.net">>, + <<"gpsi">> := <<"msisdn-440000000000">>, + <<"pei">> := <<"imeisv-3520990017614823">>, + <<"protocolType">> := <<"GTPv2">>, + <<"supi">> := <<"imsi-111111111111111">>, + <<"userLocationInfo">> := + #{<<"rai">> := + #{<<"lac">> := _, + <<"plmnId">> := + #{<<"mcc">> := <<"001">>,<<"mnc">> := <<"001">>}, + <<"rac">> := _}, + <<"sai">> := + #{<<"lac">> := _, + <<"plmnId">> := + #{<<"mcc">> := <<"001">>,<<"mnc">> := <<"001">>}, + <<"sac">> := _}}, + <<"vPlmn">> := + #{<<"cpAddress">> := #{<<"ipv4Addr">> := <<"127.127.127.127">>}, + <<"plmnId">> := #{<<"mcc">> := <<"001">>, <<"mnc">> := <<"001">>}, + <<"upAddress">> := #{<<"ipv4Addr">> := <<"127.127.127.127">>}}}, Req), + ok. + +%%-------------------------------------------------------------------- +upf_selection() -> + [{doc, "Check upf_selection_api"}]. +upf_selection(_Config) -> + Session = session(), + + ?match({ok, {_,_,_,_}}, + ergw_sbi_client:upf_selection(Session#{'3GPP-MSISDN' => <<"440000000000">>})), + ?match({ok, {_,_,_,_,_,_,_,_}}, + ergw_sbi_client:upf_selection(Session#{'3GPP-MSISDN' => <<"440000000001">>})), + ?match({ok, F} when is_binary(F), + ergw_sbi_client:upf_selection(Session#{'3GPP-MSISDN' => <<"440000000002">>})), + ?match({error, _}, + ergw_sbi_client:upf_selection(Session#{'3GPP-MSISDN' => <<"440000000009">>})), + ok. diff --git a/apps/ergw_sbi_client/test/sbi_h.erl b/apps/ergw_sbi_client/test/sbi_h.erl new file mode 100644 index 00000000..ee3c05d4 --- /dev/null +++ b/apps/ergw_sbi_client/test/sbi_h.erl @@ -0,0 +1,55 @@ +-module(sbi_h). + +-export([init/2, allowed_methods/2, content_types_accepted/2, to_json/2]). + +init(Req, State) -> + {cowboy_rest, Req, State}. + +allowed_methods(Req, State) -> + {[<<"POST">>], Req, State}. + +content_types_accepted(Req, State) -> + {[{{<<"application">>, <<"json">>, '*'}, to_json}], + Req, State}. + +read_body(Req0, Acc) -> + case cowboy_req:read_body(Req0) of + {ok, Data, Req} -> {ok, <>, Req}; + {more, Data, Req} -> read_body(Req, <>) + end. + +to_json(Req0, State) -> + {ok, Data, Req1} = read_body(Req0, <<>>), + JSON = jsx:decode(Data, [{labels, binary}, return_maps]), + + JsonHeaders = #{<<"content-type">> => <<"application/json;charset=utf-8">>}, + ProblemHeaders = #{<<"content-type">> => <<"application/problem+json">>}, + + case JSON of + #{<<"gpsi">> := <<"msisdn-440000000000">>} -> + Body = jsx:encode(#{<<"ipv4Addr">> => <<"127.0.0.1">>}), + Resp = cowboy_req:reply(200, JsonHeaders, Body, Req1), + {stop, Resp, State}; + #{<<"gpsi">> := <<"msisdn-440000000001">>} -> + Body = jsx:encode(#{<<"ipv6Addr">> => <<"::1">>}), + Resp = cowboy_req:reply(200, JsonHeaders, Body, Req1), + {stop, Resp, State}; + #{<<"gpsi">> := <<"msisdn-440000000002">>} -> + Body = jsx:encode(#{<<"fqdn">> => <<"test.node.epc">>}), + Resp = cowboy_req:reply(200, JsonHeaders, Body, Req1), + {stop, Resp, State}; + _ -> + Error = + #{<<"type">> => <<"type">>, + <<"titel">> => <<"title">>, + <<"status">> => 404, + <<"detail">> => <<"detail">>, + <<"instance">> => <<"instance">>, + <<"cause">> => <<"cause">>, + <<"invalidParams">> => + [] + }, + Body = jsx:encode(Error), + Resp = cowboy_req:reply(404, ProblemHeaders, Body, Req1), + {stop, Resp, State} + end. diff --git a/apps/k8s_dist/rebar.config b/apps/k8s_dist/rebar.config index 3210a7ec..e9b8299b 100644 --- a/apps/k8s_dist/rebar.config +++ b/apps/k8s_dist/rebar.config @@ -1,9 +1,7 @@ %-*-Erlang-*- {erl_opts, [debug_info]}. -{deps, [ - {gun, {git, "https://github.com/ninenines/gun.git", {tag, "2.0.0-rc.1"}}} -]}. +{deps, [{gun, "2.0.0-rc.2"}]}. %% xref checks to run {xref_checks, [locals_not_used, deprecated_function_calls, diff --git a/rebar.config b/rebar.config index 4b6227d2..eb5c73b8 100644 --- a/rebar.config +++ b/rebar.config @@ -29,7 +29,7 @@ {profiles, [ {test, [ {erl_opts, [nowarn_export_all]}, - {deps, [{gun, {git, "https://github.com/ninenines/gun.git", {tag, "2.0.0-pre.1"}}}, + {deps, [{gun, "2.0.0-rc.2"}, {parse_trans, "3.3.0"}, {meck, "0.8.13"}, {proper, "1.3.0"} diff --git a/rebar.lock b/rebar.lock index 9e5c988d..9a830598 100644 --- a/rebar.lock +++ b/rebar.lock @@ -16,10 +16,7 @@ {<<"fast_yaml">>,{pkg,<<"fast_yaml">>,<<"1.0.31">>},0}, {<<"gen_batch_server">>,{pkg,<<"gen_batch_server">>,<<"0.8.4">>},1}, {<<"gtplib">>,{pkg,<<"gtplib">>,<<"3.0.0">>},0}, - {<<"gun">>, - {git,"https://github.com/ninenines/gun.git", - {ref,"3a3e56fb66edaaa1a7093744a0fd8303b993b3c8"}}, - 0}, + {<<"gun">>,{pkg,<<"gun">>,<<"2.0.0-rc.2">>},0}, {<<"jesse">>,{pkg,<<"jesse">>,<<"1.5.6">>},0}, {<<"jobs">>,{pkg,<<"jobs">>,<<"0.9.0">>},0}, {<<"jsx">>,{pkg,<<"jsx">>,<<"3.0.0">>},0}, @@ -72,6 +69,7 @@ {<<"fast_yaml">>, <<"A66D0E678341DC20680AECE8E6FD566205A229981B5B3CFA698C66323B728DA2">>}, {<<"gen_batch_server">>, <<"20535E9DBD4DC234E63B3DF79BA65D8A770396B2CAD6B4068A1E37E3AE010900">>}, {<<"gtplib">>, <<"47700022BBD2D9221EA3EA9F61FDE1785A2AEC2686C7BD491993424B43147F69">>}, + {<<"gun">>, <<"7C489A32DEDCCB77B6E82D1F3C5A7DADFBFA004EC14E322CDB5E579C438632D2">>}, {<<"jesse">>, <<"593B8CAD26AF3CC0E44C727BD8CBDE56E2B0DE4C8D2738B1C258B6936A40A6A3">>}, {<<"jobs">>, <<"B2F0071531C4A48575A096FEC428DCDD4555C64E5086FE521FCEAF581B1F51DC">>}, {<<"jsx">>, <<"20A170ABD4335FC6DB24D5FAD1E5D677C55DADF83D1B20A8A33B5FE159892A39">>}, @@ -101,6 +99,7 @@ {<<"fast_yaml">>, <<"8AA129E3384C5AA9F5F1597A1E3321A63A44860F3951F23EEED950BF0EE59643">>}, {<<"gen_batch_server">>, <<"FF6B0ED0F7BE945F38B94DDD4784D128F35FF029C34DAD6CA0C6CB17AB7BC9C4">>}, {<<"gtplib">>, <<"F55C692050A0CF748BA5E084FAAAE587FCBAE0A6A771A2EFD63555C5F90F7CE6">>}, + {<<"gun">>, <<"6B9D1EAE146410D727140DBF8B404B9631302ECC2066D1D12F22097AD7D254FC">>}, {<<"jesse">>, <<"3F9475B0C5B242E09592604AABB03328501D7E3D8A528173B5A75396EEDB0060">>}, {<<"jobs">>, <<"4E50CC8EDD7B5A0AE6F4C17B25514470A772626A5F6C24E9BC06A62AEF618237">>}, {<<"jsx">>, <<"37BECA0435F5CA8A2F45F76A46211E76418FBEF80C36F0361C249FC75059DC6D">>},