osmith has submitted this change. ( https://gerrit.osmocom.org/c/osmo-s1gw/+/37046?usp=email )
Change subject: s1ap_proxy: implement patching of E-RAB SETUP REQ/RSP ......................................................................
s1ap_proxy: implement patching of E-RAB SETUP REQ/RSP
Change-Id: I88cb261d93b56a4e0ca06ad1d676d1ac16d115bb --- M src/s1ap_proxy.erl A test/s1ap_proxy_test.erl 2 files changed, 209 insertions(+), 3 deletions(-)
Approvals: osmith: Looks good to me, approved; Verified
diff --git a/src/s1ap_proxy.erl b/src/s1ap_proxy.erl index 62701ef..a78f084 100644 --- a/src/s1ap_proxy.erl +++ b/src/s1ap_proxy.erl @@ -1,17 +1,25 @@ -module(s1ap_proxy).
--export([handle_pdu/1]). +-export([handle_pdu/1, + encode_pdu/1, + decode_pdu/1]). + +-include("S1AP-PDU-Descriptions.hrl"). +-include("S1AP-PDU-Contents.hrl"). +-include("S1AP-Containers.hrl"). +-include("S1AP-Constants.hrl").
%% ------------------------------------------------------------------ %% public API %% ------------------------------------------------------------------
%% Process an S1AP PDU -handle_pdu(Data) -> +-spec handle_pdu(binary()) -> binary(). +handle_pdu(Data) when is_binary(Data) -> case decode_pdu(Data) of {ok, Pdu} -> logger:info("S1AP PDU: ~p", [Pdu]), - Data; + handle_pdu(Data, Pdu); {error, {asn1, Error}} -> logger:error("S1AP PDU decoding failed: ~p", [Error]), Data @@ -22,10 +30,111 @@ %% private API %% ------------------------------------------------------------------
+%% Encode an S1AP PDU +-spec encode_pdu(tuple()) -> {ok, binary()} | + {error, {asn1, tuple()}}. +encode_pdu(Pdu) -> + 'S1AP-PDU-Descriptions':encode('S1AP-PDU', Pdu). + + %% Decode an S1AP PDU -spec decode_pdu(binary()) -> {ok, tuple()} | {error, {asn1, tuple()}}. decode_pdu(Data) -> 'S1AP-PDU-Descriptions':decode('S1AP-PDU', Data).
+ +%% Helper function for handle_pdu/2. +%% Attempt to encode a new (modified) S1AP PDU, +%% return a new binary() on success or Data on error. +handle_pdu_new(Data, NewPdu) -> + case encode_pdu(NewPdu) of + {ok, NewData} -> + NewData; + {error, {asn1, Error}} -> + logger:error("S1AP PDU encoding failed: ~p", [Error]), + Data + end. + + +%% E-RAB SETUP REQUEST +handle_pdu(Data, {Outcome = initiatingMessage, + #'InitiatingMessage'{procedureCode = ?'id-E-RABSetup', + value = Content} = Pdu}) -> + logger:debug("Patching E-RAB SETUP REQUEST"), + IEs = handle_ies(Content#'E-RABSetupRequest'.protocolIEs, + ?'id-E-RABToBeSetupListBearerSUReq'), + NewContent = Content#'E-RABSetupRequest'{protocolIEs = IEs}, + handle_pdu_new(Data, {Outcome, Pdu#'InitiatingMessage'{value = NewContent}}); + +%% E-RAB SETUP RESPONSE +handle_pdu(Data, {Outcome = successfulOutcome, + #'SuccessfulOutcome'{procedureCode = ?'id-E-RABSetup', + value = Content} = Pdu}) -> + logger:debug("Patching E-RAB SETUP RESPONSE"), + IEs = handle_ies(Content#'E-RABSetupResponse'.protocolIEs, + ?'id-E-RABSetupListBearerSURes'), + NewContent = Content#'E-RABSetupResponse'{protocolIEs = IEs}, + handle_pdu_new(Data, {Outcome, Pdu#'SuccessfulOutcome'{value = NewContent}}); + +%% Proxy all other messages unmodified +handle_pdu(Data, _Pdu) -> + Data. + + +%% Handle a single IE (Information Element) +-spec handle_ie(tuple()) -> tuple(). + +%% E-RAB SETUP REQUEST related IEs +handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeSetupListBearerSUReq', + value = Content}) -> + %% This IE contains a list of BearerSUReq, so patch inner IEs + handle_ies(Content, ?'id-E-RABToBeSetupItemBearerSUReq'); + +handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeSetupItemBearerSUReq', + value = Content}) -> + Content#'E-RABToBeSetupItemBearerSUReq'{transportLayerAddress = transp_layer_addr()}; + +%% E-RAB SETUP RESPONSE related IEs +handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABSetupListBearerSURes', + value = Content}) -> + %% This IE contains a list of BearerSURes, so patch inner IEs + handle_ies(Content, ?'id-E-RABSetupItemBearerSURes'); + +handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABSetupItemBearerSURes', + value = Content}) -> + Content#'E-RABSetupItemBearerSURes'{transportLayerAddress = transp_layer_addr()}; + +%% Catch-all variant, which should not be called normally +handle_ie(#'ProtocolIE-Field'{value = Content} = IE) -> + logger:error("[BUG] Unhandled S1AP IE: ~p", [IE]), + Content. + + +%% Iterate over the given list of 'ProtocolIE-Field' IEs, +%% calling function handle_ie/1 for IEs matching the given IEI. +-spec handle_ies(list(), integer()) -> list(). +handle_ies(IEs, IEI) -> + handle_ies([], IEs, IEI). + +handle_ies(Acc, [IE | IEs], IEI) -> + case IE of + #'ProtocolIE-Field'{id = IEI} -> + NewIE = IE#'ProtocolIE-Field'{value = handle_ie(IE)}, + handle_ies([NewIE | Acc], IEs, IEI); + _ -> + handle_ies([IE | Acc], IEs, IEI) + end; + +handle_ies(Acc, [], _) -> + lists:reverse(Acc). + + +%% GTP-U IP address (TransportLayerAddress) to be used while patching +transp_layer_addr() -> + AddrStr = application:get_env(osmo_s1gw, transp_layer_addr, "255.255.255.255"), + {ok, Addr} = inet:parse_address(AddrStr), + %% sadly, there exists inet:ntoa/1, but not inet:aton/1 + list_to_binary(tuple_to_list(Addr)). + %% vim:set ts=4 sw=4 et: diff --git a/test/s1ap_proxy_test.erl b/test/s1ap_proxy_test.erl new file mode 100644 index 0000000..408f468 --- /dev/null +++ b/test/s1ap_proxy_test.erl @@ -0,0 +1,88 @@ +-module(s1ap_proxy_test). + +-include_lib("eunit/include/eunit.hrl"). + +%% ------------------------------------------------------------------ +%% testcases +%% ------------------------------------------------------------------ + +%% S1 SETUP REQUEST +s1_setup_req_pdu() -> + << 16#00, 16#11, 16#00, 16#1a, 16#00, 16#00, 16#02, 16#00, + 16#3b, 16#40, 16#08, 16#00, 16#00, 16#f1, 16#10, 16#00, + 16#00, 16#00, 16#00, 16#00, 16#40, 16#00, 16#07, 16#00, + 16#0c, 16#0e, 16#40, 16#00, 16#f1, 16#10 + >>. + +s1_setup_req_test() -> + OrigData = s1_setup_req_pdu(), + %% Expect the PDU to be proxied unmodified + ?assertEqual(OrigData, s1ap_proxy:handle_pdu(OrigData)). + + +%% E-RAB SETUP REQUEST +e_rab_setup_req_pdu(TLA, TEID) when is_binary(TLA), + is_binary(TEID) -> + << 16#00, 16#05, 16#00, 16#80, 16#9b, 16#00, 16#00, 16#03, + 16#00, 16#00, 16#00, 16#02, 16#00, 16#07, 16#00, 16#08, + 16#00, 16#02, 16#00, 16#09, 16#00, 16#10, 16#00, 16#80, + 16#87, 16#00, 16#00, 16#11, 16#00, 16#80, 16#81, 16#0c, + 16#00, 16#05, 16#04, 16#0f, 16#80, + TLA/bytes, %% transportLayerAddress (IPv4) + TEID/bytes, %% GTP-TEID + 16#72, 16#27, 16#c8, + 16#1a, 16#bc, 16#ec, 16#11, 16#62, 16#54, 16#c1, 16#01, + 16#05, 16#04, 16#03, 16#69, 16#6d, 16#73, 16#05, 16#01, + 16#c0, 16#a8, 16#65, 16#02, 16#5e, 16#02, 16#b3, 16#8c, + 16#58, 16#32, 16#27, 16#54, 16#80, 16#80, 16#21, 16#10, + 16#02, 16#00, 16#00, 16#10, 16#81, 16#06, 16#08, 16#08, + 16#08, 16#08, 16#83, 16#06, 16#08, 16#08, 16#04, 16#04, + 16#00, 16#0d, 16#04, 16#08, 16#08, 16#08, 16#08, 16#00, + 16#0d, 16#04, 16#08, 16#08, 16#04, 16#04, 16#00, 16#03, + 16#10, 16#20, 16#01, 16#48, 16#60, 16#48, 16#60, 16#00, + 16#00, 16#00, 16#00, 16#00, 16#00, 16#00, 16#00, 16#88, + 16#88, 16#00, 16#03, 16#10, 16#20, 16#01, 16#48, 16#60, + 16#48, 16#60, 16#00, 16#00, 16#00, 16#00, 16#00, 16#00, + 16#00, 16#00, 16#88, 16#44, 16#00, 16#0c, 16#04, 16#ac, + 16#16, 16#00, 16#15, 16#00, 16#10, 16#02, 16#05, 16#78 + >>. + +e_rab_setup_req_test() -> + %% Original input data + TEID = << 16#00, 16#00, 16#00, 16#18 >>, + OrigTLA = << 16#ac, 16#16, 16#00, 16#06 >>, + OrigData = e_rab_setup_req_pdu(OrigTLA, TEID), + + %% Expected output data + ExpTLA = << 16#7f, 16#00, 16#00, 16#01 >>, + ExpData = e_rab_setup_req_pdu(ExpTLA, TEID), + + application:set_env(osmo_s1gw, transp_layer_addr, "127.0.0.1"), + ?assertEqual(ExpData, s1ap_proxy:handle_pdu(OrigData)). + + +%% E-RAB SETUP RESPONSE +e_rab_setup_rsp_pdu(TLA, TEID) when is_binary(TLA), + is_binary(TEID) -> + << 16#20, 16#05, 16#00, 16#22, 16#00, 16#00, 16#03, 16#00, + 16#00, 16#40, 16#02, 16#00, 16#07, 16#00, 16#08, 16#40, + 16#02, 16#00, 16#09, 16#00, 16#1c, 16#40, 16#0f, 16#00, + 16#00, 16#27, 16#40, 16#0a, 16#0c, 16#1f, + TLA/bytes, %% transportLayerAddress (IPv4) + TEID/bytes %% GTP-TEID + >>. + +e_rab_setup_rsp_test() -> + %% Original input data + TEID = << 16#6c, 16#05, 16#bf, 16#56 >>, + OrigTLA = << 16#c0, 16#a8, 16#68, 16#a7 >>, + OrigData = e_rab_setup_rsp_pdu(OrigTLA, TEID), + + %% Expected output data + ExpTLA = << 16#7f, 16#00, 16#00, 16#01 >>, + ExpData = e_rab_setup_rsp_pdu(ExpTLA, TEID), + + application:set_env(osmo_s1gw, transp_layer_addr, "127.0.0.1"), + ?assertEqual(ExpData, s1ap_proxy:handle_pdu(OrigData)). + +%% vim:set ts=4 sw=4 et: