diff --git a/apps/ergw/priv/schemas/ergw_config.yaml b/apps/ergw/priv/schemas/ergw_config.yaml index f2931729..a9b451e2 100644 --- a/apps/ergw/priv/schemas/ergw_config.yaml +++ b/apps/ergw/priv/schemas/ergw_config.yaml @@ -687,6 +687,50 @@ properties: upf_nodes: type: object properties: + required_upff: + type: array + items: + type: string + enum: + - treu + - heeu + - pfdm + - ftup + - trst + - dlbd + - ddnd + - bucp + - epfar + - pfde + - frrt + - trace + - quoac + - udbc + - pdiu + - empu + - gcom + - bundl + - mte + - mnop + - sset + - ueip + - adpdp + - dpdra + - mptcp + - tscu + - ip6pl + - iptv + - norp + - vtime + - rttl + - mpas + - ethar + - ciot + - mt_edt + - gpqm + - qfqm + - atsss_ll + default: type: object properties: diff --git a/apps/ergw/src/ergw_config.erl b/apps/ergw/src/ergw_config.erl index 4b6da8f3..b8035d1e 100644 --- a/apps/ergw/src/ergw_config.erl +++ b/apps/ergw/src/ergw_config.erl @@ -463,7 +463,8 @@ config_meta_nodes() -> raddr => ip_address, rport => integer }, - #{default => Default, + #{required_upff => upff, + default => Default, entries => {map, {name, binary}, Node}}. config_meta_path_management() -> @@ -963,6 +964,11 @@ to_aaa_avp(K0, V0, M) -> to_aaa_avp(Map) when is_map(Map) -> maps:fold(fun to_aaa_avp/3, #{}, Map). +to_upff(V) when is_list(V) -> + MinUpFF = #up_function_features{_ = '_'}, + lists:foldl( + fun(X) -> to_atom(X) end, MinUpFF, V). + %% complex types to_object(Meta, K, V) @@ -1126,6 +1132,9 @@ from_aaa_avp(_K, Value) -> from_aaa_avp(Map) when is_map(Map) -> maps:fold(fun from_aaa_avp/3, #{}, Map). +from_upff(V) -> + [X || X <- '#info-'(up_function_features), '#get-'(X, V) =:= 1]. + %% complex types from_object(Meta, K, V) when is_map(Meta), is_map_key(K, Meta) -> @@ -1338,6 +1347,11 @@ load_typespecs() -> #cnf_type{ coerce = fun to_aaa_avp/1, serialize = fun from_aaa_avp/1 + }, + upff => + #cnf_type{ + coerce = fun to_upff/1, + serialize = fun from_upff/1 } }, register_typespec(Spec). diff --git a/apps/ergw/test/config_SUITE_data/pgw.json b/apps/ergw/test/config_SUITE_data/pgw.json index 644677f4..80e69d35 100644 --- a/apps/ergw/test/config_SUITE_data/pgw.json +++ b/apps/ergw/test/config_SUITE_data/pgw.json @@ -225,6 +225,7 @@ } ], "upf_nodes": { + "required_upff": ["ftup", "treu", "empu"], "default": { "heartbeat": { "interval": { diff --git a/apps/ergw_core/src/ergw_core.erl b/apps/ergw_core/src/ergw_core.erl index f14277fb..f7379591 100644 --- a/apps/ergw_core/src/ergw_core.erl +++ b/apps/ergw_core/src/ergw_core.erl @@ -40,6 +40,7 @@ terminate/3, code_change/4]). -include_lib("kernel/include/logger.hrl"). +-include_lib("pfcplib/include/pfcp_packet.hrl"). -include("ergw_core_config.hrl"). -include("include/ergw.hrl"). @@ -114,12 +115,16 @@ setopts(node_selection = What, Opts0) -> setopts(sx_defaults = What, Opts0) -> Opts = ergw_sx_node:validate_defaults(Opts0), gen_statem:call(?SERVER, {setopts, What, Opts}); +setopts(required_upff = What, Opts) when is_record(Opts, up_function_features) -> + gen_statem:call(?SERVER, {setopts, What, Opts}); setopts(path_management = What, Opts0) -> Opts = gtp_path:validate_options(Opts0), gen_statem:call(?SERVER, {setopts, What, Opts}); setopts(proxy_map = What, Opts0) -> Opts = gtp_proxy_ds:validate_options(Opts0), - gen_statem:call(?SERVER, {setopts, What, Opts}). + gen_statem:call(?SERVER, {setopts, What, Opts}); +setopts(What, Opts) -> + error(badarg, [What, Opts]). %% %% Initialize a new PFCP, GTPv1/v2-c or GTPv1-u socket @@ -380,6 +385,10 @@ handle_event({call, From}, {setopts, sx_defaults, Opts}, running, _) -> Reply = ergw_sx_node:set_defaults(Opts), {keep_state_and_data, [{reply, From, Reply}]}; +handle_event({call, From}, {setopts, required_upff, Opts}, running, _) -> + Reply = ergw_sx_node:set_required_upff(Opts), + {keep_state_and_data, [{reply, From, Reply}]}; + handle_event({call, From}, {setopts, path_management, Opts}, running, _) -> Reply = gtp_path:setopts(Opts), {keep_state_and_data, [{reply, From, Reply}]}; diff --git a/apps/ergw_core/src/ergw_sx_node.erl b/apps/ergw_core/src/ergw_sx_node.erl index 4cf4e281..46a291a7 100644 --- a/apps/ergw_core/src/ergw_sx_node.erl +++ b/apps/ergw_core/src/ergw_sx_node.erl @@ -15,7 +15,7 @@ %% API -export([request_connect/3, request_connect/5, wait_connect/1, attach/1, attach_tdf/2, notify_up/2, - set_defaults/1, add_sx_node/2]). + set_defaults/1, set_required_upff/1, add_sx_node/2]). -export([start_link/5, send/4, call/2, handle_request/3, response/3]). -export([validate_options/2, validate_defaults/1]). @@ -43,7 +43,8 @@ -include("ergw_core_config.hrl"). -include("include/ergw.hrl"). --record(data, {cfg, +-record(data, {required_upff, + cfg, mode = transient :: 'transient' | 'persistent', node_select :: atom(), retries = 0 :: non_neg_integer(), @@ -151,6 +152,11 @@ set_defaults(Opts0) -> Opts = ergw_sx_node:validate_defaults(Opts0), ergw_core_config:put(up_node_defaults, Opts). +set_required_upff(Opts) when is_record(Opts, up_function_features) -> + ergw_core_config:put(up_required_feature, Opts); +set_required_upff(Opts) -> + error(badarg, [Opts]). + add_sx_node(Name, Opts0) -> Opts = validate_options(Name, Opts0), {ok, Nodes} = ergw_core_config:get([nodes], #{}), @@ -319,6 +325,8 @@ init([Parent, Node, NodeSelect, IP4, IP6, NotifyUp]) -> #seid_key{seid = SEID}], gtp_context_reg:register(RegKeys, ?MODULE, self()), + {ok, ReqUpFF} = + ergw_core_config:get([up_required_feature], #up_function_features{_ = '_'}), {ok, Default} = ergw_core_config:get([up_node_defaults], #{}), Cfg = case ergw_core_config:get([nodes, Node], Default) of {ok, Cfg0} -> Cfg0; @@ -326,7 +334,8 @@ init([Parent, Node, NodeSelect, IP4, IP6, NotifyUp]) -> end, IP = select_node_ip(IP4, IP6), - Data0 = #data{cfg = maps:merge(Default, Cfg), + Data0 = #data{required_upff = ReqUpFF, + cfg = maps:merge(Default, Cfg), mode = mode(Cfg), node_select = NodeSelect, retries = 0, @@ -412,14 +421,13 @@ handle_event(cast, {response, association_setup_request, Error}, {next_state, dead, Data#data{retries = Retries + 1}}; handle_event(cast, {response, _, #pfcp{version = v1, type = association_setup_response, ie = IEs}}, - connecting, Data0) -> + connecting, Data) -> case IEs of #{pfcp_cause := #pfcp_cause{cause = 'Request accepted'}} -> - Data = handle_nodeup(IEs, Data0), - {next_state, {connected, init}, Data}; + handle_nodeup(IEs, Data); Other -> ?LOG(debug, "Other: ~p", [Other]), - {next_state, dead, Data0} + {next_state, dead, Data} end; handle_event(cast, {send, 'Access', _VRF, Data}, {connected, _}, @@ -789,21 +797,45 @@ heartbeat_response(ReqKey, #pfcp{type = heartbeat_request} = Request) -> ergw_sx_socket:send_response(ReqKey, Response, true). handle_nodeup(#{recovery_time_stamp := #recovery_time_stamp{time = RecoveryTS}} = IEs, - #data{pfcp_ctx = PNodeCtx, + #data{required_upff = ReqUpFF, + pfcp_ctx = PNodeCtx, dp = #node{node = Node, ip = IP}, vrfs = VRFs} = Data0) -> ?LOG(debug, "Node ~s (~s) is up", [Node, inet:ntoa(IP)]), ?LOG(debug, "Node IEs: ~s", [pfcp_packet:pretty_print(IEs)]), UPFeatures = maps:get(up_function_features, IEs, #up_function_features{_ = 0}), - UPIPResInfo = maps:get(user_plane_ip_resource_information, IEs, []), - Data = Data0#data{ - pfcp_ctx = PNodeCtx#pfcp_ctx{features = UPFeatures}, - recovery_ts = RecoveryTS, - features = UPFeatures, - vrfs = init_vrfs(VRFs, UPIPResInfo) - }, - install_cp_rules(Data). + case validate_up_features(UPFeatures, ReqUpFF) of + true -> + UPIPResInfo = maps:get(user_plane_ip_resource_information, IEs, []), + Data1 = Data0#data{ + pfcp_ctx = PNodeCtx#pfcp_ctx{features = UPFeatures}, + recovery_ts = RecoveryTS, + features = UPFeatures, + vrfs = init_vrfs(VRFs, UPIPResInfo) + }, + Data = install_cp_rules(Data1), + {next_state, {connected, init}, Data}; + {H, Got, Wanted} -> + ?LOG(critical, "~s UP Function Feature, wanted ~p to be ~p, got ~p", + [inet:ntoa(IP), H, Wanted, Got]), + {next_state, dead, Data0} + end. + +validate_up_features(UpFF, ReqUpFF) -> + validate_up_ff( + ergw_config:'#info-'(up_function_features), + tl(tuple_to_list(UpFF)), + tl(tuple_to_list(ReqUpFF))). + +validate_up_ff(_, List, List) -> + true; +validate_up_ff([_|T], [V|UpFF], [V|ReqUpFF]) -> + validate_up_ff(T, UpFF, ReqUpFF); +validate_up_ff([_|T], [_|UpFF], ['_'|ReqUpFF]) -> + validate_up_ff(T, UpFF, ReqUpFF); +validate_up_ff([H|_], [Got|_], [Wanted|_]) -> + {H, Got, Wanted}. init_node_cfg(#data{cfg = Cfg} = Data) -> Data#data{