From 03fd8d014f9871896a86534432c8757d65a576fe Mon Sep 17 00:00:00 2001 From: Jonathan Santos Date: Wed, 25 May 2011 13:54:02 -0400 Subject: Import upstream version 0.9.13 --- .tarball-version | 1 + AUTHORS | 7 + COPYING | 661 ++++ Makefile.am | 13 + Makefile.in | 762 ++++ README | 32 + aclocal.m4 | 1148 +++++++ bscconfig.h.in | 61 + configure | 6307 ++++++++++++++++++++++++++++++++++ configure.in | 111 + depcomp | 630 ++++ include/Makefile.am | 3 + include/Makefile.in | 541 +++ include/compat_af_isdn.h | 39 + include/mISDNif.h | 387 +++ include/openbsc/Makefile.am | 17 + include/openbsc/Makefile.in | 449 +++ include/openbsc/abis_nm.h | 172 + include/openbsc/abis_om2000.h | 78 + include/openbsc/abis_rsl.h | 99 + include/openbsc/auth.h | 17 + include/openbsc/bsc_api.h | 37 + include/openbsc/bsc_msc.h | 51 + include/openbsc/bsc_nat.h | 355 ++ include/openbsc/bsc_nat_sccp.h | 95 + include/openbsc/bsc_rll.h | 19 + include/openbsc/chan_alloc.h | 65 + include/openbsc/crc24.h | 8 + include/openbsc/db.h | 83 + include/openbsc/debug.h | 70 + include/openbsc/e1_input.h | 187 + include/openbsc/gb_proxy.h | 39 + include/openbsc/gprs_bssgp.h | 232 ++ include/openbsc/gprs_gmm.h | 19 + include/openbsc/gprs_llc.h | 162 + include/openbsc/gprs_ns.h | 232 ++ include/openbsc/gprs_ns_frgre.h | 6 + include/openbsc/gprs_sgsn.h | 220 ++ include/openbsc/gsm_04_08.h | 70 + include/openbsc/gsm_04_08_gprs.h | 383 +++ include/openbsc/gsm_04_11.h | 40 + include/openbsc/gsm_04_80.h | 20 + include/openbsc/gsm_data.h | 879 +++++ include/openbsc/gsm_subscriber.h | 107 + include/openbsc/handover.h | 14 + include/openbsc/ipaccess.h | 131 + include/openbsc/meas_rep.h | 85 + include/openbsc/mgcp.h | 194 ++ include/openbsc/mgcp_internal.h | 154 + include/openbsc/misdn.h | 27 + include/openbsc/mncc.h | 172 + include/openbsc/network_listen.h | 16 + include/openbsc/openbscdefines.h | 34 + include/openbsc/osmo_bsc.h | 43 + include/openbsc/osmo_bsc_grace.h | 28 + include/openbsc/osmo_bsc_rf.h | 35 + include/openbsc/osmo_msc.h | 11 + include/openbsc/osmo_msc_data.h | 76 + include/openbsc/paging.h | 71 + include/openbsc/rest_octets.h | 133 + include/openbsc/rs232.h | 9 + include/openbsc/rtp_proxy.h | 90 + include/openbsc/sgsn.h | 65 + include/openbsc/signal.h | 255 ++ include/openbsc/silent_call.h | 12 + include/openbsc/sms_queue.h | 17 + include/openbsc/socket.h | 14 + include/openbsc/subchan_demux.h | 101 + include/openbsc/system_information.h | 45 + include/openbsc/transaction.h | 75 + include/openbsc/trau_frame.h | 64 + include/openbsc/trau_mux.h | 48 + include/openbsc/ussd.h | 10 + include/openbsc/vty.h | 46 + install-sh | 520 +++ missing | 376 ++ openbsc.pc.in | 11 + src/Makefile.am | 13 + src/Makefile.in | 548 +++ src/gprs/Makefile.am | 22 + src/gprs/Makefile.in | 522 +++ src/gprs/crc24.c | 68 + src/gprs/gb_proxy.c | 684 ++++ src/gprs/gb_proxy_main.c | 288 ++ src/gprs/gb_proxy_vty.c | 104 + src/gprs/gprs_gmm.c | 1597 +++++++++ src/gprs/gprs_llc.c | 852 +++++ src/gprs/gprs_llc_vty.c | 108 + src/gprs/gprs_sgsn.c | 383 +++ src/gprs/gprs_sndcp.c | 616 ++++ src/gprs/gprs_sndcp.h | 53 + src/gprs/gprs_sndcp_vty.c | 74 + src/gprs/sgsn_libgtp.c | 610 ++++ src/gprs/sgsn_main.c | 288 ++ src/gprs/sgsn_vty.c | 357 ++ src/ipaccess/Makefile.am | 21 + src/ipaccess/Makefile.in | 520 +++ src/ipaccess/ipaccess-config.c | 865 +++++ src/ipaccess/ipaccess-find.c | 227 ++ src/ipaccess/ipaccess-firmware.c | 135 + src/ipaccess/ipaccess-proxy.c | 1373 ++++++++ src/ipaccess/network_listen.c | 251 ++ src/libabis/Makefile.am | 14 + src/libabis/Makefile.in | 548 +++ src/libabis/e1_input.c | 649 ++++ src/libabis/e1_input_vty.c | 102 + src/libabis/input/dahdi.c | 494 +++ src/libabis/input/hsl.c | 460 +++ src/libabis/input/ipaccess.c | 797 +++++ src/libabis/input/lapd.c | 710 ++++ src/libabis/input/lapd.h | 46 + src/libabis/input/misdn.c | 542 +++ src/libbsc/Makefile.am | 25 + src/libbsc/Makefile.in | 504 +++ src/libbsc/abis_nm.c | 3132 +++++++++++++++++ src/libbsc/abis_nm_vty.c | 197 ++ src/libbsc/abis_om2000.c | 1078 ++++++ src/libbsc/abis_om2000_vty.c | 513 +++ src/libbsc/abis_rsl.c | 1971 +++++++++++ src/libbsc/bsc_api.c | 675 ++++ src/libbsc/bsc_init.c | 466 +++ src/libbsc/bsc_msc.c | 259 ++ src/libbsc/bsc_rll.c | 141 + src/libbsc/bsc_vty.c | 2799 +++++++++++++++ src/libbsc/bts_ericsson_rbs2000.c | 164 + src/libbsc/bts_hsl_femtocell.c | 162 + src/libbsc/bts_ipaccess_nanobts.c | 447 +++ src/libbsc/bts_siemens_bs11.c | 591 ++++ src/libbsc/bts_unknown.c | 41 + src/libbsc/chan_alloc.c | 507 +++ src/libbsc/e1_config.c | 296 ++ src/libbsc/gsm_04_08_utils.c | 655 ++++ src/libbsc/gsm_subscriber_base.c | 149 + src/libbsc/handover_decision.c | 297 ++ src/libbsc/handover_logic.c | 393 +++ src/libbsc/meas_rep.c | 116 + src/libbsc/paging.c | 395 +++ src/libbsc/rest_octets.c | 432 +++ src/libbsc/system_information.c | 616 ++++ src/libbsc/transaction.c | 152 + src/libcommon/Makefile.am | 7 + src/libcommon/Makefile.in | 457 +++ src/libcommon/bsc_version.c | 30 + src/libcommon/common_vty.c | 225 ++ src/libcommon/debug.c | 249 ++ src/libcommon/gsm_data.c | 592 ++++ src/libcommon/socket.c | 108 + src/libcommon/talloc_ctx.c | 38 + src/libgb/Makefile.am | 9 + src/libgb/Makefile.in | 460 +++ src/libgb/gprs_bssgp.c | 856 +++++ src/libgb/gprs_bssgp_util.c | 119 + src/libgb/gprs_bssgp_vty.c | 176 + src/libgb/gprs_ns.c | 992 ++++++ src/libgb/gprs_ns_frgre.c | 304 ++ src/libgb/gprs_ns_vty.c | 569 +++ src/libmgcp/Makefile.am | 7 + src/libmgcp/Makefile.in | 453 +++ src/libmgcp/mgcp_network.c | 579 ++++ src/libmgcp/mgcp_protocol.c | 1102 ++++++ src/libmgcp/mgcp_vty.c | 742 ++++ src/libmsc/Makefile.am | 19 + src/libmsc/Makefile.in | 482 +++ src/libmsc/auth.c | 132 + src/libmsc/db.c | 1303 +++++++ src/libmsc/gsm_04_08.c | 3345 ++++++++++++++++++ src/libmsc/gsm_04_11.c | 1240 +++++++ src/libmsc/gsm_04_80.c | 175 + src/libmsc/gsm_subscriber.c | 410 +++ src/libmsc/mncc.c | 110 + src/libmsc/mncc_builtin.c | 411 +++ src/libmsc/mncc_sock.c | 337 ++ src/libmsc/osmo_msc.c | 104 + src/libmsc/rrlp.c | 105 + src/libmsc/silent_call.c | 143 + src/libmsc/sms_queue.c | 479 +++ src/libmsc/token_auth.c | 153 + src/libmsc/ussd.c | 79 + src/libmsc/vty_interface_layer3.c | 790 +++++ src/libtrau/Makefile.am | 7 + src/libtrau/Makefile.in | 455 +++ src/libtrau/rtp_proxy.c | 728 ++++ src/libtrau/subchan_demux.c | 321 ++ src/libtrau/trau_frame.c | 260 ++ src/libtrau/trau_mux.c | 312 ++ src/libtrau/trau_upqueue.c | 27 + src/osmo-bsc/Makefile.am | 18 + src/osmo-bsc/Makefile.in | 513 +++ src/osmo-bsc/osmo_bsc_api.c | 174 + src/osmo-bsc/osmo_bsc_audio.c | 70 + src/osmo-bsc/osmo_bsc_bssap.c | 548 +++ src/osmo-bsc/osmo_bsc_filter.c | 170 + src/osmo-bsc/osmo_bsc_grace.c | 107 + src/osmo-bsc/osmo_bsc_main.c | 261 ++ src/osmo-bsc/osmo_bsc_msc.c | 368 ++ src/osmo-bsc/osmo_bsc_rf.c | 364 ++ src/osmo-bsc/osmo_bsc_sccp.c | 288 ++ src/osmo-bsc/osmo_bsc_vty.c | 311 ++ src/osmo-bsc_mgcp/Makefile.am | 10 + src/osmo-bsc_mgcp/Makefile.in | 487 +++ src/osmo-bsc_mgcp/mgcp_main.c | 283 ++ src/osmo-bsc_nat/Makefile.am | 15 + src/osmo-bsc_nat/Makefile.in | 504 +++ src/osmo-bsc_nat/bsc_filter.c | 216 ++ src/osmo-bsc_nat/bsc_mgcp_utils.c | 764 ++++ src/osmo-bsc_nat/bsc_nat.c | 1387 ++++++++ src/osmo-bsc_nat/bsc_nat_utils.c | 893 +++++ src/osmo-bsc_nat/bsc_nat_vty.c | 788 +++++ src/osmo-bsc_nat/bsc_sccp.c | 249 ++ src/osmo-bsc_nat/bsc_ussd.c | 363 ++ src/osmo-nitb/Makefile.am | 14 + src/osmo-nitb/Makefile.in | 496 +++ src/osmo-nitb/bsc_hack.c | 313 ++ src/utils/Makefile.am | 12 + src/utils/Makefile.in | 496 +++ src/utils/bs11_config.c | 918 +++++ src/utils/isdnsync.c | 191 + src/utils/rs232.c | 248 ++ tests/Makefile.am | 5 + tests/Makefile.in | 538 +++ tests/bsc-nat/Makefile.am | 19 + tests/bsc-nat/Makefile.in | 535 +++ tests/bsc-nat/bsc_data.c | 187 + tests/bsc-nat/bsc_nat_test.c | 1016 ++++++ tests/channel/Makefile.am | 10 + tests/channel/Makefile.in | 451 +++ tests/channel/channel_test.c | 84 + tests/db/Makefile.am | 15 + tests/db/Makefile.in | 458 +++ tests/db/db_test.c | 105 + tests/debug/Makefile.am | 7 + tests/debug/Makefile.in | 447 +++ tests/debug/debug_test.c | 42 + tests/gsm0408/Makefile.am | 9 + tests/gsm0408/Makefile.in | 450 +++ tests/gsm0408/gsm0408_test.c | 103 + tests/mgcp/Makefile.am | 12 + tests/mgcp/Makefile.in | 453 +++ tests/mgcp/mgcp_test.c | 85 + 239 files changed, 86881 insertions(+) create mode 100644 .tarball-version create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 Makefile.am create mode 100644 Makefile.in create mode 100644 aclocal.m4 create mode 100644 bscconfig.h.in create mode 100755 configure create mode 100644 configure.in create mode 100755 depcomp create mode 100644 include/Makefile.am create mode 100644 include/Makefile.in create mode 100644 include/compat_af_isdn.h create mode 100644 include/mISDNif.h create mode 100644 include/openbsc/Makefile.am create mode 100644 include/openbsc/Makefile.in create mode 100644 include/openbsc/abis_nm.h create mode 100644 include/openbsc/abis_om2000.h create mode 100644 include/openbsc/abis_rsl.h create mode 100644 include/openbsc/auth.h create mode 100644 include/openbsc/bsc_api.h create mode 100644 include/openbsc/bsc_msc.h create mode 100644 include/openbsc/bsc_nat.h create mode 100644 include/openbsc/bsc_nat_sccp.h create mode 100644 include/openbsc/bsc_rll.h create mode 100644 include/openbsc/chan_alloc.h create mode 100644 include/openbsc/crc24.h create mode 100644 include/openbsc/db.h create mode 100644 include/openbsc/debug.h create mode 100644 include/openbsc/e1_input.h create mode 100644 include/openbsc/gb_proxy.h create mode 100644 include/openbsc/gprs_bssgp.h create mode 100644 include/openbsc/gprs_gmm.h create mode 100644 include/openbsc/gprs_llc.h create mode 100644 include/openbsc/gprs_ns.h create mode 100644 include/openbsc/gprs_ns_frgre.h create mode 100644 include/openbsc/gprs_sgsn.h create mode 100644 include/openbsc/gsm_04_08.h create mode 100644 include/openbsc/gsm_04_08_gprs.h create mode 100644 include/openbsc/gsm_04_11.h create mode 100644 include/openbsc/gsm_04_80.h create mode 100644 include/openbsc/gsm_data.h create mode 100644 include/openbsc/gsm_subscriber.h create mode 100644 include/openbsc/handover.h create mode 100644 include/openbsc/ipaccess.h create mode 100644 include/openbsc/meas_rep.h create mode 100644 include/openbsc/mgcp.h create mode 100644 include/openbsc/mgcp_internal.h create mode 100644 include/openbsc/misdn.h create mode 100644 include/openbsc/mncc.h create mode 100644 include/openbsc/network_listen.h create mode 100644 include/openbsc/openbscdefines.h create mode 100644 include/openbsc/osmo_bsc.h create mode 100644 include/openbsc/osmo_bsc_grace.h create mode 100644 include/openbsc/osmo_bsc_rf.h create mode 100644 include/openbsc/osmo_msc.h create mode 100644 include/openbsc/osmo_msc_data.h create mode 100644 include/openbsc/paging.h create mode 100644 include/openbsc/rest_octets.h create mode 100644 include/openbsc/rs232.h create mode 100644 include/openbsc/rtp_proxy.h create mode 100644 include/openbsc/sgsn.h create mode 100644 include/openbsc/signal.h create mode 100644 include/openbsc/silent_call.h create mode 100644 include/openbsc/sms_queue.h create mode 100644 include/openbsc/socket.h create mode 100644 include/openbsc/subchan_demux.h create mode 100644 include/openbsc/system_information.h create mode 100644 include/openbsc/transaction.h create mode 100644 include/openbsc/trau_frame.h create mode 100644 include/openbsc/trau_mux.h create mode 100644 include/openbsc/ussd.h create mode 100644 include/openbsc/vty.h create mode 100755 install-sh create mode 100755 missing create mode 100644 openbsc.pc.in create mode 100644 src/Makefile.am create mode 100644 src/Makefile.in create mode 100644 src/gprs/Makefile.am create mode 100644 src/gprs/Makefile.in create mode 100644 src/gprs/crc24.c create mode 100644 src/gprs/gb_proxy.c create mode 100644 src/gprs/gb_proxy_main.c create mode 100644 src/gprs/gb_proxy_vty.c create mode 100644 src/gprs/gprs_gmm.c create mode 100644 src/gprs/gprs_llc.c create mode 100644 src/gprs/gprs_llc_vty.c create mode 100644 src/gprs/gprs_sgsn.c create mode 100644 src/gprs/gprs_sndcp.c create mode 100644 src/gprs/gprs_sndcp.h create mode 100644 src/gprs/gprs_sndcp_vty.c create mode 100644 src/gprs/sgsn_libgtp.c create mode 100644 src/gprs/sgsn_main.c create mode 100644 src/gprs/sgsn_vty.c create mode 100644 src/ipaccess/Makefile.am create mode 100644 src/ipaccess/Makefile.in create mode 100644 src/ipaccess/ipaccess-config.c create mode 100644 src/ipaccess/ipaccess-find.c create mode 100644 src/ipaccess/ipaccess-firmware.c create mode 100644 src/ipaccess/ipaccess-proxy.c create mode 100644 src/ipaccess/network_listen.c create mode 100644 src/libabis/Makefile.am create mode 100644 src/libabis/Makefile.in create mode 100644 src/libabis/e1_input.c create mode 100644 src/libabis/e1_input_vty.c create mode 100644 src/libabis/input/dahdi.c create mode 100644 src/libabis/input/hsl.c create mode 100644 src/libabis/input/ipaccess.c create mode 100644 src/libabis/input/lapd.c create mode 100644 src/libabis/input/lapd.h create mode 100644 src/libabis/input/misdn.c create mode 100644 src/libbsc/Makefile.am create mode 100644 src/libbsc/Makefile.in create mode 100644 src/libbsc/abis_nm.c create mode 100644 src/libbsc/abis_nm_vty.c create mode 100644 src/libbsc/abis_om2000.c create mode 100644 src/libbsc/abis_om2000_vty.c create mode 100644 src/libbsc/abis_rsl.c create mode 100644 src/libbsc/bsc_api.c create mode 100644 src/libbsc/bsc_init.c create mode 100644 src/libbsc/bsc_msc.c create mode 100644 src/libbsc/bsc_rll.c create mode 100644 src/libbsc/bsc_vty.c create mode 100644 src/libbsc/bts_ericsson_rbs2000.c create mode 100644 src/libbsc/bts_hsl_femtocell.c create mode 100644 src/libbsc/bts_ipaccess_nanobts.c create mode 100644 src/libbsc/bts_siemens_bs11.c create mode 100644 src/libbsc/bts_unknown.c create mode 100644 src/libbsc/chan_alloc.c create mode 100644 src/libbsc/e1_config.c create mode 100644 src/libbsc/gsm_04_08_utils.c create mode 100644 src/libbsc/gsm_subscriber_base.c create mode 100644 src/libbsc/handover_decision.c create mode 100644 src/libbsc/handover_logic.c create mode 100644 src/libbsc/meas_rep.c create mode 100644 src/libbsc/paging.c create mode 100644 src/libbsc/rest_octets.c create mode 100644 src/libbsc/system_information.c create mode 100644 src/libbsc/transaction.c create mode 100644 src/libcommon/Makefile.am create mode 100644 src/libcommon/Makefile.in create mode 100644 src/libcommon/bsc_version.c create mode 100644 src/libcommon/common_vty.c create mode 100644 src/libcommon/debug.c create mode 100644 src/libcommon/gsm_data.c create mode 100644 src/libcommon/socket.c create mode 100644 src/libcommon/talloc_ctx.c create mode 100644 src/libgb/Makefile.am create mode 100644 src/libgb/Makefile.in create mode 100644 src/libgb/gprs_bssgp.c create mode 100644 src/libgb/gprs_bssgp_util.c create mode 100644 src/libgb/gprs_bssgp_vty.c create mode 100644 src/libgb/gprs_ns.c create mode 100644 src/libgb/gprs_ns_frgre.c create mode 100644 src/libgb/gprs_ns_vty.c create mode 100644 src/libmgcp/Makefile.am create mode 100644 src/libmgcp/Makefile.in create mode 100644 src/libmgcp/mgcp_network.c create mode 100644 src/libmgcp/mgcp_protocol.c create mode 100644 src/libmgcp/mgcp_vty.c create mode 100644 src/libmsc/Makefile.am create mode 100644 src/libmsc/Makefile.in create mode 100644 src/libmsc/auth.c create mode 100644 src/libmsc/db.c create mode 100644 src/libmsc/gsm_04_08.c create mode 100644 src/libmsc/gsm_04_11.c create mode 100644 src/libmsc/gsm_04_80.c create mode 100644 src/libmsc/gsm_subscriber.c create mode 100644 src/libmsc/mncc.c create mode 100644 src/libmsc/mncc_builtin.c create mode 100644 src/libmsc/mncc_sock.c create mode 100644 src/libmsc/osmo_msc.c create mode 100644 src/libmsc/rrlp.c create mode 100644 src/libmsc/silent_call.c create mode 100644 src/libmsc/sms_queue.c create mode 100644 src/libmsc/token_auth.c create mode 100644 src/libmsc/ussd.c create mode 100644 src/libmsc/vty_interface_layer3.c create mode 100644 src/libtrau/Makefile.am create mode 100644 src/libtrau/Makefile.in create mode 100644 src/libtrau/rtp_proxy.c create mode 100644 src/libtrau/subchan_demux.c create mode 100644 src/libtrau/trau_frame.c create mode 100644 src/libtrau/trau_mux.c create mode 100644 src/libtrau/trau_upqueue.c create mode 100644 src/osmo-bsc/Makefile.am create mode 100644 src/osmo-bsc/Makefile.in create mode 100644 src/osmo-bsc/osmo_bsc_api.c create mode 100644 src/osmo-bsc/osmo_bsc_audio.c create mode 100644 src/osmo-bsc/osmo_bsc_bssap.c create mode 100644 src/osmo-bsc/osmo_bsc_filter.c create mode 100644 src/osmo-bsc/osmo_bsc_grace.c create mode 100644 src/osmo-bsc/osmo_bsc_main.c create mode 100644 src/osmo-bsc/osmo_bsc_msc.c create mode 100644 src/osmo-bsc/osmo_bsc_rf.c create mode 100644 src/osmo-bsc/osmo_bsc_sccp.c create mode 100644 src/osmo-bsc/osmo_bsc_vty.c create mode 100644 src/osmo-bsc_mgcp/Makefile.am create mode 100644 src/osmo-bsc_mgcp/Makefile.in create mode 100644 src/osmo-bsc_mgcp/mgcp_main.c create mode 100644 src/osmo-bsc_nat/Makefile.am create mode 100644 src/osmo-bsc_nat/Makefile.in create mode 100644 src/osmo-bsc_nat/bsc_filter.c create mode 100644 src/osmo-bsc_nat/bsc_mgcp_utils.c create mode 100644 src/osmo-bsc_nat/bsc_nat.c create mode 100644 src/osmo-bsc_nat/bsc_nat_utils.c create mode 100644 src/osmo-bsc_nat/bsc_nat_vty.c create mode 100644 src/osmo-bsc_nat/bsc_sccp.c create mode 100644 src/osmo-bsc_nat/bsc_ussd.c create mode 100644 src/osmo-nitb/Makefile.am create mode 100644 src/osmo-nitb/Makefile.in create mode 100644 src/osmo-nitb/bsc_hack.c create mode 100644 src/utils/Makefile.am create mode 100644 src/utils/Makefile.in create mode 100644 src/utils/bs11_config.c create mode 100644 src/utils/isdnsync.c create mode 100644 src/utils/rs232.c create mode 100644 tests/Makefile.am create mode 100644 tests/Makefile.in create mode 100644 tests/bsc-nat/Makefile.am create mode 100644 tests/bsc-nat/Makefile.in create mode 100644 tests/bsc-nat/bsc_data.c create mode 100644 tests/bsc-nat/bsc_nat_test.c create mode 100644 tests/channel/Makefile.am create mode 100644 tests/channel/Makefile.in create mode 100644 tests/channel/channel_test.c create mode 100644 tests/db/Makefile.am create mode 100644 tests/db/Makefile.in create mode 100644 tests/db/db_test.c create mode 100644 tests/debug/Makefile.am create mode 100644 tests/debug/Makefile.in create mode 100644 tests/debug/debug_test.c create mode 100644 tests/gsm0408/Makefile.am create mode 100644 tests/gsm0408/Makefile.in create mode 100644 tests/gsm0408/gsm0408_test.c create mode 100644 tests/mgcp/Makefile.am create mode 100644 tests/mgcp/Makefile.in create mode 100644 tests/mgcp/mgcp_test.c diff --git a/.tarball-version b/.tarball-version new file mode 100644 index 000000000..62ea25909 --- /dev/null +++ b/.tarball-version @@ -0,0 +1 @@ +0.9.13 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..daf60e447 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,7 @@ +Harald Welte +Holger Freyther +Jan Luebbe +Stefan Schmidt +Daniel Willmann +Andreas Eversberg +Sylvain Munaut <246tnt@gmail.com> diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..dba13ed2d --- /dev/null +++ b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 000000000..c4d0aa181 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,13 @@ +AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 + +INCLUDES = $(all_includes) -I$(top_srcdir)/include +SUBDIRS = include src tests + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = openbsc.pc + +BUILT_SOURCES = $(top_srcdir)/.version +$(top_srcdir)/.version: + echo $(VERSION) > $@-t && mv $@-t $@ +dist-hook: + echo $(VERSION) > $(distdir)/.tarball-version diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 000000000..86bce3e41 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,762 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = . +DIST_COMMON = README $(am__configure_deps) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in $(srcdir)/bscconfig.h.in \ + $(srcdir)/openbsc.pc.in $(top_srcdir)/configure AUTHORS \ + COPYING depcomp install-sh missing +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ + configure.lineno config.status.lineno +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = bscconfig.h +CONFIG_CLEAN_FILES = openbsc.pc +CONFIG_CLEAN_VPATH_FILES = +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \ + html-recursive info-recursive install-data-recursive \ + install-dvi-recursive install-exec-recursive \ + install-html-recursive install-info-recursive \ + install-pdf-recursive install-ps-recursive install-recursive \ + installcheck-recursive installdirs-recursive pdf-recursive \ + ps-recursive uninstall-recursive +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__installdirs = "$(DESTDIR)$(pkgconfigdir)" +DATA = $(pkgconfig_DATA) +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \ + $(RECURSIVE_CLEAN_TARGETS:-recursive=) tags TAGS ctags CTAGS \ + distdir dist dist-all distcheck +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +distdir = $(PACKAGE)-$(VERSION) +top_distdir = $(distdir) +am__remove_distdir = \ + { test ! -d "$(distdir)" \ + || { find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \ + && rm -fr "$(distdir)"; }; } +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +DIST_ARCHIVES = $(distdir).tar.gz $(distdir).tar.bz2 +GZIP_ENV = --best +distuninstallcheck_listfiles = find . -type f -print +distcleancheck_listfiles = find . -type f -print +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 +INCLUDES = $(all_includes) -I$(top_srcdir)/include +SUBDIRS = include src tests +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = openbsc.pc +BUILT_SOURCES = $(top_srcdir)/.version +all: $(BUILT_SOURCES) bscconfig.h + $(MAKE) $(AM_MAKEFLAGS) all-recursive + +.SUFFIXES: +am--refresh: + @: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \ + $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + echo ' $(SHELL) ./config.status'; \ + $(SHELL) ./config.status;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + $(SHELL) ./config.status --recheck + +$(top_srcdir)/configure: $(am__configure_deps) + $(am__cd) $(srcdir) && $(AUTOCONF) +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) +$(am__aclocal_m4_deps): + +bscconfig.h: stamp-h1 + @if test ! -f $@; then \ + rm -f stamp-h1; \ + $(MAKE) $(AM_MAKEFLAGS) stamp-h1; \ + else :; fi + +stamp-h1: $(srcdir)/bscconfig.h.in $(top_builddir)/config.status + @rm -f stamp-h1 + cd $(top_builddir) && $(SHELL) ./config.status bscconfig.h +$(srcdir)/bscconfig.h.in: $(am__configure_deps) + ($(am__cd) $(top_srcdir) && $(AUTOHEADER)) + rm -f stamp-h1 + touch $@ + +distclean-hdr: + -rm -f bscconfig.h stamp-h1 +openbsc.pc: $(top_builddir)/config.status $(srcdir)/openbsc.pc.in + cd $(top_builddir) && $(SHELL) ./config.status $@ +install-pkgconfigDATA: $(pkgconfig_DATA) + @$(NORMAL_INSTALL) + test -z "$(pkgconfigdir)" || $(MKDIR_P) "$(DESTDIR)$(pkgconfigdir)" + @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkgconfigdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgconfigdir)" || exit $$?; \ + done + +uninstall-pkgconfigDATA: + @$(NORMAL_UNINSTALL) + @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(pkgconfigdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(pkgconfigdir)" && rm -f $$files + +# This directory's subdirectories are mostly independent; you can cd +# into them and run `make' without going through this Makefile. +# To change the values of `make' variables: instead of editing Makefiles, +# (1) if the variable is set in `config.status', edit `config.status' +# (which will cause the Makefiles to be regenerated when you run `make'); +# (2) otherwise, pass the desired values on the `make' command line. +$(RECURSIVE_TARGETS): + @fail= failcom='exit 1'; \ + for f in x $$MAKEFLAGS; do \ + case $$f in \ + *=* | --[!k]*);; \ + *k*) failcom='fail=yes';; \ + esac; \ + done; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +$(RECURSIVE_CLEAN_TARGETS): + @fail= failcom='exit 1'; \ + for f in x $$MAKEFLAGS; do \ + case $$f in \ + *=* | --[!k]*);; \ + *k*) failcom='fail=yes';; \ + esac; \ + done; \ + dot_seen=no; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + rev=''; for subdir in $$list; do \ + if test "$$subdir" = "."; then :; else \ + rev="$$subdir $$rev"; \ + fi; \ + done; \ + rev="$$rev ."; \ + target=`echo $@ | sed s/-recursive//`; \ + for subdir in $$rev; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done && test -z "$$fail" +tags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \ + done +ctags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \ + done + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: tags-recursive $(HEADERS) $(SOURCES) bscconfig.h.in $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + list='$(SOURCES) $(HEADERS) bscconfig.h.in $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: ctags-recursive $(HEADERS) $(SOURCES) bscconfig.h.in $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) bscconfig.h.in $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + $(am__remove_distdir) + test -d "$(distdir)" || mkdir "$(distdir)" + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$(top_distdir)" distdir="$(distdir)" \ + dist-hook + -test -n "$(am__skip_mode_fix)" \ + || find "$(distdir)" -type d ! -perm -755 \ + -exec chmod u+rwx,go+rx {} \; -o \ + ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ + || chmod -R a+r "$(distdir)" +dist-gzip: distdir + tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz + $(am__remove_distdir) +dist-bzip2: distdir + tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2 + $(am__remove_distdir) + +dist-lzma: distdir + tardir=$(distdir) && $(am__tar) | lzma -9 -c >$(distdir).tar.lzma + $(am__remove_distdir) + +dist-xz: distdir + tardir=$(distdir) && $(am__tar) | xz -c >$(distdir).tar.xz + $(am__remove_distdir) + +dist-tarZ: distdir + tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z + $(am__remove_distdir) + +dist-shar: distdir + shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz + $(am__remove_distdir) + +dist-zip: distdir + -rm -f $(distdir).zip + zip -rq $(distdir).zip $(distdir) + $(am__remove_distdir) + +dist dist-all: distdir + tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz + tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2 + $(am__remove_distdir) + +# This target untars the dist file and tries a VPATH configuration. Then +# it guarantees that the distribution is self-contained by making another +# tarfile. +distcheck: dist + case '$(DIST_ARCHIVES)' in \ + *.tar.gz*) \ + GZIP=$(GZIP_ENV) gzip -dc $(distdir).tar.gz | $(am__untar) ;;\ + *.tar.bz2*) \ + bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ + *.tar.lzma*) \ + lzma -dc $(distdir).tar.lzma | $(am__untar) ;;\ + *.tar.xz*) \ + xz -dc $(distdir).tar.xz | $(am__untar) ;;\ + *.tar.Z*) \ + uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ + *.shar.gz*) \ + GZIP=$(GZIP_ENV) gzip -dc $(distdir).shar.gz | unshar ;;\ + *.zip*) \ + unzip $(distdir).zip ;;\ + esac + chmod -R a-w $(distdir); chmod a+w $(distdir) + mkdir $(distdir)/_build + mkdir $(distdir)/_inst + chmod a-w $(distdir) + test -d $(distdir)/_build || exit 0; \ + dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ + && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ + && am__cwd=`pwd` \ + && $(am__cd) $(distdir)/_build \ + && ../configure --srcdir=.. --prefix="$$dc_install_base" \ + $(DISTCHECK_CONFIGURE_FLAGS) \ + && $(MAKE) $(AM_MAKEFLAGS) \ + && $(MAKE) $(AM_MAKEFLAGS) dvi \ + && $(MAKE) $(AM_MAKEFLAGS) check \ + && $(MAKE) $(AM_MAKEFLAGS) install \ + && $(MAKE) $(AM_MAKEFLAGS) installcheck \ + && $(MAKE) $(AM_MAKEFLAGS) uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ + distuninstallcheck \ + && chmod -R a-w "$$dc_install_base" \ + && ({ \ + (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ + distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ + } || { rm -rf "$$dc_destdir"; exit 1; }) \ + && rm -rf "$$dc_destdir" \ + && $(MAKE) $(AM_MAKEFLAGS) dist \ + && rm -rf $(DIST_ARCHIVES) \ + && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \ + && cd "$$am__cwd" \ + || exit 1 + $(am__remove_distdir) + @(echo "$(distdir) archives ready for distribution: "; \ + list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ + sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' +distuninstallcheck: + @$(am__cd) '$(distuninstallcheck_dir)' \ + && test `$(distuninstallcheck_listfiles) | wc -l` -le 1 \ + || { echo "ERROR: files left after uninstall:" ; \ + if test -n "$(DESTDIR)"; then \ + echo " (check DESTDIR support)"; \ + fi ; \ + $(distuninstallcheck_listfiles) ; \ + exit 1; } >&2 +distcleancheck: distclean + @if test '$(srcdir)' = . ; then \ + echo "ERROR: distcleancheck can only run from a VPATH build" ; \ + exit 1 ; \ + fi + @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ + || { echo "ERROR: files left in build directory after distclean:" ; \ + $(distcleancheck_listfiles) ; \ + exit 1; } >&2 +check-am: all-am +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-recursive +all-am: Makefile $(DATA) bscconfig.h +installdirs: installdirs-recursive +installdirs-am: + for dir in "$(DESTDIR)$(pkgconfigdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-recursive + +clean-am: clean-generic mostlyclean-am + +distclean: distclean-recursive + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-hdr distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: install-pkgconfigDATA + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -rf $(top_srcdir)/autom4te.cache + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: uninstall-pkgconfigDATA + +.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) all check \ + ctags-recursive install install-am install-strip \ + tags-recursive + +.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \ + all all-am am--refresh check check-am clean clean-generic \ + ctags ctags-recursive dist dist-all dist-bzip2 dist-gzip \ + dist-hook dist-lzma dist-shar dist-tarZ dist-xz dist-zip \ + distcheck distclean distclean-generic distclean-hdr \ + distclean-tags distcleancheck distdir distuninstallcheck dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-pkgconfigDATA install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + installdirs-am maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \ + tags-recursive uninstall uninstall-am uninstall-pkgconfigDATA + +$(top_srcdir)/.version: + echo $(VERSION) > $@-t && mv $@-t $@ +dist-hook: + echo $(VERSION) > $(distdir)/.tarball-version + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/README b/README index e69de29bb..fa01ff4cd 100644 --- a/README +++ b/README @@ -0,0 +1,32 @@ +About OpenBSC +============= + +OpenBSC is a minimalistic implementation of the GSM Network, with +particular emphasis on the functionality typically provided by the BSC, +MSC, HLR, VLR and SMSC. + +Its currently supported interfaces towards the BTS are: + + * Classic A-bis over E1 using a mISDN based E1 interface. In other + words, you can connect existing GSM Base Transceiver Station (BTS) + through E1 to OpenBSC. So far, we have only tested the Siemens BS-11 + Test reports with other BTS are much appreciated! + + * A-bis over IP as used by the ip.access nanoBTS product family + +You can find the project documentation at http://openbsc.gnumonks.org/ + +This project is still in its early days, and there are lots of areas where it +doesn't behave as per GSM spec. + + Harald Welte + + +libosmocore +=========== + +Please note that as of March 2010, OpenBSC has a dependency to a library +called "libosmocore". You can obtain that library from + + git://git.osmocom.org/libosmocore.git + diff --git a/aclocal.m4 b/aclocal.m4 new file mode 100644 index 000000000..f23923301 --- /dev/null +++ b/aclocal.m4 @@ -0,0 +1,1148 @@ +# generated automatically by aclocal 1.11.1 -*- Autoconf -*- + +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, +# 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc. +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.67],, +[m4_warning([this file was generated for autoconf 2.67. +You have another version of autoconf. It may work, but is not guaranteed to. +If you have problems, you may need to regenerate the build system entirely. +To do so, use the procedure documented by the package, typically `autoreconf'.])]) + +# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- +# serial 1 (pkg-config-0.24) +# +# Copyright © 2004 Scott James Remnant . +# +# 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. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# PKG_PROG_PKG_CONFIG([MIN-VERSION]) +# ---------------------------------- +AC_DEFUN([PKG_PROG_PKG_CONFIG], +[m4_pattern_forbid([^_?PKG_[A-Z_]+$]) +m4_pattern_allow([^PKG_CONFIG(_PATH)?$]) +AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) +AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) +AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) + +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=m4_default([$1], [0.9.0]) + AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + PKG_CONFIG="" + fi +fi[]dnl +])# PKG_PROG_PKG_CONFIG + +# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# +# Check to see whether a particular set of modules exists. Similar +# to PKG_CHECK_MODULES(), but does not set variables or print errors. +# +# Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +# only at the first occurence in configure.ac, so if the first place +# it's called might be skipped (such as if it is within an "if", you +# have to call PKG_CHECK_EXISTS manually +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_EXISTS], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +if test -n "$PKG_CONFIG" && \ + AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then + m4_default([$2], [:]) +m4_ifvaln([$3], [else + $3])dnl +fi]) + +# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) +# --------------------------------------------- +m4_define([_PKG_CONFIG], +[if test -n "$$1"; then + pkg_cv_[]$1="$$1" + elif test -n "$PKG_CONFIG"; then + PKG_CHECK_EXISTS([$3], + [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`], + [pkg_failed=yes]) + else + pkg_failed=untried +fi[]dnl +])# _PKG_CONFIG + +# _PKG_SHORT_ERRORS_SUPPORTED +# ----------------------------- +AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi[]dnl +])# _PKG_SHORT_ERRORS_SUPPORTED + + +# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +# [ACTION-IF-NOT-FOUND]) +# +# +# Note that if there is a possibility the first call to +# PKG_CHECK_MODULES might not happen, you should be sure to include an +# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac +# +# +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_MODULES], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl +AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl + +pkg_failed=no +AC_MSG_CHECKING([for $1]) + +_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) +_PKG_CONFIG([$1][_LIBS], [libs], [$2]) + +m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS +and $1[]_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details.]) + +if test $pkg_failed = yes; then + AC_MSG_RESULT([no]) + _PKG_SHORT_ERRORS_SUPPORTED + if test $_pkg_short_errors_supported = yes; then + $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "$2" 2>&1` + else + $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors "$2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD + + m4_default([$4], [AC_MSG_ERROR( +[Package requirements ($2) were not met: + +$$1_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +_PKG_TEXT])[]dnl + ]) +elif test $pkg_failed = untried; then + AC_MSG_RESULT([no]) + m4_default([$4], [AC_MSG_FAILURE( +[The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +_PKG_TEXT + +To get pkg-config, see .])[]dnl + ]) +else + $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS + $1[]_LIBS=$pkg_cv_[]$1[]_LIBS + AC_MSG_RESULT([yes]) + $3 +fi[]dnl +])# PKG_CHECK_MODULES + +# Copyright (C) 2002, 2003, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_AUTOMAKE_VERSION(VERSION) +# ---------------------------- +# Automake X.Y traces this macro to ensure aclocal.m4 has been +# generated from the m4 files accompanying Automake X.Y. +# (This private macro should not be called outside this file.) +AC_DEFUN([AM_AUTOMAKE_VERSION], +[am__api_version='1.11' +dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to +dnl require some minimum version. Point them to the right macro. +m4_if([$1], [1.11.1], [], + [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl +]) + +# _AM_AUTOCONF_VERSION(VERSION) +# ----------------------------- +# aclocal traces this macro to find the Autoconf version. +# This is a private macro too. Using m4_define simplifies +# the logic in aclocal, which can simply ignore this definition. +m4_define([_AM_AUTOCONF_VERSION], []) + +# AM_SET_CURRENT_AUTOMAKE_VERSION +# ------------------------------- +# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. +# This function is AC_REQUIREd by AM_INIT_AUTOMAKE. +AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], +[AM_AUTOMAKE_VERSION([1.11.1])dnl +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) + +# AM_AUX_DIR_EXPAND -*- Autoconf -*- + +# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets +# $ac_aux_dir to `$srcdir/foo'. In other projects, it is set to +# `$srcdir', `$srcdir/..', or `$srcdir/../..'. +# +# Of course, Automake must honor this variable whenever it calls a +# tool from the auxiliary directory. The problem is that $srcdir (and +# therefore $ac_aux_dir as well) can be either absolute or relative, +# depending on how configure is run. This is pretty annoying, since +# it makes $ac_aux_dir quite unusable in subdirectories: in the top +# source directory, any form will work fine, but in subdirectories a +# relative path needs to be adjusted first. +# +# $ac_aux_dir/missing +# fails when called from a subdirectory if $ac_aux_dir is relative +# $top_srcdir/$ac_aux_dir/missing +# fails if $ac_aux_dir is absolute, +# fails when called from a subdirectory in a VPATH build with +# a relative $ac_aux_dir +# +# The reason of the latter failure is that $top_srcdir and $ac_aux_dir +# are both prefixed by $srcdir. In an in-source build this is usually +# harmless because $srcdir is `.', but things will broke when you +# start a VPATH build or use an absolute $srcdir. +# +# So we could use something similar to $top_srcdir/$ac_aux_dir/missing, +# iff we strip the leading $srcdir from $ac_aux_dir. That would be: +# am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` +# and then we would define $MISSING as +# MISSING="\${SHELL} $am_aux_dir/missing" +# This will work as long as MISSING is not called from configure, because +# unfortunately $(top_srcdir) has no meaning in configure. +# However there are other variables, like CC, which are often used in +# configure, and could therefore not use this "fixed" $ac_aux_dir. +# +# Another solution, used here, is to always expand $ac_aux_dir to an +# absolute PATH. The drawback is that using absolute paths prevent a +# configured tree to be moved without reconfiguration. + +AC_DEFUN([AM_AUX_DIR_EXPAND], +[dnl Rely on autoconf to set up CDPATH properly. +AC_PREREQ([2.50])dnl +# expand $ac_aux_dir to an absolute path +am_aux_dir=`cd $ac_aux_dir && pwd` +]) + +# AM_CONDITIONAL -*- Autoconf -*- + +# Copyright (C) 1997, 2000, 2001, 2003, 2004, 2005, 2006, 2008 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 9 + +# AM_CONDITIONAL(NAME, SHELL-CONDITION) +# ------------------------------------- +# Define a conditional. +AC_DEFUN([AM_CONDITIONAL], +[AC_PREREQ(2.52)dnl + ifelse([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], + [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl +AC_SUBST([$1_TRUE])dnl +AC_SUBST([$1_FALSE])dnl +_AM_SUBST_NOTMAKE([$1_TRUE])dnl +_AM_SUBST_NOTMAKE([$1_FALSE])dnl +m4_define([_AM_COND_VALUE_$1], [$2])dnl +if $2; then + $1_TRUE= + $1_FALSE='#' +else + $1_TRUE='#' + $1_FALSE= +fi +AC_CONFIG_COMMANDS_PRE( +[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then + AC_MSG_ERROR([[conditional "$1" was never defined. +Usually this means the macro was only invoked conditionally.]]) +fi])]) + +# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2009 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 10 + +# There are a few dirty hacks below to avoid letting `AC_PROG_CC' be +# written in clear, in which case automake, when reading aclocal.m4, +# will think it sees a *use*, and therefore will trigger all it's +# C support machinery. Also note that it means that autoscan, seeing +# CC etc. in the Makefile, will ask for an AC_PROG_CC use... + + +# _AM_DEPENDENCIES(NAME) +# ---------------------- +# See how the compiler implements dependency checking. +# NAME is "CC", "CXX", "GCJ", or "OBJC". +# We try a few techniques and use that to set a single cache variable. +# +# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was +# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular +# dependency, and given that the user is not expected to run this macro, +# just rely on AC_PROG_CC. +AC_DEFUN([_AM_DEPENDENCIES], +[AC_REQUIRE([AM_SET_DEPDIR])dnl +AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl +AC_REQUIRE([AM_MAKE_INCLUDE])dnl +AC_REQUIRE([AM_DEP_TRACK])dnl + +ifelse([$1], CC, [depcc="$CC" am_compiler_list=], + [$1], CXX, [depcc="$CXX" am_compiler_list=], + [$1], OBJC, [depcc="$OBJC" am_compiler_list='gcc3 gcc'], + [$1], UPC, [depcc="$UPC" am_compiler_list=], + [$1], GCJ, [depcc="$GCJ" am_compiler_list='gcc3 gcc'], + [depcc="$$1" am_compiler_list=]) + +AC_CACHE_CHECK([dependency style of $depcc], + [am_cv_$1_dependencies_compiler_type], +[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named `D' -- because `-MD' means `put the output + # in D'. + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_$1_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` + fi + am__universal=false + m4_case([$1], [CC], + [case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac], + [CXX], + [case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac]) + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with + # Solaris 8's {/usr,}/bin/sh. + touch sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with `-c' and `-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle `-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # after this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvisualcpp | msvcmsys) + # This compiler won't grok `-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_$1_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_$1_dependencies_compiler_type=none +fi +]) +AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) +AM_CONDITIONAL([am__fastdep$1], [ + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) +]) + + +# AM_SET_DEPDIR +# ------------- +# Choose a directory name for dependency files. +# This macro is AC_REQUIREd in _AM_DEPENDENCIES +AC_DEFUN([AM_SET_DEPDIR], +[AC_REQUIRE([AM_SET_LEADING_DOT])dnl +AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl +]) + + +# AM_DEP_TRACK +# ------------ +AC_DEFUN([AM_DEP_TRACK], +[AC_ARG_ENABLE(dependency-tracking, +[ --disable-dependency-tracking speeds up one-time build + --enable-dependency-tracking do not reject slow dependency extractors]) +if test "x$enable_dependency_tracking" != xno; then + am_depcomp="$ac_aux_dir/depcomp" + AMDEPBACKSLASH='\' +fi +AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) +AC_SUBST([AMDEPBACKSLASH])dnl +_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl +]) + +# Generate code to set up dependency tracking. -*- Autoconf -*- + +# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2008 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +#serial 5 + +# _AM_OUTPUT_DEPENDENCY_COMMANDS +# ------------------------------ +AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], +[{ + # Autoconf 2.62 quotes --file arguments for eval, but not when files + # are listed without --file. Let's play safe and only enable the eval + # if we detect the quoting. + case $CONFIG_FILES in + *\'*) eval set x "$CONFIG_FILES" ;; + *) set x $CONFIG_FILES ;; + esac + shift + for mf + do + # Strip MF so we end up with the name of the file. + mf=`echo "$mf" | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile or not. + # We used to match only the files named `Makefile.in', but + # some people rename them; so instead we look at the file content. + # Grep'ing the first line is not enough: some people post-process + # each Makefile.in and add a new line on top of each file to say so. + # Grep'ing the whole file is not good either: AIX grep has a line + # limit of 2048, but all sed's we know have understand at least 4000. + if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then + dirpart=`AS_DIRNAME("$mf")` + else + continue + fi + # Extract the definition of DEPDIR, am__include, and am__quote + # from the Makefile without running `make'. + DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` + test -z "$DEPDIR" && continue + am__include=`sed -n 's/^am__include = //p' < "$mf"` + test -z "am__include" && continue + am__quote=`sed -n 's/^am__quote = //p' < "$mf"` + # When using ansi2knr, U may be empty or an underscore; expand it + U=`sed -n 's/^U = //p' < "$mf"` + # Find all dependency output files, they are included files with + # $(DEPDIR) in their names. We invoke sed twice because it is the + # simplest approach to changing $(DEPDIR) to its actual value in the + # expansion. + for file in `sed -n " + s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ + sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do + # Make sure the directory exists. + test -f "$dirpart/$file" && continue + fdir=`AS_DIRNAME(["$file"])` + AS_MKDIR_P([$dirpart/$fdir]) + # echo "creating $dirpart/$file" + echo '# dummy' > "$dirpart/$file" + done + done +} +])# _AM_OUTPUT_DEPENDENCY_COMMANDS + + +# AM_OUTPUT_DEPENDENCY_COMMANDS +# ----------------------------- +# This macro should only be invoked once -- use via AC_REQUIRE. +# +# This code is only required when automatic dependency tracking +# is enabled. FIXME. This creates each `.P' file that we will +# need in order to bootstrap the dependency handling code. +AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], +[AC_CONFIG_COMMANDS([depfiles], + [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], + [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"]) +]) + +# Copyright (C) 1996, 1997, 2000, 2001, 2003, 2005 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 8 + +# AM_CONFIG_HEADER is obsolete. It has been replaced by AC_CONFIG_HEADERS. +AU_DEFUN([AM_CONFIG_HEADER], [AC_CONFIG_HEADERS($@)]) + +# Do all the work for Automake. -*- Autoconf -*- + +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, +# 2005, 2006, 2008, 2009 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 16 + +# This macro actually does too much. Some checks are only needed if +# your package does certain things. But this isn't really a big deal. + +# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) +# AM_INIT_AUTOMAKE([OPTIONS]) +# ----------------------------------------------- +# The call with PACKAGE and VERSION arguments is the old style +# call (pre autoconf-2.50), which is being phased out. PACKAGE +# and VERSION should now be passed to AC_INIT and removed from +# the call to AM_INIT_AUTOMAKE. +# We support both call styles for the transition. After +# the next Automake release, Autoconf can make the AC_INIT +# arguments mandatory, and then we can depend on a new Autoconf +# release and drop the old call support. +AC_DEFUN([AM_INIT_AUTOMAKE], +[AC_PREREQ([2.62])dnl +dnl Autoconf wants to disallow AM_ names. We explicitly allow +dnl the ones we care about. +m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl +AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl +AC_REQUIRE([AC_PROG_INSTALL])dnl +if test "`cd $srcdir && pwd`" != "`pwd`"; then + # Use -I$(srcdir) only when $(srcdir) != ., so that make's output + # is not polluted with repeated "-I." + AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl + # test to see if srcdir already configured + if test -f $srcdir/config.status; then + AC_MSG_ERROR([source directory already configured; run "make distclean" there first]) + fi +fi + +# test whether we have cygpath +if test -z "$CYGPATH_W"; then + if (cygpath --version) >/dev/null 2>/dev/null; then + CYGPATH_W='cygpath -w' + else + CYGPATH_W=echo + fi +fi +AC_SUBST([CYGPATH_W]) + +# Define the identity of the package. +dnl Distinguish between old-style and new-style calls. +m4_ifval([$2], +[m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl + AC_SUBST([PACKAGE], [$1])dnl + AC_SUBST([VERSION], [$2])], +[_AM_SET_OPTIONS([$1])dnl +dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT. +m4_if(m4_ifdef([AC_PACKAGE_NAME], 1)m4_ifdef([AC_PACKAGE_VERSION], 1), 11,, + [m4_fatal([AC_INIT should be called with package and version arguments])])dnl + AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl + AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl + +_AM_IF_OPTION([no-define],, +[AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of package]) + AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version number of package])])dnl + +# Some tools Automake needs. +AC_REQUIRE([AM_SANITY_CHECK])dnl +AC_REQUIRE([AC_ARG_PROGRAM])dnl +AM_MISSING_PROG(ACLOCAL, aclocal-${am__api_version}) +AM_MISSING_PROG(AUTOCONF, autoconf) +AM_MISSING_PROG(AUTOMAKE, automake-${am__api_version}) +AM_MISSING_PROG(AUTOHEADER, autoheader) +AM_MISSING_PROG(MAKEINFO, makeinfo) +AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl +AC_REQUIRE([AM_PROG_MKDIR_P])dnl +# We need awk for the "check" target. The system "awk" is bad on +# some platforms. +AC_REQUIRE([AC_PROG_AWK])dnl +AC_REQUIRE([AC_PROG_MAKE_SET])dnl +AC_REQUIRE([AM_SET_LEADING_DOT])dnl +_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], + [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], + [_AM_PROG_TAR([v7])])]) +_AM_IF_OPTION([no-dependencies],, +[AC_PROVIDE_IFELSE([AC_PROG_CC], + [_AM_DEPENDENCIES(CC)], + [define([AC_PROG_CC], + defn([AC_PROG_CC])[_AM_DEPENDENCIES(CC)])])dnl +AC_PROVIDE_IFELSE([AC_PROG_CXX], + [_AM_DEPENDENCIES(CXX)], + [define([AC_PROG_CXX], + defn([AC_PROG_CXX])[_AM_DEPENDENCIES(CXX)])])dnl +AC_PROVIDE_IFELSE([AC_PROG_OBJC], + [_AM_DEPENDENCIES(OBJC)], + [define([AC_PROG_OBJC], + defn([AC_PROG_OBJC])[_AM_DEPENDENCIES(OBJC)])])dnl +]) +_AM_IF_OPTION([silent-rules], [AC_REQUIRE([AM_SILENT_RULES])])dnl +dnl The `parallel-tests' driver may need to know about EXEEXT, so add the +dnl `am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen. This macro +dnl is hooked onto _AC_COMPILER_EXEEXT early, see below. +AC_CONFIG_COMMANDS_PRE(dnl +[m4_provide_if([_AM_COMPILER_EXEEXT], + [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl +]) + +dnl Hook into `_AC_COMPILER_EXEEXT' early to learn its expansion. Do not +dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further +dnl mangled by Autoconf and run in a shell conditional statement. +m4_define([_AC_COMPILER_EXEEXT], +m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])]) + + +# When config.status generates a header, we must update the stamp-h file. +# This file resides in the same directory as the config header +# that is generated. The stamp files are numbered to have different names. + +# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the +# loop where config.status creates the headers, so we can generate +# our stamp files there. +AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], +[# Compute $1's index in $config_headers. +_am_arg=$1 +_am_stamp_count=1 +for _am_header in $config_headers :; do + case $_am_header in + $_am_arg | $_am_arg:* ) + break ;; + * ) + _am_stamp_count=`expr $_am_stamp_count + 1` ;; + esac +done +echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) + +# Copyright (C) 2001, 2003, 2005, 2008 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_SH +# ------------------ +# Define $install_sh. +AC_DEFUN([AM_PROG_INSTALL_SH], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +if test x"${install_sh}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; + *) + install_sh="\${SHELL} $am_aux_dir/install-sh" + esac +fi +AC_SUBST(install_sh)]) + +# Copyright (C) 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 2 + +# Check whether the underlying file-system supports filenames +# with a leading dot. For instance MS-DOS doesn't. +AC_DEFUN([AM_SET_LEADING_DOT], +[rm -rf .tst 2>/dev/null +mkdir .tst 2>/dev/null +if test -d .tst; then + am__leading_dot=. +else + am__leading_dot=_ +fi +rmdir .tst 2>/dev/null +AC_SUBST([am__leading_dot])]) + +# Check to see how 'make' treats includes. -*- Autoconf -*- + +# Copyright (C) 2001, 2002, 2003, 2005, 2009 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 4 + +# AM_MAKE_INCLUDE() +# ----------------- +# Check to see how make treats includes. +AC_DEFUN([AM_MAKE_INCLUDE], +[am_make=${MAKE-make} +cat > confinc << 'END' +am__doit: + @echo this is the am__doit target +.PHONY: am__doit +END +# If we don't find an include directive, just comment out the code. +AC_MSG_CHECKING([for style of include used by $am_make]) +am__include="#" +am__quote= +_am_result=none +# First try GNU make style include. +echo "include confinc" > confmf +# Ignore all kinds of additional output from `make'. +case `$am_make -s -f confmf 2> /dev/null` in #( +*the\ am__doit\ target*) + am__include=include + am__quote= + _am_result=GNU + ;; +esac +# Now try BSD make style include. +if test "$am__include" = "#"; then + echo '.include "confinc"' > confmf + case `$am_make -s -f confmf 2> /dev/null` in #( + *the\ am__doit\ target*) + am__include=.include + am__quote="\"" + _am_result=BSD + ;; + esac +fi +AC_SUBST([am__include]) +AC_SUBST([am__quote]) +AC_MSG_RESULT([$_am_result]) +rm -f confinc confmf +]) + +# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- + +# Copyright (C) 1997, 1999, 2000, 2001, 2003, 2004, 2005, 2008 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 6 + +# AM_MISSING_PROG(NAME, PROGRAM) +# ------------------------------ +AC_DEFUN([AM_MISSING_PROG], +[AC_REQUIRE([AM_MISSING_HAS_RUN]) +$1=${$1-"${am_missing_run}$2"} +AC_SUBST($1)]) + + +# AM_MISSING_HAS_RUN +# ------------------ +# Define MISSING if not defined so far and test if it supports --run. +# If it does, set am_missing_run to use it, otherwise, to nothing. +AC_DEFUN([AM_MISSING_HAS_RUN], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +AC_REQUIRE_AUX_FILE([missing])dnl +if test x"${MISSING+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; + *) + MISSING="\${SHELL} $am_aux_dir/missing" ;; + esac +fi +# Use eval to expand $SHELL +if eval "$MISSING --run true"; then + am_missing_run="$MISSING --run " +else + am_missing_run= + AC_MSG_WARN([`missing' script is too old or missing]) +fi +]) + +# Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_MKDIR_P +# --------------- +# Check for `mkdir -p'. +AC_DEFUN([AM_PROG_MKDIR_P], +[AC_PREREQ([2.60])dnl +AC_REQUIRE([AC_PROG_MKDIR_P])dnl +dnl Automake 1.8 to 1.9.6 used to define mkdir_p. We now use MKDIR_P, +dnl while keeping a definition of mkdir_p for backward compatibility. +dnl @MKDIR_P@ is magic: AC_OUTPUT adjusts its value for each Makefile. +dnl However we cannot define mkdir_p as $(MKDIR_P) for the sake of +dnl Makefile.ins that do not define MKDIR_P, so we do our own +dnl adjustment using top_builddir (which is defined more often than +dnl MKDIR_P). +AC_SUBST([mkdir_p], ["$MKDIR_P"])dnl +case $mkdir_p in + [[\\/$]]* | ?:[[\\/]]*) ;; + */*) mkdir_p="\$(top_builddir)/$mkdir_p" ;; +esac +]) + +# Helper functions for option handling. -*- Autoconf -*- + +# Copyright (C) 2001, 2002, 2003, 2005, 2008 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 4 + +# _AM_MANGLE_OPTION(NAME) +# ----------------------- +AC_DEFUN([_AM_MANGLE_OPTION], +[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) + +# _AM_SET_OPTION(NAME) +# ------------------------------ +# Set option NAME. Presently that only means defining a flag for this option. +AC_DEFUN([_AM_SET_OPTION], +[m4_define(_AM_MANGLE_OPTION([$1]), 1)]) + +# _AM_SET_OPTIONS(OPTIONS) +# ---------------------------------- +# OPTIONS is a space-separated list of Automake options. +AC_DEFUN([_AM_SET_OPTIONS], +[m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) + +# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) +# ------------------------------------------- +# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. +AC_DEFUN([_AM_IF_OPTION], +[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) + +# Check to make sure that the build environment is sane. -*- Autoconf -*- + +# Copyright (C) 1996, 1997, 2000, 2001, 2003, 2005, 2008 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 5 + +# AM_SANITY_CHECK +# --------------- +AC_DEFUN([AM_SANITY_CHECK], +[AC_MSG_CHECKING([whether build environment is sane]) +# Just in case +sleep 1 +echo timestamp > conftest.file +# Reject unsafe characters in $srcdir or the absolute working directory +# name. Accept space and tab only in the latter. +am_lf=' +' +case `pwd` in + *[[\\\"\#\$\&\'\`$am_lf]]*) + AC_MSG_ERROR([unsafe absolute working directory name]);; +esac +case $srcdir in + *[[\\\"\#\$\&\'\`$am_lf\ \ ]]*) + AC_MSG_ERROR([unsafe srcdir value: `$srcdir']);; +esac + +# Do `set' in a subshell so we don't clobber the current shell's +# arguments. Must try -L first in case configure is actually a +# symlink; some systems play weird games with the mod time of symlinks +# (eg FreeBSD returns the mod time of the symlink's containing +# directory). +if ( + set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` + if test "$[*]" = "X"; then + # -L didn't work. + set X `ls -t "$srcdir/configure" conftest.file` + fi + rm -f conftest.file + if test "$[*]" != "X $srcdir/configure conftest.file" \ + && test "$[*]" != "X conftest.file $srcdir/configure"; then + + # If neither matched, then we have a broken ls. This can happen + # if, for instance, CONFIG_SHELL is bash and it inherits a + # broken ls alias from the environment. This has actually + # happened. Such a system could not be considered "sane". + AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken +alias in your environment]) + fi + + test "$[2]" = conftest.file + ) +then + # Ok. + : +else + AC_MSG_ERROR([newly created file is older than distributed files! +Check your system clock]) +fi +AC_MSG_RESULT(yes)]) + +# Copyright (C) 2009 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 1 + +# AM_SILENT_RULES([DEFAULT]) +# -------------------------- +# Enable less verbose build rules; with the default set to DEFAULT +# (`yes' being less verbose, `no' or empty being verbose). +AC_DEFUN([AM_SILENT_RULES], +[AC_ARG_ENABLE([silent-rules], +[ --enable-silent-rules less verbose build output (undo: `make V=1') + --disable-silent-rules verbose build output (undo: `make V=0')]) +case $enable_silent_rules in +yes) AM_DEFAULT_VERBOSITY=0;; +no) AM_DEFAULT_VERBOSITY=1;; +*) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);; +esac +AC_SUBST([AM_DEFAULT_VERBOSITY])dnl +AM_BACKSLASH='\' +AC_SUBST([AM_BACKSLASH])dnl +_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl +]) + +# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_STRIP +# --------------------- +# One issue with vendor `install' (even GNU) is that you can't +# specify the program used to strip binaries. This is especially +# annoying in cross-compiling environments, where the build's strip +# is unlikely to handle the host's binaries. +# Fortunately install-sh will honor a STRIPPROG variable, so we +# always use install-sh in `make install-strip', and initialize +# STRIPPROG with the value of the STRIP variable (set by the user). +AC_DEFUN([AM_PROG_INSTALL_STRIP], +[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +# Installed binaries are usually stripped using `strip' when the user +# run `make install-strip'. However `strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the `STRIP' environment variable to overrule this program. +dnl Don't test for $cross_compiling = yes, because it might be `maybe'. +if test "$cross_compiling" != no; then + AC_CHECK_TOOL([STRIP], [strip], :) +fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" +AC_SUBST([INSTALL_STRIP_PROGRAM])]) + +# Copyright (C) 2006, 2008 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 2 + +# _AM_SUBST_NOTMAKE(VARIABLE) +# --------------------------- +# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in. +# This macro is traced by Automake. +AC_DEFUN([_AM_SUBST_NOTMAKE]) + +# AM_SUBST_NOTMAKE(VARIABLE) +# --------------------------- +# Public sister of _AM_SUBST_NOTMAKE. +AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) + +# Check how to create a tarball. -*- Autoconf -*- + +# Copyright (C) 2004, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 2 + +# _AM_PROG_TAR(FORMAT) +# -------------------- +# Check how to create a tarball in format FORMAT. +# FORMAT should be one of `v7', `ustar', or `pax'. +# +# Substitute a variable $(am__tar) that is a command +# writing to stdout a FORMAT-tarball containing the directory +# $tardir. +# tardir=directory && $(am__tar) > result.tar +# +# Substitute a variable $(am__untar) that extract such +# a tarball read from stdin. +# $(am__untar) < result.tar +AC_DEFUN([_AM_PROG_TAR], +[# Always define AMTAR for backward compatibility. +AM_MISSING_PROG([AMTAR], [tar]) +m4_if([$1], [v7], + [am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -'], + [m4_case([$1], [ustar],, [pax],, + [m4_fatal([Unknown tar format])]) +AC_MSG_CHECKING([how to create a $1 tar archive]) +# Loop over all known methods to create a tar archive until one works. +_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' +_am_tools=${am_cv_prog_tar_$1-$_am_tools} +# Do not fold the above two line into one, because Tru64 sh and +# Solaris sh will not grok spaces in the rhs of `-'. +for _am_tool in $_am_tools +do + case $_am_tool in + gnutar) + for _am_tar in tar gnutar gtar; + do + AM_RUN_LOG([$_am_tar --version]) && break + done + am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' + am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' + am__untar="$_am_tar -xf -" + ;; + plaintar) + # Must skip GNU tar: if it does not support --format= it doesn't create + # ustar tarball either. + (tar --version) >/dev/null 2>&1 && continue + am__tar='tar chf - "$$tardir"' + am__tar_='tar chf - "$tardir"' + am__untar='tar xf -' + ;; + pax) + am__tar='pax -L -x $1 -w "$$tardir"' + am__tar_='pax -L -x $1 -w "$tardir"' + am__untar='pax -r' + ;; + cpio) + am__tar='find "$$tardir" -print | cpio -o -H $1 -L' + am__tar_='find "$tardir" -print | cpio -o -H $1 -L' + am__untar='cpio -i -H $1 -d' + ;; + none) + am__tar=false + am__tar_=false + am__untar=false + ;; + esac + + # If the value was cached, stop now. We just wanted to have am__tar + # and am__untar set. + test -n "${am_cv_prog_tar_$1}" && break + + # tar/untar a dummy directory, and stop if the command works + rm -rf conftest.dir + mkdir conftest.dir + echo GrepMe > conftest.dir/file + AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) + rm -rf conftest.dir + if test -s conftest.tar; then + AM_RUN_LOG([$am__untar /dev/null 2>&1 && break + fi +done +rm -rf conftest.dir + +AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) +AC_MSG_RESULT([$am_cv_prog_tar_$1])]) +AC_SUBST([am__tar]) +AC_SUBST([am__untar]) +]) # _AM_PROG_TAR + diff --git a/bscconfig.h.in b/bscconfig.h.in new file mode 100644 index 000000000..fc5dfdef0 --- /dev/null +++ b/bscconfig.h.in @@ -0,0 +1,61 @@ +/* bscconfig.h.in. Generated from configure.in by autoheader. */ + +/* Define to 1 if you have the header file. */ +#undef HAVE_DAHDI_USER_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Version number of package */ +#undef VERSION + +/* Use crypt functionality of vty. */ +#undef VTY_CRYPT_PW diff --git a/configure b/configure new file mode 100755 index 000000000..86e47aeb2 --- /dev/null +++ b/configure @@ -0,0 +1,6307 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.67 for openbsc 0.9.13. +# +# Report bugs to . +# +# +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, +# 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software +# Foundation, Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : + +else + exitcode=1; echo positional parameters were not saved. +fi +test x\$exitcode = x0 || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null; then : + as_have_required=yes +else + as_have_required=no +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir/$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + CONFIG_SHELL=$as_shell as_have_required=yes + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + break 2 +fi +fi + done;; + esac + as_found=false +done +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi; } +IFS=$as_save_IFS + + + if test "x$CONFIG_SHELL" != x; then : + # We cannot yet assume a decent shell, so we have to provide a + # neutralization value for shells without unset; and this also + # works around shells that cannot unset nonexistent variables. + BASH_ENV=/dev/null + ENV=/dev/null + (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV + export CONFIG_SHELL + exec "$CONFIG_SHELL" "$as_myself" ${1+"$@"} +fi + + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." + else + $as_echo "$0: Please tell bug-autoconf@gnu.org and +$0: openbsc-devel@lists.openbsc.org about your system, +$0: including any error possibly output before this +$0: message. Then install a modern shell, or manually run +$0: the script under such a shell if you do have one." + fi + exit 1 +fi +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -p'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -p' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -p' + fi +else + as_ln_s='cp -p' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +if test -x / >/dev/null 2>&1; then + as_test_x='test -x' +else + if ls -dL / >/dev/null 2>&1; then + as_ls_L_option=L + else + as_ls_L_option= + fi + as_test_x=' + eval sh -c '\'' + if test -d "$1"; then + test -d "$1/."; + else + case $1 in #( + -*)set "./$1";; + esac; + case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( + ???[sx]*):;;*)false;;esac;fi + '\'' sh + ' +fi +as_executable_p=$as_test_x + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +test -n "$DJDIR" || exec 7<&0 &1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME='openbsc' +PACKAGE_TARNAME='openbsc' +PACKAGE_VERSION='0.9.13' +PACKAGE_STRING='openbsc 0.9.13' +PACKAGE_BUGREPORT='openbsc-devel@lists.openbsc.org' +PACKAGE_URL='' + +# Factoring default headers for most tests. +ac_includes_default="\ +#include +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined STDC_HEADERS && defined HAVE_MEMORY_H +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif" + +ac_subst_vars='am__EXEEXT_FALSE +am__EXEEXT_TRUE +LTLIBOBJS +LIBOBJS +COVERAGE_LDFLAGS +COVERAGE_CFLAGS +SYMBOL_VISIBILITY +EGREP +GREP +CPP +LIBOSMOVTY_LIBS +LIBOSMOVTY_CFLAGS +LIBOSMOCORE_LIBS +LIBOSMOCORE_CFLAGS +BUILD_BSC_FALSE +BUILD_BSC_TRUE +BUILD_NAT_FALSE +BUILD_NAT_TRUE +LIBOSMOSCCP_LIBS +LIBOSMOSCCP_CFLAGS +PKG_CONFIG_LIBDIR +PKG_CONFIG_PATH +PKG_CONFIG +HAVE_LIBGTP_FALSE +HAVE_LIBGTP_TRUE +GPRS_LIBGTP +RANLIB +am__fastdepCC_FALSE +am__fastdepCC_TRUE +CCDEPMODE +AMDEPBACKSLASH +AMDEP_FALSE +AMDEP_TRUE +am__quote +am__include +DEPDIR +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +AM_BACKSLASH +AM_DEFAULT_VERBOSITY +am__untar +am__tar +AMTAR +am__leading_dot +SET_MAKE +AWK +mkdir_p +MKDIR_P +INSTALL_STRIP_PROGRAM +STRIP +install_sh +MAKEINFO +AUTOHEADER +AUTOMAKE +AUTOCONF +ACLOCAL +VERSION +PACKAGE +CYGPATH_W +am__isrc +INSTALL_DATA +INSTALL_SCRIPT +INSTALL_PROGRAM +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +enable_silent_rules +enable_dependency_tracking +enable_nat +enable_osmo_bsc +enable_coverage +' + ac_precious_vars='build_alias +host_alias +target_alias +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS +PKG_CONFIG +PKG_CONFIG_PATH +PKG_CONFIG_LIBDIR +LIBOSMOSCCP_CFLAGS +LIBOSMOSCCP_LIBS +LIBOSMOCORE_CFLAGS +LIBOSMOCORE_LIBS +LIBOSMOVTY_CFLAGS +LIBOSMOVTY_LIBS +CPP' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option} + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: `$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + $as_echo "$as_me: WARNING: if you wanted to set the --build type, don't use --host. + If a cross compiler is detected then cross compile mode will be used" >&2 + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +\`configure' configures openbsc 0.9.13 to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print \`checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for \`--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or \`..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/openbsc] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF + +Program names: + --program-prefix=PREFIX prepend PREFIX to installed program names + --program-suffix=SUFFIX append SUFFIX to installed program names + --program-transform-name=PROGRAM run sed PROGRAM on installed program names +_ACEOF +fi + +if test -n "$ac_init_help"; then + case $ac_init_help in + short | recursive ) echo "Configuration of openbsc 0.9.13:";; + esac + cat <<\_ACEOF + +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --enable-silent-rules less verbose build output (undo: `make V=1') + --disable-silent-rules verbose build output (undo: `make V=0') + --disable-dependency-tracking speeds up one-time build + --enable-dependency-tracking do not reject slow dependency extractors + --enable-nat Build the BSC NAT. Requires SCCP + --enable-osmo-bsc Build the Osmo BSC + --enable-coverage enable code coverage support [default=no] + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L if you have libraries in a + nonstandard directory + LIBS libraries to pass to the linker, e.g. -l + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if + you have headers in a nonstandard directory + PKG_CONFIG path to pkg-config utility + PKG_CONFIG_PATH + directories to add to pkg-config's search path + PKG_CONFIG_LIBDIR + path overriding pkg-config's built-in search path + LIBOSMOSCCP_CFLAGS + C compiler flags for LIBOSMOSCCP, overriding pkg-config + LIBOSMOSCCP_LIBS + linker flags for LIBOSMOSCCP, overriding pkg-config + LIBOSMOCORE_CFLAGS + C compiler flags for LIBOSMOCORE, overriding pkg-config + LIBOSMOCORE_LIBS + linker flags for LIBOSMOCORE, overriding pkg-config + LIBOSMOVTY_CFLAGS + C compiler flags for LIBOSMOVTY, overriding pkg-config + LIBOSMOVTY_LIBS + linker flags for LIBOSMOVTY, overriding pkg-config + CPP C preprocessor + +Use these variables to override the choices made by `configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to . +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for guested configure. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +openbsc configure 0.9.13 +generated by GNU Autoconf 2.67 + +Copyright (C) 2010 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link + +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp + +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes +# that executables *can* be run. +ac_fn_c_try_run () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then : + ac_retval=0 +else + $as_echo "$as_me: program exited with status $ac_status" >&5 + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run + +# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists, giving a warning if it cannot be compiled using +# the include files in INCLUDES and setting the cache variable VAR +# accordingly. +ac_fn_c_check_header_mongrel () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if eval "test \"\${$3+set}\"" = set; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval "test \"\${$3+set}\"" = set; then : + $as_echo_n "(cached) " >&6 +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +else + # Is the header compilable? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 +$as_echo_n "checking $2 usability... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_header_compiler=yes +else + ac_header_compiler=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 +$as_echo "$ac_header_compiler" >&6; } + +# Is the header present? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 +$as_echo_n "checking $2 presence... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <$2> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + ac_header_preproc=yes +else + ac_header_preproc=no +fi +rm -f conftest.err conftest.i conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 +$as_echo "$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( + yes:no: ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 +$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; + no:yes:* ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 +$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 +$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 +$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 +$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} +( $as_echo "## ---------------------------------------------- ## +## Report this to openbsc-devel@lists.openbsc.org ## +## ---------------------------------------------- ##" + ) | sed "s/^/$as_me: WARNING: /" >&2 + ;; +esac + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval "test \"\${$3+set}\"" = set; then : + $as_echo_n "(cached) " >&6 +else + eval "$3=\$ac_header_compiler" +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +fi + eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} + +} # ac_fn_c_check_header_mongrel + +# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_c_check_header_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval "test \"\${$3+set}\"" = set; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} + +} # ac_fn_c_check_header_compile +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by openbsc $as_me 0.9.13, which was +generated by GNU Autoconf 2.67. Invocation command line was + + $ $0 $@ + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Save into config.log some information that might help in debugging. + { + echo + + $as_echo "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + $as_echo "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + $as_echo "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + $as_echo "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +$as_echo "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE +if test -n "$CONFIG_SITE"; then + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac +elif test "x$prefix" != xNONE; then + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site +else + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site +fi +for ac_site_file in "$ac_site_file1" "$ac_site_file2" +do + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See \`config.log' for more details" "$LINENO" 5 ; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +am__api_version='1.11' + +ac_aux_dir= +for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do + if test -f "$ac_dir/install-sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f "$ac_dir/install.sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + elif test -f "$ac_dir/shtool"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/shtool install -c" + break + fi +done +if test -z "$ac_aux_dir"; then + as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5 +fi + +# These three variables are undocumented and unsupported, +# and are intended to be withdrawn in a future Autoconf release. +# They can cause serious problems if a builder's source tree is in a directory +# whose full name contains unusual characters. +ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. +ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. +ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. + + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AmigaOS /C/install, which installs bootblocks on floppy discs +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# OS/2's system install, which has a completely different semantic +# ./install, which can be erroneously created by make from ./install.sh. +# Reject install programs that cannot install multiple files. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 +$as_echo_n "checking for a BSD-compatible install... " >&6; } +if test -z "$INSTALL"; then +if test "${ac_cv_path_install+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + # Account for people who put trailing slashes in PATH elements. +case $as_dir/ in #(( + ./ | .// | /[cC]/* | \ + /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ + ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ + /usr/ucb/* ) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; }; then + if test $ac_prog = install && + grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + elif test $ac_prog = install && + grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # program-specific install script used by HP pwplus--don't use. + : + else + rm -rf conftest.one conftest.two conftest.dir + echo one > conftest.one + echo two > conftest.two + mkdir conftest.dir + if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && + test -s conftest.one && test -s conftest.two && + test -s conftest.dir/conftest.one && + test -s conftest.dir/conftest.two + then + ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" + break 3 + fi + fi + fi + done + done + ;; +esac + + done +IFS=$as_save_IFS + +rm -rf conftest.one conftest.two conftest.dir + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL=$ac_cv_path_install + else + # As a last resort, use the slow shell script. Don't cache a + # value for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + INSTALL=$ac_install_sh + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 +$as_echo "$INSTALL" >&6; } + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5 +$as_echo_n "checking whether build environment is sane... " >&6; } +# Just in case +sleep 1 +echo timestamp > conftest.file +# Reject unsafe characters in $srcdir or the absolute working directory +# name. Accept space and tab only in the latter. +am_lf=' +' +case `pwd` in + *[\\\"\#\$\&\'\`$am_lf]*) + as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5 ;; +esac +case $srcdir in + *[\\\"\#\$\&\'\`$am_lf\ \ ]*) + as_fn_error $? "unsafe srcdir value: \`$srcdir'" "$LINENO" 5 ;; +esac + +# Do `set' in a subshell so we don't clobber the current shell's +# arguments. Must try -L first in case configure is actually a +# symlink; some systems play weird games with the mod time of symlinks +# (eg FreeBSD returns the mod time of the symlink's containing +# directory). +if ( + set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` + if test "$*" = "X"; then + # -L didn't work. + set X `ls -t "$srcdir/configure" conftest.file` + fi + rm -f conftest.file + if test "$*" != "X $srcdir/configure conftest.file" \ + && test "$*" != "X conftest.file $srcdir/configure"; then + + # If neither matched, then we have a broken ls. This can happen + # if, for instance, CONFIG_SHELL is bash and it inherits a + # broken ls alias from the environment. This has actually + # happened. Such a system could not be considered "sane". + as_fn_error $? "ls -t appears to fail. Make sure there is not a broken +alias in your environment" "$LINENO" 5 + fi + + test "$2" = conftest.file + ) +then + # Ok. + : +else + as_fn_error $? "newly created file is older than distributed files! +Check your system clock" "$LINENO" 5 +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +test "$program_prefix" != NONE && + program_transform_name="s&^&$program_prefix&;$program_transform_name" +# Use a double $ so make ignores it. +test "$program_suffix" != NONE && + program_transform_name="s&\$&$program_suffix&;$program_transform_name" +# Double any \ or $. +# By default was `s,x,x', remove it if useless. +ac_script='s/[\\$]/&&/g;s/;s,x,x,$//' +program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"` + +# expand $ac_aux_dir to an absolute path +am_aux_dir=`cd $ac_aux_dir && pwd` + +if test x"${MISSING+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; + *) + MISSING="\${SHELL} $am_aux_dir/missing" ;; + esac +fi +# Use eval to expand $SHELL +if eval "$MISSING --run true"; then + am_missing_run="$MISSING --run " +else + am_missing_run= + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: \`missing' script is too old or missing" >&5 +$as_echo "$as_me: WARNING: \`missing' script is too old or missing" >&2;} +fi + +if test x"${install_sh}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; + *) + install_sh="\${SHELL} $am_aux_dir/install-sh" + esac +fi + +# Installed binaries are usually stripped using `strip' when the user +# run `make install-strip'. However `strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the `STRIP' environment variable to overrule this program. +if test "$cross_compiling" != no; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. +set dummy ${ac_tool_prefix}strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_STRIP+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$STRIP"; then + ac_cv_prog_STRIP="$STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_STRIP="${ac_tool_prefix}strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +STRIP=$ac_cv_prog_STRIP +if test -n "$STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 +$as_echo "$STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_STRIP"; then + ac_ct_STRIP=$STRIP + # Extract the first word of "strip", so it can be a program name with args. +set dummy strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_STRIP+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_STRIP"; then + ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_STRIP="strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP +if test -n "$ac_ct_STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 +$as_echo "$ac_ct_STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_STRIP" = x; then + STRIP=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + STRIP=$ac_ct_STRIP + fi +else + STRIP="$ac_cv_prog_STRIP" +fi + +fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a thread-safe mkdir -p" >&5 +$as_echo_n "checking for a thread-safe mkdir -p... " >&6; } +if test -z "$MKDIR_P"; then + if test "${ac_cv_path_mkdir+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in mkdir gmkdir; do + for ac_exec_ext in '' $ac_executable_extensions; do + { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; } || continue + case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #( + 'mkdir (GNU coreutils) '* | \ + 'mkdir (coreutils) '* | \ + 'mkdir (fileutils) '4.1*) + ac_cv_path_mkdir=$as_dir/$ac_prog$ac_exec_ext + break 3;; + esac + done + done + done +IFS=$as_save_IFS + +fi + + test -d ./--version && rmdir ./--version + if test "${ac_cv_path_mkdir+set}" = set; then + MKDIR_P="$ac_cv_path_mkdir -p" + else + # As a last resort, use the slow shell script. Don't cache a + # value for MKDIR_P within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + MKDIR_P="$ac_install_sh -d" + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5 +$as_echo "$MKDIR_P" >&6; } + +mkdir_p="$MKDIR_P" +case $mkdir_p in + [\\/$]* | ?:[\\/]*) ;; + */*) mkdir_p="\$(top_builddir)/$mkdir_p" ;; +esac + +for ac_prog in gawk mawk nawk awk +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_AWK+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$AWK"; then + ac_cv_prog_AWK="$AWK" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_AWK="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +AWK=$ac_cv_prog_AWK +if test -n "$AWK"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5 +$as_echo "$AWK" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$AWK" && break +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 +$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } +set x ${MAKE-make} +ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` +if eval "test \"\${ac_cv_prog_make_${ac_make}_set+set}\"" = set; then : + $as_echo_n "(cached) " >&6 +else + cat >conftest.make <<\_ACEOF +SHELL = /bin/sh +all: + @echo '@@@%%%=$(MAKE)=@@@%%%' +_ACEOF +# GNU make sometimes prints "make[1]: Entering ...", which would confuse us. +case `${MAKE-make} -f conftest.make 2>/dev/null` in + *@@@%%%=?*=@@@%%%*) + eval ac_cv_prog_make_${ac_make}_set=yes;; + *) + eval ac_cv_prog_make_${ac_make}_set=no;; +esac +rm -f conftest.make +fi +if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + SET_MAKE= +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + SET_MAKE="MAKE=${MAKE-make}" +fi + +rm -rf .tst 2>/dev/null +mkdir .tst 2>/dev/null +if test -d .tst; then + am__leading_dot=. +else + am__leading_dot=_ +fi +rmdir .tst 2>/dev/null + +if test "`cd $srcdir && pwd`" != "`pwd`"; then + # Use -I$(srcdir) only when $(srcdir) != ., so that make's output + # is not polluted with repeated "-I." + am__isrc=' -I$(srcdir)' + # test to see if srcdir already configured + if test -f $srcdir/config.status; then + as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5 + fi +fi + +# test whether we have cygpath +if test -z "$CYGPATH_W"; then + if (cygpath --version) >/dev/null 2>/dev/null; then + CYGPATH_W='cygpath -w' + else + CYGPATH_W=echo + fi +fi + + +# Define the identity of the package. + PACKAGE='openbsc' + VERSION='0.9.13' + + +cat >>confdefs.h <<_ACEOF +#define PACKAGE "$PACKAGE" +_ACEOF + + +cat >>confdefs.h <<_ACEOF +#define VERSION "$VERSION" +_ACEOF + +# Some tools Automake needs. + +ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"} + + +AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"} + + +AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"} + + +AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"} + + +MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} + +# We need awk for the "check" target. The system "awk" is bad on +# some platforms. +# Always define AMTAR for backward compatibility. + +AMTAR=${AMTAR-"${am_missing_run}tar"} + +am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -' + + + + + + +# Check whether --enable-silent-rules was given. +if test "${enable_silent_rules+set}" = set; then : + enableval=$enable_silent_rules; +fi + +case $enable_silent_rules in +yes) AM_DEFAULT_VERBOSITY=0;; +no) AM_DEFAULT_VERBOSITY=1;; +*) AM_DEFAULT_VERBOSITY=0;; +esac +AM_BACKSLASH='\' + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 +$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } +set x ${MAKE-make} +ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` +if eval "test \"\${ac_cv_prog_make_${ac_make}_set+set}\"" = set; then : + $as_echo_n "(cached) " >&6 +else + cat >conftest.make <<\_ACEOF +SHELL = /bin/sh +all: + @echo '@@@%%%=$(MAKE)=@@@%%%' +_ACEOF +# GNU make sometimes prints "make[1]: Entering ...", which would confuse us. +case `${MAKE-make} -f conftest.make 2>/dev/null` in + *@@@%%%=?*=@@@%%%*) + eval ac_cv_prog_make_${ac_make}_set=yes;; + *) + eval ac_cv_prog_make_${ac_make}_set=no;; +esac +rm -f conftest.make +fi +if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + SET_MAKE= +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + SET_MAKE="MAKE=${MAKE-make}" +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_CC+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_CC+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_CC+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_CC+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_CC+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_CC+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5 ; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error 77 "C compiler cannot create executables +See \`config.log' for more details" "$LINENO" 5 ; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details" "$LINENO" 5 ; } +fi +rm -f conftest conftest$ac_cv_exeext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +FILE *f = fopen ("conftest.out", "w"); + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5 ; } + fi + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if test "${ac_cv_objext+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See \`config.log' for more details" "$LINENO" 5 ; } +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if test "${ac_cv_c_compiler_gnu+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if test "${ac_cv_prog_cc_g+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if test "${ac_cv_prog_cc_c89+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +DEPDIR="${am__leading_dot}deps" + +ac_config_commands="$ac_config_commands depfiles" + + +am_make=${MAKE-make} +cat > confinc << 'END' +am__doit: + @echo this is the am__doit target +.PHONY: am__doit +END +# If we don't find an include directive, just comment out the code. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for style of include used by $am_make" >&5 +$as_echo_n "checking for style of include used by $am_make... " >&6; } +am__include="#" +am__quote= +_am_result=none +# First try GNU make style include. +echo "include confinc" > confmf +# Ignore all kinds of additional output from `make'. +case `$am_make -s -f confmf 2> /dev/null` in #( +*the\ am__doit\ target*) + am__include=include + am__quote= + _am_result=GNU + ;; +esac +# Now try BSD make style include. +if test "$am__include" = "#"; then + echo '.include "confinc"' > confmf + case `$am_make -s -f confmf 2> /dev/null` in #( + *the\ am__doit\ target*) + am__include=.include + am__quote="\"" + _am_result=BSD + ;; + esac +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $_am_result" >&5 +$as_echo "$_am_result" >&6; } +rm -f confinc confmf + +# Check whether --enable-dependency-tracking was given. +if test "${enable_dependency_tracking+set}" = set; then : + enableval=$enable_dependency_tracking; +fi + +if test "x$enable_dependency_tracking" != xno; then + am_depcomp="$ac_aux_dir/depcomp" + AMDEPBACKSLASH='\' +fi + if test "x$enable_dependency_tracking" != xno; then + AMDEP_TRUE= + AMDEP_FALSE='#' +else + AMDEP_TRUE='#' + AMDEP_FALSE= +fi + + + +depcc="$CC" am_compiler_list= + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 +$as_echo_n "checking dependency style of $depcc... " >&6; } +if test "${am_cv_CC_dependencies_compiler_type+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named `D' -- because `-MD' means `put the output + # in D'. + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_CC_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` + fi + am__universal=false + case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with + # Solaris 8's {/usr,}/bin/sh. + touch sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with `-c' and `-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle `-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # after this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvisualcpp | msvcmsys) + # This compiler won't grok `-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_CC_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_CC_dependencies_compiler_type=none +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5 +$as_echo "$am_cv_CC_dependencies_compiler_type" >&6; } +CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type + + if + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then + am__fastdepCC_TRUE= + am__fastdepCC_FALSE='#' +else + am__fastdepCC_TRUE='#' + am__fastdepCC_FALSE= +fi + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. +set dummy ${ac_tool_prefix}ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_RANLIB+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +RANLIB=$ac_cv_prog_RANLIB +if test -n "$RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 +$as_echo "$RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RANLIB"; then + ac_ct_RANLIB=$RANLIB + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_RANLIB+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_RANLIB"; then + ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB +if test -n "$ac_ct_RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 +$as_echo "$ac_ct_RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_RANLIB" = x; then + RANLIB=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RANLIB=$ac_ct_RANLIB + fi +else + RANLIB="$ac_cv_prog_RANLIB" +fi + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing crypt" >&5 +$as_echo_n "checking for library containing crypt... " >&6; } +if test "${ac_cv_search_crypt+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char crypt (); +int +main () +{ +return crypt (); + ; + return 0; +} +_ACEOF +for ac_lib in '' crypt; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_crypt=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if test "${ac_cv_search_crypt+set}" = set; then : + break +fi +done +if test "${ac_cv_search_crypt+set}" = set; then : + +else + ac_cv_search_crypt=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_crypt" >&5 +$as_echo "$ac_cv_search_crypt" >&6; } +ac_res=$ac_cv_search_crypt +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + LIBCRYPT="-lcrypt"; +$as_echo "#define VTY_CRYPT_PW /**/" >>confdefs.h + +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing gtp_new" >&5 +$as_echo_n "checking for library containing gtp_new... " >&6; } +if test "${ac_cv_search_gtp_new+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char gtp_new (); +int +main () +{ +return gtp_new (); + ; + return 0; +} +_ACEOF +for ac_lib in '' gtp; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_gtp_new=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if test "${ac_cv_search_gtp_new+set}" = set; then : + break +fi +done +if test "${ac_cv_search_gtp_new+set}" = set; then : + +else + ac_cv_search_gtp_new=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_gtp_new" >&5 +$as_echo "$ac_cv_search_gtp_new" >&6; } +ac_res=$ac_cv_search_gtp_new +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + LIBCRYPT="-lgtp"; GPRS_LIBGTP=1 + +fi + + + if test "x$GPRS_LIBGTP" != "x"; then + HAVE_LIBGTP_TRUE= + HAVE_LIBGTP_FALSE='#' +else + HAVE_LIBGTP_TRUE='#' + HAVE_LIBGTP_FALSE= +fi + + + + + + + + + +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. +set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_path_PKG_CONFIG+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + case $PKG_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +PKG_CONFIG=$ac_cv_path_PKG_CONFIG +if test -n "$PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 +$as_echo "$PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_path_PKG_CONFIG"; then + ac_pt_PKG_CONFIG=$PKG_CONFIG + # Extract the first word of "pkg-config", so it can be a program name with args. +set dummy pkg-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_path_ac_pt_PKG_CONFIG+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + case $ac_pt_PKG_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG +if test -n "$ac_pt_PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5 +$as_echo "$ac_pt_PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_pt_PKG_CONFIG" = x; then + PKG_CONFIG="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + PKG_CONFIG=$ac_pt_PKG_CONFIG + fi +else + PKG_CONFIG="$ac_cv_path_PKG_CONFIG" +fi + +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=0.9.0 + { $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5 +$as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; } + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + PKG_CONFIG="" + fi +fi +# Check whether --enable-nat was given. +if test "${enable_nat+set}" = set; then : + enableval=$enable_nat; + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBOSMOSCCP" >&5 +$as_echo_n "checking for LIBOSMOSCCP... " >&6; } + +if test -n "$LIBOSMOSCCP_CFLAGS"; then + pkg_cv_LIBOSMOSCCP_CFLAGS="$LIBOSMOSCCP_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmo-sccp >= 0.0.2\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libosmo-sccp >= 0.0.2") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_LIBOSMOSCCP_CFLAGS=`$PKG_CONFIG --cflags "libosmo-sccp >= 0.0.2" 2>/dev/null` +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$LIBOSMOSCCP_LIBS"; then + pkg_cv_LIBOSMOSCCP_LIBS="$LIBOSMOSCCP_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmo-sccp >= 0.0.2\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libosmo-sccp >= 0.0.2") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_LIBOSMOSCCP_LIBS=`$PKG_CONFIG --libs "libosmo-sccp >= 0.0.2" 2>/dev/null` +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + LIBOSMOSCCP_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "libosmo-sccp >= 0.0.2" 2>&1` + else + LIBOSMOSCCP_PKG_ERRORS=`$PKG_CONFIG --print-errors "libosmo-sccp >= 0.0.2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$LIBOSMOSCCP_PKG_ERRORS" >&5 + + as_fn_error $? "Package requirements (libosmo-sccp >= 0.0.2) were not met: + +$LIBOSMOSCCP_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +Alternatively, you may set the environment variables LIBOSMOSCCP_CFLAGS +and LIBOSMOSCCP_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details." "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +Alternatively, you may set the environment variables LIBOSMOSCCP_CFLAGS +and LIBOSMOSCCP_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details. + +To get pkg-config, see . +See \`config.log' for more details" "$LINENO" 5 ; } +else + LIBOSMOSCCP_CFLAGS=$pkg_cv_LIBOSMOSCCP_CFLAGS + LIBOSMOSCCP_LIBS=$pkg_cv_LIBOSMOSCCP_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +fi + osmo_ac_build_nat="yes" + +else + + osmo_ac_build_nat="no" + +fi + + if test "x$osmo_ac_build_nat" = "xyes"; then + BUILD_NAT_TRUE= + BUILD_NAT_FALSE='#' +else + BUILD_NAT_TRUE='#' + BUILD_NAT_FALSE= +fi + + +# Check whether --enable-osmo-bsc was given. +if test "${enable_osmo_bsc+set}" = set; then : + enableval=$enable_osmo_bsc; + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBOSMOSCCP" >&5 +$as_echo_n "checking for LIBOSMOSCCP... " >&6; } + +if test -n "$LIBOSMOSCCP_CFLAGS"; then + pkg_cv_LIBOSMOSCCP_CFLAGS="$LIBOSMOSCCP_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmo-sccp >= 0.0.2\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libosmo-sccp >= 0.0.2") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_LIBOSMOSCCP_CFLAGS=`$PKG_CONFIG --cflags "libosmo-sccp >= 0.0.2" 2>/dev/null` +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$LIBOSMOSCCP_LIBS"; then + pkg_cv_LIBOSMOSCCP_LIBS="$LIBOSMOSCCP_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmo-sccp >= 0.0.2\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libosmo-sccp >= 0.0.2") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_LIBOSMOSCCP_LIBS=`$PKG_CONFIG --libs "libosmo-sccp >= 0.0.2" 2>/dev/null` +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + LIBOSMOSCCP_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "libosmo-sccp >= 0.0.2" 2>&1` + else + LIBOSMOSCCP_PKG_ERRORS=`$PKG_CONFIG --print-errors "libosmo-sccp >= 0.0.2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$LIBOSMOSCCP_PKG_ERRORS" >&5 + + as_fn_error $? "Package requirements (libosmo-sccp >= 0.0.2) were not met: + +$LIBOSMOSCCP_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +Alternatively, you may set the environment variables LIBOSMOSCCP_CFLAGS +and LIBOSMOSCCP_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details." "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +Alternatively, you may set the environment variables LIBOSMOSCCP_CFLAGS +and LIBOSMOSCCP_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details. + +To get pkg-config, see . +See \`config.log' for more details" "$LINENO" 5 ; } +else + LIBOSMOSCCP_CFLAGS=$pkg_cv_LIBOSMOSCCP_CFLAGS + LIBOSMOSCCP_LIBS=$pkg_cv_LIBOSMOSCCP_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +fi + osmo_ac_build_bsc="yes" + +else + + osmo_ac_build_bsc="no" + +fi + + if test "x$osmo_ac_build_bsc" = "xyes"; then + BUILD_BSC_TRUE= + BUILD_BSC_FALSE='#' +else + BUILD_BSC_TRUE='#' + BUILD_BSC_FALSE= +fi + + + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBOSMOCORE" >&5 +$as_echo_n "checking for LIBOSMOCORE... " >&6; } + +if test -n "$LIBOSMOCORE_CFLAGS"; then + pkg_cv_LIBOSMOCORE_CFLAGS="$LIBOSMOCORE_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmocore >= 0.1.30\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libosmocore >= 0.1.30") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_LIBOSMOCORE_CFLAGS=`$PKG_CONFIG --cflags "libosmocore >= 0.1.30" 2>/dev/null` +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$LIBOSMOCORE_LIBS"; then + pkg_cv_LIBOSMOCORE_LIBS="$LIBOSMOCORE_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmocore >= 0.1.30\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libosmocore >= 0.1.30") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_LIBOSMOCORE_LIBS=`$PKG_CONFIG --libs "libosmocore >= 0.1.30" 2>/dev/null` +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + LIBOSMOCORE_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "libosmocore >= 0.1.30" 2>&1` + else + LIBOSMOCORE_PKG_ERRORS=`$PKG_CONFIG --print-errors "libosmocore >= 0.1.30" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$LIBOSMOCORE_PKG_ERRORS" >&5 + + as_fn_error $? "Package requirements (libosmocore >= 0.1.30) were not met: + +$LIBOSMOCORE_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +Alternatively, you may set the environment variables LIBOSMOCORE_CFLAGS +and LIBOSMOCORE_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details." "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +Alternatively, you may set the environment variables LIBOSMOCORE_CFLAGS +and LIBOSMOCORE_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details. + +To get pkg-config, see . +See \`config.log' for more details" "$LINENO" 5 ; } +else + LIBOSMOCORE_CFLAGS=$pkg_cv_LIBOSMOCORE_CFLAGS + LIBOSMOCORE_LIBS=$pkg_cv_LIBOSMOCORE_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +fi + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBOSMOVTY" >&5 +$as_echo_n "checking for LIBOSMOVTY... " >&6; } + +if test -n "$LIBOSMOVTY_CFLAGS"; then + pkg_cv_LIBOSMOVTY_CFLAGS="$LIBOSMOVTY_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmovty >= 0.1.28\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libosmovty >= 0.1.28") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_LIBOSMOVTY_CFLAGS=`$PKG_CONFIG --cflags "libosmovty >= 0.1.28" 2>/dev/null` +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$LIBOSMOVTY_LIBS"; then + pkg_cv_LIBOSMOVTY_LIBS="$LIBOSMOVTY_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmovty >= 0.1.28\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libosmovty >= 0.1.28") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_LIBOSMOVTY_LIBS=`$PKG_CONFIG --libs "libosmovty >= 0.1.28" 2>/dev/null` +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + LIBOSMOVTY_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "libosmovty >= 0.1.28" 2>&1` + else + LIBOSMOVTY_PKG_ERRORS=`$PKG_CONFIG --print-errors "libosmovty >= 0.1.28" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$LIBOSMOVTY_PKG_ERRORS" >&5 + + as_fn_error $? "Package requirements (libosmovty >= 0.1.28) were not met: + +$LIBOSMOVTY_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +Alternatively, you may set the environment variables LIBOSMOVTY_CFLAGS +and LIBOSMOVTY_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details." "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +Alternatively, you may set the environment variables LIBOSMOVTY_CFLAGS +and LIBOSMOVTY_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details. + +To get pkg-config, see . +See \`config.log' for more details" "$LINENO" 5 ; } +else + LIBOSMOVTY_CFLAGS=$pkg_cv_LIBOSMOVTY_CFLAGS + LIBOSMOVTY_LIBS=$pkg_cv_LIBOSMOVTY_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if test "${ac_cv_prog_CPP+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CPP=$CPP + +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5 ; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if test "${ac_cv_path_GREP+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + { test -f "$ac_path_GREP" && $as_test_x "$ac_path_GREP"; } || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_GREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_GREP=$GREP +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if test "${ac_cv_path_EGREP+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + { test -f "$ac_path_EGREP" && $as_test_x "$ac_path_EGREP"; } || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP=$EGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if test "${ac_cv_header_stdc+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_header_stdc=yes +else + ac_cv_header_stdc=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + +else + ac_cv_header_stdc=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then + +$as_echo "#define STDC_HEADERS 1" >>confdefs.h + +fi + +# On IRIX 5.3, sys/types and inttypes.h are conflicting. +for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ + inttypes.h stdint.h unistd.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default +" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + +for ac_header in dahdi/user.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "dahdi/user.h" "ac_cv_header_dahdi_user_h" "$ac_includes_default" +if test "x$ac_cv_header_dahdi_user_h" = x""yes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_DAHDI_USER_H 1 +_ACEOF + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: DAHDI input driver will not be built" >&5 +$as_echo "$as_me: WARNING: DAHDI input driver will not be built" >&2;} +fi + +done + + + + +# The following test is taken from WebKit's webkit.m4 +saved_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -fvisibility=hidden " +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if ${CC} supports -fvisibility=hidden" >&5 +$as_echo_n "checking if ${CC} supports -fvisibility=hidden... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +char foo; +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + SYMBOL_VISIBILITY="-fvisibility=hidden" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +CFLAGS="$saved_CFLAGS" + + +# Coverage build taken from WebKit's configure.in +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to enable code coverage support" >&5 +$as_echo_n "checking whether to enable code coverage support... " >&6; } +# Check whether --enable-coverage was given. +if test "${enable_coverage+set}" = set; then : + enableval=$enable_coverage; +else + enable_coverage="no" +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_coverage" >&5 +$as_echo "$enable_coverage" >&6; } +if test "$enable_coverage" = "yes"; then + COVERAGE_CFLAGS="-ftest-coverage -fprofile-arcs" + COVERAGE_LDFLAGS="-ftest-coverage -fprofile-arcs" + + +fi + + +ac_config_headers="$ac_config_headers bscconfig.h" + + +ac_config_files="$ac_config_files openbsc.pc include/openbsc/Makefile include/Makefile src/Makefile src/libtrau/Makefile src/libabis/Makefile src/libbsc/Makefile src/libmsc/Makefile src/libmgcp/Makefile src/libcommon/Makefile src/osmo-nitb/Makefile src/osmo-bsc/Makefile src/osmo-bsc_nat/Makefile src/osmo-bsc_mgcp/Makefile src/ipaccess/Makefile src/utils/Makefile src/libgb/Makefile src/gprs/Makefile tests/Makefile tests/debug/Makefile tests/gsm0408/Makefile tests/db/Makefile tests/channel/Makefile tests/bsc-nat/Makefile tests/mgcp/Makefile Makefile" + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # `set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + test "x$cache_file" != "x/dev/null" && + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} + cat confcache >$cache_file + else + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +DEFS=-DHAVE_CONFIG_H + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + + if test -n "$EXEEXT"; then + am__EXEEXT_TRUE= + am__EXEEXT_FALSE='#' +else + am__EXEEXT_TRUE='#' + am__EXEEXT_FALSE= +fi + +if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then + as_fn_error $? "conditional \"AMDEP\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then + as_fn_error $? "conditional \"am__fastdepCC\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${HAVE_LIBGTP_TRUE}" && test -z "${HAVE_LIBGTP_FALSE}"; then + as_fn_error $? "conditional \"HAVE_LIBGTP\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${BUILD_NAT_TRUE}" && test -z "${BUILD_NAT_FALSE}"; then + as_fn_error $? "conditional \"BUILD_NAT\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${BUILD_BSC_TRUE}" && test -z "${BUILD_BSC_FALSE}"; then + as_fn_error $? "conditional \"BUILD_BSC\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi + +: ${CONFIG_STATUS=./config.status} +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -p'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -p' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -p' + fi +else + as_ln_s='cp -p' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +if test -x / >/dev/null 2>&1; then + as_test_x='test -x' +else + if ls -dL / >/dev/null 2>&1; then + as_ls_L_option=L + else + as_ls_L_option= + fi + as_test_x=' + eval sh -c '\'' + if test -d "$1"; then + test -d "$1/."; + else + case $1 in #( + -*)set "./$1";; + esac; + case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( + ???[sx]*):;;*)false;;esac;fi + '\'' sh + ' +fi +as_executable_p=$as_test_x + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by openbsc $as_me 0.9.13, which was +generated by GNU Autoconf 2.67. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + +case $ac_config_headers in *" +"*) set x $ac_config_headers; shift; ac_config_headers=$*;; +esac + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" +config_headers="$ac_config_headers" +config_commands="$ac_config_commands" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + --header=FILE[:TEMPLATE] + instantiate the configuration header FILE + +Configuration files: +$config_files + +Configuration headers: +$config_headers + +Configuration commands: +$config_commands + +Report bugs to ." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_version="\\ +openbsc config.status 0.9.13 +configured by $0, generated by GNU Autoconf 2.67, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2010 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +INSTALL='$INSTALL' +MKDIR_P='$MKDIR_P' +AWK='$AWK' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --header | --heade | --head | --hea ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + as_fn_append CONFIG_HEADERS " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h) + # Conflict between --help and --header + as_fn_error $? "ambiguous option: \`$1' +Try \`$0 --help' for more information.";; + --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X '$SHELL' '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# +# INIT-COMMANDS +# +AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;; + "bscconfig.h") CONFIG_HEADERS="$CONFIG_HEADERS bscconfig.h" ;; + "openbsc.pc") CONFIG_FILES="$CONFIG_FILES openbsc.pc" ;; + "include/openbsc/Makefile") CONFIG_FILES="$CONFIG_FILES include/openbsc/Makefile" ;; + "include/Makefile") CONFIG_FILES="$CONFIG_FILES include/Makefile" ;; + "src/Makefile") CONFIG_FILES="$CONFIG_FILES src/Makefile" ;; + "src/libtrau/Makefile") CONFIG_FILES="$CONFIG_FILES src/libtrau/Makefile" ;; + "src/libabis/Makefile") CONFIG_FILES="$CONFIG_FILES src/libabis/Makefile" ;; + "src/libbsc/Makefile") CONFIG_FILES="$CONFIG_FILES src/libbsc/Makefile" ;; + "src/libmsc/Makefile") CONFIG_FILES="$CONFIG_FILES src/libmsc/Makefile" ;; + "src/libmgcp/Makefile") CONFIG_FILES="$CONFIG_FILES src/libmgcp/Makefile" ;; + "src/libcommon/Makefile") CONFIG_FILES="$CONFIG_FILES src/libcommon/Makefile" ;; + "src/osmo-nitb/Makefile") CONFIG_FILES="$CONFIG_FILES src/osmo-nitb/Makefile" ;; + "src/osmo-bsc/Makefile") CONFIG_FILES="$CONFIG_FILES src/osmo-bsc/Makefile" ;; + "src/osmo-bsc_nat/Makefile") CONFIG_FILES="$CONFIG_FILES src/osmo-bsc_nat/Makefile" ;; + "src/osmo-bsc_mgcp/Makefile") CONFIG_FILES="$CONFIG_FILES src/osmo-bsc_mgcp/Makefile" ;; + "src/ipaccess/Makefile") CONFIG_FILES="$CONFIG_FILES src/ipaccess/Makefile" ;; + "src/utils/Makefile") CONFIG_FILES="$CONFIG_FILES src/utils/Makefile" ;; + "src/libgb/Makefile") CONFIG_FILES="$CONFIG_FILES src/libgb/Makefile" ;; + "src/gprs/Makefile") CONFIG_FILES="$CONFIG_FILES src/gprs/Makefile" ;; + "tests/Makefile") CONFIG_FILES="$CONFIG_FILES tests/Makefile" ;; + "tests/debug/Makefile") CONFIG_FILES="$CONFIG_FILES tests/debug/Makefile" ;; + "tests/gsm0408/Makefile") CONFIG_FILES="$CONFIG_FILES tests/gsm0408/Makefile" ;; + "tests/db/Makefile") CONFIG_FILES="$CONFIG_FILES tests/db/Makefile" ;; + "tests/channel/Makefile") CONFIG_FILES="$CONFIG_FILES tests/channel/Makefile" ;; + "tests/bsc-nat/Makefile") CONFIG_FILES="$CONFIG_FILES tests/bsc-nat/Makefile" ;; + "tests/mgcp/Makefile") CONFIG_FILES="$CONFIG_FILES tests/mgcp/Makefile" ;; + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5 ;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files + test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers + test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= + trap 'exit_status=$? + { test -z "$tmp" || test ! -d "$tmp" || rm -fr "$tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -n "$tmp" && test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$tmp/subs1.awk" > "$tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + +# Set up the scripts for CONFIG_HEADERS section. +# No need to generate them if there are no CONFIG_HEADERS. +# This happens for instance with `./config.status Makefile'. +if test -n "$CONFIG_HEADERS"; then +cat >"$tmp/defines.awk" <<\_ACAWK || +BEGIN { +_ACEOF + +# Transform confdefs.h into an awk script `defines.awk', embedded as +# here-document in config.status, that substitutes the proper values into +# config.h.in to produce config.h. + +# Create a delimiter string that does not exist in confdefs.h, to ease +# handling of long lines. +ac_delim='%!_!# ' +for ac_last_try in false false :; do + ac_t=`sed -n "/$ac_delim/p" confdefs.h` + if test -z "$ac_t"; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done + +# For the awk script, D is an array of macro values keyed by name, +# likewise P contains macro parameters if any. Preserve backslash +# newline sequences. + +ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* +sed -n ' +s/.\{148\}/&'"$ac_delim"'/g +t rset +:rset +s/^[ ]*#[ ]*define[ ][ ]*/ / +t def +d +:def +s/\\$// +t bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3"/p +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p +d +:bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3\\\\\\n"\\/p +t cont +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p +t cont +d +:cont +n +s/.\{148\}/&'"$ac_delim"'/g +t clear +:clear +s/\\$// +t bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/"/p +d +:bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p +b cont +' >$CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + for (key in D) D_is_set[key] = 1 + FS = "" +} +/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { + line = \$ 0 + split(line, arg, " ") + if (arg[1] == "#") { + defundef = arg[2] + mac1 = arg[3] + } else { + defundef = substr(arg[1], 2) + mac1 = arg[2] + } + split(mac1, mac2, "(") #) + macro = mac2[1] + prefix = substr(line, 1, index(line, defundef) - 1) + if (D_is_set[macro]) { + # Preserve the white space surrounding the "#". + print prefix "define", macro P[macro] D[macro] + next + } else { + # Replace #undef with comments. This is necessary, for example, + # in the case of _POSIX_SOURCE, which is predefined and required + # on some systems where configure will not decide to define it. + if (defundef == "undef") { + print "/*", prefix defundef, macro, "*/" + next + } + } +} +{ print } +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 +fi # test -n "$CONFIG_HEADERS" + + +eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS" +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5 ;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5 ;; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + + case $INSTALL in + [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; + *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; + esac + ac_MKDIR_P=$MKDIR_P + case $MKDIR_P in + [\\/$]* | ?:[\\/]* ) ;; + */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;; + esac +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +s&@INSTALL@&$ac_INSTALL&;t t +s&@MKDIR_P@&$ac_MKDIR_P&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$tmp/subs.awk" >$tmp/out \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' "$tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$tmp/stdin" + case $ac_file in + -) cat "$tmp/out" && rm -f "$tmp/out";; + *) rm -f "$ac_file" && mv "$tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + :H) + # + # CONFIG_HEADER + # + if test x"$ac_file" != x-; then + { + $as_echo "/* $configure_input */" \ + && eval '$AWK -f "$tmp/defines.awk"' "$ac_file_inputs" + } >"$tmp/config.h" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + if diff "$ac_file" "$tmp/config.h" >/dev/null 2>&1; then + { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 +$as_echo "$as_me: $ac_file is unchanged" >&6;} + else + rm -f "$ac_file" + mv "$tmp/config.h" "$ac_file" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + fi + else + $as_echo "/* $configure_input */" \ + && eval '$AWK -f "$tmp/defines.awk"' "$ac_file_inputs" \ + || as_fn_error $? "could not create -" "$LINENO" 5 + fi +# Compute "$ac_file"'s index in $config_headers. +_am_arg="$ac_file" +_am_stamp_count=1 +for _am_header in $config_headers :; do + case $_am_header in + $_am_arg | $_am_arg:* ) + break ;; + * ) + _am_stamp_count=`expr $_am_stamp_count + 1` ;; + esac +done +echo "timestamp for $_am_arg" >`$as_dirname -- "$_am_arg" || +$as_expr X"$_am_arg" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$_am_arg" : 'X\(//\)[^/]' \| \ + X"$_am_arg" : 'X\(//\)$' \| \ + X"$_am_arg" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$_am_arg" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'`/stamp-h$_am_stamp_count + ;; + + :C) { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 +$as_echo "$as_me: executing $ac_file commands" >&6;} + ;; + esac + + + case $ac_file$ac_mode in + "depfiles":C) test x"$AMDEP_TRUE" != x"" || { + # Autoconf 2.62 quotes --file arguments for eval, but not when files + # are listed without --file. Let's play safe and only enable the eval + # if we detect the quoting. + case $CONFIG_FILES in + *\'*) eval set x "$CONFIG_FILES" ;; + *) set x $CONFIG_FILES ;; + esac + shift + for mf + do + # Strip MF so we end up with the name of the file. + mf=`echo "$mf" | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile or not. + # We used to match only the files named `Makefile.in', but + # some people rename them; so instead we look at the file content. + # Grep'ing the first line is not enough: some people post-process + # each Makefile.in and add a new line on top of each file to say so. + # Grep'ing the whole file is not good either: AIX grep has a line + # limit of 2048, but all sed's we know have understand at least 4000. + if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then + dirpart=`$as_dirname -- "$mf" || +$as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$mf" : 'X\(//\)[^/]' \| \ + X"$mf" : 'X\(//\)$' \| \ + X"$mf" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$mf" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + else + continue + fi + # Extract the definition of DEPDIR, am__include, and am__quote + # from the Makefile without running `make'. + DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` + test -z "$DEPDIR" && continue + am__include=`sed -n 's/^am__include = //p' < "$mf"` + test -z "am__include" && continue + am__quote=`sed -n 's/^am__quote = //p' < "$mf"` + # When using ansi2knr, U may be empty or an underscore; expand it + U=`sed -n 's/^U = //p' < "$mf"` + # Find all dependency output files, they are included files with + # $(DEPDIR) in their names. We invoke sed twice because it is the + # simplest approach to changing $(DEPDIR) to its actual value in the + # expansion. + for file in `sed -n " + s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ + sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do + # Make sure the directory exists. + test -f "$dirpart/$file" && continue + fdir=`$as_dirname -- "$file" || +$as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$file" : 'X\(//\)[^/]' \| \ + X"$file" : 'X\(//\)$' \| \ + X"$file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir=$dirpart/$fdir; as_fn_mkdir_p + # echo "creating $dirpart/$file" + echo '# dummy' > "$dirpart/$file" + done + done +} + ;; + + esac +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + diff --git a/configure.in b/configure.in new file mode 100644 index 000000000..1d2db88c0 --- /dev/null +++ b/configure.in @@ -0,0 +1,111 @@ +dnl Process this file with autoconf to produce a configure script +AC_INIT([openbsc], + m4_esyscmd([./git-version-gen .tarball-version]), + [openbsc-devel@lists.openbsc.org]) + +AM_INIT_AUTOMAKE([dist-bzip2]) + +dnl kernel style compile messages +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +dnl checks for programs +AC_PROG_MAKE_SET +AC_PROG_CC +AC_PROG_INSTALL +AC_PROG_RANLIB + +dnl checks for libraries +AC_SEARCH_LIBS(crypt, crypt, + [LIBCRYPT="-lcrypt"; AC_DEFINE([VTY_CRYPT_PW], [], [Use crypt functionality of vty.])]) +AC_SEARCH_LIBS(gtp_new, gtp, + [LIBCRYPT="-lgtp"; AC_SUBST([GPRS_LIBGTP], [1])]) + +AM_CONDITIONAL(HAVE_LIBGTP, test "x$GPRS_LIBGTP" != "x") + + +AC_ARG_ENABLE([nat], [AS_HELP_STRING([--enable-nat], [Build the BSC NAT. Requires SCCP])], + [ + PKG_CHECK_MODULES(LIBOSMOSCCP, libosmo-sccp >= 0.0.2) + osmo_ac_build_nat="yes" + ], + [ + osmo_ac_build_nat="no" + ]) +AM_CONDITIONAL(BUILD_NAT, test "x$osmo_ac_build_nat" = "xyes") + +AC_ARG_ENABLE([osmo-bsc], [AS_HELP_STRING([--enable-osmo-bsc], [Build the Osmo BSC])], + [ + PKG_CHECK_MODULES(LIBOSMOSCCP, libosmo-sccp >= 0.0.2) + osmo_ac_build_bsc="yes" + ], + [ + osmo_ac_build_bsc="no" + ]) +AM_CONDITIONAL(BUILD_BSC, test "x$osmo_ac_build_bsc" = "xyes") + +PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.1.30) +PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.1.28) + +dnl checks for header files +AC_HEADER_STDC +AC_CHECK_HEADERS(dahdi/user.h,,AC_MSG_WARN(DAHDI input driver will not be built)) + + +dnl Checks for typedefs, structures and compiler characteristics + +# The following test is taken from WebKit's webkit.m4 +saved_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -fvisibility=hidden " +AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden]) +AC_COMPILE_IFELSE([char foo;], + [ AC_MSG_RESULT([yes]) + SYMBOL_VISIBILITY="-fvisibility=hidden"], + AC_MSG_RESULT([no])) +CFLAGS="$saved_CFLAGS" +AC_SUBST(SYMBOL_VISIBILITY) + +# Coverage build taken from WebKit's configure.in +AC_MSG_CHECKING([whether to enable code coverage support]) +AC_ARG_ENABLE(coverage, + AC_HELP_STRING([--enable-coverage], + [enable code coverage support [default=no]]), + [],[enable_coverage="no"]) +AC_MSG_RESULT([$enable_coverage]) +if test "$enable_coverage" = "yes"; then + COVERAGE_CFLAGS="-ftest-coverage -fprofile-arcs" + COVERAGE_LDFLAGS="-ftest-coverage -fprofile-arcs" + AC_SUBST([COVERAGE_CFLAGS]) + AC_SUBST([COVERAGE_LDFLAGS]) +fi + + +dnl Generate the output +AM_CONFIG_HEADER(bscconfig.h) + +AC_OUTPUT( + openbsc.pc + include/openbsc/Makefile + include/Makefile + src/Makefile + src/libtrau/Makefile + src/libabis/Makefile + src/libbsc/Makefile + src/libmsc/Makefile + src/libmgcp/Makefile + src/libcommon/Makefile + src/osmo-nitb/Makefile + src/osmo-bsc/Makefile + src/osmo-bsc_nat/Makefile + src/osmo-bsc_mgcp/Makefile + src/ipaccess/Makefile + src/utils/Makefile + src/libgb/Makefile + src/gprs/Makefile + tests/Makefile + tests/debug/Makefile + tests/gsm0408/Makefile + tests/db/Makefile + tests/channel/Makefile + tests/bsc-nat/Makefile + tests/mgcp/Makefile + Makefile) diff --git a/depcomp b/depcomp new file mode 100755 index 000000000..df8eea7e4 --- /dev/null +++ b/depcomp @@ -0,0 +1,630 @@ +#! /bin/sh +# depcomp - compile a program generating dependencies as side-effects + +scriptversion=2009-04-28.21; # UTC + +# Copyright (C) 1999, 2000, 2003, 2004, 2005, 2006, 2007, 2009 Free +# Software Foundation, Inc. + +# 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, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Originally written by Alexandre Oliva . + +case $1 in + '') + echo "$0: No command. Try \`$0 --help' for more information." 1>&2 + exit 1; + ;; + -h | --h*) + cat <<\EOF +Usage: depcomp [--help] [--version] PROGRAM [ARGS] + +Run PROGRAMS ARGS to compile a file, generating dependencies +as side-effects. + +Environment variables: + depmode Dependency tracking mode. + source Source file read by `PROGRAMS ARGS'. + object Object file output by `PROGRAMS ARGS'. + DEPDIR directory where to store dependencies. + depfile Dependency file to output. + tmpdepfile Temporary file to use when outputing dependencies. + libtool Whether libtool is used (yes/no). + +Report bugs to . +EOF + exit $? + ;; + -v | --v*) + echo "depcomp $scriptversion" + exit $? + ;; +esac + +if test -z "$depmode" || test -z "$source" || test -z "$object"; then + echo "depcomp: Variables source, object and depmode must be set" 1>&2 + exit 1 +fi + +# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po. +depfile=${depfile-`echo "$object" | + sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`} +tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} + +rm -f "$tmpdepfile" + +# Some modes work just like other modes, but use different flags. We +# parameterize here, but still list the modes in the big case below, +# to make depend.m4 easier to write. Note that we *cannot* use a case +# here, because this file can only contain one case statement. +if test "$depmode" = hp; then + # HP compiler uses -M and no extra arg. + gccflag=-M + depmode=gcc +fi + +if test "$depmode" = dashXmstdout; then + # This is just like dashmstdout with a different argument. + dashmflag=-xM + depmode=dashmstdout +fi + +cygpath_u="cygpath -u -f -" +if test "$depmode" = msvcmsys; then + # This is just like msvisualcpp but w/o cygpath translation. + # Just convert the backslash-escaped backslashes to single forward + # slashes to satisfy depend.m4 + cygpath_u="sed s,\\\\\\\\,/,g" + depmode=msvisualcpp +fi + +case "$depmode" in +gcc3) +## gcc 3 implements dependency tracking that does exactly what +## we want. Yay! Note: for some reason libtool 1.4 doesn't like +## it if -MD -MP comes after the -MF stuff. Hmm. +## Unfortunately, FreeBSD c89 acceptance of flags depends upon +## the command line argument order; so add the flags where they +## appear in depend2.am. Note that the slowdown incurred here +## affects only configure: in makefiles, %FASTDEP% shortcuts this. + for arg + do + case $arg in + -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;; + *) set fnord "$@" "$arg" ;; + esac + shift # fnord + shift # $arg + done + "$@" + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile" + exit $stat + fi + mv "$tmpdepfile" "$depfile" + ;; + +gcc) +## There are various ways to get dependency output from gcc. Here's +## why we pick this rather obscure method: +## - Don't want to use -MD because we'd like the dependencies to end +## up in a subdir. Having to rename by hand is ugly. +## (We might end up doing this anyway to support other compilers.) +## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like +## -MM, not -M (despite what the docs say). +## - Using -M directly means running the compiler twice (even worse +## than renaming). + if test -z "$gccflag"; then + gccflag=-MD, + fi + "$@" -Wp,"$gccflag$tmpdepfile" + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + alpha=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz +## The second -e expression handles DOS-style file names with drive letters. + sed -e 's/^[^:]*: / /' \ + -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" +## This next piece of magic avoids the `deleted header file' problem. +## The problem is that when a header file which appears in a .P file +## is deleted, the dependency causes make to die (because there is +## typically no way to rebuild the header). We avoid this by adding +## dummy dependencies for each header file. Too bad gcc doesn't do +## this for us directly. + tr ' ' ' +' < "$tmpdepfile" | +## Some versions of gcc put a space before the `:'. On the theory +## that the space means something, we add a space to the output as +## well. +## Some versions of the HPUX 10.20 sed can't process this invocation +## correctly. Breaking it into two sed invocations is a workaround. + sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +sgi) + if test "$libtool" = yes; then + "$@" "-Wp,-MDupdate,$tmpdepfile" + else + "$@" -MDupdate "$tmpdepfile" + fi + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + + if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files + echo "$object : \\" > "$depfile" + + # Clip off the initial element (the dependent). Don't try to be + # clever and replace this with sed code, as IRIX sed won't handle + # lines with more than a fixed number of characters (4096 in + # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; + # the IRIX cc adds comments like `#:fec' to the end of the + # dependency line. + tr ' ' ' +' < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' | \ + tr ' +' ' ' >> "$depfile" + echo >> "$depfile" + + # The second pass generates a dummy entry for each header file. + tr ' ' ' +' < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ + >> "$depfile" + else + # The sourcefile does not contain any dependencies, so just + # store a dummy comment line, to avoid errors with the Makefile + # "include basename.Plo" scheme. + echo "#dummy" > "$depfile" + fi + rm -f "$tmpdepfile" + ;; + +aix) + # The C for AIX Compiler uses -M and outputs the dependencies + # in a .u file. In older versions, this file always lives in the + # current directory. Also, the AIX compiler puts `$object:' at the + # start of each line; $object doesn't have directory information. + # Version 6 uses the directory in both cases. + dir=`echo "$object" | sed -e 's|/[^/]*$|/|'` + test "x$dir" = "x$object" && dir= + base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'` + if test "$libtool" = yes; then + tmpdepfile1=$dir$base.u + tmpdepfile2=$base.u + tmpdepfile3=$dir.libs/$base.u + "$@" -Wc,-M + else + tmpdepfile1=$dir$base.u + tmpdepfile2=$dir$base.u + tmpdepfile3=$dir$base.u + "$@" -M + fi + stat=$? + + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + do + test -f "$tmpdepfile" && break + done + if test -f "$tmpdepfile"; then + # Each line is of the form `foo.o: dependent.h'. + # Do two passes, one to just change these to + # `$object: dependent.h' and one to simply `dependent.h:'. + sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile" + # That's a tab and a space in the []. + sed -e 's,^.*\.[a-z]*:[ ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile" + else + # The sourcefile does not contain any dependencies, so just + # store a dummy comment line, to avoid errors with the Makefile + # "include basename.Plo" scheme. + echo "#dummy" > "$depfile" + fi + rm -f "$tmpdepfile" + ;; + +icc) + # Intel's C compiler understands `-MD -MF file'. However on + # icc -MD -MF foo.d -c -o sub/foo.o sub/foo.c + # ICC 7.0 will fill foo.d with something like + # foo.o: sub/foo.c + # foo.o: sub/foo.h + # which is wrong. We want: + # sub/foo.o: sub/foo.c + # sub/foo.o: sub/foo.h + # sub/foo.c: + # sub/foo.h: + # ICC 7.1 will output + # foo.o: sub/foo.c sub/foo.h + # and will wrap long lines using \ : + # foo.o: sub/foo.c ... \ + # sub/foo.h ... \ + # ... + + "$@" -MD -MF "$tmpdepfile" + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + # Each line is of the form `foo.o: dependent.h', + # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. + # Do two passes, one to just change these to + # `$object: dependent.h' and one to simply `dependent.h:'. + sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process this invocation + # correctly. Breaking it into two sed invocations is a workaround. + sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" | + sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp2) + # The "hp" stanza above does not work with aCC (C++) and HP's ia64 + # compilers, which have integrated preprocessors. The correct option + # to use with these is +Maked; it writes dependencies to a file named + # 'foo.d', which lands next to the object file, wherever that + # happens to be. + # Much of this is similar to the tru64 case; see comments there. + dir=`echo "$object" | sed -e 's|/[^/]*$|/|'` + test "x$dir" = "x$object" && dir= + base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'` + if test "$libtool" = yes; then + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir.libs/$base.d + "$@" -Wc,+Maked + else + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir$base.d + "$@" +Maked + fi + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile1" "$tmpdepfile2" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" + do + test -f "$tmpdepfile" && break + done + if test -f "$tmpdepfile"; then + sed -e "s,^.*\.[a-z]*:,$object:," "$tmpdepfile" > "$depfile" + # Add `dependent.h:' lines. + sed -ne '2,${ + s/^ *// + s/ \\*$// + s/$/:/ + p + }' "$tmpdepfile" >> "$depfile" + else + echo "#dummy" > "$depfile" + fi + rm -f "$tmpdepfile" "$tmpdepfile2" + ;; + +tru64) + # The Tru64 compiler uses -MD to generate dependencies as a side + # effect. `cc -MD -o foo.o ...' puts the dependencies into `foo.o.d'. + # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put + # dependencies in `foo.d' instead, so we check for that too. + # Subdirectories are respected. + dir=`echo "$object" | sed -e 's|/[^/]*$|/|'` + test "x$dir" = "x$object" && dir= + base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'` + + if test "$libtool" = yes; then + # With Tru64 cc, shared objects can also be used to make a + # static library. This mechanism is used in libtool 1.4 series to + # handle both shared and static libraries in a single compilation. + # With libtool 1.4, dependencies were output in $dir.libs/$base.lo.d. + # + # With libtool 1.5 this exception was removed, and libtool now + # generates 2 separate objects for the 2 libraries. These two + # compilations output dependencies in $dir.libs/$base.o.d and + # in $dir$base.o.d. We have to check for both files, because + # one of the two compilations can be disabled. We should prefer + # $dir$base.o.d over $dir.libs/$base.o.d because the latter is + # automatically cleaned when .libs/ is deleted, while ignoring + # the former would cause a distcleancheck panic. + tmpdepfile1=$dir.libs/$base.lo.d # libtool 1.4 + tmpdepfile2=$dir$base.o.d # libtool 1.5 + tmpdepfile3=$dir.libs/$base.o.d # libtool 1.5 + tmpdepfile4=$dir.libs/$base.d # Compaq CCC V6.2-504 + "$@" -Wc,-MD + else + tmpdepfile1=$dir$base.o.d + tmpdepfile2=$dir$base.d + tmpdepfile3=$dir$base.d + tmpdepfile4=$dir$base.d + "$@" -MD + fi + + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4" + do + test -f "$tmpdepfile" && break + done + if test -f "$tmpdepfile"; then + sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile" + # That's a tab and a space in the []. + sed -e 's,^.*\.[a-z]*:[ ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile" + else + echo "#dummy" > "$depfile" + fi + rm -f "$tmpdepfile" + ;; + +#nosideeffect) + # This comment above is used by automake to tell side-effect + # dependency tracking mechanisms from slower ones. + +dashmstdout) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout, regardless of -o. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + # Remove `-o $object'. + IFS=" " + for arg + do + case $arg in + -o) + shift + ;; + $object) + shift + ;; + *) + set fnord "$@" "$arg" + shift # fnord + shift # $arg + ;; + esac + done + + test -z "$dashmflag" && dashmflag=-M + # Require at least two characters before searching for `:' + # in the target name. This is to cope with DOS-style filenames: + # a dependency such as `c:/foo/bar' could be seen as target `c' otherwise. + "$@" $dashmflag | + sed 's:^[ ]*[^: ][^:][^:]*\:[ ]*:'"$object"'\: :' > "$tmpdepfile" + rm -f "$depfile" + cat < "$tmpdepfile" > "$depfile" + tr ' ' ' +' < "$tmpdepfile" | \ +## Some versions of the HPUX 10.20 sed can't process this invocation +## correctly. Breaking it into two sed invocations is a workaround. + sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +dashXmstdout) + # This case only exists to satisfy depend.m4. It is never actually + # run, as this mode is specially recognized in the preamble. + exit 1 + ;; + +makedepend) + "$@" || exit $? + # Remove any Libtool call + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + # X makedepend + shift + cleared=no eat=no + for arg + do + case $cleared in + no) + set ""; shift + cleared=yes ;; + esac + if test $eat = yes; then + eat=no + continue + fi + case "$arg" in + -D*|-I*) + set fnord "$@" "$arg"; shift ;; + # Strip any option that makedepend may not understand. Remove + # the object too, otherwise makedepend will parse it as a source file. + -arch) + eat=yes ;; + -*|$object) + ;; + *) + set fnord "$@" "$arg"; shift ;; + esac + done + obj_suffix=`echo "$object" | sed 's/^.*\././'` + touch "$tmpdepfile" + ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" + rm -f "$depfile" + cat < "$tmpdepfile" > "$depfile" + sed '1,2d' "$tmpdepfile" | tr ' ' ' +' | \ +## Some versions of the HPUX 10.20 sed can't process this invocation +## correctly. Breaking it into two sed invocations is a workaround. + sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" "$tmpdepfile".bak + ;; + +cpp) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + # Remove `-o $object'. + IFS=" " + for arg + do + case $arg in + -o) + shift + ;; + $object) + shift + ;; + *) + set fnord "$@" "$arg" + shift # fnord + shift # $arg + ;; + esac + done + + "$@" -E | + sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ + -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' | + sed '$ s: \\$::' > "$tmpdepfile" + rm -f "$depfile" + echo "$object : \\" > "$depfile" + cat < "$tmpdepfile" >> "$depfile" + sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvisualcpp) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + IFS=" " + for arg + do + case "$arg" in + -o) + shift + ;; + $object) + shift + ;; + "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI") + set fnord "$@" + shift + shift + ;; + *) + set fnord "$@" "$arg" + shift + shift + ;; + esac + done + "$@" -E 2>/dev/null | + sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile" + rm -f "$depfile" + echo "$object : \\" > "$depfile" + sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s:: \1 \\:p' >> "$depfile" + echo " " >> "$depfile" + sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvcmsys) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +none) + exec "$@" + ;; + +*) + echo "Unknown depmode $depmode" 1>&2 + exit 1 + ;; +esac + +exit 0 + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 000000000..4596b6e3c --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = openbsc + +noinst_HEADERS = mISDNif.h compat_af_isdn.h diff --git a/include/Makefile.in b/include/Makefile.in new file mode 100644 index 000000000..ecbeebe28 --- /dev/null +++ b/include/Makefile.in @@ -0,0 +1,541 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = include +DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \ + html-recursive info-recursive install-data-recursive \ + install-dvi-recursive install-exec-recursive \ + install-html-recursive install-info-recursive \ + install-pdf-recursive install-ps-recursive install-recursive \ + installcheck-recursive installdirs-recursive pdf-recursive \ + ps-recursive uninstall-recursive +HEADERS = $(noinst_HEADERS) +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \ + $(RECURSIVE_CLEAN_TARGETS:-recursive=) tags TAGS ctags CTAGS \ + distdir +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = openbsc +noinst_HEADERS = mISDNif.h compat_af_isdn.h +all: all-recursive + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu include/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu include/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +# This directory's subdirectories are mostly independent; you can cd +# into them and run `make' without going through this Makefile. +# To change the values of `make' variables: instead of editing Makefiles, +# (1) if the variable is set in `config.status', edit `config.status' +# (which will cause the Makefiles to be regenerated when you run `make'); +# (2) otherwise, pass the desired values on the `make' command line. +$(RECURSIVE_TARGETS): + @fail= failcom='exit 1'; \ + for f in x $$MAKEFLAGS; do \ + case $$f in \ + *=* | --[!k]*);; \ + *k*) failcom='fail=yes';; \ + esac; \ + done; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +$(RECURSIVE_CLEAN_TARGETS): + @fail= failcom='exit 1'; \ + for f in x $$MAKEFLAGS; do \ + case $$f in \ + *=* | --[!k]*);; \ + *k*) failcom='fail=yes';; \ + esac; \ + done; \ + dot_seen=no; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + rev=''; for subdir in $$list; do \ + if test "$$subdir" = "."; then :; else \ + rev="$$subdir $$rev"; \ + fi; \ + done; \ + rev="$$rev ."; \ + target=`echo $@ | sed s/-recursive//`; \ + for subdir in $$rev; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done && test -z "$$fail" +tags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \ + done +ctags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \ + done + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: tags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: ctags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile $(HEADERS) +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) ctags-recursive \ + install-am install-strip tags-recursive + +.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \ + all all-am check check-am clean clean-generic ctags \ + ctags-recursive distclean distclean-generic distclean-tags \ + distdir dvi dvi-am html html-am info info-am install \ + install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + installdirs-am maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \ + tags-recursive uninstall uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/include/compat_af_isdn.h b/include/compat_af_isdn.h new file mode 100644 index 000000000..56cbfb3f2 --- /dev/null +++ b/include/compat_af_isdn.h @@ -0,0 +1,39 @@ +#ifdef MISDN_OLD_AF_COMPATIBILITY +#undef AF_ISDN +#undef PF_ISDN + +extern int AF_ISDN; +#define PF_ISDN AF_ISDN + +int AF_ISDN; + +#endif + +extern void init_af_isdn(void); + +#ifdef AF_COMPATIBILITY_FUNC +#ifdef MISDN_OLD_AF_COMPATIBILITY +void init_af_isdn(void) +{ + int s; + + /* test for new value */ + AF_ISDN = 34; + s = socket(AF_ISDN, SOCK_RAW, ISDN_P_BASE); + if (s >= 0) { + close(s); + return; + } + AF_ISDN = 27; + s = socket(AF_ISDN, SOCK_RAW, ISDN_P_BASE); + if (s >= 0) { + close(s); + return; + } +} +#else +void init_af_isdn(void) +{ +} +#endif +#endif diff --git a/include/mISDNif.h b/include/mISDNif.h new file mode 100644 index 000000000..8e065d24b --- /dev/null +++ b/include/mISDNif.h @@ -0,0 +1,387 @@ +/* + * + * Author Karsten Keil + * + * Copyright 2008 by Karsten Keil + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE + * version 2.1 as published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU LESSER GENERAL PUBLIC LICENSE for more details. + * + */ + +#ifndef mISDNIF_H +#define mISDNIF_H + +#include +#ifdef linux +#include +#include +#include +#else +#include +#include +#include +#endif + +/* + * ABI Version 32 bit + * + * <8 bit> Major version + * - changed if any interface become backwards incompatible + * + * <8 bit> Minor version + * - changed if any interface is extended but backwards compatible + * + * <16 bit> Release number + * - should be incremented on every checkin + */ +#define MISDN_MAJOR_VERSION 1 +#define MISDN_MINOR_VERSION 1 +#define MISDN_RELEASE 20 + +/* primitives for information exchange + * generell format + * <16 bit 0 > + * <8 bit command> + * BIT 8 = 1 LAYER private + * BIT 7 = 1 answer + * BIT 6 = 1 DATA + * <8 bit target layer mask> + * + * Layer = 00 is reserved for general commands + Layer = 01 L2 -> HW + Layer = 02 HW -> L2 + Layer = 04 L3 -> L2 + Layer = 08 L2 -> L3 + * Layer = FF is reserved for broadcast commands + */ + +#define MISDN_CMDMASK 0xff00 +#define MISDN_LAYERMASK 0x00ff + +/* generell commands */ +#define OPEN_CHANNEL 0x0100 +#define CLOSE_CHANNEL 0x0200 +#define CONTROL_CHANNEL 0x0300 +#define CHECK_DATA 0x0400 + +/* layer 2 -> layer 1 */ +#define PH_ACTIVATE_REQ 0x0101 +#define PH_DEACTIVATE_REQ 0x0201 +#define PH_DATA_REQ 0x2001 +#define MPH_ACTIVATE_REQ 0x0501 +#define MPH_DEACTIVATE_REQ 0x0601 +#define MPH_INFORMATION_REQ 0x0701 +#define PH_CONTROL_REQ 0x0801 + +/* layer 1 -> layer 2 */ +#define PH_ACTIVATE_IND 0x0102 +#define PH_ACTIVATE_CNF 0x4102 +#define PH_DEACTIVATE_IND 0x0202 +#define PH_DEACTIVATE_CNF 0x4202 +#define PH_DATA_IND 0x2002 +#define PH_DATA_E_IND 0x3002 +#define MPH_ACTIVATE_IND 0x0502 +#define MPH_DEACTIVATE_IND 0x0602 +#define MPH_INFORMATION_IND 0x0702 +#define PH_DATA_CNF 0x6002 +#define PH_CONTROL_IND 0x0802 +#define PH_CONTROL_CNF 0x4802 + +/* layer 3 -> layer 2 */ +#define DL_ESTABLISH_REQ 0x1004 +#define DL_RELEASE_REQ 0x1104 +#define DL_DATA_REQ 0x3004 +#define DL_UNITDATA_REQ 0x3104 +#define DL_INFORMATION_REQ 0x0004 + +/* layer 2 -> layer 3 */ +#define DL_ESTABLISH_IND 0x1008 +#define DL_ESTABLISH_CNF 0x5008 +#define DL_RELEASE_IND 0x1108 +#define DL_RELEASE_CNF 0x5108 +#define DL_DATA_IND 0x3008 +#define DL_UNITDATA_IND 0x3108 +#define DL_INFORMATION_IND 0x0008 + +/* intern layer 2 managment */ +#define MDL_ASSIGN_REQ 0x1804 +#define MDL_ASSIGN_IND 0x1904 +#define MDL_REMOVE_REQ 0x1A04 +#define MDL_REMOVE_IND 0x1B04 +#define MDL_STATUS_UP_IND 0x1C04 +#define MDL_STATUS_DOWN_IND 0x1D04 +#define MDL_STATUS_UI_IND 0x1E04 +#define MDL_ERROR_IND 0x1F04 +#define MDL_ERROR_RSP 0x5F04 + +/* DL_INFORMATION_IND types */ +#define DL_INFO_L2_CONNECT 0x0001 +#define DL_INFO_L2_REMOVED 0x0002 + +/* PH_CONTROL types */ +/* TOUCH TONE IS 0x20XX XX "0"..."9", "A","B","C","D","*","#" */ +#define DTMF_TONE_VAL 0x2000 +#define DTMF_TONE_MASK 0x007F +#define DTMF_TONE_START 0x2100 +#define DTMF_TONE_STOP 0x2200 +#define DTMF_HFC_COEF 0x4000 +#define DSP_CONF_JOIN 0x2403 +#define DSP_CONF_SPLIT 0x2404 +#define DSP_RECEIVE_OFF 0x2405 +#define DSP_RECEIVE_ON 0x2406 +#define DSP_ECHO_ON 0x2407 +#define DSP_ECHO_OFF 0x2408 +#define DSP_MIX_ON 0x2409 +#define DSP_MIX_OFF 0x240a +#define DSP_DELAY 0x240b +#define DSP_JITTER 0x240c +#define DSP_TXDATA_ON 0x240d +#define DSP_TXDATA_OFF 0x240e +#define DSP_TX_DEJITTER 0x240f +#define DSP_TX_DEJ_OFF 0x2410 +#define DSP_TONE_PATT_ON 0x2411 +#define DSP_TONE_PATT_OFF 0x2412 +#define DSP_VOL_CHANGE_TX 0x2413 +#define DSP_VOL_CHANGE_RX 0x2414 +#define DSP_BF_ENABLE_KEY 0x2415 +#define DSP_BF_DISABLE 0x2416 +#define DSP_BF_ACCEPT 0x2416 +#define DSP_BF_REJECT 0x2417 +#define DSP_PIPELINE_CFG 0x2418 +#define HFC_VOL_CHANGE_TX 0x2601 +#define HFC_VOL_CHANGE_RX 0x2602 +#define HFC_SPL_LOOP_ON 0x2603 +#define HFC_SPL_LOOP_OFF 0x2604 + +/* DSP_TONE_PATT_ON parameter */ +#define TONE_OFF 0x0000 +#define TONE_GERMAN_DIALTONE 0x0001 +#define TONE_GERMAN_OLDDIALTONE 0x0002 +#define TONE_AMERICAN_DIALTONE 0x0003 +#define TONE_GERMAN_DIALPBX 0x0004 +#define TONE_GERMAN_OLDDIALPBX 0x0005 +#define TONE_AMERICAN_DIALPBX 0x0006 +#define TONE_GERMAN_RINGING 0x0007 +#define TONE_GERMAN_OLDRINGING 0x0008 +#define TONE_AMERICAN_RINGPBX 0x000b +#define TONE_GERMAN_RINGPBX 0x000c +#define TONE_GERMAN_OLDRINGPBX 0x000d +#define TONE_AMERICAN_RINGING 0x000e +#define TONE_GERMAN_BUSY 0x000f +#define TONE_GERMAN_OLDBUSY 0x0010 +#define TONE_AMERICAN_BUSY 0x0011 +#define TONE_GERMAN_HANGUP 0x0012 +#define TONE_GERMAN_OLDHANGUP 0x0013 +#define TONE_AMERICAN_HANGUP 0x0014 +#define TONE_SPECIAL_INFO 0x0015 +#define TONE_GERMAN_GASSENBESETZT 0x0016 +#define TONE_GERMAN_AUFSCHALTTON 0x0016 + +/* MPH_INFORMATION_IND */ +#define L1_SIGNAL_LOS_OFF 0x0010 +#define L1_SIGNAL_LOS_ON 0x0011 +#define L1_SIGNAL_AIS_OFF 0x0012 +#define L1_SIGNAL_AIS_ON 0x0013 +#define L1_SIGNAL_RDI_OFF 0x0014 +#define L1_SIGNAL_RDI_ON 0x0015 +#define L1_SIGNAL_SLIP_RX 0x0020 +#define L1_SIGNAL_SLIP_TX 0x0021 + +/* + * protocol ids + * D channel 1-31 + * B channel 33 - 63 + */ + +#define ISDN_P_NONE 0 +#define ISDN_P_BASE 0 +#define ISDN_P_TE_S0 0x01 +#define ISDN_P_NT_S0 0x02 +#define ISDN_P_TE_E1 0x03 +#define ISDN_P_NT_E1 0x04 +#define ISDN_P_TE_UP0 0x05 +#define ISDN_P_NT_UP0 0x06 + +#define IS_ISDN_P_TE(p) ((p == ISDN_P_TE_S0) || (p == ISDN_P_TE_E1) || \ + (p == ISDN_P_TE_UP0) || (p == ISDN_P_LAPD_TE)) +#define IS_ISDN_P_NT(p) ((p == ISDN_P_NT_S0) || (p == ISDN_P_NT_E1) || \ + (p == ISDN_P_NT_UP0) || (p == ISDN_P_LAPD_NT)) +#define IS_ISDN_P_S0(p) ((p == ISDN_P_TE_S0) || (p == ISDN_P_NT_S0)) +#define IS_ISDN_P_E1(p) ((p == ISDN_P_TE_E1) || (p == ISDN_P_NT_E1)) +#define IS_ISDN_P_UP0(p) ((p == ISDN_P_TE_UP0) || (p == ISDN_P_NT_UP0)) + + +#define ISDN_P_LAPD_TE 0x10 +#define ISDN_P_LAPD_NT 0x11 + +#define ISDN_P_B_MASK 0x1f +#define ISDN_P_B_START 0x20 + +#define ISDN_P_B_RAW 0x21 +#define ISDN_P_B_HDLC 0x22 +#define ISDN_P_B_X75SLP 0x23 +#define ISDN_P_B_L2DTMF 0x24 +#define ISDN_P_B_L2DSP 0x25 +#define ISDN_P_B_L2DSPHDLC 0x26 + +#define OPTION_L2_PMX 1 +#define OPTION_L2_PTP 2 +#define OPTION_L2_FIXEDTEI 3 +#define OPTION_L2_CLEANUP 4 + +/* should be in sync with linux/kobject.h:KOBJ_NAME_LEN */ +#define MISDN_MAX_IDLEN 20 + +struct mISDNhead { + unsigned int prim; + unsigned int id; +} __attribute__((packed)); + +#define MISDN_HEADER_LEN sizeof(struct mISDNhead) +#define MAX_DATA_SIZE 2048 +#define MAX_DATA_MEM (MAX_DATA_SIZE + MISDN_HEADER_LEN) +#define MAX_DFRAME_LEN 260 + +#define MISDN_ID_ADDR_MASK 0xFFFF +#define MISDN_ID_TEI_MASK 0xFF00 +#define MISDN_ID_SAPI_MASK 0x00FF +#define MISDN_ID_TEI_ANY 0x7F00 + +#define MISDN_ID_ANY 0xFFFF +#define MISDN_ID_NONE 0xFFFE + +#define GROUP_TEI 127 +#define TEI_SAPI 63 +#define CTRL_SAPI 0 + +#define MISDN_MAX_CHANNEL 127 +#define MISDN_CHMAP_SIZE ((MISDN_MAX_CHANNEL + 1) >> 3) + +#define SOL_MISDN 0 + +struct sockaddr_mISDN { + sa_family_t family; + unsigned char dev; + unsigned char channel; + unsigned char sapi; + unsigned char tei; +}; + +struct mISDNversion { + unsigned char major; + unsigned char minor; + unsigned short release; +}; + +#define MAX_DEVICE_ID 63 + +struct mISDN_devinfo { + u_int id; + u_int Dprotocols; + u_int Bprotocols; + u_int protocol; + u_char channelmap[MISDN_CHMAP_SIZE]; + u_int nrbchan; + char name[MISDN_MAX_IDLEN]; +}; + +struct mISDN_devrename { + u_int id; + char name[MISDN_MAX_IDLEN]; +}; + +struct ph_info_ch { + int32_t protocol; + int64_t Flags; +}; + +struct ph_info_dch { + struct ph_info_ch ch; + int16_t state; + int16_t num_bch; +}; + +struct ph_info { + struct ph_info_dch dch; + struct ph_info_ch bch[]; +}; + +/* timer device ioctl */ +#define IMADDTIMER _IOR('I', 64, int) +#define IMDELTIMER _IOR('I', 65, int) +/* socket ioctls */ +#define IMGETVERSION _IOR('I', 66, int) +#define IMGETCOUNT _IOR('I', 67, int) +#define IMGETDEVINFO _IOR('I', 68, int) +#define IMCTRLREQ _IOR('I', 69, int) +#define IMCLEAR_L2 _IOR('I', 70, int) +#define IMSETDEVNAME _IOR('I', 71, struct mISDN_devrename) + +static inline int +test_channelmap(u_int nr, u_char *map) +{ + if (nr <= MISDN_MAX_CHANNEL) + return map[nr >> 3] & (1 << (nr & 7)); + else + return 0; +} + +static inline void +set_channelmap(u_int nr, u_char *map) +{ + map[nr >> 3] |= (1 << (nr & 7)); +} + +static inline void +clear_channelmap(u_int nr, u_char *map) +{ + map[nr >> 3] &= ~(1 << (nr & 7)); +} + +/* CONTROL_CHANNEL parameters */ +#define MISDN_CTRL_GETOP 0x0000 +#define MISDN_CTRL_LOOP 0x0001 +#define MISDN_CTRL_CONNECT 0x0002 +#define MISDN_CTRL_DISCONNECT 0x0004 +#define MISDN_CTRL_PCMCONNECT 0x0010 +#define MISDN_CTRL_PCMDISCONNECT 0x0020 +#define MISDN_CTRL_SETPEER 0x0040 +#define MISDN_CTRL_UNSETPEER 0x0080 +#define MISDN_CTRL_RX_OFF 0x0100 +#define MISDN_CTRL_FILL_EMPTY 0x0200 +#define MISDN_CTRL_GETPEER 0x0400 +#define MISDN_CTRL_HW_FEATURES_OP 0x2000 +#define MISDN_CTRL_HW_FEATURES 0x2001 +#define MISDN_CTRL_HFC_OP 0x4000 +#define MISDN_CTRL_HFC_PCM_CONN 0x4001 +#define MISDN_CTRL_HFC_PCM_DISC 0x4002 +#define MISDN_CTRL_HFC_CONF_JOIN 0x4003 +#define MISDN_CTRL_HFC_CONF_SPLIT 0x4004 +#define MISDN_CTRL_HFC_RECEIVE_OFF 0x4005 +#define MISDN_CTRL_HFC_RECEIVE_ON 0x4006 +#define MISDN_CTRL_HFC_ECHOCAN_ON 0x4007 +#define MISDN_CTRL_HFC_ECHOCAN_OFF 0x4008 + + +/* socket options */ +#define MISDN_TIME_STAMP 0x0001 + +struct mISDN_ctrl_req { + int op; + int channel; + int p1; + int p2; +}; + +/* muxer options */ +#define MISDN_OPT_ALL 1 +#define MISDN_OPT_TEIMGR 2 + +#endif /* mISDNIF_H */ diff --git a/include/openbsc/Makefile.am b/include/openbsc/Makefile.am new file mode 100644 index 000000000..325d66d93 --- /dev/null +++ b/include/openbsc/Makefile.am @@ -0,0 +1,17 @@ +noinst_HEADERS = abis_nm.h abis_rsl.h db.h gsm_04_08.h gsm_data.h \ + gsm_subscriber.h gsm_04_11.h debug.h signal.h \ + misdn.h chan_alloc.h paging.h \ + subchan_demux.h trau_frame.h e1_input.h trau_mux.h \ + ipaccess.h rs232.h openbscdefines.h rtp_proxy.h \ + bsc_rll.h mncc.h transaction.h ussd.h gsm_04_80.h \ + silent_call.h mgcp.h meas_rep.h rest_octets.h \ + system_information.h handover.h mgcp_internal.h \ + vty.h socket.h \ + crc24.h gprs_bssgp.h gprs_llc.h gprs_ns.h gprs_gmm.h \ + gb_proxy.h gprs_sgsn.h gsm_04_08_gprs.h sgsn.h \ + gprs_ns_frgre.h auth.h osmo_msc.h bsc_msc.h bsc_nat.h \ + osmo_bsc_rf.h osmo_bsc.h network_listen.h bsc_nat_sccp.h \ + osmo_msc_data.h osmo_bsc_grace.h sms_queue.h abis_om2000.h + +openbsc_HEADERS = gsm_04_08.h meas_rep.h bsc_api.h +openbscdir = $(includedir)/openbsc diff --git a/include/openbsc/Makefile.in b/include/openbsc/Makefile.in new file mode 100644 index 000000000..faca5a31e --- /dev/null +++ b/include/openbsc/Makefile.in @@ -0,0 +1,449 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = include/openbsc +DIST_COMMON = $(noinst_HEADERS) $(openbsc_HEADERS) \ + $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +SOURCES = +DIST_SOURCES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__installdirs = "$(DESTDIR)$(openbscdir)" +HEADERS = $(noinst_HEADERS) $(openbsc_HEADERS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +noinst_HEADERS = abis_nm.h abis_rsl.h db.h gsm_04_08.h gsm_data.h \ + gsm_subscriber.h gsm_04_11.h debug.h signal.h \ + misdn.h chan_alloc.h paging.h \ + subchan_demux.h trau_frame.h e1_input.h trau_mux.h \ + ipaccess.h rs232.h openbscdefines.h rtp_proxy.h \ + bsc_rll.h mncc.h transaction.h ussd.h gsm_04_80.h \ + silent_call.h mgcp.h meas_rep.h rest_octets.h \ + system_information.h handover.h mgcp_internal.h \ + vty.h socket.h \ + crc24.h gprs_bssgp.h gprs_llc.h gprs_ns.h gprs_gmm.h \ + gb_proxy.h gprs_sgsn.h gsm_04_08_gprs.h sgsn.h \ + gprs_ns_frgre.h auth.h osmo_msc.h bsc_msc.h bsc_nat.h \ + osmo_bsc_rf.h osmo_bsc.h network_listen.h bsc_nat_sccp.h \ + osmo_msc_data.h osmo_bsc_grace.h sms_queue.h abis_om2000.h + +openbsc_HEADERS = gsm_04_08.h meas_rep.h bsc_api.h +openbscdir = $(includedir)/openbsc +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu include/openbsc/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu include/openbsc/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-openbscHEADERS: $(openbsc_HEADERS) + @$(NORMAL_INSTALL) + test -z "$(openbscdir)" || $(MKDIR_P) "$(DESTDIR)$(openbscdir)" + @list='$(openbsc_HEADERS)'; test -n "$(openbscdir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(openbscdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(openbscdir)" || exit $$?; \ + done + +uninstall-openbscHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(openbsc_HEADERS)'; test -n "$(openbscdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(openbscdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(openbscdir)" && rm -f $$files + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(openbscdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-openbscHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-openbscHEADERS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + ctags distclean distclean-generic distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-openbscHEADERS install-pdf install-pdf-am install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \ + uninstall uninstall-am uninstall-openbscHEADERS + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/include/openbsc/abis_nm.h b/include/openbsc/abis_nm.h new file mode 100644 index 000000000..c93db582c --- /dev/null +++ b/include/openbsc/abis_nm.h @@ -0,0 +1,172 @@ +/* GSM Network Management messages on the A-bis interface + * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */ + +/* (C) 2008-2009 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef _NM_H +#define _NM_H + +#include +#include +#include + +struct cell_global_id { + u_int16_t mcc; + u_int16_t mnc; + u_int16_t lac; + u_int16_t ci; +}; + +/* The BCCH info from an ip.access test, in host byte order + * and already parsed... */ +struct ipac_bcch_info { + struct llist_head list; + + u_int16_t info_type; + u_int8_t freq_qual; + u_int16_t arfcn; + u_int8_t rx_lev; + u_int8_t rx_qual; + int16_t freq_err; + u_int16_t frame_offset; + u_int32_t frame_nr_offset; + u_int8_t bsic; + struct cell_global_id cgi; + u_int8_t ba_list_si2[16]; + u_int8_t ba_list_si2bis[16]; + u_int8_t ba_list_si2ter[16]; + u_int8_t ca_list_si1[16]; +}; + +extern const struct value_string abis_nm_adm_state_names[]; +extern const struct value_string abis_nm_obj_class_names[]; +extern const struct tlv_definition nm_att_tlvdef; + +/* PUBLIC */ + +struct msgb; + +struct abis_nm_cfg { + /* callback for unidirectional reports */ + int (*report_cb)(struct msgb *, + struct abis_om_fom_hdr *); + /* callback for software activate requests from BTS */ + int (*sw_act_req)(struct msgb *); +}; + +extern int abis_nm_rcvmsg(struct msgb *msg); + +int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const u_int8_t *buf, int len); +int abis_nm_rx(struct msgb *msg); +int abis_nm_opstart(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i0, u_int8_t i1, u_int8_t i2); +int abis_nm_chg_adm_state(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i0, + u_int8_t i1, u_int8_t i2, enum abis_nm_adm_state adm_state); +int abis_nm_establish_tei(struct gsm_bts *bts, u_int8_t trx_nr, + u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_t e1_subslot, + u_int8_t tei); +int abis_nm_conn_terr_sign(struct gsm_bts_trx *trx, + u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_t e1_subslot); +int abis_nm_conn_terr_traf(struct gsm_bts_trx_ts *ts, + u_int8_t e1_port, u_int8_t e1_timeslot, + u_int8_t e1_subslot); +int abis_nm_set_bts_attr(struct gsm_bts *bts, u_int8_t *attr, int attr_len); +int abis_nm_set_radio_attr(struct gsm_bts_trx *trx, u_int8_t *attr, int attr_len); +int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, u_int8_t chan_comb); +int abis_nm_sw_act_req_ack(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i1, + u_int8_t i2, u_int8_t i3, int nack, u_int8_t *attr, int att_len); +int abis_nm_raw_msg(struct gsm_bts *bts, int len, u_int8_t *msg); +int abis_nm_event_reports(struct gsm_bts *bts, int on); +int abis_nm_reset_resource(struct gsm_bts *bts); +int abis_nm_software_load(struct gsm_bts *bts, int trx_nr, const char *fname, + u_int8_t win_size, int forced, + gsm_cbfn *cbfn, void *cb_data); +int abis_nm_software_load_status(struct gsm_bts *bts); +int abis_nm_software_activate(struct gsm_bts *bts, const char *fname, + gsm_cbfn *cbfn, void *cb_data); + +int abis_nm_conn_mdrop_link(struct gsm_bts *bts, u_int8_t e1_port0, u_int8_t ts0, + u_int8_t e1_port1, u_int8_t ts1); + +int abis_nm_perform_test(struct gsm_bts *bts, u_int8_t obj_class, + u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr, + u_int8_t test_nr, u_int8_t auton_report, struct msgb *msg); + +int abis_nm_chcomb4pchan(enum gsm_phys_chan_config pchan); + +/* Siemens / BS-11 specific */ +int abis_nm_bs11_reset_resource(struct gsm_bts *bts); +int abis_nm_bs11_db_transmission(struct gsm_bts *bts, int begin); +int abis_nm_bs11_create_object(struct gsm_bts *bts, enum abis_bs11_objtype type, + u_int8_t idx, u_int8_t attr_len, const u_int8_t *attr); +int abis_nm_bs11_create_envaBTSE(struct gsm_bts *bts, u_int8_t idx); +int abis_nm_bs11_create_bport(struct gsm_bts *bts, u_int8_t idx); +int abis_nm_bs11_delete_object(struct gsm_bts *bts, + enum abis_bs11_objtype type, u_int8_t idx); +int abis_nm_bs11_delete_bport(struct gsm_bts *bts, u_int8_t idx); +int abis_nm_bs11_conn_oml_tei(struct gsm_bts *bts, u_int8_t e1_port, + u_int8_t e1_timeslot, u_int8_t e1_subslot, u_int8_t tei); +int abis_nm_bs11_get_oml_tei_ts(struct gsm_bts *bts); +int abis_nm_bs11_get_serno(struct gsm_bts *bts); +int abis_nm_bs11_set_trx_power(struct gsm_bts_trx *trx, u_int8_t level); +int abis_nm_bs11_get_trx_power(struct gsm_bts_trx *trx); +int abis_nm_bs11_logon(struct gsm_bts *bts, u_int8_t level, const char *name, int on); +int abis_nm_bs11_factory_logon(struct gsm_bts *bts, int on); +int abis_nm_bs11_infield_logon(struct gsm_bts *bts, int on); +int abis_nm_bs11_set_trx1_pw(struct gsm_bts *bts, const char *password); +int abis_nm_bs11_set_pll_locked(struct gsm_bts *bts, int locked); +int abis_nm_bs11_get_pll_mode(struct gsm_bts *bts); +int abis_nm_bs11_set_pll(struct gsm_bts *bts, int value); +int abis_nm_bs11_get_cclk(struct gsm_bts *bts); +int abis_nm_bs11_get_state(struct gsm_bts *bts); +int abis_nm_bs11_load_swl(struct gsm_bts *bts, const char *fname, + u_int8_t win_size, int forced, gsm_cbfn *cbfn); +int abis_nm_bs11_set_ext_time(struct gsm_bts *bts); +int abis_nm_bs11_get_bport_line_cfg(struct gsm_bts *bts, u_int8_t bport); +int abis_nm_bs11_set_bport_line_cfg(struct gsm_bts *bts, u_int8_t bport, enum abis_bs11_line_cfg line_cfg); +int abis_nm_bs11_bsc_disconnect(struct gsm_bts *bts, int reconnect); +int abis_nm_bs11_restart(struct gsm_bts *bts); + +/* ip.access nanoBTS specific commands */ +int abis_nm_ipaccess_msg(struct gsm_bts *bts, u_int8_t msg_type, + u_int8_t obj_class, u_int8_t bts_nr, + u_int8_t trx_nr, u_int8_t ts_nr, + u_int8_t *attr, int attr_len); +int abis_nm_ipaccess_set_nvattr(struct gsm_bts_trx *trx, u_int8_t *attr, + int attr_len); +int abis_nm_ipaccess_restart(struct gsm_bts_trx *trx); +int abis_nm_ipaccess_set_attr(struct gsm_bts *bts, u_int8_t obj_class, + u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr, + u_int8_t *attr, u_int8_t attr_len); +int abis_nm_ipaccess_rsl_connect(struct gsm_bts_trx *trx, + u_int32_t ip, u_int16_t port, u_int8_t stream); +void abis_nm_ipaccess_cgi(u_int8_t *buf, struct gsm_bts *bts); +int ipac_parse_bcch_info(struct ipac_bcch_info *binf, u_int8_t *buf); +const char *ipacc_testres_name(u_int8_t res); + +/* Functions calling into other code parts */ +const char *nm_opstate_name(u_int8_t os); +const char *nm_avail_name(u_int8_t avail); +int nm_is_running(struct gsm_nm_state *s); + +int abis_nm_vty_init(void); + +void abis_nm_clear_queue(struct gsm_bts *bts); + + +#endif /* _NM_H */ diff --git a/include/openbsc/abis_om2000.h b/include/openbsc/abis_om2000.h new file mode 100644 index 000000000..e4f19cf9c --- /dev/null +++ b/include/openbsc/abis_om2000.h @@ -0,0 +1,78 @@ +#ifndef OPENBSC_ABIS_OM2K_H +#define OPENBSC_ABIS_OM2K_H +/* Ericsson RBS 2xxx GSM O&M (OM2000) messages on the A-bis interface + * implemented based on protocol trace analysis, no formal documentation */ + +/* (C) 2010-2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +enum abis_om2k_mo_cls { + OM2K_MO_CLS_TRXC = 0x01, + OM2K_MO_CLS_TS = 0x03, + OM2K_MO_CLS_TF = 0x04, + OM2K_MO_CLS_IS = 0x05, + OM2K_MO_CLS_CON = 0x06, + OM2K_MO_CLS_DP = 0x07, + OM2K_MO_CLS_CF = 0x0a, + OM2K_MO_CLS_TX = 0x0b, + OM2K_MO_CLS_RX = 0x0c, +}; + +struct abis_om2k_mo { + uint8_t class; + uint8_t bts; + uint8_t assoc_so; + uint8_t inst; +} __attribute__ ((packed)); + +struct om2k_is_conn_grp { + uint16_t icp1; + uint16_t icp2; + uint8_t cont_idx; +} __attribute__ ((packed)); + +extern const struct value_string om2k_mo_class_short_vals[]; + +int abis_om2k_rcvmsg(struct msgb *msg); + +extern const struct abis_om2k_mo om2k_mo_cf; + +int abis_om2k_tx_reset_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo); +int abis_om2k_tx_start_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo); +int abis_om2k_tx_status_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo); +int abis_om2k_tx_connect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo); +int abis_om2k_tx_disconnect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo); +int abis_om2k_tx_enable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo); +int abis_om2k_tx_disable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo); +int abis_om2k_tx_test_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo); +int abis_om2k_tx_op_info(struct gsm_bts *bts, const struct abis_om2k_mo *mo, + uint8_t operational); +int abis_om2k_tx_is_conf_req(struct gsm_bts *bts, struct om2k_is_conn_grp *cg, + unsigned int num_cg); +int abis_om2k_tx_tf_conf_req(struct gsm_bts *bts); +int abis_om2k_tx_rx_conf_req(struct gsm_bts_trx *trx); +int abis_om2k_tx_tx_conf_req(struct gsm_bts_trx *trx); +int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts); + +int abis_om2k_vty_init(void); + +struct vty; +void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts); + +#endif /* OPENBCS_ABIS_OM2K_H */ diff --git a/include/openbsc/abis_rsl.h b/include/openbsc/abis_rsl.h new file mode 100644 index 000000000..295b01fd3 --- /dev/null +++ b/include/openbsc/abis_rsl.h @@ -0,0 +1,99 @@ +/* GSM Radio Signalling Link messages on the A-bis interface + * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */ + +/* (C) 2008 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef _RSL_H +#define _RSL_H + +#include + +#include + +struct gsm_bts; +struct gsm_lchan; +struct gsm_subscriber; +struct gsm_bts_trx_ts; + + +int rsl_bcch_info(struct gsm_bts_trx *trx, u_int8_t type, + const u_int8_t *data, int len); +int rsl_sacch_filling(struct gsm_bts_trx *trx, u_int8_t type, + const u_int8_t *data, int len); +int rsl_chan_activate(struct gsm_bts_trx *trx, u_int8_t chan_nr, + u_int8_t act_type, + struct rsl_ie_chan_mode *chan_mode, + struct rsl_ie_chan_ident *chan_ident, + u_int8_t bs_power, u_int8_t ms_power, + u_int8_t ta); +int rsl_chan_activate_lchan(struct gsm_lchan *lchan, u_int8_t act_type, + u_int8_t ta, u_int8_t ho_ref); +int rsl_chan_mode_modify_req(struct gsm_lchan *ts); +int rsl_encryption_cmd(struct msgb *msg); +int rsl_paging_cmd(struct gsm_bts *bts, u_int8_t paging_group, u_int8_t len, + u_int8_t *ms_ident, u_int8_t chan_needed); +int rsl_imm_assign_cmd(struct gsm_bts *bts, u_int8_t len, u_int8_t *val); + +int rsl_data_request(struct msgb *msg, u_int8_t link_id); +int rsl_establish_request(struct gsm_lchan *lchan, u_int8_t link_id); +int rsl_relase_request(struct gsm_lchan *lchan, u_int8_t link_id); + +/* Siemens vendor-specific RSL extensions */ +int rsl_siemens_mrpci(struct gsm_lchan *lchan, struct rsl_mrpci *mrpci); + +/* ip.access specfic RSL extensions */ +int rsl_ipacc_crcx(struct gsm_lchan *lchan); +int rsl_ipacc_mdcx(struct gsm_lchan *lchan, u_int32_t ip, + u_int16_t port, u_int8_t rtp_payload2); +int rsl_ipacc_mdcx_to_rtpsock(struct gsm_lchan *lchan); +int rsl_ipacc_pdch_activate(struct gsm_bts_trx_ts *ts, int act); + +int abis_rsl_rcvmsg(struct msgb *msg); + +unsigned int get_paging_group(u_int64_t imsi, unsigned int bs_cc_chans, + int n_pag_blocks); +unsigned int n_pag_blocks(int bs_ccch_sdcch_comb, unsigned int bs_ag_blks_res); +u_int64_t str_to_imsi(const char *imsi_str); +u_int8_t lchan2chan_nr(const struct gsm_lchan *lchan); +int rsl_release_request(struct gsm_lchan *lchan, u_int8_t link_id, u_int8_t reason); + +int rsl_lchan_set_state(struct gsm_lchan *lchan, int); + +/* to be provided by external code */ +int abis_rsl_sendmsg(struct msgb *msg); +int rsl_deact_sacch(struct gsm_lchan *lchan); +int rsl_lchan_rll_release(struct gsm_lchan *lchan, u_int8_t link_id); + +/* BCCH related code */ +int rsl_ccch_conf_to_bs_cc_chans(int ccch_conf); +int rsl_ccch_conf_to_bs_ccch_sdcch_comb(int ccch_conf); +int rsl_number_of_paging_subchannels(struct gsm_bts *bts); + +int rsl_sacch_info_modify(struct gsm_lchan *lchan, u_int8_t type, + const u_int8_t *data, int len); + +int rsl_chan_bs_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int db); +int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int dbm); + +/* SMSCB functionality */ +int rsl_sms_cb_command(struct gsm_bts *bts, uint8_t chan_number, + uint8_t cb_command, const uint8_t *data, int len); + +#endif /* RSL_MT_H */ + diff --git a/include/openbsc/auth.h b/include/openbsc/auth.h new file mode 100644 index 000000000..2364fb3d2 --- /dev/null +++ b/include/openbsc/auth.h @@ -0,0 +1,17 @@ +#ifndef _AUTH_H +#define _AUTH_H + +struct gsm_auth_tuple; +struct gsm_subscriber; + +enum auth_action { + AUTH_NOT_AVAIL = 0, /* No auth tuple available */ + AUTH_DO_AUTH_THAN_CIPH = 1, /* Firsth authenticate, then cipher */ + AUTH_DO_CIPH = 2, /* Only ciphering */ + AUTH_DO_AUTH = 3, /* Only authentication, no ciphering */ +}; + +int auth_get_tuple_for_subscr(struct gsm_auth_tuple *atuple, + struct gsm_subscriber *subscr, int key_seq); + +#endif /* _AUTH_H */ diff --git a/include/openbsc/bsc_api.h b/include/openbsc/bsc_api.h new file mode 100644 index 000000000..36ec3705c --- /dev/null +++ b/include/openbsc/bsc_api.h @@ -0,0 +1,37 @@ +/* GSM 08.08 like API for OpenBSC */ + +#ifndef OPENBSC_BSC_API_H +#define OPENBSC_BSC_API_H + +#include "gsm_data.h" + +#define BSC_API_CONN_POL_ACCEPT 0 +#define BSC_API_CONN_POL_REJECT 1 + +struct bsc_api { + void (*sapi_n_reject)(struct gsm_subscriber_connection *conn, int dlci); + void (*cipher_mode_compl)(struct gsm_subscriber_connection *conn, + struct msgb *msg, uint8_t chosen_encr); + int (*compl_l3)(struct gsm_subscriber_connection *conn, + struct msgb *msg, uint16_t chosen_channel); + void (*dtap)(struct gsm_subscriber_connection *conn, uint8_t link_id, + struct msgb *msg); + void (*assign_compl)(struct gsm_subscriber_connection *conn, + uint8_t rr_cause, uint8_t chosen_channel, + uint8_t encr_alg_id, uint8_t speech_mode); + void (*assign_fail)(struct gsm_subscriber_connection *conn, + uint8_t cause, uint8_t *rr_cause); + int (*clear_request)(struct gsm_subscriber_connection *conn, + uint32_t cause); +}; + +int bsc_api_init(struct gsm_network *network, struct bsc_api *api); +int gsm0808_submit_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, int link_id, int allow_sach); +int gsm0808_assign_req(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate); +int gsm0808_cipher_mode(struct gsm_subscriber_connection *conn, int cipher, + const uint8_t *key, int len, int include_imeisv); +int gsm0808_page(struct gsm_bts *bts, unsigned int page_group, + unsigned int mi_len, uint8_t *mi, int chan_type); +int gsm0808_clear(struct gsm_subscriber_connection *conn); + +#endif diff --git a/include/openbsc/bsc_msc.h b/include/openbsc/bsc_msc.h new file mode 100644 index 000000000..d06ae0511 --- /dev/null +++ b/include/openbsc/bsc_msc.h @@ -0,0 +1,51 @@ +/* Routines to talk to the MSC using the IPA Protocol */ +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef BSC_MSC_H +#define BSC_MSC_H + +#include +#include + +struct bsc_msc_connection { + struct write_queue write_queue; + int is_connected; + int is_authenticated; + int first_contact; + const char *ip; + int port; + int prio; + + void (*connection_loss) (struct bsc_msc_connection *); + void (*connected) (struct bsc_msc_connection *); + struct timer_list reconnect_timer; + struct timer_list timeout_timer; +}; + +struct bsc_msc_connection *bsc_msc_create(const char *ip, int port, int prio); +int bsc_msc_connect(struct bsc_msc_connection *); +void bsc_msc_schedule_connect(struct bsc_msc_connection *); + +void bsc_msc_lost(struct bsc_msc_connection *); + +struct msgb *bsc_msc_id_get_resp(const char *token); + +#endif diff --git a/include/openbsc/bsc_nat.h b/include/openbsc/bsc_nat.h new file mode 100644 index 000000000..f74cae2a8 --- /dev/null +++ b/include/openbsc/bsc_nat.h @@ -0,0 +1,355 @@ +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef BSC_NAT_H +#define BSC_NAT_H + +#include "mgcp.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DIR_BSC 1 +#define DIR_MSC 2 + +struct sccp_source_reference; +struct sccp_connections; +struct bsc_nat_parsed; +struct bsc_nat; +struct bsc_nat_ussd_con; + +enum { + NAT_CON_TYPE_NONE, + NAT_CON_TYPE_LU, + NAT_CON_TYPE_CM_SERV_REQ, + NAT_CON_TYPE_PAG_RESP, + NAT_CON_TYPE_SSA, + NAT_CON_TYPE_LOCAL_REJECT, + NAT_CON_TYPE_OTHER, +}; + +/* + * Per BSC data structure + */ +struct bsc_connection { + struct llist_head list_entry; + + /* do we know anything about this BSC? */ + int authenticated; + + /* the fd we use to communicate */ + struct write_queue write_queue; + + /* the BSS associated */ + struct bsc_config *cfg; + + /* a timeout node */ + struct timer_list id_timeout; + + /* pong timeout */ + struct timer_list ping_timeout; + struct timer_list pong_timeout; + + /* mgcp related code */ + char *_endpoint_status; + int number_multiplexes; + int max_endpoints; + int last_endpoint; + + /* a back pointer */ + struct bsc_nat *nat; +}; + +/** + * Stats per BSC + */ +struct bsc_config_stats { + struct rate_ctr_group *ctrg; +}; + +enum bsc_cfg_ctr { + BCFG_CTR_SCCP_CONN, + BCFG_CTR_SCCP_CALLS, + BCFG_CTR_NET_RECONN, + BCFG_CTR_DROPPED_SCCP, + BCFG_CTR_DROPPED_CALLS, + BCFG_CTR_REJECTED_CR, + BCFG_CTR_REJECTED_MSG, + BCFG_CTR_ILL_PACKET, + BCFG_CTR_CON_TYPE_LU, + BCFG_CTR_CON_CMSERV_RQ, + BCFG_CTR_CON_PAG_RESP, + BCFG_CTR_CON_SSA, + BCFG_CTR_CON_OTHER, +}; + +/** + * One BSC entry in the config + */ +struct bsc_config { + struct llist_head entry; + + char *token; + int nr; + + char *description; + + /* imsi white and blacklist */ + char *acc_lst_name; + + int forbid_paging; + + /* audio handling */ + int max_endpoints; + + /* backpointer */ + struct bsc_nat *nat; + + struct bsc_config_stats stats; + + struct llist_head lac_list; +}; + +struct bsc_lac_entry { + struct llist_head entry; + uint16_t lac; +}; + +/** + * BSCs point of view of endpoints + */ +struct bsc_endpoint { + /* the operation that is carried out */ + int transaction_state; + /* the pending transaction id */ + char *transaction_id; + /* the bsc we are talking to */ + struct bsc_connection *bsc; +}; + +/** + * Statistic for the nat. + */ +struct bsc_nat_statistics { + struct { + struct counter *conn; + struct counter *calls; + } sccp; + + struct { + struct counter *reconn; + struct counter *auth_fail; + } bsc; + + struct { + struct counter *reconn; + } msc; + + struct { + struct counter *reconn; + } ussd; +}; + +enum bsc_nat_acc_ctr { + ACC_LIST_BSC_FILTER, + ACC_LIST_NAT_FILTER, +}; + +struct bsc_nat_acc_lst { + struct llist_head list; + + /* counter */ + struct rate_ctr_group *stats; + + /* the name of the list */ + const char *name; + struct llist_head fltr_list; +}; + +struct bsc_nat_acc_lst_entry { + struct llist_head list; + + /* the filter */ + char *imsi_allow; + regex_t imsi_allow_re; + char *imsi_deny; + regex_t imsi_deny_re; +}; + +/** + * the structure of the "nat" network + */ +struct bsc_nat { + /* active SCCP connections that need patching */ + struct llist_head sccp_connections; + + /* active BSC connections that need patching */ + struct llist_head bsc_connections; + + /* access lists */ + struct llist_head access_lists; + + /* known BSC's */ + struct llist_head bsc_configs; + int num_bsc; + int bsc_ip_dscp; + + /* MGCP config */ + struct mgcp_config *mgcp_cfg; + uint8_t mgcp_msg[4096]; + int mgcp_length; + + /* msc things */ + char *msc_ip; + int msc_port; + struct bsc_msc_connection *msc_con; + char *token; + + /* timeouts */ + int auth_timeout; + int ping_timeout; + int pong_timeout; + + struct bsc_endpoint *bsc_endpoints; + + /* filter */ + char *acc_lst_name; + + /* number rewriting */ + char *num_rewr_name; + struct msg_entries *num_rewr; + + /* USSD messages we want to match */ + char *ussd_lst_name; + char *ussd_query; + char *ussd_token; + char *ussd_local; + struct bsc_fd ussd_listen; + struct bsc_nat_ussd_con *ussd_con; + + /* statistics */ + struct bsc_nat_statistics stats; +}; + +/* create and init the structures */ +struct bsc_config *bsc_config_alloc(struct bsc_nat *nat, const char *token); +struct bsc_config *bsc_config_num(struct bsc_nat *nat, int num); +void bsc_config_free(struct bsc_config *); +void bsc_config_add_lac(struct bsc_config *cfg, int lac); +void bsc_config_del_lac(struct bsc_config *cfg, int lac); +int bsc_config_handles_lac(struct bsc_config *cfg, int lac); + +struct bsc_nat *bsc_nat_alloc(void); +struct bsc_connection *bsc_connection_alloc(struct bsc_nat *nat); +void bsc_nat_set_msc_ip(struct bsc_nat *bsc, const char *ip); + +void sccp_connection_destroy(struct sccp_connections *); +void bsc_close_connection(struct bsc_connection *); + +const char *bsc_con_type_to_string(int type); + +/** + * parse the given message into the above structure + */ +struct bsc_nat_parsed *bsc_nat_parse(struct msgb *msg); + +/** + * filter based on IP Access header in both directions + */ +int bsc_nat_filter_ipa(int direction, struct msgb *msg, struct bsc_nat_parsed *parsed); +int bsc_nat_vty_init(struct bsc_nat *nat); +struct bsc_connection *bsc_nat_find_bsc(struct bsc_nat *nat, struct msgb *msg, int *_lac); + +/** + * Content filtering. + */ +int bsc_nat_filter_sccp_cr(struct bsc_connection *bsc, struct msgb *msg, + struct bsc_nat_parsed *, int *con_type, char **imsi); +int bsc_nat_filter_dt(struct bsc_connection *bsc, struct msgb *msg, + struct sccp_connections *con, struct bsc_nat_parsed *parsed); + +/** + * SCCP patching and handling + */ +struct sccp_connections *create_sccp_src_ref(struct bsc_connection *bsc, struct bsc_nat_parsed *parsed); +int update_sccp_src_ref(struct sccp_connections *sccp, struct bsc_nat_parsed *parsed); +void remove_sccp_src_ref(struct bsc_connection *bsc, struct msgb *msg, struct bsc_nat_parsed *parsed); +struct sccp_connections *patch_sccp_src_ref_to_bsc(struct msgb *, struct bsc_nat_parsed *, struct bsc_nat *); +struct sccp_connections *patch_sccp_src_ref_to_msc(struct msgb *, struct bsc_nat_parsed *, struct bsc_connection *); +struct sccp_connections *bsc_nat_find_con_by_bsc(struct bsc_nat *, struct sccp_source_reference *); + +/** + * MGCP/Audio handling + */ +int bsc_mgcp_nr_multiplexes(int max_endpoints); +int bsc_write_mgcp(struct bsc_connection *bsc, const uint8_t *data, unsigned int length); +int bsc_mgcp_assign_patch(struct sccp_connections *, struct msgb *msg); +void bsc_mgcp_init(struct sccp_connections *); +void bsc_mgcp_dlcx(struct sccp_connections *); +void bsc_mgcp_free_endpoints(struct bsc_nat *nat); +int bsc_mgcp_nat_init(struct bsc_nat *nat); + +struct sccp_connections *bsc_mgcp_find_con(struct bsc_nat *, int endpoint_number); +struct msgb *bsc_mgcp_rewrite(char *input, int length, int endp, const char *ip, int port); +void bsc_mgcp_forward(struct bsc_connection *bsc, struct msgb *msg); + +void bsc_mgcp_clear_endpoints_for(struct bsc_connection *bsc); +int bsc_mgcp_parse_response(const char *str, int *code, char transaction[60]); +uint32_t bsc_mgcp_extract_ci(const char *resp); + + +int bsc_write(struct bsc_connection *bsc, struct msgb *msg, int id); +int bsc_do_write(struct write_queue *queue, struct msgb *msg, int id); +int bsc_write_msg(struct write_queue *queue, struct msgb *msg); +int bsc_write_cb(struct bsc_fd *bfd, struct msgb *msg); + +/* IMSI allow/deny handling */ +void bsc_parse_reg(void *ctx, regex_t *reg, char **imsi, int argc, const char **argv); +struct bsc_nat_acc_lst *bsc_nat_acc_lst_find(struct bsc_nat *nat, const char *name); +struct bsc_nat_acc_lst *bsc_nat_acc_lst_get(struct bsc_nat *nat, const char *name); +void bsc_nat_acc_lst_delete(struct bsc_nat_acc_lst *lst); + +struct bsc_nat_acc_lst_entry *bsc_nat_acc_lst_entry_create(struct bsc_nat_acc_lst *); +int bsc_nat_lst_check_allow(struct bsc_nat_acc_lst *lst, const char *imsi); + +int bsc_nat_msc_is_connected(struct bsc_nat *nat); + +int bsc_conn_type_to_ctr(struct sccp_connections *conn); + +struct gsm48_hdr *bsc_unpack_dtap(struct bsc_nat_parsed *parsed, struct msgb *msg, uint32_t *len); + +/** USSD filtering */ +int bsc_ussd_init(struct bsc_nat *nat); +int bsc_check_ussd(struct sccp_connections *con, struct bsc_nat_parsed *parsed, struct msgb *msg); +int bsc_close_ussd_connections(struct bsc_nat *nat); + +struct msgb *bsc_nat_rewrite_setup(struct bsc_nat *nat, struct msgb *msg, struct bsc_nat_parsed *, const char *imsi); + +#endif diff --git a/include/openbsc/bsc_nat_sccp.h b/include/openbsc/bsc_nat_sccp.h new file mode 100644 index 000000000..0ade668c4 --- /dev/null +++ b/include/openbsc/bsc_nat_sccp.h @@ -0,0 +1,95 @@ +/* NAT utilities using SCCP types */ +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef BSC_NAT_SCCP_H +#define BSC_NAT_SCCP_H + +#include +#include + +/* + * For the NAT we will need to analyze and later patch + * the received message. This would require us to parse + * the IPA and SCCP header twice. Instead of doing this + * we will have one analyze structure and have the patching + * and filter operate on the same structure. + */ +struct bsc_nat_parsed { + /* ip access prototype */ + int ipa_proto; + + /* source local reference */ + struct sccp_source_reference *src_local_ref; + + /* destination local reference */ + struct sccp_source_reference *dest_local_ref; + + /* called ssn number */ + int called_ssn; + + /* calling ssn number */ + int calling_ssn; + + /* sccp message type */ + int sccp_type; + + /* bssap type, e.g. 0 for BSS Management */ + int bssap; + + /* the gsm0808 message type */ + int gsm_type; +}; + +/* + * Per SCCP source local reference patch table. It needs to + * be updated on new SCCP connections, connection confirm and reject, + * and on the loss of the BSC connection. + */ +struct sccp_connections { + struct llist_head list_entry; + + struct bsc_connection *bsc; + struct bsc_msc_connection *msc_con; + + struct sccp_source_reference real_ref; + struct sccp_source_reference patched_ref; + struct sccp_source_reference remote_ref; + int has_remote_ref; + + /* status */ + int con_type; + int con_local; + int imsi_checked; + char *imsi; + + /* + * audio handling. Remember if we have ever send a CRCX, + * remember the endpoint used by the MSC and BSC. + */ + int msc_endp; + int bsc_endp; + + /* timeout handling */ + struct timespec creation_time; +}; + + +#endif diff --git a/include/openbsc/bsc_rll.h b/include/openbsc/bsc_rll.h new file mode 100644 index 000000000..b2898d1b0 --- /dev/null +++ b/include/openbsc/bsc_rll.h @@ -0,0 +1,19 @@ +#ifndef _BSC_RLL_H +#define _BSC_RLL_H + +#include + +enum bsc_rllr_ind { + BSC_RLLR_IND_EST_CONF, + BSC_RLLR_IND_REL_IND, + BSC_RLLR_IND_ERR_IND, + BSC_RLLR_IND_TIMEOUT, +}; + +int rll_establish(struct gsm_lchan *lchan, u_int8_t link_id, + void (*cb)(struct gsm_lchan *, u_int8_t, void *, + enum bsc_rllr_ind), + void *data); +void rll_indication(struct gsm_lchan *lchan, u_int8_t link_id, u_int8_t type); + +#endif /* _BSC_RLL_H */ diff --git a/include/openbsc/chan_alloc.h b/include/openbsc/chan_alloc.h new file mode 100644 index 000000000..5eda312ac --- /dev/null +++ b/include/openbsc/chan_alloc.h @@ -0,0 +1,65 @@ +/* Management functions to allocate/release struct gsm_lchan */ +/* (C) 2008 by Harald Welte + * (C) 2009 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#ifndef _CHAN_ALLOC_H +#define _CHAN_ALLOC_H + +#include "gsm_data.h" + +struct gsm_subscriber_connection; + +/* Special allocator for C0 of BTS */ +struct gsm_bts_trx_ts *ts_c0_alloc(struct gsm_bts *bts, + enum gsm_phys_chan_config pchan); + +/* Regular physical channel allocator */ +struct gsm_bts_trx_ts *ts_alloc(struct gsm_bts *bts, + enum gsm_phys_chan_config pchan); + +/* Regular physical channel (TS) */ +void ts_free(struct gsm_bts_trx_ts *ts); + +/* Find an allocated channel for a specified subscriber */ +struct gsm_subscriber_connection *connection_for_subscr(struct gsm_subscriber *subscr); + +/* Allocate a logical channel (SDCCH, TCH, ...) */ +struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type, int allow_bigger); + +/* Free a logical channel (SDCCH, TCH, ...) */ +void lchan_free(struct gsm_lchan *lchan); +void lchan_reset(struct gsm_lchan *lchan); + +/* Release the given lchan */ +int lchan_release(struct gsm_lchan *lchan, int sach_deact, int reason); + +struct load_counter { + unsigned int total; + unsigned int used; +}; + +struct pchan_load { + struct load_counter pchan[GSM_PCHAN_UNKNOWN]; +}; + +void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts); +void network_chan_load(struct pchan_load *pl, struct gsm_network *net); + +int trx_is_usable(struct gsm_bts_trx *trx); + +#endif /* _CHAN_ALLOC_H */ diff --git a/include/openbsc/crc24.h b/include/openbsc/crc24.h new file mode 100644 index 000000000..358fcb58f --- /dev/null +++ b/include/openbsc/crc24.h @@ -0,0 +1,8 @@ +#ifndef _CRC24_H +#define _CRC24_H + +#define INIT_CRC24 0xffffff + +u_int32_t crc24_calc(u_int32_t fcs, u_int8_t *cp, unsigned int len); + +#endif diff --git a/include/openbsc/db.h b/include/openbsc/db.h new file mode 100644 index 000000000..a939b0d63 --- /dev/null +++ b/include/openbsc/db.h @@ -0,0 +1,83 @@ +/* (C) 2008 by Jan Luebbe + * (C) 2009 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef _DB_H +#define _DB_H + +#include + +struct gsm_equipment; +struct gsm_network; +struct gsm_auth_info; +struct gsm_auth_tuple; +struct gsm_sms; +struct gsm_subscriber; + +enum gsm_subscriber_field; + +/* one time initialisation */ +int db_init(const char *name); +int db_prepare(); +int db_fini(); + +/* subscriber management */ +struct gsm_subscriber *db_create_subscriber(struct gsm_network *net, + char *imsi); +struct gsm_subscriber *db_get_subscriber(struct gsm_network *net, + enum gsm_subscriber_field field, + const char *subscr); +int db_sync_subscriber(struct gsm_subscriber *subscriber); +int db_subscriber_alloc_tmsi(struct gsm_subscriber *subscriber); +int db_subscriber_alloc_exten(struct gsm_subscriber *subscriber); +int db_subscriber_alloc_token(struct gsm_subscriber *subscriber, u_int32_t* token); +int db_subscriber_assoc_imei(struct gsm_subscriber *subscriber, char *imei); +int db_sync_equipment(struct gsm_equipment *equip); +int db_subscriber_update(struct gsm_subscriber *subscriber); + +/* auth info */ +int db_get_authinfo_for_subscr(struct gsm_auth_info *ainfo, + struct gsm_subscriber *subscr); +int db_sync_authinfo_for_subscr(struct gsm_auth_info *ainfo, + struct gsm_subscriber *subscr); +int db_get_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple, + struct gsm_subscriber *subscr); +int db_sync_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple, + struct gsm_subscriber *subscr); + +/* SMS store-and-forward */ +int db_sms_store(struct gsm_sms *sms); +struct gsm_sms *db_sms_get(struct gsm_network *net, unsigned long long id); +struct gsm_sms *db_sms_get_unsent(struct gsm_network *net, unsigned long long min_id); +struct gsm_sms *db_sms_get_unsent_by_subscr(struct gsm_network *net, unsigned long long min_subscr_id, unsigned int failed); +struct gsm_sms *db_sms_get_unsent_for_subscr(struct gsm_subscriber *subscr); +int db_sms_mark_sent(struct gsm_sms *sms); +int db_sms_inc_deliver_attempts(struct gsm_sms *sms); + +/* APDU blob storage */ +int db_apdu_blob_store(struct gsm_subscriber *subscr, + u_int8_t apdu_id_flags, u_int8_t len, + u_int8_t *apdu); + +/* Statistics counter storage */ +struct counter; +int db_store_counter(struct counter *ctr); +struct rate_ctr_group; +int db_store_rate_ctr_group(struct rate_ctr_group *ctrg); + +#endif /* _DB_H */ diff --git a/include/openbsc/debug.h b/include/openbsc/debug.h new file mode 100644 index 000000000..eb290e416 --- /dev/null +++ b/include/openbsc/debug.h @@ -0,0 +1,70 @@ +#ifndef _DEBUG_H +#define _DEBUG_H + +#include +#include + +#define DEBUG +#include + +/* Debug Areas of the code */ +enum { + DRLL, + DCC, + DMM, + DRR, + DRSL, + DNM, + DMNCC, + DSMS, + DPAG, + DMEAS, + DMI, + DMIB, + DMUX, + DINP, + DSCCP, + DMSC, + DMGCP, + DHO, + DDB, + DREF, + DGPRS, + DNS, + DBSSGP, + DLLC, + DSNDCP, + DNAT, + Debug_LastEntry, +}; + +/* context */ +#define BSC_CTX_LCHAN 0 +#define BSC_CTX_SUBSCR 1 +#define BSC_CTX_BTS 2 +#define BSC_CTX_SCCP 3 +#define BSC_CTX_NSVC 4 +#define BSC_CTX_BVC 5 + +/* target */ + +enum { + //DEBUG_FILTER_ALL = 1 << 0, + LOG_FILTER_IMSI = 1 << 1, + LOG_FILTER_NSVC = 1 << 2, + LOG_FILTER_BVC = 1 << 3, +}; + +/* we don't need a header dependency for this... */ +struct gprs_nsvc; +struct bssgp_bvc_ctx; + +void log_set_imsi_filter(struct log_target *target, const char *imsi); +void log_set_nsvc_filter(struct log_target *target, + struct gprs_nsvc *nsvc); +void log_set_bvc_filter(struct log_target *target, + struct bssgp_bvc_ctx *bctx); + +extern const struct log_info log_info; + +#endif /* _DEBUG_H */ diff --git a/include/openbsc/e1_input.h b/include/openbsc/e1_input.h new file mode 100644 index 000000000..3c8af3877 --- /dev/null +++ b/include/openbsc/e1_input.h @@ -0,0 +1,187 @@ +#ifndef _E1_INPUT_H +#define _E1_INPUT_H + +#include +#include + +#include +#include +#include +#include +#include + +#define NUM_E1_TS 32 + +enum e1inp_sign_type { + E1INP_SIGN_NONE, + E1INP_SIGN_OML, + E1INP_SIGN_RSL, +}; +const char *e1inp_signtype_name(enum e1inp_sign_type tp); + +struct e1inp_ts; + +struct e1inp_sign_link { + /* list of signalling links */ + struct llist_head list; + + /* to which timeslot do we belong? */ + struct e1inp_ts *ts; + + enum e1inp_sign_type type; + + /* trx for msg->trx of received msgs */ + struct gsm_bts_trx *trx; + + /* msgb queue of to-be-transmitted msgs */ + struct llist_head tx_list; + + /* SAPI and TEI on the E1 TS */ + u_int8_t sapi; + u_int8_t tei; + + union { + struct { + u_int8_t channel; + } misdn; + } driver; +}; + +enum e1inp_ts_type { + E1INP_TS_TYPE_NONE, + E1INP_TS_TYPE_SIGN, + E1INP_TS_TYPE_TRAU, +}; +const char *e1inp_tstype_name(enum e1inp_ts_type tp); + +/* A timeslot in the E1 interface */ +struct e1inp_ts { + enum e1inp_ts_type type; + int num; + + /* to which line do we belong ? */ + struct e1inp_line *line; + + union { + struct { + /* list of all signalling links on this TS */ + struct llist_head sign_links; + /* delay for the queue */ + int delay; + /* timer when to dequeue next frame */ + struct timer_list tx_timer; + } sign; + struct { + /* subchannel demuxer for frames from E1 */ + struct subch_demux demux; + /* subchannel muxer for frames to E1 */ + struct subch_mux mux; + } trau; + }; + union { + struct { + /* mISDN driver has one fd for each ts */ + struct bsc_fd fd; + } misdn; + struct { + /* ip.access driver has one fd for each ts */ + struct bsc_fd fd; + } ipaccess; + struct { + /* DAHDI driver has one fd for each ts */ + struct bsc_fd fd; + struct lapd_instance *lapd; + } dahdi; + } driver; +}; + +struct e1inp_driver { + struct llist_head list; + const char *name; + int (*want_write)(struct e1inp_ts *ts); + int (*line_update)(struct e1inp_line *line); + int default_delay; +}; + +struct e1inp_line { + struct llist_head list; + unsigned int num; + const char *name; + + /* array of timestlots */ + struct e1inp_ts ts[NUM_E1_TS]; + + struct e1inp_driver *driver; + void *driver_data; +}; + +/* register a driver with the E1 core */ +int e1inp_driver_register(struct e1inp_driver *drv); + +/* fine a previously registered driver */ +struct e1inp_driver *e1inp_driver_find(const char *name); + +/* register a line with the E1 core */ +int e1inp_line_register(struct e1inp_line *line); + +/* get a line by its ID */ +struct e1inp_line *e1inp_line_get(u_int8_t e1_nr); + +/* create a line in the E1 input core */ +struct e1inp_line *e1inp_line_create(u_int8_t e1_nr, const char *driver_name); + +/* find a sign_link for given TEI and SAPI in a TS */ +struct e1inp_sign_link * +e1inp_lookup_sign_link(struct e1inp_ts *ts, u_int8_t tei, + u_int8_t sapi); + +/* create a new signalling link in a E1 timeslot */ +struct e1inp_sign_link * +e1inp_sign_link_create(struct e1inp_ts *ts, enum e1inp_sign_type type, + struct gsm_bts_trx *trx, u_int8_t tei, + u_int8_t sapi); + +/* configure and initialize one e1inp_ts */ +int e1inp_ts_config(struct e1inp_ts *ts, struct e1inp_line *line, + enum e1inp_ts_type type); + +/* Call from the Stack: configuration of this TS has changed */ +int e1inp_update_ts(struct e1inp_ts *ts); + +/* Receive a packet from the E1 driver */ +int e1inp_rx_ts(struct e1inp_ts *ts, struct msgb *msg, + u_int8_t tei, u_int8_t sapi); + +/* called by driver if it wants to transmit on a given TS */ +struct msgb *e1inp_tx_ts(struct e1inp_ts *e1i_ts, + struct e1inp_sign_link **sign_link); + +/* called by driver in case some kind of link state event */ +int e1inp_event(struct e1inp_ts *ts, int evt, u_int8_t tei, u_int8_t sapi); + +/* Write LAPD frames to the fd. */ +void e1_set_pcap_fd(int fd); + +/* called by TRAU muxer to obtain the destination mux entity */ +struct subch_mux *e1inp_get_mux(u_int8_t e1_nr, u_int8_t ts_nr); + +void e1inp_sign_link_destroy(struct e1inp_sign_link *link); +int e1inp_line_update(struct e1inp_line *line); + +/* e1_config.c */ +int e1_reconfig_ts(struct gsm_bts_trx_ts *ts); +int e1_reconfig_trx(struct gsm_bts_trx *trx); +int e1_reconfig_bts(struct gsm_bts *bts); + +int ia_config_connect(struct gsm_bts *bts, struct sockaddr_in *sin); +int ipaccess_setup(struct gsm_network *gsmnet); + +extern struct llist_head e1inp_driver_list; +extern struct llist_head e1inp_line_list; + +int e1inp_vty_init(void); +void e1inp_init(void); + +int _abis_nm_sendmsg(struct msgb *msg, int to_trx_oml); + +#endif /* _E1_INPUT_H */ diff --git a/include/openbsc/gb_proxy.h b/include/openbsc/gb_proxy.h new file mode 100644 index 000000000..18ded2295 --- /dev/null +++ b/include/openbsc/gb_proxy.h @@ -0,0 +1,39 @@ +#ifndef _GB_PROXY_H +#define _GB_PROXY_H + +#include + +#include + +#include +#include + +struct gbproxy_config { + /* parsed from config file */ + u_int16_t nsip_sgsn_nsei; + + /* misc */ + struct gprs_ns_inst *nsi; +}; + +extern struct gbproxy_config gbcfg; +extern struct cmd_element show_gbproxy_cmd; + +/* gb_proxy_vty .c */ + +int gbproxy_vty_init(void); +int gbproxy_parse_config(const char *config_file, struct gbproxy_config *cfg); + + +/* gb_proxy.c */ + +/* Main input function for Gb proxy */ +int gbprox_rcvmsg(struct msgb *msg, struct gprs_nsvc *nsvc, uint16_t ns_bvci); + +int gbprox_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data); + +/* Reset all persistent NS-VC's */ +int gbprox_reset_persistent_nsvcs(struct gprs_ns_inst *nsi); + +#endif diff --git a/include/openbsc/gprs_bssgp.h b/include/openbsc/gprs_bssgp.h new file mode 100644 index 000000000..e432cf750 --- /dev/null +++ b/include/openbsc/gprs_bssgp.h @@ -0,0 +1,232 @@ +#ifndef _GPRS_BSSGP_H +#define _GPRS_BSSGP_H + +#include + +/* Section 5.4.1 */ +#define BVCI_SIGNALLING 0x0000 +#define BVCI_PTM 0x0001 + +/* Section 11.3.26 / Table 11.27 */ +enum bssgp_pdu_type { + /* PDUs between RL and BSSGP SAPs */ + BSSGP_PDUT_DL_UNITDATA = 0x00, + BSSGP_PDUT_UL_UNITDATA = 0x01, + BSSGP_PDUT_RA_CAPABILITY = 0x02, + BSSGP_PDUT_PTM_UNITDATA = 0x03, + /* PDUs between GMM SAPs */ + BSSGP_PDUT_PAGING_PS = 0x06, + BSSGP_PDUT_PAGING_CS = 0x07, + BSSGP_PDUT_RA_CAPA_UDPATE = 0x08, + BSSGP_PDUT_RA_CAPA_UPDATE_ACK = 0x09, + BSSGP_PDUT_RADIO_STATUS = 0x0a, + BSSGP_PDUT_SUSPEND = 0x0b, + BSSGP_PDUT_SUSPEND_ACK = 0x0c, + BSSGP_PDUT_SUSPEND_NACK = 0x0d, + BSSGP_PDUT_RESUME = 0x0e, + BSSGP_PDUT_RESUME_ACK = 0x0f, + BSSGP_PDUT_RESUME_NACK = 0x10, + /* PDus between NM SAPs */ + BSSGP_PDUT_BVC_BLOCK = 0x20, + BSSGP_PDUT_BVC_BLOCK_ACK = 0x21, + BSSGP_PDUT_BVC_RESET = 0x22, + BSSGP_PDUT_BVC_RESET_ACK = 0x23, + BSSGP_PDUT_BVC_UNBLOCK = 0x24, + BSSGP_PDUT_BVC_UNBLOCK_ACK = 0x25, + BSSGP_PDUT_FLOW_CONTROL_BVC = 0x26, + BSSGP_PDUT_FLOW_CONTROL_BVC_ACK = 0x27, + BSSGP_PDUT_FLOW_CONTROL_MS = 0x28, + BSSGP_PDUT_FLOW_CONTROL_MS_ACK = 0x29, + BSSGP_PDUT_FLUSH_LL = 0x2a, + BSSGP_PDUT_FLUSH_LL_ACK = 0x2b, + BSSGP_PDUT_LLC_DISCARD = 0x2c, + BSSGP_PDUT_SGSN_INVOKE_TRACE = 0x40, + BSSGP_PDUT_STATUS = 0x41, + /* PDUs between PFM SAP's */ + BSSGP_PDUT_DOWNLOAD_BSS_PFC = 0x50, + BSSGP_PDUT_CREATE_BSS_PFC = 0x51, + BSSGP_PDUT_CREATE_BSS_PFC_ACK = 0x52, + BSSGP_PDUT_CREATE_BSS_PFC_NACK = 0x53, + BSSGP_PDUT_MODIFY_BSS_PFC = 0x54, + BSSGP_PDUT_MODIFY_BSS_PFC_ACK = 0x55, + BSSGP_PDUT_DELETE_BSS_PFC = 0x56, + BSSGP_PDUT_DELETE_BSS_PFC_ACK = 0x57, +}; + +/* Section 10.2.1 and 10.2.2 */ +struct bssgp_ud_hdr { + uint8_t pdu_type; + uint32_t tlli; + uint8_t qos_profile[3]; + uint8_t data[0]; /* TLV's */ +} __attribute__((packed)); + +struct bssgp_normal_hdr { + uint8_t pdu_type; + uint8_t data[0]; /* TLV's */ +}; + +enum bssgp_iei_type { + BSSGP_IE_ALIGNMENT = 0x00, + BSSGP_IE_BMAX_DEFAULT_MS = 0x01, + BSSGP_IE_BSS_AREA_ID = 0x02, + BSSGP_IE_BUCKET_LEAK_RATE = 0x03, + BSSGP_IE_BVCI = 0x04, + BSSGP_IE_BVC_BUCKET_SIZE = 0x05, + BSSGP_IE_BVC_MEASUREMENT = 0x06, + BSSGP_IE_CAUSE = 0x07, + BSSGP_IE_CELL_ID = 0x08, + BSSGP_IE_CHAN_NEEDED = 0x09, + BSSGP_IE_DRX_PARAMS = 0x0a, + BSSGP_IE_EMLPP_PRIO = 0x0b, + BSSGP_IE_FLUSH_ACTION = 0x0c, + BSSGP_IE_IMSI = 0x0d, + BSSGP_IE_LLC_PDU = 0x0e, + BSSGP_IE_LLC_FRAMES_DISCARDED = 0x0f, + BSSGP_IE_LOCATION_AREA = 0x10, + BSSGP_IE_MOBILE_ID = 0x11, + BSSGP_IE_MS_BUCKET_SIZE = 0x12, + BSSGP_IE_MS_RADIO_ACCESS_CAP = 0x13, + BSSGP_IE_OMC_ID = 0x14, + BSSGP_IE_PDU_IN_ERROR = 0x15, + BSSGP_IE_PDU_LIFETIME = 0x16, + BSSGP_IE_PRIORITY = 0x17, + BSSGP_IE_QOS_PROFILE = 0x18, + BSSGP_IE_RADIO_CAUSE = 0x19, + BSSGP_IE_RA_CAP_UPD_CAUSE = 0x1a, + BSSGP_IE_ROUTEING_AREA = 0x1b, + BSSGP_IE_R_DEFAULT_MS = 0x1c, + BSSGP_IE_SUSPEND_REF_NR = 0x1d, + BSSGP_IE_TAG = 0x1e, + BSSGP_IE_TLLI = 0x1f, + BSSGP_IE_TMSI = 0x20, + BSSGP_IE_TRACE_REFERENC = 0x21, + BSSGP_IE_TRACE_TYPE = 0x22, + BSSGP_IE_TRANSACTION_ID = 0x23, + BSSGP_IE_TRIGGER_ID = 0x24, + BSSGP_IE_NUM_OCT_AFF = 0x25, + BSSGP_IE_LSA_ID_LIST = 0x26, + BSSGP_IE_LSA_INFORMATION = 0x27, + BSSGP_IE_PACKET_FLOW_ID = 0x28, + BSSGP_IE_PACKET_FLOW_TIMER = 0x29, + BSSGP_IE_AGG_BSS_QOS_PROFILE = 0x3a, + BSSGP_IE_FEATURE_BITMAP = 0x3b, + BSSGP_IE_BUCKET_FULL_RATIO = 0x3c, + BSSGP_IE_SERVICE_UTRAN_CCO = 0x3d, +}; + +/* Section 11.3.8 / Table 11.10: Cause coding */ +enum gprs_bssgp_cause { + BSSGP_CAUSE_PROC_OVERLOAD = 0x00, + BSSGP_CAUSE_EQUIP_FAIL = 0x01, + BSSGP_CAUSE_TRASIT_NET_FAIL = 0x02, + BSSGP_CAUSE_CAPA_GREATER_0KPBS = 0x03, + BSSGP_CAUSE_UNKNOWN_MS = 0x04, + BSSGP_CAUSE_UNKNOWN_BVCI = 0x05, + BSSGP_CAUSE_CELL_TRAF_CONG = 0x06, + BSSGP_CAUSE_SGSN_CONG = 0x07, + BSSGP_CAUSE_OML_INTERV = 0x08, + BSSGP_CAUSE_BVCI_BLOCKED = 0x09, + BSSGP_CAUSE_PFC_CREATE_FAIL = 0x0a, + BSSGP_CAUSE_SEM_INCORR_PDU = 0x20, + BSSGP_CAUSE_INV_MAND_INF = 0x21, + BSSGP_CAUSE_MISSING_MAND_IE = 0x22, + BSSGP_CAUSE_MISSING_COND_IE = 0x23, + BSSGP_CAUSE_UNEXP_COND_IE = 0x24, + BSSGP_CAUSE_COND_IE_ERR = 0x25, + BSSGP_CAUSE_PDU_INCOMP_STATE = 0x26, + BSSGP_CAUSE_PROTO_ERR_UNSPEC = 0x27, + BSSGP_CAUSE_PDU_INCOMP_FEAT = 0x28, +}; + +/* Our implementation */ + +/* gprs_bssgp_util.c */ +extern struct gprs_ns_inst *bssgp_nsi; +struct msgb *bssgp_msgb_alloc(void); +const char *bssgp_cause_str(enum gprs_bssgp_cause cause); +/* Transmit a simple response such as BLOCK/UNBLOCK/RESET ACK/NACK */ +int bssgp_tx_simple_bvci(uint8_t pdu_type, uint16_t nsei, + uint16_t bvci, uint16_t ns_bvci); +/* Chapter 10.4.14: Status */ +int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg); + +/* gprs_bssgp.c */ + +#define BVC_S_BLOCKED 0x0001 + +/* The per-BTS context that we keep on the SGSN side of the BSSGP link */ +struct bssgp_bvc_ctx { + struct llist_head list; + + /* parsed RA ID and Cell ID of the remote BTS */ + struct gprs_ra_id ra_id; + uint16_t cell_id; + + /* NSEI and BVCI of underlying Gb link. Together they + * uniquely identify a link to a BTS (5.4.4) */ + uint16_t bvci; + uint16_t nsei; + + uint32_t state; + + struct rate_ctr_group *ctrg; + + /* we might want to add this as a shortcut later, avoiding the NSVC + * lookup for every packet, similar to a routing cache */ + //struct gprs_nsvc *nsvc; +}; +extern struct llist_head bssgp_bvc_ctxts; +/* Find a BTS Context based on parsed RA ID and Cell ID */ +struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t cid); +/* Find a BTS context based on BVCI+NSEI tuple */ +struct bssgp_bvc_ctx *btsctx_by_bvci_nsei(uint16_t bvci, uint16_t nsei); + +#include + +/* BSSGP-UL-UNITDATA.ind */ +int gprs_bssgp_rcvmsg(struct msgb *msg); + +/* BSSGP-DL-UNITDATA.req */ +struct sgsn_mm_ctx; +int gprs_bssgp_tx_dl_ud(struct msgb *msg, struct sgsn_mm_ctx *mmctx); + +uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf); + +/* Wrapper around TLV parser to parse BSSGP IEs */ +static inline int bssgp_tlv_parse(struct tlv_parsed *tp, uint8_t *buf, int len) +{ + return tlv_parse(tp, &tvlv_att_def, buf, len, 0, 0); +} + +enum bssgp_paging_mode { + BSSGP_PAGING_PS, + BSSGP_PAGING_CS, +}; + +enum bssgp_paging_scope { + BSSGP_PAGING_BSS_AREA, /* all cells in BSS */ + BSSGP_PAGING_LOCATION_AREA, /* all cells in LA */ + BSSGP_PAGING_ROUTEING_AREA, /* all cells in RA */ + BSSGP_PAGING_BVCI, /* one cell */ +}; + +struct bssgp_paging_info { + enum bssgp_paging_mode mode; + enum bssgp_paging_scope scope; + struct gprs_ra_id raid; + uint16_t bvci; + const char *imsi; + uint32_t *ptmsi; + uint16_t drx_params; + uint8_t qos[3]; +}; + +/* Send a single GMM-PAGING.req to a given NSEI/NS-BVCI */ +int gprs_bssgp_tx_paging(uint16_t nsei, uint16_t ns_bvci, + struct bssgp_paging_info *pinfo); + +/* gprs_bssgp_vty.c */ +int gprs_bssgp_vty_init(void); + +#endif /* _GPRS_BSSGP_H */ diff --git a/include/openbsc/gprs_gmm.h b/include/openbsc/gprs_gmm.h new file mode 100644 index 000000000..bd129ae4d --- /dev/null +++ b/include/openbsc/gprs_gmm.h @@ -0,0 +1,19 @@ +#ifndef _GPRS_GMM_H +#define _GPRS_GMM_H + +#include +#include + +int gsm48_tx_gsm_deact_pdp_req(struct sgsn_pdp_ctx *pdp, uint8_t sm_cause); +int gsm48_tx_gsm_act_pdp_rej(struct sgsn_mm_ctx *mm, uint8_t tid, + uint8_t cause, uint8_t pco_len, uint8_t *pco_v); +int gsm48_tx_gsm_act_pdp_acc(struct sgsn_pdp_ctx *pdp); +int gsm48_tx_gsm_deact_pdp_acc(struct sgsn_pdp_ctx *pdp); + +int gsm0408_gprs_rcvmsg(struct msgb *msg, struct gprs_llc_llme *llme); + +int gprs_gmm_rx_suspend(struct gprs_ra_id *raid, uint32_t tlli); +int gprs_gmm_rx_resume(struct gprs_ra_id *raid, uint32_t tlli, + uint8_t suspend_ref); + +#endif /* _GPRS_GMM_H */ diff --git a/include/openbsc/gprs_llc.h b/include/openbsc/gprs_llc.h new file mode 100644 index 000000000..02945e164 --- /dev/null +++ b/include/openbsc/gprs_llc.h @@ -0,0 +1,162 @@ +#ifndef _GPRS_LLC_H +#define _GPRS_LLC_H + +#include +#include + +/* Section 4.7 LLC Layer Structure */ +enum gprs_llc_sapi { + GPRS_SAPI_GMM = 1, + GPRS_SAPI_TOM2 = 2, + GPRS_SAPI_SNDCP3 = 3, + GPRS_SAPI_SNDCP5 = 5, + GPRS_SAPI_SMS = 7, + GPRS_SAPI_TOM8 = 8, + GPRS_SAPI_SNDCP9 = 9, + GPRS_SAPI_SNDCP11 = 11, +}; + +/* Section 6.4 Commands and Responses */ +enum gprs_llc_u_cmd { + GPRS_LLC_U_DM_RESP = 0x01, + GPRS_LLC_U_DISC_CMD = 0x04, + GPRS_LLC_U_UA_RESP = 0x06, + GPRS_LLC_U_SABM_CMD = 0x07, + GPRS_LLC_U_FRMR_RESP = 0x08, + GPRS_LLC_U_XID = 0x0b, + GPRS_LLC_U_NULL_CMD = 0x00, +}; + +/* TS 04.64 Section 7.1.2 Table 7: LLC layer primitives (GMM/SNDCP/SMS/TOM) */ +/* TS 04.65 Section 5.1.2 Table 2: Service primitives used by SNDCP */ +enum gprs_llc_primitive { + /* GMM <-> LLME */ + LLGMM_ASSIGN_REQ, /* GMM tells us new TLLI: TLLI old, TLLI new, Kc, CiphAlg */ + LLGMM_RESET_REQ, /* GMM tells us to perform XID negotiation: TLLI */ + LLGMM_RESET_CNF, /* LLC informs GMM that XID has completed: TLLI */ + LLGMM_SUSPEND_REQ, /* GMM tells us MS has suspended: TLLI, Page */ + LLGMM_RESUME_REQ, /* GMM tells us MS has resumed: TLLI */ + LLGMM_PAGE_IND, /* LLC asks GMM to page MS: TLLI */ + LLGMM_IOV_REQ, /* GMM tells us to perform XID: TLLI */ + LLGMM_STATUS_IND, /* LLC informs GMM about error: TLLI, Cause */ + /* LLE <-> (GMM/SNDCP/SMS/TOM) */ + LL_RESET_IND, /* TLLI */ + LL_ESTABLISH_REQ, /* TLLI, XID Req */ + LL_ESTABLISH_IND, /* TLLI, XID Req, N201-I, N201-U */ + LL_ESTABLISH_RESP, /* TLLI, XID Negotiated */ + LL_ESTABLISH_CONF, /* TLLI, XID Neg, N201-i, N201-U */ + LL_RELEASE_REQ, /* TLLI, Local */ + LL_RELEASE_IND, /* TLLI, Cause */ + LL_RELEASE_CONF, /* TLLI */ + LL_XID_REQ, /* TLLI, XID Requested */ + LL_XID_IND, /* TLLI, XID Req, N201-I, N201-U */ + LL_XID_RESP, /* TLLI, XID Negotiated */ + LL_XID_CONF, /* TLLI, XID Neg, N201-I, N201-U */ + LL_DATA_REQ, /* TLLI, SN-PDU, Ref, QoS, Radio Prio, Ciph */ + LL_DATA_IND, /* TLLI, SN-PDU */ + LL_DATA_CONF, /* TLLI, Ref */ + LL_UNITDATA_REQ, /* TLLI, SN-PDU, Ref, QoS, Radio Prio, Ciph */ + LL_UNITDATA_IND, /* TLLI, SN-PDU */ + LL_STATUS_IND, /* TLLI, Cause */ +}; + +/* Section 4.5.2 Logical Link States + Annex C.2 */ +enum gprs_llc_lle_state { + GPRS_LLES_UNASSIGNED = 1, /* No TLLI yet */ + GPRS_LLES_ASSIGNED_ADM = 2, /* TLLI assigned */ + GPRS_LLES_LOCAL_EST = 3, /* Local Establishment */ + GPRS_LLES_REMOTE_EST = 4, /* Remote Establishment */ + GPRS_LLES_ABM = 5, + GPRS_LLES_LOCAL_REL = 6, /* Local Release */ + GPRS_LLES_TIMER_REC = 7, /* Timer Recovery */ +}; + +enum gprs_llc_llme_state { + GPRS_LLMS_UNASSIGNED = 1, /* No TLLI yet */ + GPRS_LLMS_ASSIGNED = 2, /* TLLI assigned */ +}; + +/* Section 8.9.9 LLC layer parameter default values */ +struct gprs_llc_params { + uint16_t iov_i_exp; + uint16_t t200_201; + uint16_t n200; + uint16_t n201_u; + uint16_t n201_i; + uint16_t mD; + uint16_t mU; + uint16_t kD; + uint16_t kU; +}; + +/* Section 4.7.1: Logical Link Entity: One per DLCI (TLLI + SAPI) */ +struct gprs_llc_lle { + struct llist_head list; + + uint32_t sapi; + + struct gprs_llc_llme *llme; + + enum gprs_llc_lle_state state; + + struct timer_list t200; + struct timer_list t201; /* wait for acknowledgement */ + + uint16_t v_sent; + uint16_t v_ack; + uint16_t v_recv; + + uint16_t vu_send; + uint16_t vu_recv; + + /* Overflow Counter for ABM */ + uint32_t oc_i_send; + uint32_t oc_i_recv; + + /* Overflow Counter for unconfirmed transfer */ + uint32_t oc_ui_send; + uint32_t oc_ui_recv; + + unsigned int retrans_ctr; + + struct gprs_llc_params params; +}; + +#define NUM_SAPIS 16 + +struct gprs_llc_llme { + struct llist_head list; + + enum gprs_llc_llme_state state; + + uint32_t tlli; + uint32_t old_tlli; + + /* Crypto parameters */ + enum gprs_ciph_algo algo; + uint8_t kc[8]; + + /* over which BSSGP BTS ctx do we need to transmit */ + uint16_t bvci; + uint16_t nsei; + struct gprs_llc_lle lle[NUM_SAPIS]; +}; + +extern struct llist_head gprs_llc_llmes; + +/* BSSGP-UL-UNITDATA.ind */ +int gprs_llc_rcvmsg(struct msgb *msg, struct tlv_parsed *tv); + +/* LL-UNITDATA.req */ +int gprs_llc_tx_ui(struct msgb *msg, uint8_t sapi, int command, + void *mmctx); + +/* 04.64 Chapter 7.2.1.1 LLGMM-ASSIGN */ +int gprs_llgmm_assign(struct gprs_llc_llme *llme, + uint32_t old_tlli, uint32_t new_tlli, + enum gprs_ciph_algo alg, const uint8_t *kc); + +int gprs_llc_init(const char *cipher_plugin_path); +int gprs_llc_vty_init(void); + +#endif diff --git a/include/openbsc/gprs_ns.h b/include/openbsc/gprs_ns.h new file mode 100644 index 000000000..953c364b8 --- /dev/null +++ b/include/openbsc/gprs_ns.h @@ -0,0 +1,232 @@ +#ifndef _GPRS_NS_H +#define _GPRS_NS_H + +#include + +/* GPRS Networks Service (NS) messages on the Gb interface + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) + * 3GPP TS 48.016 version 6.5.0 Release 6 / ETSI TS 148 016 V6.5.0 (2005-11) */ + +struct gprs_ns_hdr { + uint8_t pdu_type; + uint8_t data[0]; +} __attribute__((packed)); + +/* TS 08.16, Section 10.3.7, Table 14 */ +enum ns_pdu_type { + NS_PDUT_UNITDATA = 0x00, + NS_PDUT_RESET = 0x02, + NS_PDUT_RESET_ACK = 0x03, + NS_PDUT_BLOCK = 0x04, + NS_PDUT_BLOCK_ACK = 0x05, + NS_PDUT_UNBLOCK = 0x06, + NS_PDUT_UNBLOCK_ACK = 0x07, + NS_PDUT_STATUS = 0x08, + NS_PDUT_ALIVE = 0x0a, + NS_PDUT_ALIVE_ACK = 0x0b, + /* TS 48.016 Section 10.3.7, Table 10.3.7.1 */ + SNS_PDUT_ACK = 0x0c, + SNS_PDUT_ADD = 0x0d, + SNS_PDUT_CHANGE_WEIGHT = 0x0e, + SNS_PDUT_CONFIG = 0x0f, + SNS_PDUT_CONFIG_ACK = 0x10, + SNS_PDUT_DELETE = 0x11, + SNS_PDUT_SIZE = 0x12, + SNS_PDUT_SIZE_ACK = 0x13, +}; + +/* TS 08.16, Section 10.3, Table 12 */ +enum ns_ctrl_ie { + NS_IE_CAUSE = 0x00, + NS_IE_VCI = 0x01, + NS_IE_PDU = 0x02, + NS_IE_BVCI = 0x03, + NS_IE_NSEI = 0x04, + /* TS 48.016 Section 10.3, Table 10.3.1 */ + NS_IE_IPv4_LIST = 0x05, + NS_IE_IPv6_LIST = 0x06, + NS_IE_MAX_NR_NSVC = 0x07, + NS_IE_IPv4_EP_NR = 0x08, + NS_IE_IPv6_EP_NR = 0x09, + NS_IE_RESET_FLAG = 0x0a, + NS_IE_IP_ADDR = 0x0b, +}; + +/* TS 08.16, Section 10.3.2, Table 13 */ +enum ns_cause { + NS_CAUSE_TRANSIT_FAIL = 0x00, + NS_CAUSE_OM_INTERVENTION = 0x01, + NS_CAUSE_EQUIP_FAIL = 0x02, + NS_CAUSE_NSVC_BLOCKED = 0x03, + NS_CAUSE_NSVC_UNKNOWN = 0x04, + NS_CAUSE_BVCI_UNKNOWN = 0x05, + NS_CAUSE_SEM_INCORR_PDU = 0x08, + NS_CAUSE_PDU_INCOMP_PSTATE = 0x0a, + NS_CAUSE_PROTO_ERR_UNSPEC = 0x0b, + NS_CAUSE_INVAL_ESSENT_IE = 0x0c, + NS_CAUSE_MISSING_ESSENT_IE = 0x0d, + /* TS 48.016 Section 10.3.2, Table 10.3.2.1 */ + NS_CAUSE_INVAL_NR_IPv4_EP = 0x0e, + NS_CAUSE_INVAL_NR_IPv6_EP = 0x0f, + NS_CAUSE_INVAL_NR_NS_VC = 0x10, + NS_CAUSE_INVAL_WEIGH = 0x11, + NS_CAUSE_UNKN_IP_EP = 0x12, + NS_CAUSE_UNKN_IP_ADDR = 0x13, + NS_CAUSE_UNKN_IP_TEST_FAILED = 0x14, +}; + +/* Our Implementation */ +#include +#include +#include +#include +#include + +#define NS_TIMERS_COUNT 7 +#define NS_TIMERS "(tns-block|tns-block-retries|tns-reset|tns-reset-retries|tns-test|tns-alive|tns-alive-retries)" +#define NS_TIMERS_HELP \ + "(un)blocking Timer (Tns-block) timeout\n" \ + "(un)blocking Timer (Tns-block) number of retries\n" \ + "Reset Timer (Tns-reset) timeout\n" \ + "Reset Timer (Tns-reset) number of retries\n" \ + "Test Timer (Tns-test) timeout\n" \ + +enum ns_timeout { + NS_TOUT_TNS_BLOCK, + NS_TOUT_TNS_BLOCK_RETRIES, + NS_TOUT_TNS_RESET, + NS_TOUT_TNS_RESET_RETRIES, + NS_TOUT_TNS_TEST, + NS_TOUT_TNS_ALIVE, + NS_TOUT_TNS_ALIVE_RETRIES, +}; + +#define NSE_S_BLOCKED 0x0001 +#define NSE_S_ALIVE 0x0002 + +enum gprs_ns_ll { + GPRS_NS_LL_UDP, + GPRS_NS_LL_E1, + GPRS_NS_LL_FR_GRE, +}; + +enum gprs_ns_evt { + GPRS_NS_EVT_UNIT_DATA, +}; + +struct gprs_nsvc; +typedef int gprs_ns_cb_t(enum gprs_ns_evt event, struct gprs_nsvc *nsvc, + struct msgb *msg, uint16_t bvci); + +/* An instance of the NS protocol stack */ +struct gprs_ns_inst { + /* callback to the user for incoming UNIT DATA IND */ + gprs_ns_cb_t *cb; + + /* linked lists of all NSVC in this instance */ + struct llist_head gprs_nsvcs; + + /* a NSVC object that's needed to deal with packets for unknown NSVC */ + struct gprs_nsvc *unknown_nsvc; + + uint16_t timeout[NS_TIMERS_COUNT]; + + /* NS-over-IP specific bits */ + struct { + struct bsc_fd fd; + uint32_t local_ip; + uint16_t local_port; + } nsip; + /* NS-over-FR-over-GRE-over-IP specific bits */ + struct { + struct bsc_fd fd; + uint32_t local_ip; + int enabled:1; + } frgre; +}; + +enum nsvc_timer_mode { + /* standard timers */ + NSVC_TIMER_TNS_TEST, + NSVC_TIMER_TNS_ALIVE, + NSVC_TIMER_TNS_RESET, + _NSVC_TIMER_NR, +}; + +struct gprs_nsvc { + struct llist_head list; + struct gprs_ns_inst *nsi; + + uint16_t nsei; /* end-to-end significance */ + uint16_t nsvci; /* uniquely identifies NS-VC at SGSN */ + + uint32_t state; + uint32_t remote_state; + + struct timer_list timer; + enum nsvc_timer_mode timer_mode; + int alive_retries; + + unsigned int remote_end_is_sgsn:1; + unsigned int persistent:1; + + struct rate_ctr_group *ctrg; + + /* which link-layer are we based on? */ + enum gprs_ns_ll ll; + + union { + struct { + struct sockaddr_in bts_addr; + } ip; + struct { + struct sockaddr_in bts_addr; + } frgre; + }; +}; + +/* Create a new NS protocol instance */ +struct gprs_ns_inst *gprs_ns_instantiate(gprs_ns_cb_t *cb); + +/* Destroy a NS protocol instance */ +void gprs_ns_destroy(struct gprs_ns_inst *nsi); + +/* Listen for incoming GPRS packets via NS/UDP */ +int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi); + +struct sockaddr_in; + +/* main function for higher layers (BSSGP) to send NS messages */ +int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg); + +int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause); +int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause); +int gprs_ns_tx_unblock(struct gprs_nsvc *nsvc); + +/* Listen for incoming GPRS packets via NS/FR/GRE */ +int gprs_ns_frgre_listen(struct gprs_ns_inst *nsi); + +/* Establish a connection (from the BSS) to the SGSN */ +struct gprs_nsvc *nsip_connect(struct gprs_ns_inst *nsi, + struct sockaddr_in *dest, uint16_t nsei, + uint16_t nsvci); + +struct gprs_nsvc *nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci); +void nsvc_delete(struct gprs_nsvc *nsvc); +struct gprs_nsvc *nsvc_by_nsei(struct gprs_ns_inst *nsi, uint16_t nsei); +struct gprs_nsvc *nsvc_by_nsvci(struct gprs_ns_inst *nsi, uint16_t nsvci); + +/* Initiate a RESET procedure (including timer start, ...)*/ +void gprs_nsvc_reset(struct gprs_nsvc *nsvc, uint8_t cause); + +/* Add NS-specific VTY stuff */ +int gprs_ns_vty_init(struct gprs_ns_inst *nsi); + +#define NS_ALLOC_SIZE 2048 +#define NS_ALLOC_HEADROOM 20 +static inline struct msgb *gprs_ns_msgb_alloc(void) +{ + return msgb_alloc_headroom(NS_ALLOC_SIZE, NS_ALLOC_HEADROOM, "GPRS/NS"); +} + +#endif diff --git a/include/openbsc/gprs_ns_frgre.h b/include/openbsc/gprs_ns_frgre.h new file mode 100644 index 000000000..abcd43ffb --- /dev/null +++ b/include/openbsc/gprs_ns_frgre.h @@ -0,0 +1,6 @@ +#ifndef _GPRS_NS_FRGRE_H +#define _GPRS_NS_FRGRE_H + +int gprs_ns_frgre_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg); + +#endif diff --git a/include/openbsc/gprs_sgsn.h b/include/openbsc/gprs_sgsn.h new file mode 100644 index 000000000..b470c536c --- /dev/null +++ b/include/openbsc/gprs_sgsn.h @@ -0,0 +1,220 @@ +#ifndef _GPRS_SGSN_H +#define _GPRS_SGSN_H + +#include +#include + +#include + +#include + +#define GSM_IMSI_LENGTH 17 +#define GSM_IMEI_LENGTH 17 +#define GSM_EXTENSION_LENGTH 15 + +struct gprs_llc_lle; + +/* TS 04.08 4.1.3.3 GMM mobility management states on the network side */ +enum gprs_mm_state { + GMM_DEREGISTERED, /* 4.1.3.3.1.1 */ + GMM_COMMON_PROC_INIT, /* 4.1.3.3.1.2 */ + GMM_REGISTERED_NORMAL, /* 4.1.3.3.2.1 */ + GMM_REGISTERED_SUSPENDED, /* 4.1.3.3.2.2 */ + GMM_DEREGISTERED_INIT, /* 4.1.3.3.1.4 */ +}; + +enum gprs_mm_ctr { + GMM_CTR_PKTS_SIG_IN, + GMM_CTR_PKTS_SIG_OUT, + GMM_CTR_PKTS_UDATA_IN, + GMM_CTR_PKTS_UDATA_OUT, + GMM_CTR_BYTES_UDATA_IN, + GMM_CTR_BYTES_UDATA_OUT, + GMM_CTR_PDP_CTX_ACT, + GMM_CTR_SUSPEND, + GMM_CTR_PAGING_PS, + GMM_CTR_PAGING_CS, + GMM_CTR_RA_UPDATE, +}; + +enum gprs_pdp_ctx { + PDP_CTR_PKTS_UDATA_IN, + PDP_CTR_PKTS_UDATA_OUT, + PDP_CTR_BYTES_UDATA_IN, + PDP_CTR_BYTES_UDATA_OUT, +}; + +enum gprs_t3350_mode { + GMM_T3350_MODE_ATT, + GMM_T3350_MODE_RAU, + GMM_T3350_MODE_PTMSI_REALL, +}; + +#define MS_RADIO_ACCESS_CAPA + +/* According to TS 03.60, Table 5: SGSN MM and PDP Contexts */ +/* Extended by 3GPP TS 23.060, Table 6: SGSN MM and PDP Contexts */ +struct sgsn_mm_ctx { + struct llist_head list; + + char imsi[GSM_IMSI_LENGTH]; + enum gprs_mm_state mm_state; + uint32_t p_tmsi; + uint32_t p_tmsi_old; /* old P-TMSI before new is confirmed */ + uint32_t p_tmsi_sig; + char imei[GSM_IMEI_LENGTH]; + /* Opt: Software Version Numbber / TS 23.195 */ + char msisdn[GSM_EXTENSION_LENGTH]; + struct gprs_ra_id ra; + uint16_t cell_id; + uint32_t cell_id_age; + uint16_t sac; /* Iu: Service Area Code */ + uint32_t sac_age;/* Iu: Service Area Code age */ + /* VLR number */ + uint32_t new_sgsn_addr; + /* Authentication Triplets */ + /* Kc */ + /* Iu: CK, IK, KSI */ + /* CKSN */ + enum gprs_ciph_algo ciph_algo; + struct { + uint8_t buf[14]; /* 10.5.5.12a */ + uint8_t len; + } ms_radio_access_capa; + struct { + uint8_t buf[4]; /* 10.5.5.12 */ + uint8_t len; + } ms_network_capa; + uint16_t drx_parms; + int mnrg; /* MS reported to HLR? */ + int ngaf; /* MS reported to MSC/VLR? */ + int ppf; /* paging for GPRS + non-GPRS? */ + /* SMS Parameters */ + int recovery; + uint8_t radio_prio_sms; + + struct llist_head pdp_list; + + /* Additional bits not present in the GSM TS */ + struct gprs_llc_llme *llme; + uint32_t tlli; + uint32_t tlli_new; + uint16_t nsei; + uint16_t bvci; + struct rate_ctr_group *ctrg; + struct timer_list timer; + unsigned int T; /* Txxxx number */ + unsigned int num_T_exp; /* number of consecutive T expirations */ + + enum gprs_t3350_mode t3350_mode; + uint8_t t3370_id_type; +}; + +/* look-up a SGSN MM context based on TLLI + RAI */ +struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli(uint32_t tlli, + const struct gprs_ra_id *raid); +struct sgsn_mm_ctx *sgsn_mm_ctx_by_ptmsi(uint32_t tmsi); +struct sgsn_mm_ctx *sgsn_mm_ctx_by_imsi(const char *imsi); + +/* Allocate a new SGSN MM context */ +struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t tlli, + const struct gprs_ra_id *raid); +void sgsn_mm_ctx_free(struct sgsn_mm_ctx *mm); + + +enum pdp_ctx_state { + PDP_STATE_NONE, + PDP_STATE_CR_REQ, + PDP_STATE_CR_CONF, + + /* 04.08 / Figure 6.2 / 6.1.2.2 */ + PDP_STATE_INACT_PEND, + PDP_STATE_INACTIVE = PDP_STATE_NONE, +}; + +enum pdp_type { + PDP_TYPE_NONE, + PDP_TYPE_ETSI_PPP, + PDP_TYPE_IANA_IPv4, + PDP_TYPE_IANA_IPv6, +}; + +struct sgsn_pdp_ctx { + struct llist_head list; /* list_head for mmctx->pdp_list */ + struct llist_head g_list; /* list_head for global list */ + struct sgsn_mm_ctx *mm; /* back pointer to MM CTX */ + struct sgsn_ggsn_ctx *ggsn; /* which GGSN serves this PDP */ + struct rate_ctr_group *ctrg; + + //unsigned int id; + struct pdp_t *lib; /* pointer to libgtp PDP ctx */ + enum pdp_ctx_state state; + enum pdp_type type; + uint32_t address; + char *apn_subscribed; + //char *apn_used; + uint16_t nsapi; /* SNDCP */ + uint16_t sapi; /* LLC */ + uint8_t ti; /* transaction identifier */ + int vplmn_allowed; + uint32_t qos_profile_subscr; + //uint32_t qos_profile_req; + //uint32_t qos_profile_neg; + uint8_t radio_prio; + uint32_t tx_npdu_nr; + uint32_t rx_npdu_nr; + uint32_t tx_gtp_snd; + uint32_t rx_gtp_snu; + //uint32_t charging_id; + int reordering_reqd; + + struct timer_list timer; + unsigned int T; /* Txxxx number */ + unsigned int num_T_exp; /* number of consecutive T expirations */ +}; + + +/* look up PDP context by MM context and NSAPI */ +struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_nsapi(const struct sgsn_mm_ctx *mm, + uint8_t nsapi); +/* look up PDP context by MM context and transaction ID */ +struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_tid(const struct sgsn_mm_ctx *mm, + uint8_t tid); + +struct sgsn_pdp_ctx *sgsn_pdp_ctx_alloc(struct sgsn_mm_ctx *mm, + uint8_t nsapi); +void sgsn_pdp_ctx_free(struct sgsn_pdp_ctx *pdp); + + +struct sgsn_ggsn_ctx { + struct llist_head list; + uint32_t id; + unsigned int gtp_version; + struct in_addr remote_addr; + int remote_restart_ctr; + struct gsn_t *gsn; +}; +struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_alloc(uint32_t id); +struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_id(uint32_t id); +struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_addr(struct in_addr *addr); +struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_find_alloc(uint32_t id); + +struct apn_ctx { + struct llist_head list; + struct sgsn_ggsn_ctx *ggsn; + char *name; + char *description; +}; + +extern struct llist_head sgsn_mm_ctxts; +extern struct llist_head sgsn_ggsn_ctxts; +extern struct llist_head sgsn_apn_ctxts; +extern struct llist_head sgsn_pdp_ctxts; + +uint32_t sgsn_alloc_ptmsi(void); + +/* High-level function to be called in case a GGSN has disappeared or + * ottherwise lost state (recovery procedure) */ +int drop_all_pdp_for_ggsn(struct sgsn_ggsn_ctx *ggsn); + +#endif /* _GPRS_SGSN_H */ diff --git a/include/openbsc/gsm_04_08.h b/include/openbsc/gsm_04_08.h new file mode 100644 index 000000000..1c879edce --- /dev/null +++ b/include/openbsc/gsm_04_08.h @@ -0,0 +1,70 @@ +#ifndef _GSM_04_08_H +#define _GSM_04_08_H + +#include + +#include +#include + +struct msgb; +struct gsm_bts; +struct gsm_subscriber; +struct gsm_network; +struct gsm_trans; +struct gsm_subscriber_connection; + +#define GSM48_ALLOC_SIZE 2048 +#define GSM48_ALLOC_HEADROOM 256 + +static inline struct msgb *gsm48_msgb_alloc(void) +{ + return msgb_alloc_headroom(GSM48_ALLOC_SIZE, GSM48_ALLOC_HEADROOM, + "GSM 04.08"); +} + +/* config options controlling the behaviour of the lower leves */ +void gsm0408_allow_everyone(int allow); +void gsm0408_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause); +void gsm0408_clear_all_trans(struct gsm_network *net, int protocol); +int gsm0408_dispatch(struct gsm_subscriber_connection *conn, struct msgb *msg); + +int gsm0408_rcvmsg(struct msgb *msg, u_int8_t link_id); +int gsm0408_new_conn(struct gsm_subscriber_connection *conn); +enum gsm_chan_t get_ctype_by_chreq(struct gsm_network *bts, u_int8_t ra); +enum gsm_chreq_reason_t get_reason_by_chreq(u_int8_t ra, int neci); +void gsm_net_update_ctype(struct gsm_network *net); + +int gsm48_tx_mm_info(struct gsm_subscriber_connection *conn); +int gsm48_tx_mm_auth_req(struct gsm_subscriber_connection *conn, u_int8_t *rand, int key_seq); +int gsm48_tx_mm_auth_rej(struct gsm_subscriber_connection *conn); +int gsm48_send_rr_release(struct gsm_lchan *lchan); +int gsm48_send_rr_ciph_mode(struct gsm_lchan *lchan, int want_imeisv); +int gsm48_send_rr_app_info(struct gsm_subscriber_connection *conn, u_int8_t apdu_id, + u_int8_t apdu_len, const u_int8_t *apdu); +int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, u_int8_t power_class); +int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan, + u_int8_t power_command, u_int8_t ho_ref); + +int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg); + +/* convert a ASCII phone number to call-control BCD */ +int encode_bcd_number(u_int8_t *bcd_lv, u_int8_t max_len, + int h_len, const char *input); +int decode_bcd_number(char *output, int output_len, const u_int8_t *bcd_lv, + int h_len); + +int send_siemens_mrpci(struct gsm_lchan *lchan, u_int8_t *classmark2_lv); +int gsm48_extract_mi(uint8_t *classmark2, int length, char *mi_string, uint8_t *mi_type); +int gsm48_paging_extract_mi(struct gsm48_pag_resp *pag, int length, char *mi_string, u_int8_t *mi_type); +int gsm48_handle_paging_resp(struct gsm_subscriber_connection *conn, struct msgb *msg, struct gsm_subscriber *subscr); + +int gsm48_lchan_modify(struct gsm_lchan *lchan, u_int8_t lchan_mode); +int gsm48_rx_rr_modif_ack(struct msgb *msg); +int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg); + +struct msgb *gsm48_create_mm_serv_rej(enum gsm48_reject_value value); +struct msgb *gsm48_create_loc_upd_rej(uint8_t cause); +void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd, + const struct gsm_lchan *lchan); + +#endif diff --git a/include/openbsc/gsm_04_08_gprs.h b/include/openbsc/gsm_04_08_gprs.h new file mode 100644 index 000000000..8de636293 --- /dev/null +++ b/include/openbsc/gsm_04_08_gprs.h @@ -0,0 +1,383 @@ +#ifndef _GSM48_GPRS_H +#define _GSM48_GPRS_H + +#include +#include + +/* Table 10.4 / 10.4a, GPRS Mobility Management (GMM) */ +#define GSM48_MT_GMM_ATTACH_REQ 0x01 +#define GSM48_MT_GMM_ATTACH_ACK 0x02 +#define GSM48_MT_GMM_ATTACH_COMPL 0x03 +#define GSM48_MT_GMM_ATTACH_REJ 0x04 +#define GSM48_MT_GMM_DETACH_REQ 0x05 +#define GSM48_MT_GMM_DETACH_ACK 0x06 + +#define GSM48_MT_GMM_RA_UPD_REQ 0x08 +#define GSM48_MT_GMM_RA_UPD_ACK 0x09 +#define GSM48_MT_GMM_RA_UPD_COMPL 0x0a +#define GSM48_MT_GMM_RA_UPD_REJ 0x0b + +#define GSM48_MT_GMM_PTMSI_REALL_CMD 0x10 +#define GSM48_MT_GMM_PTMSI_REALL_COMPL 0x11 +#define GSM48_MT_GMM_AUTH_CIPH_REQ 0x12 +#define GSM48_MT_GMM_AUTH_CIPH_RESP 0x13 +#define GSM48_MT_GMM_AUTH_CIPH_REJ 0x14 +#define GSM48_MT_GMM_ID_REQ 0x15 +#define GSM48_MT_GMM_ID_RESP 0x16 +#define GSM48_MT_GMM_STATUS 0x20 +#define GSM48_MT_GMM_INFO 0x21 + +/* Table 10.4a, GPRS Session Management (GSM) */ +#define GSM48_MT_GSM_ACT_PDP_REQ 0x41 +#define GSM48_MT_GSM_ACT_PDP_ACK 0x42 +#define GSM48_MT_GSM_ACT_PDP_REJ 0x43 +#define GSM48_MT_GSM_REQ_PDP_ACT 0x44 +#define GSM48_MT_GSM_REQ_PDP_ACT_REJ 0x45 +#define GSM48_MT_GSM_DEACT_PDP_REQ 0x46 +#define GSM48_MT_GSM_DEACT_PDP_ACK 0x47 +#define GSM48_MT_GSM_ACT_AA_PDP_REQ 0x50 +#define GSM48_MT_GSM_ACT_AA_PDP_ACK 0x51 +#define GSM48_MT_GSM_ACT_AA_PDP_REJ 0x52 +#define GSM48_MT_GSM_DEACT_AA_PDP_REQ 0x53 +#define GSM48_MT_GSM_DEACT_AA_PDP_ACK 0x54 +#define GSM48_MT_GSM_STATUS 0x55 + +/* Chapter 10.5.5.2 / Table 10.5.135 */ +#define GPRS_ATT_T_ATTACH 1 +#define GPRS_ATT_T_ATT_WHILE_IMSI 2 +#define GPRS_ATT_T_COMBINED 3 + +/* Chapter 10.5.5.5 / Table 10.5.138 */ +#define GPRS_DET_T_MO_GPRS 1 +#define GPRS_DET_T_MO_IMSI 2 +#define GPRS_DET_T_MO_COMBINED 3 +/* Network to MS direction */ +#define GPRS_DET_T_MT_REATT_REQ 1 +#define GPRS_DET_T_MT_REATT_NOTREQ 2 +#define GPRS_DET_T_MT_IMSI 3 + +/* Chapter 10.5.5.18 / Table 105.150 */ +#define GPRS_UPD_T_RA 0 +#define GPRS_UPD_T_RA_LA 1 +#define GPRS_UPD_T_RA_LA_IMSI_ATT 2 +#define GPRS_UPD_T_PERIODIC 3 + +enum gsm48_gprs_ie_mm { + GSM48_IE_GMM_CIPH_CKSN = 0x08, /* 10.5.1.2 */ + GSM48_IE_GMM_TIMER_READY = 0x17, /* 10.5.7.3 */ + GSM48_IE_GMM_ALLOC_PTMSI = 0x18, /* 10.5.1.4 */ + GSM48_IE_GMM_PTMSI_SIG = 0x19, /* 10.5.5.8 */ + GSM48_IE_GMM_AUTH_RAND = 0x21, /* 10.5.3.1 */ + GSM48_IE_GMM_AUTH_SRES = 0x22, /* 10.5.3.2 */ + GSM48_IE_GMM_IMEISV = 0x23, /* 10.5.1.4 */ + GSM48_IE_GMM_DRX_PARAM = 0x27, /* 10.5.5.6 */ + GSM48_IE_GMM_MS_NET_CAPA = 0x31, /* 10.5.5.12 */ + GSM48_IE_GMM_PDP_CTX_STATUS = 0x32, /* 10.5.7.1 */ + GSM48_IE_GMM_PS_LCS_CAPA = 0x33, /* 10.5.5.22 */ + GSM48_IE_GMM_GMM_MBMS_CTX_ST = 0x35, /* 10.5.7.6 */ +}; + +enum gsm48_gprs_ie_sm { + GSM48_IE_GSM_APN = 0x28, /* 10.5.6.1 */ + GSM48_IE_GSM_PROTO_CONF_OPT = 0x27, /* 10.5.6.3 */ + GSM48_IE_GSM_PDP_ADDR = 0x2b, /* 10.5.6.4 */ + GSM48_IE_GSM_AA_TMR = 0x29, /* 10.5.7.3 */ + GSM48_IE_GSM_NAME_FULL = 0x43, /* 10.5.3.5a */ + GSM48_IE_GSM_NAME_SHORT = 0x45, /* 10.5.3.5a */ + GSM48_IE_GSM_TIMEZONE = 0x46, /* 10.5.3.8 */ + GSM48_IE_GSM_UTC_AND_TZ = 0x47, /* 10.5.3.9 */ + GSM48_IE_GSM_LSA_ID = 0x48, /* 10.5.3.11 */ + + /* Fake IEs that are not present on the Layer3 air interface, + * but which we use to simplify internal APIs */ + OSMO_IE_GSM_REQ_QOS = 0xfd, + OSMO_IE_GSM_REQ_PDP_ADDR = 0xfe, +}; + +/* Chapter 9.4.15 / Table 9.4.15 */ +struct gsm48_ra_upd_ack { + uint8_t force_stby:4, /* 10.5.5.7 */ + upd_result:4; /* 10.5.5.17 */ + uint8_t ra_upd_timer; /* 10.5.7.3 */ + struct gsm48_ra_id ra_id; /* 10.5.5.15 */ + uint8_t data[0]; +} __attribute__((packed)); + +/* Chapter 10.5.7.3 */ +enum gsm48_gprs_tmr_unit { + GPRS_TMR_2SECONDS = 0 << 5, + GPRS_TMR_MINUTE = 1 << 5, + GPRS_TMR_6MINUTE = 2 << 5, + GPRS_TMR_DEACTIVATED = 3 << 5, +}; + +/* Chapter 9.4.2 / Table 9.4.2 */ +struct gsm48_attach_ack { + uint8_t att_result:4, /* 10.5.5.7 */ + force_stby:4; /* 10.5.5.1 */ + uint8_t ra_upd_timer; /* 10.5.7.3 */ + uint8_t radio_prio; /* 10.5.7.2 */ + struct gsm48_ra_id ra_id; /* 10.5.5.15 */ + uint8_t data[0]; +} __attribute__((packed)); + +/* Chapter 9.4.9 / Table 9.4.9 */ +struct gsm48_auth_ciph_req { + uint8_t ciph_alg:4, /* 10.5.5.3 */ + imeisv_req:4; /* 10.5.5.10 */ + uint8_t force_stby:4, /* 10.5.5.7 */ + ac_ref_nr:4; /* 10.5.5.19 */ + uint8_t data[0]; +} __attribute__((packed)); +/* optional: TV RAND, TV CKSN */ + +struct gsm48_auth_ciph_resp { + uint8_t ac_ref_nr:4, + spare:4; + uint8_t data[0]; +} __attribute__((packed)); + +/* Chapter 9.5.1 / Table 9.5.1 */ +struct gsm48_act_pdp_ctx_req { + uint8_t req_nsapi; + uint8_t req_llc_sapi; + uint8_t data[0]; +} __attribute__((packed)); + +/* Chapter 10.5.5.14 / Table 10.5.147 */ +enum gsm48_gmm_cause { + GMM_CAUSE_IMSI_UNKNOWN = 0x02, + GMM_CAUSE_ILLEGAL_MS = 0x03, + GMM_CAUSE_ILLEGAL_ME = 0x06, + GMM_CAUSE_GPRS_NOTALLOWED = 0x07, + GMM_CAUSE_GPRS_OTHER_NOTALLOWED = 0x08, + GMM_CAUSE_MS_ID_NOT_DERIVED = 0x09, + GMM_CAUSE_IMPL_DETACHED = 0x0a, + GMM_CAUSE_PLMN_NOTALLOWED = 0x0b, + GMM_CAUSE_LA_NOTALLOWED = 0x0c, + GMM_CAUSE_ROAMING_NOTALLOWED = 0x0d, + GMM_CAUSE_NO_GPRS_PLMN = 0x0e, + GMM_CAUSE_MSC_TEMP_NOTREACH = 0x10, + GMM_CAUSE_NET_FAIL = 0x11, + GMM_CAUSE_CONGESTION = 0x16, + GMM_CAUSE_SEM_INCORR_MSG = 0x5f, + GMM_CAUSE_INV_MAND_INFO = 0x60, + GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL = 0x61, + GMM_CAUSE_MSGT_INCOMP_P_STATE = 0x62, + GMM_CAUSE_IE_NOTEXIST_NOTIMPL = 0x63, + GMM_CAUSE_COND_IE_ERR = 0x64, + GMM_CAUSE_MSG_INCOMP_P_STATE = 0x65, + GMM_CAUSE_PROTO_ERR_UNSPEC = 0x6f, +}; + +/* Chapter 10.4.6.6 / Table 10.5.157 */ +enum gsm48_gsm_cause { + GSM_CAUSE_INSUFF_RSRC = 0x1a, + GSM_CAUSE_MISSING_APN = 0x1b, + GSM_CAUSE_UNKNOWN_PDP = 0x1c, + GSM_CAUSE_AUTH_FAILED = 0x1d, + GSM_CAUSE_ACT_REJ_GGSN = 0x1e, + GSM_CAUSE_ACT_REJ_UNSPEC = 0x1f, + GSM_CAUSE_SERV_OPT_NOTSUPP = 0x20, + GSM_CAUSE_REQ_SERV_OPT_NOTSUB = 0x21, + GSM_CAUSE_SERV_OPT_TEMP_OOO = 0x22, + GSM_CAUSE_NSAPI_IN_USE = 0x23, + GSM_CAUSE_DEACT_REGULAR = 0x24, + GSM_CAUSE_QOS_NOT_ACCEPTED = 0x25, + GSM_CAUSE_NET_FAIL = 0x26, + GSM_CAUSE_REACT_RQD = 0x27, + GSM_CAUSE_FEATURE_NOTSUPP = 0x28, + GSM_CAUSE_INVALID_TRANS_ID = 0x51, + GSM_CAUSE_SEM_INCORR_MSG = 0x5f, + GSM_CAUSE_INV_MAND_INFO = 0x60, + GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL = 0x61, + GSM_CAUSE_MSGT_INCOMP_P_STATE = 0x62, + GSM_CAUSE_IE_NOTEXIST_NOTIMPL = 0x63, + GSM_CAUSE_COND_IE_ERR = 0x64, + GSM_CAUSE_MSG_INCOMP_P_STATE = 0x65, + GSM_CAUSE_PROTO_ERR_UNSPEC = 0x6f, +}; + +/* Section 6.1.2.2: Session management states on the network side */ +enum gsm48_pdp_state { + PDP_S_INACTIVE, + PDP_S_ACTIVE_PENDING, + PDP_S_ACTIVE, + PDP_S_INACTIVE_PENDING, + PDP_S_MODIFY_PENDING, +}; + +/* Table 10.5.155/3GPP TS 24.008 */ +enum gsm48_pdp_type_org { + PDP_TYPE_ORG_ETSI = 0x00, + PDP_TYPE_ORG_IETF = 0x01, +}; +enum gsm48_pdp_type_nr { + PDP_TYPE_N_ETSI_RESERVED = 0x00, + PDP_TYPE_N_ETSI_PPP = 0x01, + PDP_TYPE_N_IETF_IPv4 = 0x21, + PDP_TYPE_N_IETF_IPv6 = 0x57, +}; + +/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */ +enum gsm48_qos_reliab_class { + GSM48_QOS_RC_LLC_ACK_RLC_ACK_DATA_PROT = 2, + GSM48_QOS_RC_LLC_UN_RLC_ACK_DATA_PROT = 3, + GSM48_QOS_RC_LLC_UN_RLC_UN_PROT_DATA = 4, + GSM48_QOS_RC_LLC_UN_RLC_UN_DATA_UN = 5, +}; + +/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */ +enum gsm48_qos_preced_class { + GSM48_QOS_PC_HIGH = 1, + GSM48_QOS_PC_NORMAL = 2, + GSM48_QOS_PC_LOW = 3, +}; + +/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */ +enum gsm48_qos_peak_tput { + GSM48_QOS_PEAK_TPUT_1000bps = 1, + GSM48_QOS_PEAK_TPUT_2000bps = 2, + GSM48_QOS_PEAK_TPUT_4000bps = 3, + GSM48_QOS_PEAK_TPUT_8000bps = 4, + GSM48_QOS_PEAK_TPUT_16000bps = 5, + GSM48_QOS_PEAK_TPUT_32000bps = 6, + GSM48_QOS_PEAK_TPUT_64000bps = 7, + GSM48_QOS_PEAK_TPUT_128000bps = 8, + GSM48_QOS_PEAK_TPUT_256000bps = 9, +}; + +/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */ +enum gsm48_qos_mean_tput { + GSM48_QOS_MEAN_TPUT_100bph = 1, + GSM48_QOS_MEAN_TPUT_200bph = 2, + GSM48_QOS_MEAN_TPUT_500bph = 3, + GSM48_QOS_MEAN_TPUT_1000bph = 4, + GSM48_QOS_MEAN_TPUT_2000bph = 5, + GSM48_QOS_MEAN_TPUT_5000bph = 6, + GSM48_QOS_MEAN_TPUT_10000bph = 7, + GSM48_QOS_MEAN_TPUT_20000bph = 8, + GSM48_QOS_MEAN_TPUT_50000bph = 9, + GSM48_QOS_MEAN_TPUT_100kbph = 10, + GSM48_QOS_MEAN_TPUT_200kbph = 11, + GSM48_QOS_MEAN_TPUT_500kbph = 0xc, + GSM48_QOS_MEAN_TPUT_1Mbph = 0xd, + GSM48_QOS_MEAN_TPUT_2Mbph = 0xe, + GSM48_QOS_MEAN_TPUT_5Mbph = 0xf, + GSM48_QOS_MEAN_TPUT_10Mbph = 0x10, + GSM48_QOS_MEAN_TPUT_20Mbph = 0x11, + GSM48_QOS_MEAN_TPUT_50Mbph = 0x12, + GSM48_QOS_MEAN_TPUT_BEST_EFFORT = 0x1f, +}; + +/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */ +enum gsm48_qos_err_sdu { + GSM48_QOS_ERRSDU_NODETECT = 1, + GSM48_QOS_ERRSDU_YES = 2, + GSM48_QOS_ERRSDU_NO = 3, +}; + +/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */ +enum gsm48_qos_deliv_order { + GSM48_QOS_DO_ORDERED = 1, + GSM48_QOS_DO_UNORDERED = 2, +}; + +/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */ +enum gsm48_qos_traf_class { + GSM48_QOS_TC_CONVERSATIONAL = 1, + GSM48_QOS_TC_STREAMING = 2, + GSM48_QOS_TC_INTERACTIVE = 3, + GSM48_QOS_TC_BACKGROUND = 4, +}; + +/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */ +enum gsm48_qos_max_sdu_size { + /* values below in 10 octet granularity */ + GSM48_QOS_MAXSDU_1502 = 0x97, + GSM48_QOS_MAXSDU_1510 = 0x98, + GSM48_QOS_MAXSDU_1520 = 0x99, +}; + +/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */ +enum gsm48_qos_max_bitrate { + GSM48_QOS_MBRATE_1k = 0x01, + GSM48_QOS_MBRATE_63k = 0x3f, + GSM48_QOS_MBRATE_64k = 0x40, + GSM48_QOS_MBRATE_568k = 0x7f, + GSM48_QOS_MBRATE_576k = 0x80, + GSM48_QOS_MBRATE_8640k = 0xfe, + GSM48_QOS_MBRATE_0k = 0xff, +}; + +/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */ +enum gsm48_qos_resid_ber { + GSM48_QOS_RBER_5e_2 = 0x01, + GSM48_QOS_RBER_1e_2 = 0x02, + GSM48_QOS_RBER_5e_3 = 0x03, + GSM48_QOS_RBER_4e_3 = 0x04, + GSM48_QOS_RBER_1e_3 = 0x05, + GSM48_QOS_RBER_1e_4 = 0x06, + GSM48_QOS_RBER_1e_5 = 0x07, + GSM48_QOS_RBER_1e_6 = 0x08, + GSM48_QOS_RBER_6e_8 = 0x09, +}; + +/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */ +enum gsm48_qos_sdu_err { + GSM48_QOS_SERR_1e_2 = 0x01, + GSM48_QOS_SERR_7e_2 = 0x02, + GSM48_QOS_SERR_1e_3 = 0x03, + GSM48_QOS_SERR_1e_4 = 0x04, + GSM48_QOS_SERR_1e_5 = 0x05, + GSM48_QOS_SERR_1e_6 = 0x06, + GSM48_QOS_SERR_1e_1 = 0x07, +}; + +/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */ +struct gsm48_qos { + /* octet 3 */ + uint8_t reliab_class:3; + uint8_t delay_class:3; + uint8_t spare:2; + /* octet 4 */ + uint8_t preced_class:3; + uint8_t spare2:1; + uint8_t peak_tput:4; + /* octet 5 */ + uint8_t mean_tput:5; + uint8_t spare3:3; + /* octet 6 */ + uint8_t deliv_err_sdu:3; + uint8_t deliv_order:2; + uint8_t traf_class:3; + /* octet 7 */ + uint8_t max_sdu_size; + /* octet 8 */ + uint8_t max_bitrate_up; + /* octet 9 */ + uint8_t max_bitrate_down; + /* octet 10 */ + uint8_t sdu_err_ratio:4; + uint8_t resid_ber:4; + /* octet 11 */ + uint8_t handling_prio:2; + uint8_t xfer_delay:6; + /* octet 12 */ + uint8_t guar_bitrate_up; + /* octet 13 */ + uint8_t guar_bitrate_down; + /* octet 14 */ + uint8_t src_stats_desc:4; + uint8_t sig_ind:1; + uint8_t spare5:3; + /* octet 15 */ + uint8_t max_bitrate_down_ext; + /* octet 16 */ + uint8_t guar_bitrate_down_ext; +}; + + +int gprs_tlli_type(uint32_t tlli); + +#endif /* _GSM48_GPRS_H */ diff --git a/include/openbsc/gsm_04_11.h b/include/openbsc/gsm_04_11.h new file mode 100644 index 000000000..5969788d5 --- /dev/null +++ b/include/openbsc/gsm_04_11.h @@ -0,0 +1,40 @@ +#ifndef _GSM_04_11_H +#define _GSM_04_11_H + +#include + +#define UM_SAPI_SMS 3 /* See GSM 04.05/04.06 */ + +/* SMS deliver PDU */ +struct sms_deliver { + u_int8_t mti:2; /* message type indicator */ + u_int8_t mms:1; /* more messages to send */ + u_int8_t rp:1; /* reply path */ + u_int8_t udhi:1; /* user data header indicator */ + u_int8_t sri:1; /* status report indication */ + u_int8_t *orig_addr; /* originating address */ + u_int8_t pid; /* protocol identifier */ + u_int8_t dcs; /* data coding scheme */ + /* service centre time stamp */ + u_int8_t ud_len; /* user data length */ + u_int8_t *user_data; /* user data */ + + u_int8_t msg_ref; /* message reference */ + u_int8_t *smsc; +}; + +struct msgb; + +int gsm0411_rcv_sms(struct gsm_subscriber_connection *conn, struct msgb *msg); + +struct gsm_sms *sms_alloc(void); +void sms_free(struct gsm_sms *sms); +struct gsm_sms *sms_from_text(struct gsm_subscriber *receiver, int dcs, const char *text); + +void _gsm411_sms_trans_free(struct gsm_trans *trans); +int gsm411_send_sms_subscr(struct gsm_subscriber *subscr, + struct gsm_sms *sms); +int gsm411_send_sms(struct gsm_subscriber_connection *conn, + struct gsm_sms *sms); +void gsm411_sapi_n_reject(struct gsm_subscriber_connection *conn); +#endif diff --git a/include/openbsc/gsm_04_80.h b/include/openbsc/gsm_04_80.h new file mode 100644 index 000000000..796a1c11e --- /dev/null +++ b/include/openbsc/gsm_04_80.h @@ -0,0 +1,20 @@ +#ifndef _GSM_04_80_H +#define _GSM_04_80_H + +#include +#include +#include + +struct gsm_subscriber_connection; + +int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn, + const struct msgb *in_msg, const char* response_text, + const struct ussd_request *req); +int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn, + const struct msgb *msg, + const struct ussd_request *request); + +int gsm0480_send_ussdNotify(struct gsm_subscriber_connection *conn, int level, const char *text); +int gsm0480_send_releaseComplete(struct gsm_subscriber_connection *conn); + +#endif diff --git a/include/openbsc/gsm_data.h b/include/openbsc/gsm_data.h new file mode 100644 index 000000000..ae448c4c1 --- /dev/null +++ b/include/openbsc/gsm_data.h @@ -0,0 +1,879 @@ +#ifndef _GSM_DATA_H +#define _GSM_DATA_H + +#include + +struct osmo_msc_data; +struct osmo_bsc_sccp_con; +struct gsm_sms_queue; + +enum gsm_phys_chan_config { + GSM_PCHAN_NONE, + GSM_PCHAN_CCCH, + GSM_PCHAN_CCCH_SDCCH4, + GSM_PCHAN_TCH_F, + GSM_PCHAN_TCH_H, + GSM_PCHAN_SDCCH8_SACCH8C, + GSM_PCHAN_PDCH, /* GPRS PDCH */ + GSM_PCHAN_TCH_F_PDCH, /* TCH/F if used, PDCH otherwise */ + GSM_PCHAN_UNKNOWN, +}; + +enum gsm_chan_t { + GSM_LCHAN_NONE, + GSM_LCHAN_SDCCH, + GSM_LCHAN_TCH_F, + GSM_LCHAN_TCH_H, + GSM_LCHAN_UNKNOWN, +}; + +/* RRLP mode of operation */ +enum rrlp_mode { + RRLP_MODE_NONE, + RRLP_MODE_MS_BASED, + RRLP_MODE_MS_PREF, + RRLP_MODE_ASS_PREF, +}; + +/* Channel Request reason */ +enum gsm_chreq_reason_t { + GSM_CHREQ_REASON_EMERG, + GSM_CHREQ_REASON_PAG, + GSM_CHREQ_REASON_CALL, + GSM_CHREQ_REASON_LOCATION_UPD, + GSM_CHREQ_REASON_OTHER, +}; + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + + +#define TRX_NR_TS 8 +#define TS_MAX_LCHAN 8 + +#define HARDCODED_ARFCN 123 +#define HARDCODED_TSC 7 +#define HARDCODED_BSIC 0x3f /* NCC = 7 / BCC = 7 */ + +/* for multi-drop config */ +#define HARDCODED_BTS0_TS 1 +#define HARDCODED_BTS1_TS 6 +#define HARDCODED_BTS2_TS 11 + +/* reserved according to GSM 03.03 § 2.4 */ +#define GSM_RESERVED_TMSI 0xFFFFFFFF + +enum gsm_hooks { + GSM_HOOK_NM_SWLOAD, + GSM_HOOK_RR_PAGING, + GSM_HOOK_RR_SECURITY, +}; + +enum gsm_paging_event { + GSM_PAGING_SUCCEEDED, + GSM_PAGING_EXPIRED, + GSM_PAGING_OOM, + GSM_PAGING_BUSY, +}; + +enum bts_gprs_mode { + BTS_GPRS_NONE = 0, + BTS_GPRS_GPRS = 1, + BTS_GPRS_EGPRS = 2, +}; + +#define OBSC_NM_W_ACK_CB(__msgb) (__msgb)->cb[3] + +/* the data structure stored in msgb->cb for openbsc apps */ +struct openbsc_msgb_cb { + unsigned char *bssgph; + unsigned char *llch; + + /* Cell Identifier */ + unsigned char *bssgp_cell_id; + + /* Identifiers of a BTS, equal to 'struct bssgp_bts_ctx' */ + u_int16_t nsei; + u_int16_t bvci; + + /* Identifier of a MS (inside BTS), equal to 'struct sgsn_mm_ctx' */ + u_int32_t tlli; +} __attribute__((packed)); +#define OBSC_MSGB_CB(__msgb) ((struct openbsc_msgb_cb *)&((__msgb)->cb[0])) +#define msgb_tlli(__x) OBSC_MSGB_CB(__x)->tlli +#define msgb_nsei(__x) OBSC_MSGB_CB(__x)->nsei +#define msgb_bvci(__x) OBSC_MSGB_CB(__x)->bvci +#define msgb_gmmh(__x) (__x)->l3h +#define msgb_bssgph(__x) OBSC_MSGB_CB(__x)->bssgph +#define msgb_bssgp_len(__x) ((__x)->tail - (uint8_t *)msgb_bssgph(__x)) +#define msgb_bcid(__x) OBSC_MSGB_CB(__x)->bssgp_cell_id +#define msgb_llch(__x) OBSC_MSGB_CB(__x)->llch + +#define OBSC_LINKID_CB(__msgb) (__msgb)->cb[3] + +enum gsm_security_event { + GSM_SECURITY_NOAVAIL, + GSM_SECURITY_AUTH_FAILED, + GSM_SECURITY_SUCCEEDED, +}; + +struct msgb; +typedef int gsm_cbfn(unsigned int hooknum, + unsigned int event, + struct msgb *msg, + void *data, void *param); + +/* Real authentication information containing Ki */ +enum gsm_auth_algo { + AUTH_ALGO_NONE, + AUTH_ALGO_XOR, + AUTH_ALGO_COMP128v1, +}; + +struct gsm_auth_info { + enum gsm_auth_algo auth_algo; + unsigned int a3a8_ki_len; + u_int8_t a3a8_ki[16]; +}; + +struct gsm_auth_tuple { + int use_count; + int key_seq; + u_int8_t rand[16]; + u_int8_t sres[4]; + u_int8_t kc[8]; +}; +#define GSM_KEY_SEQ_INVAL 7 /* GSM 04.08 - 10.5.1.2 */ + + +struct gsm_lchan; +struct gsm_subscriber; +struct gsm_mncc; +struct rtp_socket; +struct bsc_api; + +/* Network Management State */ +struct gsm_nm_state { + u_int8_t operational; + u_int8_t administrative; + u_int8_t availability; +}; + +/* + * LOCATION UPDATING REQUEST state + * + * Our current operation is: + * - Get imei/tmsi + * - Accept/Reject according to global policy + */ +struct gsm_loc_updating_operation { + struct timer_list updating_timer; + unsigned int waiting_for_imsi : 1; + unsigned int waiting_for_imei : 1; + unsigned int key_seq : 4; +}; + +/* + * AUTHENTICATION/CIPHERING state + */ +struct gsm_security_operation { + struct gsm_auth_tuple atuple; + gsm_cbfn *cb; + void *cb_data; +}; + +/* + * A dummy to keep a connection up for at least + * a couple of seconds to work around MSC issues. + */ +struct gsm_anchor_operation { + struct timer_list timeout; +}; + +/* Maximum number of neighbor cells whose average we track */ +#define MAX_NEIGH_MEAS 10 +/* Maximum size of the averaging window for neighbor cells */ +#define MAX_WIN_NEIGH_AVG 10 + +/* processed neighbor measurements for one cell */ +struct neigh_meas_proc { + u_int16_t arfcn; + u_int8_t bsic; + u_int8_t rxlev[MAX_WIN_NEIGH_AVG]; + unsigned int rxlev_cnt; + u_int8_t last_seen_nr; +}; + +#define MAX_A5_KEY_LEN (128/8) +#define A38_XOR_MIN_KEY_LEN 12 +#define A38_XOR_MAX_KEY_LEN 16 +#define A38_COMP128_KEY_LEN 16 +#define RSL_ENC_ALG_A5(x) (x+1) + +/* is the data link established? who established it? */ +#define LCHAN_SAPI_UNUSED 0 +#define LCHAN_SAPI_MS 1 +#define LCHAN_SAPI_NET 2 + +/* state of a logical channel */ +enum gsm_lchan_state { + LCHAN_S_NONE, /* channel is not active */ + LCHAN_S_ACT_REQ, /* channel activatin requested */ + LCHAN_S_ACTIVE, /* channel is active and operational */ + LCHAN_S_REL_REQ, /* channel release has been requested */ + LCHAN_S_REL_ERR, /* channel is in an error state */ + LCHAN_S_INACTIVE, /* channel is set inactive */ +}; + +/* the per subscriber data for lchan */ +struct gsm_subscriber_connection { + struct llist_head entry; + + /* To whom we are allocated at the moment */ + struct gsm_subscriber *subscr; + + /* + * Operations that have a state and might be pending + */ + struct gsm_loc_updating_operation *loc_operation; + struct gsm_security_operation *sec_operation; + struct gsm_anchor_operation *anch_operation; + + /* Are we part of a special "silent" call */ + int silent_call; + int put_channel; + + /* bsc structures */ + struct osmo_bsc_sccp_con *sccp_con; + + /* back pointers */ + int in_release; + struct gsm_lchan *lchan; + struct gsm_lchan *ho_lchan; + struct gsm_bts *bts; + + /* for assignment handling */ + struct timer_list T10; + struct gsm_lchan *secondary_lchan; + +}; + +struct gsm_lchan { + /* The TS that we're part of */ + struct gsm_bts_trx_ts *ts; + /* The logical subslot number in the TS */ + u_int8_t nr; + /* The logical channel type */ + enum gsm_chan_t type; + /* RSL channel mode */ + enum rsl_cmod_spd rsl_cmode; + /* If TCH, traffic channel mode */ + enum gsm48_chan_mode tch_mode; + /* State */ + enum gsm_lchan_state state; + /* Power levels for MS and BTS */ + u_int8_t bs_power; + u_int8_t ms_power; + /* Encryption information */ + struct { + u_int8_t alg_id; + u_int8_t key_len; + u_int8_t key[MAX_A5_KEY_LEN]; + } encr; + + struct timer_list T3101; + struct timer_list T3111; + struct timer_list error_timer; + + /* AMR bits */ + struct gsm48_multi_rate_conf mr_conf; + + /* Established data link layer services */ + u_int8_t sapis[8]; + int sach_deact; + int release_reason; + + /* GSM Random Access data */ + struct gsm48_req_ref *rqd_ref; + uint8_t rqd_ta; + + /* cache of last measurement reports on this lchan */ + struct gsm_meas_rep meas_rep[6]; + int meas_rep_idx; + + /* table of neighbor cell measurements */ + struct neigh_meas_proc neigh_meas[MAX_NEIGH_MEAS]; + + struct { + u_int32_t bound_ip; + u_int32_t connect_ip; + u_int16_t bound_port; + u_int16_t connect_port; + u_int16_t conn_id; + u_int8_t rtp_payload; + u_int8_t rtp_payload2; + u_int8_t speech_mode; + struct rtp_socket *rtp_socket; + } abis_ip; + + struct gsm_subscriber_connection *conn; +}; + +struct gsm_e1_subslot { + /* Number of E1 link */ + u_int8_t e1_nr; + /* Number of E1 TS inside E1 link */ + u_int8_t e1_ts; + /* Sub-slot within the E1 TS, 0xff if full TS */ + u_int8_t e1_ts_ss; +}; + +#define TS_F_PDCH_MODE 0x1000 +/* One Timeslot in a TRX */ +struct gsm_bts_trx_ts { + struct gsm_bts_trx *trx; + /* number of this timeslot at the TRX */ + u_int8_t nr; + + enum gsm_phys_chan_config pchan; + + unsigned int flags; + struct gsm_nm_state nm_state; + struct tlv_parsed nm_attr; + u_int8_t nm_chan_comb; + + struct { + /* Parameters below are configured by VTY */ + int enabled; + u_int8_t maio; + u_int8_t hsn; + struct bitvec arfcns; + u_int8_t arfcns_data[1024/8]; + /* This is the pre-computed MA for channel assignments */ + struct bitvec ma; + u_int8_t ma_len; /* part of ma_data that is used */ + u_int8_t ma_data[8]; /* 10.5.2.21: max 8 bytes value part */ + } hopping; + + /* To which E1 subslot are we connected */ + struct gsm_e1_subslot e1_link; + + struct gsm_lchan lchan[TS_MAX_LCHAN]; +}; + +/* One TRX in a BTS */ +struct gsm_bts_trx { + /* list header in bts->trx_list */ + struct llist_head list; + + struct gsm_bts *bts; + /* number of this TRX in the BTS */ + u_int8_t nr; + /* human readable name / description */ + char *description; + /* how do we talk RSL with this TRX? */ + struct gsm_e1_subslot rsl_e1_link; + u_int8_t rsl_tei; + struct e1inp_sign_link *rsl_link; + /* Some BTS (specifically Ericsson RBS) have a per-TRX OML Link */ + struct e1inp_sign_link *oml_link; + + struct gsm_nm_state nm_state; + struct tlv_parsed nm_attr; + struct { + struct gsm_nm_state nm_state; + } bb_transc; + + u_int16_t arfcn; + int nominal_power; /* in dBm */ + unsigned int max_power_red; /* in actual dB */ + + union { + struct { + struct { + struct gsm_nm_state nm_state; + } bbsig; + struct { + struct gsm_nm_state nm_state; + } pa; + } bs11; + struct { + unsigned int test_state; + u_int8_t test_nr; + struct rxlev_stats rxlev_stat; + } ipaccess; + }; + struct gsm_bts_trx_ts ts[TRX_NR_TS]; +}; + +#define GSM_BTS_SI(bts, i) (void *)(bts->si_buf[i]) + +enum gsm_bts_type { + GSM_BTS_TYPE_UNKNOWN, + GSM_BTS_TYPE_BS11, + GSM_BTS_TYPE_NANOBTS, + GSM_BTS_TYPE_RBS2000, + GSM_BTS_TYPE_HSL_FEMTO, +}; + +struct vty; + +struct gsm_bts_model { + struct llist_head list; + + enum gsm_bts_type type; + const char *name; + + int (*oml_rcvmsg)(struct msgb *msg); + + void (*config_write_bts)(struct vty *vty, struct gsm_bts *bts); + void (*config_write_trx)(struct vty *vty, struct gsm_bts_trx *trx); + void (*config_write_ts)(struct vty *vty, struct gsm_bts_trx_ts *ts); + + struct tlv_definition nm_att_tlvdef; + + struct bitvec features; + uint8_t _features_data[128/8]; +}; + +enum gsm_bts_features { + BTS_FEAT_HSCSD, + BTS_FEAT_GPRS, + BTS_FEAT_EGPRS, + BTS_FEAT_ECSD, + BTS_FEAT_HOPPING, +}; + +/* + * This keeps track of the paging status of one BTS. It + * includes a number of pending requests, a back pointer + * to the gsm_bts, a timer and some more state. + */ +struct gsm_bts_paging_state { + /* pending requests */ + struct llist_head pending_requests; + struct gsm_bts *bts; + + struct timer_list work_timer; + struct timer_list credit_timer; + + /* free chans needed */ + int free_chans_need; + + /* load */ + u_int16_t available_slots; +}; + +struct gsm_envabtse { + struct gsm_nm_state nm_state; +}; + +struct gsm_bts_gprs_nsvc { + struct gsm_bts *bts; + /* data read via VTY config file, to configure the BTS + * via OML from BSC */ + int id; + u_int16_t nsvci; + u_int16_t local_port; /* on the BTS */ + u_int16_t remote_port; /* on the SGSN */ + u_int32_t remote_ip; /* on the SGSN */ + + struct gsm_nm_state nm_state; +}; + +enum neigh_list_manual_mode { + NL_MODE_AUTOMATIC = 0, + NL_MODE_MANUAL = 1, + NL_MODE_MANUAL_SI5SEP = 2, /* SI2 and SI5 have separate neighbor lists */ +}; + +/* One BTS */ +struct gsm_bts { + /* list header in net->bts_list */ + struct llist_head list; + + struct gsm_network *network; + /* number of ths BTS in network */ + u_int8_t nr; + /* human readable name / description */ + char *description; + /* Cell Identity */ + u_int16_t cell_identity; + /* location area code of this BTS */ + u_int16_t location_area_code; + /* Training Sequence Code */ + u_int8_t tsc; + /* Base Station Identification Code (BSIC) */ + u_int8_t bsic; + /* type of BTS */ + enum gsm_bts_type type; + struct gsm_bts_model *model; + enum gsm_band band; + /* should the channel allocator allocate channels from high TRX to TRX0, + * rather than starting from TRX0 and go upwards? */ + int chan_alloc_reverse; + /* maximum Tx power that the MS is permitted to use in this cell */ + int ms_max_power; + + /* how do we talk OML with this TRX? */ + struct gsm_e1_subslot oml_e1_link; + u_int8_t oml_tei; + struct e1inp_sign_link *oml_link; + + /* Abis network management O&M handle */ + struct abis_nm_h *nmh; + struct gsm_nm_state nm_state; + struct tlv_parsed nm_attr; + + /* number of this BTS on given E1 link */ + u_int8_t bts_nr; + + /* paging state and control */ + struct gsm_bts_paging_state paging; + + /* CCCH is on C0 */ + struct gsm_bts_trx *c0; + + struct { + struct gsm_nm_state nm_state; + } site_mgr; + + enum neigh_list_manual_mode neigh_list_manual_mode; + /* parameters from which we build SYSTEM INFORMATION */ + struct { + struct gsm48_rach_control rach_control; + u_int8_t ncc_permitted; + struct gsm48_cell_sel_par cell_sel_par; + struct gsm48_si_selection_params cell_ro_sel_par; /* rest octet */ + struct gsm48_cell_options cell_options; + struct gsm48_control_channel_descr chan_desc; + struct bitvec neigh_list; + struct bitvec cell_alloc; + struct bitvec si5_neigh_list; + struct { + /* bitmask large enough for all possible ARFCN's */ + u_int8_t neigh_list[1024/8]; + u_int8_t cell_alloc[1024/8]; + /* If the user wants a different neighbor list in SI5 than in SI2 */ + u_int8_t si5_neigh_list[1024/8]; + } data; + } si_common; + + /* do we use static (user-defined) system information messages? (bitmask) */ + uint32_t si_mode_static; + /* bitmask of all SI that are present/valid in si_buf */ + uint32_t si_valid; + /* buffers where we put the pre-computed SI */ + sysinfo_buf_t si_buf[_MAX_SYSINFO_TYPE]; + + /* ip.accesss Unit ID's have Site/BTS/TRX layout */ + union { + struct { + u_int16_t site_id; + u_int16_t bts_id; + u_int32_t flags; + } ip_access; + struct { + struct { + struct gsm_nm_state nm_state; + } cclk; + struct { + struct gsm_nm_state nm_state; + } rack; + struct gsm_envabtse envabtse[4]; + } bs11; + struct { + struct { + struct llist_head conn_groups; + } is; + struct { + struct llist_head conn_groups; + } con; + } rbs2000; + struct { + unsigned long serno; + } hsl; + }; + + /* Not entirely sure how ip.access specific this is */ + struct { + enum bts_gprs_mode mode; + struct { + struct gsm_nm_state nm_state; + u_int16_t nsei; + uint8_t timer[7]; + } nse; + struct { + struct gsm_nm_state nm_state; + u_int16_t bvci; + uint8_t timer[11]; + } cell; + struct gsm_bts_gprs_nsvc nsvc[2]; + u_int8_t rac; + } gprs; + + /* RACH NM values */ + int rach_b_thresh; + int rach_ldavg_slots; + + /* transceivers */ + int num_trx; + struct llist_head trx_list; + + /* Abis NM queue */ + struct llist_head abis_queue; + int abis_nm_pend; +}; + +/* Some statistics of our network */ +struct gsmnet_stats { + struct { + struct counter *total; + struct counter *no_channel; + } chreq; + struct { + struct counter *attempted; + struct counter *no_channel; /* no channel available */ + struct counter *timeout; /* T3103 timeout */ + struct counter *completed; /* HO COMPL received */ + struct counter *failed; /* HO FAIL received */ + } handover; + struct { + struct counter *attach; + struct counter *normal; + struct counter *periodic; + struct counter *detach; + } loc_upd_type; + struct { + struct counter *reject; + struct counter *accept; + } loc_upd_resp; + struct { + struct counter *attempted; + struct counter *detached; + struct counter *completed; + struct counter *expired; + } paging; + struct { + struct counter *submitted; /* MO SMS submissions */ + struct counter *no_receiver; + struct counter *delivered; /* MT SMS deliveries */ + struct counter *rp_err_mem; + struct counter *rp_err_other; + } sms; + struct { + struct counter *mo_setup; + struct counter *mo_connect_ack; + struct counter *mt_setup; + struct counter *mt_connect; + } call; + struct { + struct counter *rf_fail; + struct counter *rll_err; + } chan; + struct { + struct counter *oml_fail; + struct counter *rsl_fail; + } bts; +}; + +enum gsm_auth_policy { + GSM_AUTH_POLICY_CLOSED, /* only subscribers authorized in DB */ + GSM_AUTH_POLICY_ACCEPT_ALL, /* accept everyone, even if not authorized in DB */ + GSM_AUTH_POLICY_TOKEN, /* accept first, send token per sms, then revoke authorization */ +}; + +#define GSM_T3101_DEFAULT 10 +#define GSM_T3113_DEFAULT 60 + +struct gsm_network { + /* global parameters */ + u_int16_t country_code; + u_int16_t network_code; + char *name_long; + char *name_short; + enum gsm_auth_policy auth_policy; + enum gsm48_reject_value reject_cause; + int a5_encryption; + int neci; + int send_mm_info; + struct { + int active; + /* Window RXLEV averaging */ + unsigned int win_rxlev_avg; /* number of SACCH frames */ + /* Window RXQUAL averaging */ + unsigned int win_rxqual_avg; /* number of SACCH frames */ + /* Window RXLEV neighbouring cells averaging */ + unsigned int win_rxlev_avg_neigh; /* number of SACCH frames */ + + /* how often should we check for power budget HO */ + unsigned int pwr_interval; /* SACCH frames */ + /* how much better does a neighbor cell have to be ? */ + unsigned int pwr_hysteresis; /* dBm */ + /* maximum distacne before we try a handover */ + unsigned int max_distance; /* TA values */ + } handover; + + struct gsmnet_stats stats; + + /* layer 4 */ + int (*mncc_recv) (struct gsm_network *net, struct msgb *msg); + struct llist_head upqueue; + struct llist_head trans_list; + struct bsc_api *bsc_api; + + unsigned int num_bts; + struct llist_head bts_list; + + /* timer values */ + int T3101; + int T3103; + int T3105; + int T3107; + int T3109; + int T3111; + int T3113; + int T3115; + int T3117; + int T3119; + int T3122; + int T3141; + + /* Radio Resource Location Protocol (TS 04.31) */ + struct { + enum rrlp_mode mode; + } rrlp; + + /* enable the DTXu and DTXd for this network */ + int dtx_enabled; + + enum gsm_chan_t ctype_by_chreq[16]; + + /* Use a TCH for handling requests of type paging any */ + int pag_any_tch; + + /* MSC data in case we are a true BSC */ + struct osmo_msc_data *msc_data; + int hardcoded_rtp_payload; + + /* subscriber related features */ + int keep_subscr; + struct gsm_sms_queue *sms_queue; +}; + +#define SMS_HDR_SIZE 128 +#define SMS_TEXT_SIZE 256 +struct gsm_sms { + unsigned long long id; + struct gsm_subscriber *sender; + struct gsm_subscriber *receiver; + + unsigned long validity_minutes; + u_int8_t reply_path_req; + u_int8_t status_rep_req; + u_int8_t ud_hdr_ind; + u_int8_t protocol_id; + u_int8_t data_coding_scheme; + u_int8_t msg_ref; + char dest_addr[20+1]; /* DA LV is 12 bytes max, i.e. 10 bytes + * BCD == 20 bytes string */ + u_int8_t user_data_len; + u_int8_t user_data[SMS_TEXT_SIZE]; + + char text[SMS_TEXT_SIZE]; +}; + + +struct gsm_network *gsm_network_init(u_int16_t country_code, u_int16_t network_code, + int (*mncc_recv)(struct gsm_network *, struct msgb *)); +struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, enum gsm_bts_type type, + u_int8_t tsc, u_int8_t bsic); +struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts); +int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type); + +struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num); + +/* Get reference to a neighbor cell on a given BCCH ARFCN */ +struct gsm_bts *gsm_bts_neighbor(const struct gsm_bts *bts, + u_int16_t arfcn, u_int8_t bsic); + +struct gsm_bts_trx *gsm_bts_trx_num(struct gsm_bts *bts, int num); + +const char *gsm_pchan_name(enum gsm_phys_chan_config c); +enum gsm_phys_chan_config gsm_pchan_parse(const char *name); +const char *gsm_lchant_name(enum gsm_chan_t c); +const char *gsm_chreq_name(enum gsm_chreq_reason_t c); +char *gsm_trx_name(struct gsm_bts_trx *trx); +char *gsm_ts_name(struct gsm_bts_trx_ts *ts); +char *gsm_lchan_name(struct gsm_lchan *lchan); +const char *gsm_lchans_name(enum gsm_lchan_state s); + +void set_ts_e1link(struct gsm_bts_trx_ts *ts, u_int8_t e1_nr, + u_int8_t e1_ts, u_int8_t e1_ts_ss); +enum gsm_bts_type parse_btstype(const char *arg); +const char *btstype2str(enum gsm_bts_type type); +struct gsm_bts_trx *gsm_bts_trx_by_nr(struct gsm_bts *bts, int nr); +struct gsm_bts *gsm_bts_by_lac(struct gsm_network *net, unsigned int lac, + struct gsm_bts *start_bts); + +extern void *tall_bsc_ctx; +extern int ipacc_rtp_direct; + +static inline int is_ipaccess_bts(struct gsm_bts *bts) +{ + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + return 1; + default: + break; + } + return 0; +} + +static inline int is_siemens_bts(struct gsm_bts *bts) +{ + switch (bts->type) { + case GSM_BTS_TYPE_BS11: + return 1; + default: + break; + } + + return 0; +} + + +enum gsm_auth_policy gsm_auth_policy_parse(const char *arg); +const char *gsm_auth_policy_name(enum gsm_auth_policy policy); + +enum rrlp_mode rrlp_mode_parse(const char *arg); +const char *rrlp_mode_name(enum rrlp_mode mode); + +enum bts_gprs_mode bts_gprs_mode_parse(const char *arg); +const char *bts_gprs_mode_name(enum bts_gprs_mode mode); + +void gsm_trx_lock_rf(struct gsm_bts_trx *trx, int locked); + +int gsm48_ra_id_by_bts(u_int8_t *buf, struct gsm_bts *bts); +void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts); +struct gsm_meas_rep *lchan_next_meas_rep(struct gsm_lchan *lchan); + +int gsm_btsmodel_set_feature(struct gsm_bts_model *model, enum gsm_bts_features feat); +int gsm_bts_has_feature(struct gsm_bts *bts, enum gsm_bts_features feat); +int gsm_bts_model_register(struct gsm_bts_model *model); + +struct gsm_subscriber_connection *subscr_con_allocate(struct gsm_lchan *lchan); +void subscr_con_free(struct gsm_subscriber_connection *conn); + +#endif diff --git a/include/openbsc/gsm_subscriber.h b/include/openbsc/gsm_subscriber.h new file mode 100644 index 000000000..c365bc7d7 --- /dev/null +++ b/include/openbsc/gsm_subscriber.h @@ -0,0 +1,107 @@ +#ifndef _GSM_SUBSCR_H +#define _GSM_SUBSCR_H + +#include +#include "gsm_data.h" +#include + +#define GSM_IMEI_LENGTH 17 +#define GSM_IMSI_LENGTH 17 +#define GSM_NAME_LENGTH 160 + +#define GSM_EXTENSION_LENGTH 15 /* MSISDN can only be 15 digits length */ +#define GSM_MIN_EXTEN 20000 +#define GSM_MAX_EXTEN 49999 + +#define GSM_SUBSCRIBER_FIRST_CONTACT 0x00000001 +#define tmsi_from_string(str) strtoul(str, NULL, 10) + +struct vty; + +struct gsm_equipment { + long long unsigned int id; + char imei[GSM_IMEI_LENGTH]; + char name[GSM_NAME_LENGTH]; + + struct gsm48_classmark1 classmark1; + u_int8_t classmark2_len; + u_int8_t classmark2[3]; + u_int8_t classmark3_len; + u_int8_t classmark3[14]; +}; + +struct gsm_subscriber { + struct gsm_network *net; + long long unsigned int id; + char imsi[GSM_IMSI_LENGTH]; + u_int32_t tmsi; + u_int16_t lac; + char name[GSM_NAME_LENGTH]; + char extension[GSM_EXTENSION_LENGTH]; + int authorized; + + /* Temporary field which is not stored in the DB/HLR */ + u_int32_t flags; + + /* Every user can only have one equipment in use at any given + * point in time */ + struct gsm_equipment equipment; + + /* for internal management */ + int use_count; + struct llist_head entry; + + /* pending requests */ + int in_callback; + struct llist_head requests; +}; + +enum gsm_subscriber_field { + GSM_SUBSCRIBER_IMSI, + GSM_SUBSCRIBER_TMSI, + GSM_SUBSCRIBER_EXTENSION, + GSM_SUBSCRIBER_ID, +}; + +enum gsm_subscriber_update_reason { + GSM_SUBSCRIBER_UPDATE_ATTACHED, + GSM_SUBSCRIBER_UPDATE_DETACHED, + GSM_SUBSCRIBER_UPDATE_EQUIPMENT, +}; + +struct gsm_subscriber *subscr_get(struct gsm_subscriber *subscr); +struct gsm_subscriber *subscr_put(struct gsm_subscriber *subscr); +struct gsm_subscriber *subscr_get_by_tmsi(struct gsm_network *net, + u_int32_t tmsi); +struct gsm_subscriber *subscr_get_by_imsi(struct gsm_network *net, + const char *imsi); +struct gsm_subscriber *subscr_get_by_extension(struct gsm_network *net, + const char *ext); +struct gsm_subscriber *subscr_get_by_id(struct gsm_network *net, + unsigned long long id); +struct gsm_subscriber *subscr_get_or_create(struct gsm_network *net, + const char *imsi); +int subscr_update(struct gsm_subscriber *s, struct gsm_bts *bts, int reason); +void subscr_put_channel(struct gsm_subscriber *subscr); +void subscr_get_channel(struct gsm_subscriber *subscr, + int type, gsm_cbfn *cbfn, void *param); +struct gsm_subscriber *subscr_active_by_tmsi(struct gsm_network *net, + uint32_t tmsi); +struct gsm_subscriber *subscr_active_by_imsi(struct gsm_network *net, + const char *imsi); + +int subscr_pending_requests(struct gsm_subscriber *subscr); +int subscr_pending_clear(struct gsm_subscriber *subscr); +int subscr_pending_dump(struct gsm_subscriber *subscr, struct vty *vty); +int subscr_pending_kick(struct gsm_subscriber *subscr); + +char *subscr_name(struct gsm_subscriber *subscr); + +int subscr_purge_inactive(struct gsm_network *net); +void subscr_update_from_db(struct gsm_subscriber *subscr); + +/* internal */ +struct gsm_subscriber *subscr_alloc(void); +extern struct llist_head active_subscribers; + +#endif /* _GSM_SUBSCR_H */ diff --git a/include/openbsc/handover.h b/include/openbsc/handover.h new file mode 100644 index 000000000..9d9a90b84 --- /dev/null +++ b/include/openbsc/handover.h @@ -0,0 +1,14 @@ +#ifndef _HANDOVER_H +#define _HANDOVER_H + +struct gsm_subscriber_connection; + +/* Hand over the specified logical channel to the specified new BTS. + * This is the main entry point for the actual handover algorithm, + * after it has decided it wants to initiate HO to a specific BTS */ +int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts); + +/* clear any operation for this connection */ +void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan); + +#endif /* _HANDOVER_H */ diff --git a/include/openbsc/ipaccess.h b/include/openbsc/ipaccess.h new file mode 100644 index 000000000..1d00d9792 --- /dev/null +++ b/include/openbsc/ipaccess.h @@ -0,0 +1,131 @@ +#ifndef _IPACCESS_H +#define _IPACCESS_H + +#include "e1_input.h" +#include "gsm_subscriber.h" +#include + +#define IPA_TCP_PORT_OML 3002 +#define IPA_TCP_PORT_RSL 3003 + +struct ipaccess_head { + u_int16_t len; /* network byte order */ + u_int8_t proto; + u_int8_t data[0]; +} __attribute__ ((packed)); + +struct ipaccess_head_ext { + uint8_t proto; + uint8_t data[0]; +} __attribute__ ((packed)); + +enum ipaccess_proto { + IPAC_PROTO_RSL = 0x00, + IPAC_PROTO_IPACCESS = 0xfe, + IPAC_PROTO_SCCP = 0xfd, + IPAC_PROTO_OML = 0xff, + + + /* OpenBSC extensions */ + IPAC_PROTO_OSMO = 0xee, + IPAC_PROTO_MGCP_OLD = 0xfc, +}; + +enum ipaccess_msgtype { + IPAC_MSGT_PING = 0x00, + IPAC_MSGT_PONG = 0x01, + IPAC_MSGT_ID_GET = 0x04, + IPAC_MSGT_ID_RESP = 0x05, + IPAC_MSGT_ID_ACK = 0x06, + + /* OpenBSC extension */ + IPAC_MSGT_SCCP_OLD = 0xff, +}; + +enum ipaccess_id_tags { + IPAC_IDTAG_SERNR = 0x00, + IPAC_IDTAG_UNITNAME = 0x01, + IPAC_IDTAG_LOCATION1 = 0x02, + IPAC_IDTAG_LOCATION2 = 0x03, + IPAC_IDTAG_EQUIPVERS = 0x04, + IPAC_IDTAG_SWVERSION = 0x05, + IPAC_IDTAG_IPADDR = 0x06, + IPAC_IDTAG_MACADDR = 0x07, + IPAC_IDTAG_UNIT = 0x08, +}; + +struct ipac_msgt_sccp_state { + uint8_t src_ref[3]; + uint8_t dst_ref[3]; + uint8_t trans_id; + uint8_t invoke_id; + char imsi[GSM_IMSI_LENGTH]; +} __attribute__((packed)); + +int ipaccess_connect(struct e1inp_line *line, struct sockaddr_in *sa); + +/* + * methods for parsing and sending a message + */ +int ipaccess_rcvmsg_base(struct msgb *msg, struct bsc_fd *bfd); +struct msgb *ipaccess_read_msg(struct bsc_fd *bfd, int *error); +void ipaccess_prepend_header(struct msgb *msg, int proto); +int ipaccess_send_id_ack(int fd); +int ipaccess_send_id_req(int fd); + +int ipaccess_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len); + +int ipaccess_drop_oml(struct gsm_bts *bts); +int ipaccess_drop_rsl(struct gsm_bts_trx *trx); + +/* + * Firmware specific header + */ +struct sdp_firmware { + char magic[4]; + char more_magic[2]; + u_int16_t more_more_magic; + u_int32_t header_length; + u_int32_t file_length; + char sw_part[20]; + char text1[64]; + char time[12]; + char date[14]; + char text2[10]; + char version[20]; + u_int16_t table_offset; + /* stuff i don't know */ +} __attribute__((packed)); + +struct sdp_header_entry { + u_int16_t something1; + char text1[64]; + char time[12]; + char date[14]; + char text2[10]; + char version[20]; + u_int32_t length; + u_int32_t addr1; + u_int32_t addr2; + u_int32_t start; +} __attribute__((packed)); + +struct sdp_header_item { + struct sdp_header_entry header_entry; + struct llist_head entry; + off_t absolute_offset; +}; + +struct sdp_header { + struct sdp_firmware firmware_info; + + /* for more_magic a list of sdp_header_entry_list */ + struct llist_head header_list; + + /* the entry of the sdp_header */ + struct llist_head entry; +}; + +int ipaccess_analyze_file(int fd, const unsigned int st_size, const unsigned base_offset, struct llist_head *list); + +#endif /* _IPACCESS_H */ diff --git a/include/openbsc/meas_rep.h b/include/openbsc/meas_rep.h new file mode 100644 index 000000000..3c2c8d1c1 --- /dev/null +++ b/include/openbsc/meas_rep.h @@ -0,0 +1,85 @@ +#ifndef _MEAS_REP_H +#define _MEAS_REP_H + +#define MRC_F_PROCESSED 0x0001 + +/* extracted from a L3 measurement report IE */ +struct gsm_meas_rep_cell { + u_int8_t rxlev; + u_int8_t bsic; + u_int8_t neigh_idx; + u_int16_t arfcn; + unsigned int flags; +}; + +/* RX Level and RX Quality */ +struct gsm_rx_lev_qual { + u_int8_t rx_lev; + u_int8_t rx_qual; +}; + +/* unidirectional measumrement report */ +struct gsm_meas_rep_unidir { + struct gsm_rx_lev_qual full; + struct gsm_rx_lev_qual sub; +}; + +#define MEAS_REP_F_UL_DTX 0x01 +#define MEAS_REP_F_DL_VALID 0x02 +#define MEAS_REP_F_BA1 0x04 +#define MEAS_REP_F_DL_DTX 0x08 +#define MEAS_REP_F_MS_TO 0x10 +#define MEAS_REP_F_MS_L1 0x20 +#define MEAS_REP_F_FPC 0x40 + +/* parsed uplink and downlink measurement result */ +struct gsm_meas_rep { + /* back-pointer to the logical channel */ + struct gsm_lchan *lchan; + + /* number of the measurement report */ + u_int8_t nr; + /* flags, see MEAS_REP_F_* */ + unsigned int flags; + + /* uplink and downlink rxlev, rxqual; full and sub */ + struct gsm_meas_rep_unidir ul; + struct gsm_meas_rep_unidir dl; + + u_int8_t bs_power; + u_int8_t ms_timing_offset; + struct { + int8_t pwr; /* MS power in dBm */ + u_int8_t ta; /* MS timing advance */ + } ms_l1; + + /* neighbor measurement reports for up to 6 cells */ + int num_cell; + struct gsm_meas_rep_cell cell[6]; +}; + +enum meas_rep_field { + MEAS_REP_DL_RXLEV_FULL, + MEAS_REP_DL_RXLEV_SUB, + MEAS_REP_DL_RXQUAL_FULL, + MEAS_REP_DL_RXQUAL_SUB, + MEAS_REP_UL_RXLEV_FULL, + MEAS_REP_UL_RXLEV_SUB, + MEAS_REP_UL_RXQUAL_FULL, + MEAS_REP_UL_RXQUAL_SUB, +}; + +/* obtain an average over the last 'num' fields in the meas reps */ +int get_meas_rep_avg(const struct gsm_lchan *lchan, + enum meas_rep_field field, unsigned int num); + +/* Check if N out of M last values for FIELD are >= bd */ +int meas_rep_n_out_of_m_be(const struct gsm_lchan *lchan, + enum meas_rep_field field, + unsigned int n, unsigned int m, int be); + +unsigned int calc_initial_idx(unsigned int array_size, + unsigned int meas_rep_idx, + unsigned int num_values); + +#endif /* _MEAS_REP_H */ diff --git a/include/openbsc/mgcp.h b/include/openbsc/mgcp.h new file mode 100644 index 000000000..516b76edf --- /dev/null +++ b/include/openbsc/mgcp.h @@ -0,0 +1,194 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ + +/* + * (C) 2009-2011 by Holger Hans Peter Freyther + * (C) 2009-2011 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef OPENBSC_MGCP_H +#define OPENBSC_MGCP_H + +#include +#include + +#include "debug.h" + +#include + +#define RTP_PORT_DEFAULT 4000 +#define RTP_PORT_NET_DEFAULT 16000 + +/** + * Calculate the RTP audio port for the given multiplex + * and the direction. This allows a semi static endpoint + * to port calculation removing the need for the BSC + * and the MediaGateway to communicate. + * + * Port usage explained: + * base + (multiplex * 2) + 0 == local port to wait for network packets + * base + (multiplex * 2) + 1 == local port for rtcp + * + * The above port will receive packets from the BTS that need + * to be patched and forwarded to the network. + * The above port will receive packets from the network that + * need to be patched and forwarded to the BTS. + * + * We assume to have a static BTS IP address so we can differentiate + * network and BTS. + * + */ +static inline int rtp_calculate_port(int multiplex, int base) +{ + return base + (multiplex * 2); +} + + +/* + * Handling of MGCP Endpoints and the MGCP Config + */ +struct mgcp_endpoint; +struct mgcp_config; +struct mgcp_trunk_config; + +#define MGCP_ENDP_CRCX 1 +#define MGCP_ENDP_DLCX 2 +#define MGCP_ENDP_MDCX 3 + +/* + * what to do with the msg? + * - continue as usual? + * - reject and send a failure code? + * - defer? do not send anything + */ +#define MGCP_POLICY_CONT 4 +#define MGCP_POLICY_REJECT 5 +#define MGCP_POLICY_DEFER 6 + +typedef int (*mgcp_realloc)(struct mgcp_trunk_config *cfg, int endpoint); +typedef int (*mgcp_change)(struct mgcp_trunk_config *cfg, int endpoint, int state); +typedef int (*mgcp_policy)(struct mgcp_trunk_config *cfg, int endpoint, int state, const char *transactio_id); +typedef int (*mgcp_reset)(struct mgcp_config *cfg); + +#define PORT_ALLOC_STATIC 0 +#define PORT_ALLOC_DYNAMIC 1 + +/** + * This holds information on how to allocate ports + */ +struct mgcp_port_range { + int mode; + + /* pre-allocated from a base? */ + int base_port; + + /* dynamically allocated */ + int range_start; + int range_end; + int last_port; +}; + +struct mgcp_trunk_config { + struct llist_head entry; + + struct mgcp_config *cfg; + + int trunk_nr; + int trunk_type; + + char *audio_name; + int audio_payload; + int audio_loop; + + /* spec handling */ + int force_realloc; + + unsigned int number_endpoints; + struct mgcp_endpoint *endpoints; +}; + +struct mgcp_config { + int source_port; + char *local_ip; + char *source_addr; + char *bts_ip; + char *call_agent_addr; + + struct in_addr bts_in; + + /* transcoder handling */ + char *transcoder_ip; + struct in_addr transcoder_in; + int transcoder_remote_base; + + struct write_queue gw_fd; + + struct mgcp_port_range bts_ports; + struct mgcp_port_range net_ports; + struct mgcp_port_range transcoder_ports; + int endp_dscp; + + mgcp_change change_cb; + mgcp_policy policy_cb; + mgcp_reset reset_cb; + mgcp_realloc realloc_cb; + void *data; + + uint32_t last_call_id; + + /* trunk handling */ + struct mgcp_trunk_config trunk; + struct llist_head trunks; + + /* only used for start with a static configuration */ + int last_net_port; + int last_bts_port; +}; + +/* config management */ +struct mgcp_config *mgcp_config_alloc(void); +int mgcp_parse_config(const char *config_file, struct mgcp_config *cfg); +int mgcp_vty_init(void); +int mgcp_endpoints_allocate(struct mgcp_trunk_config *cfg); +void mgcp_free_endp(struct mgcp_endpoint *endp); +int mgcp_reset_transcoder(struct mgcp_config *cfg); + +/* + * format helper functions + */ +struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg); +struct msgb *mgcp_create_response_with_data(int code, const char *txt, const char *msg, const char *trans, const char *data); + +/* adc helper */ +static inline int mgcp_timeslot_to_endpoint(int multiplex, int timeslot) +{ + if (timeslot == 0) { + LOGP(DMGCP, LOGL_ERROR, "Timeslot should not be 0\n"); + timeslot = 255; + } + + return timeslot + (32 * multiplex); +} + +static inline void mgcp_endpoint_to_timeslot(int endpoint, int *multiplex, int *timeslot) +{ + *multiplex = endpoint / 32; + *timeslot = endpoint % 32; +} + + +#endif diff --git a/include/openbsc/mgcp_internal.h b/include/openbsc/mgcp_internal.h new file mode 100644 index 000000000..7c6bb5425 --- /dev/null +++ b/include/openbsc/mgcp_internal.h @@ -0,0 +1,154 @@ +/* MGCP Private Data */ + +/* + * (C) 2009-2011 by Holger Hans Peter Freyther + * (C) 2009-2011 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef OPENBSC_MGCP_DATA_H +#define OPENBSC_MGCP_DATA_H + +#include + +#define CI_UNUSED 0 + +enum mgcp_connection_mode { + MGCP_CONN_NONE = 0, + MGCP_CONN_RECV_ONLY = 1, + MGCP_CONN_SEND_ONLY = 2, + MGCP_CONN_RECV_SEND = MGCP_CONN_RECV_ONLY | MGCP_CONN_SEND_ONLY, + MGCP_CONN_LOOPBACK = 4, +}; + +enum mgcp_trunk_type { + MGCP_TRUNK_VIRTUAL, + MGCP_TRUNK_E1, +}; + +struct mgcp_rtp_state { + int initialized; + int patch; + + uint32_t orig_ssrc; + uint32_t ssrc; + uint16_t seq_no; + int lost_no; + int seq_offset; + uint32_t last_timestamp; + int32_t timestamp_offset; +}; + +struct mgcp_rtp_end { + /* statistics */ + unsigned int packets; + struct in_addr addr; + + /* in network byte order */ + int rtp_port, rtcp_port; + + int payload_type; + + /* + * Each end has a socket... + */ + struct bsc_fd rtp; + struct bsc_fd rtcp; + + int local_port; + int local_alloc; +}; + +enum { + MGCP_TAP_BTS_IN, + MGCP_TAP_BTS_OUT, + MGCP_TAP_NET_IN, + MGCP_TAP_NET_OUT, + + /* last element */ + MGCP_TAP_COUNT +}; + +struct mgcp_rtp_tap { + int enabled; + struct sockaddr_in forward; +}; + +struct mgcp_endpoint { + int allocated; + uint32_t ci; + char *callid; + char *local_options; + int conn_mode; + int orig_mode; + + /* backpointer */ + struct mgcp_config *cfg; + struct mgcp_trunk_config *tcfg; + + /* port status for bts/net */ + struct mgcp_rtp_end bts_end; + struct mgcp_rtp_end net_end; + + /* + * For transcoding we will send from the local_port + * of trans_bts and it will arrive at trans_net from + * where we will forward it to the network. + */ + struct mgcp_rtp_end trans_bts; + struct mgcp_rtp_end trans_net; + int is_transcoded; + + /* sequence bits */ + struct mgcp_rtp_state net_state; + struct mgcp_rtp_state bts_state; + + /* SSRC/seq/ts patching for loop */ + int allow_patch; + + /* tap for the endpoint */ + struct mgcp_rtp_tap taps[MGCP_TAP_COUNT]; +}; + +#define ENDPOINT_NUMBER(endp) abs(endp - endp->tcfg->endpoints) + +struct mgcp_msg_ptr { + unsigned int start; + unsigned int length; +}; + +int mgcp_analyze_header(struct mgcp_config *cfg, struct msgb *msg, + struct mgcp_msg_ptr *ptr, int size, + const char **transaction_id, struct mgcp_endpoint **endp); +int mgcp_send_dummy(struct mgcp_endpoint *endp); +int mgcp_bind_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port); +int mgcp_bind_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port); +int mgcp_bind_trans_bts_rtp_port(struct mgcp_endpoint *enp, int rtp_port); +int mgcp_bind_trans_net_rtp_port(struct mgcp_endpoint *enp, int rtp_port); +int mgcp_free_rtp_port(struct mgcp_rtp_end *end); + +/* For transcoding we need to manage an in and an output that are connected */ +static inline int endp_back_channel(int endpoint) +{ + return endpoint + 60; +} + +struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int index); +struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index); + + +#endif diff --git a/include/openbsc/misdn.h b/include/openbsc/misdn.h new file mode 100644 index 000000000..0a8b063a4 --- /dev/null +++ b/include/openbsc/misdn.h @@ -0,0 +1,27 @@ +/* (C) 2008 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef MISDN_H +#define MISDN_H + +#include "e1_input.h" + +int mi_setup(int cardnr, struct e1inp_line *line, int release_l2); +int mi_e1_line_update(struct e1inp_line *line); + +#endif diff --git a/include/openbsc/mncc.h b/include/openbsc/mncc.h new file mode 100644 index 000000000..e514c19ae --- /dev/null +++ b/include/openbsc/mncc.h @@ -0,0 +1,172 @@ +/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface + * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */ + +/* (C) 2008-2009 by Harald Welte + * (C) 2008, 2009 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef _MNCC_H +#define _MNCC_H + +#include +#include + +#include + +struct gsm_network; +struct msgb; + + +/* One end of a call */ +struct gsm_call { + struct llist_head entry; + + /* network handle */ + void *net; + + /* the 'local' transaction */ + uint32_t callref; + /* the 'remote' transaction */ + uint32_t remote_ref; +}; + +#define MNCC_SETUP_REQ 0x0101 +#define MNCC_SETUP_IND 0x0102 +#define MNCC_SETUP_RSP 0x0103 +#define MNCC_SETUP_CNF 0x0104 +#define MNCC_SETUP_COMPL_REQ 0x0105 +#define MNCC_SETUP_COMPL_IND 0x0106 +/* MNCC_REJ_* is perfomed via MNCC_REL_* */ +#define MNCC_CALL_CONF_IND 0x0107 +#define MNCC_CALL_PROC_REQ 0x0108 +#define MNCC_PROGRESS_REQ 0x0109 +#define MNCC_ALERT_REQ 0x010a +#define MNCC_ALERT_IND 0x010b +#define MNCC_NOTIFY_REQ 0x010c +#define MNCC_NOTIFY_IND 0x010d +#define MNCC_DISC_REQ 0x010e +#define MNCC_DISC_IND 0x010f +#define MNCC_REL_REQ 0x0110 +#define MNCC_REL_IND 0x0111 +#define MNCC_REL_CNF 0x0112 +#define MNCC_FACILITY_REQ 0x0113 +#define MNCC_FACILITY_IND 0x0114 +#define MNCC_START_DTMF_IND 0x0115 +#define MNCC_START_DTMF_RSP 0x0116 +#define MNCC_START_DTMF_REJ 0x0117 +#define MNCC_STOP_DTMF_IND 0x0118 +#define MNCC_STOP_DTMF_RSP 0x0119 +#define MNCC_MODIFY_REQ 0x011a +#define MNCC_MODIFY_IND 0x011b +#define MNCC_MODIFY_RSP 0x011c +#define MNCC_MODIFY_CNF 0x011d +#define MNCC_MODIFY_REJ 0x011e +#define MNCC_HOLD_IND 0x011f +#define MNCC_HOLD_CNF 0x0120 +#define MNCC_HOLD_REJ 0x0121 +#define MNCC_RETRIEVE_IND 0x0122 +#define MNCC_RETRIEVE_CNF 0x0123 +#define MNCC_RETRIEVE_REJ 0x0124 +#define MNCC_USERINFO_REQ 0x0125 +#define MNCC_USERINFO_IND 0x0126 +#define MNCC_REJ_REQ 0x0127 +#define MNCC_REJ_IND 0x0128 + +#define MNCC_BRIDGE 0x0200 +#define MNCC_FRAME_RECV 0x0201 +#define MNCC_FRAME_DROP 0x0202 +#define MNCC_LCHAN_MODIFY 0x0203 + +#define GSM_TCHF_FRAME 0x0300 +#define GSM_TCHF_FRAME_EFR 0x0301 + +#define GSM_MAX_FACILITY 128 +#define GSM_MAX_SSVERSION 128 +#define GSM_MAX_USERUSER 128 + +#define MNCC_F_BEARER_CAP 0x0001 +#define MNCC_F_CALLED 0x0002 +#define MNCC_F_CALLING 0x0004 +#define MNCC_F_REDIRECTING 0x0008 +#define MNCC_F_CONNECTED 0x0010 +#define MNCC_F_CAUSE 0x0020 +#define MNCC_F_USERUSER 0x0040 +#define MNCC_F_PROGRESS 0x0080 +#define MNCC_F_EMERGENCY 0x0100 +#define MNCC_F_FACILITY 0x0200 +#define MNCC_F_SSVERSION 0x0400 +#define MNCC_F_CCCAP 0x0800 +#define MNCC_F_KEYPAD 0x1000 +#define MNCC_F_SIGNAL 0x2000 + +struct gsm_mncc { + /* context based information */ + uint32_t msg_type; + uint32_t callref; + + /* which fields are present */ + uint32_t fields; + + /* data derived informations (MNCC_F_ based) */ + struct gsm_mncc_bearer_cap bearer_cap; + struct gsm_mncc_number called; + struct gsm_mncc_number calling; + struct gsm_mncc_number redirecting; + struct gsm_mncc_number connected; + struct gsm_mncc_cause cause; + struct gsm_mncc_progress progress; + struct gsm_mncc_useruser useruser; + struct gsm_mncc_facility facility; + struct gsm_mncc_cccap cccap; + struct gsm_mncc_ssversion ssversion; + struct { + int sup; + int inv; + } clir; + int signal; + + /* data derived information, not MNCC_F based */ + int keypad; + int more; + int notify; /* 0..127 */ + int emergency; + char imsi[16]; + + unsigned char lchan_mode; +}; + +struct gsm_data_frame { + uint32_t msg_type; + uint32_t callref; + unsigned char data[0]; +}; + +char *get_mncc_name(int value); +void mncc_set_cause(struct gsm_mncc *data, int loc, int val); +void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg); + +/* input from CC code into mncc_builtin */ +int int_mncc_recv(struct gsm_network *net, struct msgb *msg); + +/* input from CC code into mncc_sock */ +int mncc_sock_from_cc(struct gsm_network *net, struct msgb *msg); + +int mncc_sock_init(struct gsm_network *gsmnet); + +#endif diff --git a/include/openbsc/network_listen.h b/include/openbsc/network_listen.h new file mode 100644 index 000000000..67d1f4ef7 --- /dev/null +++ b/include/openbsc/network_listen.h @@ -0,0 +1,16 @@ +#ifndef _OPENBSC_NWL_H +#define _OPENBSC_NWL_H + +#include +#include + +void ipac_nwl_init(void); + +/* Start a NWL test. It will raise the S_IPAC_TEST_COMPLETE signal. */ +int ipac_nwl_test_start(struct gsm_bts_trx *trx, uint8_t testnr, + const uint8_t *phys_conf, unsigned int phys_conf_len); + +int ipac_rxlevstat2whitelist(uint16_t *buf, const struct rxlev_stats *st, uint8_t min_rxlev, + uint16_t max_num_arfcns); + +#endif /* _OPENBSC_NWL_H */ diff --git a/include/openbsc/openbscdefines.h b/include/openbsc/openbscdefines.h new file mode 100644 index 000000000..c6ac153b8 --- /dev/null +++ b/include/openbsc/openbscdefines.h @@ -0,0 +1,34 @@ +/* + * (C) 2009 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef OPENBSCDEFINES_H +#define OPENBSCDEFINES_H + +#ifdef BUILDING_ON_WINDOWS + #ifdef BUILDING_OPENBSC + #define BSC_API __declspec(dllexport) + #else + #define BSC_API __declspec(dllimport) + #endif +#else + #define BSC_API __attribute__((visibility("default"))) +#endif + +#endif diff --git a/include/openbsc/osmo_bsc.h b/include/openbsc/osmo_bsc.h new file mode 100644 index 000000000..ef0f11a3b --- /dev/null +++ b/include/openbsc/osmo_bsc.h @@ -0,0 +1,43 @@ +/* OpenBSC BSC code */ + +#ifndef OSMO_BSC_H +#define OSMO_BSC_H + +#include "bsc_api.h" + +struct sccp_connection; + +struct osmo_bsc_sccp_con { + struct llist_head entry; + + int ciphering_handled; + int rtp_port; + + /* SCCP connection realted */ + struct sccp_connection *sccp; + struct bsc_msc_connection *msc_con; + struct timer_list sccp_it_timeout; + struct timer_list sccp_cc_timeout; + + struct llist_head sccp_queue; + unsigned int sccp_queue_size; + + struct gsm_subscriber_connection *conn; + uint8_t new_subscriber; +}; + +struct bsc_api *osmo_bsc_api(); + +int bsc_queue_for_msc(struct osmo_bsc_sccp_con *conn, struct msgb *msg); +int bsc_open_connection(struct osmo_bsc_sccp_con *sccp, struct msgb *msg); +int bsc_create_new_connection(struct gsm_subscriber_connection *conn); +int bsc_delete_connection(struct osmo_bsc_sccp_con *sccp); + +int bsc_scan_bts_msg(struct gsm_subscriber_connection *conn, struct msgb *msg); +int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg); + +int bsc_handle_udt(struct gsm_network *net, struct bsc_msc_connection *conn, struct msgb *msg, unsigned int length); +int bsc_handle_dt1(struct osmo_bsc_sccp_con *conn, struct msgb *msg, unsigned int len); + + +#endif diff --git a/include/openbsc/osmo_bsc_grace.h b/include/openbsc/osmo_bsc_grace.h new file mode 100644 index 000000000..45d4db8ec --- /dev/null +++ b/include/openbsc/osmo_bsc_grace.h @@ -0,0 +1,28 @@ +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef OSMO_BSC_GRACE_H +#define OSMO_BSC_GRACE_H + +#include "gsm_data.h" + +int bsc_grace_allow_new_connection(struct gsm_network *network); + +#endif diff --git a/include/openbsc/osmo_bsc_rf.h b/include/openbsc/osmo_bsc_rf.h new file mode 100644 index 000000000..e43ae2e3f --- /dev/null +++ b/include/openbsc/osmo_bsc_rf.h @@ -0,0 +1,35 @@ +#ifndef OSMO_BSC_RF +#define OSMO_BSC_RF + +#include +#include + +struct gsm_network; + +struct osmo_bsc_rf { + /* the value of signal.h */ + int policy; + struct bsc_fd listen; + struct gsm_network *gsm_network; + + const char *last_state_command; + + /* delay the command */ + char last_request; + struct timer_list delay_cmd; + + /* verify that RF is up as it should be */ + struct timer_list rf_check; + + /* some handling for the automatic grace switch */ + struct timer_list grace_timeout; +}; + +struct osmo_bsc_rf_conn { + struct write_queue queue; + struct osmo_bsc_rf *rf; +}; + +struct osmo_bsc_rf *osmo_bsc_rf_create(const char *path, struct gsm_network *net); + +#endif diff --git a/include/openbsc/osmo_msc.h b/include/openbsc/osmo_msc.h new file mode 100644 index 000000000..beb3f5e4c --- /dev/null +++ b/include/openbsc/osmo_msc.h @@ -0,0 +1,11 @@ +/* Routines for the MSC handling */ + +#ifndef OSMO_MSC_H +#define OSMO_MSC_H + +#include "bsc_api.h" + +struct bsc_api *msc_bsc_api(); +void msc_release_connection(struct gsm_subscriber_connection *conn); + +#endif diff --git a/include/openbsc/osmo_msc_data.h b/include/openbsc/osmo_msc_data.h new file mode 100644 index 000000000..8f9ca6856 --- /dev/null +++ b/include/openbsc/osmo_msc_data.h @@ -0,0 +1,76 @@ +/* + * Data for the true BSC + * + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef _OSMO_MSC_DATA_H +#define _OSMO_MSC_DATA_H + +#include "bsc_msc.h" + +#include + +struct osmo_bsc_rf; +struct gsm_network; + +struct gsm_audio_support { + uint8_t hr : 1, + ver : 7; +}; + +struct osmo_msc_data { + /* Connection data */ + char *bsc_token; + int msc_port; + int msc_ip_dscp; + char *msc_ip; + int ping_timeout; + int pong_timeout; + struct timer_list ping_timer; + struct timer_list pong_timer; + struct bsc_msc_connection *msc_con; + int core_ncc; + int core_mcc; + int rtp_base; + + /* audio codecs */ + struct gsm_audio_support **audio_support; + int audio_length; + + + /* mgcp agent */ + struct write_queue mgcp_agent; + + /* rf ctl related bits */ + char *mid_call_txt; + int mid_call_timeout; + struct osmo_bsc_rf *rf_ctl; + + /* ussd welcome text */ + char *ussd_welcome_txt; +}; + +int osmo_bsc_msc_init(struct gsm_network *network); +int osmo_bsc_sccp_init(struct gsm_network *gsmnet); +int msc_queue_write(struct bsc_msc_connection *conn, struct msgb *msg, int proto); + +int osmo_bsc_audio_init(struct gsm_network *network); + +#endif diff --git a/include/openbsc/paging.h b/include/openbsc/paging.h new file mode 100644 index 000000000..f71919916 --- /dev/null +++ b/include/openbsc/paging.h @@ -0,0 +1,71 @@ +/* Paging helper and manager.... */ +/* (C) 2009 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef PAGING_H +#define PAGING_H + +#include +#include + +#include +#include "gsm_data.h" +#include "gsm_subscriber.h" +#include + +/** + * A pending paging request + */ +struct gsm_paging_request { + /* list_head for list of all paging requests */ + struct llist_head entry; + /* the subscriber which we're paging. Later gsm_paging_request + * should probably become a part of the gsm_subscriber struct? */ + struct gsm_subscriber *subscr; + /* back-pointer to the BTS on which we are paging */ + struct gsm_bts *bts; + /* what kind of channel type do we ask the MS to establish */ + int chan_type; + + /* Timer 3113: how long do we try to page? */ + struct timer_list T3113; + + /* How often did we ask the BTS to page? */ + int attempts; + + /* callback to be called in case paging completes */ + gsm_cbfn *cbfn; + void *cbfn_param; +}; + +/* call once for every gsm_bts... */ +void paging_init(struct gsm_bts *bts); + +/* schedule paging request */ +int paging_request(struct gsm_network *network, struct gsm_subscriber *subscr, + int type, gsm_cbfn *cbfn, void *data); + +/* stop paging requests */ +void paging_request_stop(struct gsm_bts *bts, struct gsm_subscriber *subscr, + struct gsm_subscriber_connection *conn, + struct msgb *msg); + +/* update paging load */ +void paging_update_buffer_space(struct gsm_bts *bts, u_int16_t); + +#endif diff --git a/include/openbsc/rest_octets.h b/include/openbsc/rest_octets.h new file mode 100644 index 000000000..6d9011963 --- /dev/null +++ b/include/openbsc/rest_octets.h @@ -0,0 +1,133 @@ +#ifndef _REST_OCTETS_H +#define _REST_OCTETS_H + +#include +#include + +/* generate SI1 rest octets */ +int rest_octets_si1(u_int8_t *data, u_int8_t *nch_pos); + +struct gsm48_si_selection_params { + u_int16_t penalty_time:5, + temp_offs:3, + cell_resel_off:6, + cbq:1, + present:1; +}; + +struct gsm48_si_power_offset { + u_int8_t power_offset:2, + present:1; +}; + +struct gsm48_si3_gprs_ind { + u_int8_t si13_position:1, + ra_colour:3, + present:1; +}; + +struct gsm48_lsa_params { + u_int32_t prio_thr:3, + lsa_offset:3, + mcc:12, + mnc:12; + unsigned int present; +}; + +struct gsm48_si_ro_info { + struct gsm48_si_selection_params selection_params; + struct gsm48_si_power_offset power_offset; + u_int8_t si2ter_indicator; + u_int8_t early_cm_ctrl; + struct { + u_int8_t where:3, + present:1; + } scheduling; + struct gsm48_si3_gprs_ind gprs_ind; + + /* SI 4 specific */ + struct gsm48_lsa_params lsa_params; + u_int16_t cell_id; + u_int8_t break_ind; /* do we have SI7 + SI8 ? */ +}; + + +/* Generate SI3 Rest Octests (Chapter 10.5.2.34 / Table 10.4.72) */ +int rest_octets_si3(u_int8_t *data, const struct gsm48_si_ro_info *si3); + +/* Generate SI4 Rest Octets (Chapter 10.5.2.35) */ +int rest_octets_si4(u_int8_t *data, const struct gsm48_si_ro_info *si4); + +enum pbcch_carrier_type { + PBCCH_BCCH, + PBCCH_ARFCN, + PBCCH_MAIO +}; + +/* TS 03.60 Chapter 6.3.3.1: Network Mode of Operation */ +enum gprs_nmo { + GPRS_NMO_I = 0, /* CS pagin on GPRS paging or traffic channel */ + GPRS_NMO_II = 1, /* all paging on CCCH */ + GPRS_NMO_III = 2, /* no paging coordination */ +}; + +/* TS 04.60 12.24 */ +struct gprs_cell_options { + enum gprs_nmo nmo; + /* T3168: wait for packet uplink assignment message */ + u_int32_t t3168; /* in milliseconds */ + /* T3192: wait for release of the TBF after reception of the final block */ + u_int32_t t3192; /* in milliseconds */ + u_int32_t drx_timer_max;/* in seconds */ + u_int32_t bs_cv_max; + + u_int8_t ext_info_present; + struct { + u_int8_t egprs_supported; + u_int8_t use_egprs_p_ch_req; + u_int8_t bep_period; + u_int8_t pfc_supported; + u_int8_t dtm_supported; + u_int8_t bss_paging_coordination; + } ext_info; +}; + +/* TS 04.60 Table 12.9.2 */ +struct gprs_power_ctrl_pars { + u_int8_t alpha; + u_int8_t t_avg_w; + u_int8_t t_avg_t; + u_int8_t pc_meas_chan; + u_int8_t n_avg_i; +}; + +struct gsm48_si13_info { + struct gprs_cell_options cell_opts; + struct gprs_power_ctrl_pars pwr_ctrl_pars; + u_int8_t bcch_change_mark; + u_int8_t si_change_field; + u_int8_t pbcch_present; + + union { + struct { + u_int8_t rac; + u_int8_t spgc_ccch_sup; + u_int8_t net_ctrl_ord; + u_int8_t prio_acc_thr; + } no_pbcch; + struct { + u_int8_t psi1_rep_per; + u_int8_t pb; + u_int8_t tsc; + u_int8_t tn; + enum pbcch_carrier_type carrier_type; + u_int16_t arfcn; + u_int8_t maio; + } pbcch; + }; +}; + +/* Generate SI13 Rest Octests (Chapter 10.5.2.37b) */ +int rest_octets_si13(u_int8_t *data, const struct gsm48_si13_info *si13); + +#endif /* _REST_OCTETS_H */ diff --git a/include/openbsc/rs232.h b/include/openbsc/rs232.h new file mode 100644 index 000000000..61187ca62 --- /dev/null +++ b/include/openbsc/rs232.h @@ -0,0 +1,9 @@ +#ifndef _RS232_H +#define _RS232_H + +int rs232_setup(const char *serial_port, unsigned int delay_ms, + struct gsm_bts *bts); + +int handle_serial_msg(struct msgb *msg); + +#endif /* _RS232_H */ diff --git a/include/openbsc/rtp_proxy.h b/include/openbsc/rtp_proxy.h new file mode 100644 index 000000000..53b58b4b5 --- /dev/null +++ b/include/openbsc/rtp_proxy.h @@ -0,0 +1,90 @@ +#ifndef _RTP_PROXY_H +#define _RTP_PROXY_H + +/* RTP proxy handling for ip.access nanoBTS */ + +/* (C) 2009 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include + +#include +#include + +#define RTP_PT_GSM_FULL 3 +#define RTP_PT_GSM_HALF 96 +#define RTP_PT_GSM_EFR 97 +#define RTP_PT_AMR_FULL 98 +#define RTP_PT_AMR_HALF 99 + +enum rtp_rx_action { + RTP_NONE, + RTP_PROXY, + RTP_RECV_UPSTREAM, +}; + +enum rtp_tx_action { + RTP_SEND_NONE, + RTP_SEND_DOWNSTREAM, +}; + +struct rtp_sub_socket { + struct sockaddr_in sin_local; + struct sockaddr_in sin_remote; + + struct bsc_fd bfd; + /* linked list of to-be-transmitted msgb's */ + struct llist_head tx_queue; +}; + +struct rtp_socket { + struct llist_head list; + + struct rtp_sub_socket rtp; + struct rtp_sub_socket rtcp; + + /* what should we do on receive? */ + enum rtp_rx_action rx_action; + union { + struct { + struct rtp_socket *other_sock; + } proxy; + struct { + struct gsm_network *net; + u_int32_t callref; + } receive; + }; + enum rtp_tx_action tx_action; + struct { + u_int16_t sequence; + u_int32_t timestamp; + u_int32_t ssrc; + struct timeval last_tv; + } transmit; +}; + +struct rtp_socket *rtp_socket_create(void); +int rtp_socket_bind(struct rtp_socket *rs, u_int32_t ip); +int rtp_socket_connect(struct rtp_socket *rs, u_int32_t ip, u_int16_t port); +int rtp_socket_proxy(struct rtp_socket *this, struct rtp_socket *other); +int rtp_socket_upstream(struct rtp_socket *this, struct gsm_network *net, u_int32_t callref); +int rtp_socket_free(struct rtp_socket *rs); +int rtp_send_frame(struct rtp_socket *rs, struct gsm_data_frame *frame); + +#endif /* _RTP_PROXY_H */ diff --git a/include/openbsc/sgsn.h b/include/openbsc/sgsn.h new file mode 100644 index 000000000..84db87e91 --- /dev/null +++ b/include/openbsc/sgsn.h @@ -0,0 +1,65 @@ +#ifndef _SGSN_H +#define _SGSN_H + +#include + +#include + +#include +#include + +struct sgsn_config { + /* parsed from config file */ + + char *gtp_statedir; + struct sockaddr_in gtp_listenaddr; + + /* misc */ + struct gprs_ns_inst *nsi; +}; + +struct sgsn_instance { + char *config_file; + struct sgsn_config cfg; + /* File descriptor wrappers for LibGTP */ + struct bsc_fd gtp_fd0; + struct bsc_fd gtp_fd1c; + struct bsc_fd gtp_fd1u; + /* Timer for libGTP */ + struct timer_list gtp_timer; + /* GSN instance for libgtp */ + struct gsn_t *gsn; +}; + +extern struct sgsn_instance *sgsn; + +/* sgsn_vty.c */ + +int sgsn_vty_init(void); +int sgsn_parse_config(const char *config_file, struct sgsn_config *cfg); + +/* sgsn.c */ + +/* Main input function for Gb proxy */ +int sgsn_rcvmsg(struct msgb *msg, struct gprs_nsvc *nsvc, uint16_t ns_bvci); + + +struct sgsn_pdp_ctx *sgsn_create_pdp_ctx(struct sgsn_ggsn_ctx *ggsn, + struct sgsn_mm_ctx *mmctx, + uint16_t nsapi, + struct tlv_parsed *tp); +int sgsn_delete_pdp_ctx(struct sgsn_pdp_ctx *pctx); + +/* gprs_sndcp.c */ + +/* Entry point for the SNSM-ACTIVATE.indication */ +int sndcp_sm_activate_ind(struct gprs_llc_lle *lle, uint8_t nsapi); +/* Entry point for the SNSM-DEACTIVATE.indication */ +int sndcp_sm_deactivate_ind(struct gprs_llc_lle *lle, uint8_t nsapi); +/* Called by SNDCP when it has received/re-assembled a N-PDU */ +int sgsn_rx_sndcp_ud_ind(struct gprs_ra_id *ra_id, int32_t tlli, uint8_t nsapi, + struct msgb *msg, uint32_t npdu_len, uint8_t *npdu); +int sndcp_unitdata_req(struct msgb *msg, struct gprs_llc_lle *lle, uint8_t nsapi, + void *mmcontext); + +#endif diff --git a/include/openbsc/signal.h b/include/openbsc/signal.h new file mode 100644 index 000000000..a2257db73 --- /dev/null +++ b/include/openbsc/signal.h @@ -0,0 +1,255 @@ +/* Generic signalling/notification infrastructure */ +/* (C) 2009-2010 by Holger Hans Peter Freyther + * (C) 2009 by Harald Welte + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef OPENBSC_SIGNAL_H +#define OPENBSC_SIGNAL_H + +#include +#include + +#include + +#include + +/* + * Signalling subsystems + */ +enum signal_subsystems { + SS_PAGING, + SS_SMS, + SS_ABISIP, + SS_NM, + SS_LCHAN, + SS_SUBSCR, + SS_SCALL, + SS_GLOBAL, + SS_CHALLOC, + SS_NS, + SS_IPAC_NWL, + SS_RF, + SS_MSC, + SS_HO, + SS_INPUT, +}; + +/* SS_PAGING signals */ +enum signal_paging { + S_PAGING_SUCCEEDED, + S_PAGING_EXPIRED, +}; + +/* SS_SMS signals */ +enum signal_sms { + S_SMS_SUBMITTED, /* A SMS has been successfully submitted to us */ + S_SMS_DELIVERED, /* A SMS has been successfully delivered to a MS */ + S_SMS_SMMA, /* A MS tells us it has more space available */ + S_SMS_MEM_EXCEEDED, /* A MS tells us it has no more space available */ + S_SMS_UNKNOWN_ERROR, /* A MS tells us it has an error */ +}; + +/* SS_ABISIP signals */ +enum signal_abisip { + S_ABISIP_CRCX_ACK, + S_ABISIP_MDCX_ACK, + S_ABISIP_DLCX_IND, +}; + +/* SS_NM signals */ +enum signal_nm { + S_NM_SW_ACTIV_REP, /* GSM 12.21 software activated report */ + S_NM_FAIL_REP, /* GSM 12.21 failure event report */ + S_NM_NACK, /* GSM 12.21 various NM_MT_*_NACK happened */ + S_NM_IPACC_NACK, /* GSM 12.21 nanoBTS extensions NM_MT_IPACC_*_*_NACK happened */ + S_NM_IPACC_ACK, /* GSM 12.21 nanoBTS extensions NM_MT_IPACC_*_*_ACK happened */ + S_NM_IPACC_RESTART_ACK, /* nanoBTS has send a restart ack */ + S_NM_IPACC_RESTART_NACK,/* nanoBTS has send a restart ack */ + S_NM_TEST_REP, /* GSM 12.21 Test Report */ + S_NM_STATECHG_OPER, /* Operational State changed*/ + S_NM_STATECHG_ADM, /* Administrative State changed */ +}; + +/* SS_LCHAN signals */ +enum signal_lchan { + /* + * The lchan got freed with an use_count != 0 and error + * recovery needs to be carried out from within the + * signal handler. + */ + S_LCHAN_UNEXPECTED_RELEASE, + S_LCHAN_ACTIVATE_ACK, /* 08.58 Channel Activate ACK */ + S_LCHAN_ACTIVATE_NACK, /* 08.58 Channel Activate NACK */ + S_LCHAN_HANDOVER_COMPL, /* 04.08 Handover Completed */ + S_LCHAN_HANDOVER_FAIL, /* 04.08 Handover Failed */ + S_LCHAN_HANDOVER_DETECT, /* 08.58 Handover Detect */ + S_LCHAN_MEAS_REP, /* 08.58 Measurement Report */ +}; + +/* SS_CHALLOC signals */ +enum signal_challoc { + S_CHALLOC_ALLOC_FAIL, /* allocation of lchan has failed */ + S_CHALLOC_FREED, /* lchan has been successfully freed */ +}; + +/* SS_SUBSCR signals */ +enum signal_subscr { + S_SUBSCR_ATTACHED, + S_SUBSCR_DETACHED, + S_SUBSCR_IDENTITY, /* we've received some identity information */ +}; + +/* SS_SCALL signals */ +enum signal_scall { + S_SCALL_SUCCESS, + S_SCALL_EXPIRED, + S_SCALL_DETACHED, +}; + +/* SS_IPAC_NWL signals */ +enum signal_ipaccess { + S_IPAC_NWL_COMPLETE, +}; + +enum signal_global { + S_GLOBAL_SHUTDOWN, + S_GLOBAL_BTS_CLOSE_OM, +}; + +/* SS_RF signals */ +enum signal_rf { + S_RF_OFF, + S_RF_ON, + S_RF_GRACE, +}; + +/* SS_INPUT signals */ +enum signal_input { + S_INP_NONE, + S_INP_TEI_UP, + S_INP_TEI_DN, + S_INP_LINE_INIT, + S_INP_LINE_ALARM, + S_INP_LINE_NOALARM, +}; + +struct gsm_subscriber; + +struct paging_signal_data { + struct gsm_subscriber *subscr; + struct gsm_bts *bts; + + int paging_result; + + /* NULL in case the paging didn't work */ + struct gsm_subscriber_connection *conn; +}; + +struct scall_signal_data { + struct gsm_subscriber *subscr; + struct gsm_subscriber_connection *conn; + void *data; +}; + +struct ipacc_ack_signal_data { + struct gsm_bts_trx *trx; + u_int8_t msg_type; +}; + +struct nm_statechg_signal_data { + u_int8_t obj_class; + void *obj; + struct gsm_nm_state *old_state; + struct gsm_nm_state *new_state; + struct abis_om_obj_inst *obj_inst; +}; + +struct nm_nack_signal_data { + struct msgb *msg; + uint8_t mt; +}; + +struct challoc_signal_data { + struct gsm_bts *bts; + struct gsm_lchan *lchan; + enum gsm_chan_t type; +}; + +struct rf_signal_data { + struct gsm_network *net; +}; + +struct sms_signal_data { + /* The transaction where this occured */ + struct gsm_trans *trans; + /* Can be NULL for SMMA */ + struct gsm_sms *sms; + /* int paging result. Only the ones with > 0 */ + int paging_result; +}; + +struct lchan_signal_data { + /* The lchan the signal happened on */ + struct gsm_lchan *lchan; + /* Measurement reports on this lchan */ + struct gsm_meas_rep *mr; +}; + +enum signal_ns { + S_NS_RESET, + S_NS_BLOCK, + S_NS_UNBLOCK, + S_NS_ALIVE_EXP, /* Tns-alive expired more than N times */ +}; + +struct ns_signal_data { + struct gprs_nsvc *nsvc; + uint8_t cause; +}; + +/* MSC signals */ +enum signal_msc { + S_MSC_LOST, + S_MSC_CONNECTED, +}; + +struct osmo_msc_data; +struct msc_signal_data { + struct osmo_msc_data *data; +}; + +/* handover */ +enum signal_ho { + S_HANDOVER_ACK, +}; + +struct ho_signal_data { + struct gsm_lchan *old_lchan; + struct gsm_lchan *new_lchan; +}; + +struct input_signal_data { + int link_type; + uint8_t tei; + uint8_t sapi; + struct gsm_bts_trx *trx; + struct e1inp_line *line; +}; + +#endif diff --git a/include/openbsc/silent_call.h b/include/openbsc/silent_call.h new file mode 100644 index 000000000..2492903c2 --- /dev/null +++ b/include/openbsc/silent_call.h @@ -0,0 +1,12 @@ +#ifndef _SILENT_CALL_H +#define _SILENT_CALL_H + +struct gsm_subscriber_connection; + +extern int gsm_silent_call_start(struct gsm_subscriber *subscr, + void *data, int type); +extern int gsm_silent_call_stop(struct gsm_subscriber *subscr); +extern int silent_call_rx(struct gsm_subscriber_connection *conn, struct msgb *msg); +extern int silent_call_reroute(struct gsm_subscriber_connection *conn, struct msgb *msg); + +#endif /* _SILENT_CALL_H */ diff --git a/include/openbsc/sms_queue.h b/include/openbsc/sms_queue.h new file mode 100644 index 000000000..2a8bd5850 --- /dev/null +++ b/include/openbsc/sms_queue.h @@ -0,0 +1,17 @@ +#ifndef SMS_QUEUE_H +#define SMS_QUEUE_H + +struct gsm_network; +struct gsm_sms_queue; +struct vty; + +int sms_queue_start(struct gsm_network *, int in_flight); +int sms_queue_trigger(struct gsm_sms_queue *); + +/* vty helper functions */ +int sms_queue_stats(struct gsm_sms_queue *, struct vty* vty); +int sms_queue_set_max_pending(struct gsm_sms_queue *, int max); +int sms_queue_set_max_failure(struct gsm_sms_queue *, int fail); +int sms_queue_clear(struct gsm_sms_queue *); + +#endif diff --git a/include/openbsc/socket.h b/include/openbsc/socket.h new file mode 100644 index 000000000..4d3161146 --- /dev/null +++ b/include/openbsc/socket.h @@ -0,0 +1,14 @@ +#ifndef _BSC_SOCKET_H +#define _BSC_SOCKET_H + +#include +#include + +#ifndef IPPROTO_GRE +#define IPPROTO_GRE 47 +#endif + +int make_sock(struct bsc_fd *bfd, int proto, u_int32_t ip, u_int16_t port, + int (*cb)(struct bsc_fd *fd, unsigned int what)); + +#endif /* _BSC_SOCKET_H */ diff --git a/include/openbsc/subchan_demux.h b/include/openbsc/subchan_demux.h new file mode 100644 index 000000000..da2a7f325 --- /dev/null +++ b/include/openbsc/subchan_demux.h @@ -0,0 +1,101 @@ +#ifndef _SUBCH_DEMUX_H +#define _SUBCH_DEMUX_H +/* A E1 sub-channel (de)multiplexer with TRAU frame sync */ + +/* (C) 2009 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#define NR_SUBCH 4 +#define TRAU_FRAME_SIZE 40 +#define TRAU_FRAME_BITS (TRAU_FRAME_SIZE*8) + +/***********************************************************************/ +/* DEMULTIPLEXER */ +/***********************************************************************/ + +struct demux_subch { + u_int8_t out_bitbuf[TRAU_FRAME_BITS]; + u_int16_t out_idx; /* next bit to be written in out_bitbuf */ + /* number of consecutive zeros that we have received (for sync) */ + unsigned int consecutive_zeros; + /* are we in TRAU frame sync or not? */ + unsigned int in_sync; +}; + +struct subch_demux { + /* bitmask of currently active subchannels */ + u_int8_t chan_activ; + /* one demux_subch struct for every subchannel */ + struct demux_subch subch[NR_SUBCH]; + /* callback to be called once we have received a complete + * frame on a given subchannel */ + int (*out_cb)(struct subch_demux *dmx, int ch, u_int8_t *data, int len, + void *); + /* user-provided data, transparently passed to out_cb() */ + void *data; +}; + +/* initialize one demultiplexer instance */ +int subch_demux_init(struct subch_demux *dmx); + +/* feed 'len' number of muxed bytes into the demultiplexer */ +int subch_demux_in(struct subch_demux *dmx, u_int8_t *data, int len); + +/* activate decoding/processing for one subchannel */ +int subch_demux_activate(struct subch_demux *dmx, int subch); + +/* deactivate decoding/processing for one subchannel */ +int subch_demux_deactivate(struct subch_demux *dmx, int subch); + +/***********************************************************************/ +/* MULTIPLEXER */ +/***********************************************************************/ + +/* one element in the tx_queue of a muxer sub-channel */ +struct subch_txq_entry { + struct llist_head list; + + unsigned int bit_len; /* total number of bits in 'bits' */ + unsigned int next_bit; /* next bit to be transmitted */ + + u_int8_t bits[0]; /* one bit per byte */ +}; + +struct mux_subch { + struct llist_head tx_queue; +}; + +/* structure representing one instance of the subchannel muxer */ +struct subch_mux { + struct mux_subch subch[NR_SUBCH]; +}; + +/* initialize a subchannel muxer instance */ +int subchan_mux_init(struct subch_mux *mx); + +/* request the output of 'len' multiplexed bytes */ +int subchan_mux_out(struct subch_mux *mx, u_int8_t *data, int len); + +/* enqueue some data into one sub-channel of the muxer */ +int subchan_mux_enqueue(struct subch_mux *mx, int s_nr, const u_int8_t *data, + int len); + +#endif /* _SUBCH_DEMUX_H */ diff --git a/include/openbsc/system_information.h b/include/openbsc/system_information.h new file mode 100644 index 000000000..da662e912 --- /dev/null +++ b/include/openbsc/system_information.h @@ -0,0 +1,45 @@ +#ifndef _SYSTEM_INFO_H +#define _SYSTEM_INFO_H + +#include + +#define GSM_MACBLOCK_LEN 23 + +struct gsm_bts; + + +enum osmo_sysinfo_type { + SYSINFO_TYPE_NONE, + SYSINFO_TYPE_1, + SYSINFO_TYPE_2, + SYSINFO_TYPE_3, + SYSINFO_TYPE_4, + SYSINFO_TYPE_5, + SYSINFO_TYPE_6, + SYSINFO_TYPE_7, + SYSINFO_TYPE_8, + SYSINFO_TYPE_9, + SYSINFO_TYPE_10, + SYSINFO_TYPE_13, + SYSINFO_TYPE_16, + SYSINFO_TYPE_17, + SYSINFO_TYPE_18, + SYSINFO_TYPE_19, + SYSINFO_TYPE_20, + SYSINFO_TYPE_2bis, + SYSINFO_TYPE_2ter, + SYSINFO_TYPE_2quater, + SYSINFO_TYPE_5bis, + SYSINFO_TYPE_5ter, + /* FIXME all the various bis and ter */ + _MAX_SYSINFO_TYPE +}; + +typedef u_int8_t sysinfo_buf_t[GSM_MACBLOCK_LEN]; + +extern const struct value_string osmo_sitype_strs[_MAX_SYSINFO_TYPE]; +uint8_t gsm_sitype2rsl(enum osmo_sysinfo_type si_type); +const char *gsm_sitype_name(enum osmo_sysinfo_type si_type); +int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type type); + +#endif diff --git a/include/openbsc/transaction.h b/include/openbsc/transaction.h new file mode 100644 index 000000000..e41d8ef91 --- /dev/null +++ b/include/openbsc/transaction.h @@ -0,0 +1,75 @@ +#ifndef _TRANSACT_H +#define _TRANSACT_H + +#include +#include +#include +#include + +/* One transaction */ +struct gsm_trans { + /* Entry in list of all transactions */ + struct llist_head entry; + + /* The protocol within which we live */ + u_int8_t protocol; + + /* The current transaction ID */ + u_int8_t transaction_id; + + /* To whom we belong, unique identifier of remote MM entity */ + struct gsm_subscriber *subscr; + + /* The associated connection we are using to transmit messages */ + struct gsm_subscriber_connection *conn; + + /* reference from MNCC or other application */ + u_int32_t callref; + + /* if traffic channel receive was requested */ + int tch_recv; + + /* is thats one paging? */ + struct gsm_network **paging_request; + + union { + struct { + + /* current call state */ + int state; + + /* current timer and message queue */ + int Tcurrent; /* current CC timer */ + int T308_second; /* used to send release again */ + struct timer_list timer; + struct gsm_mncc msg; /* stores setup/disconnect/release message */ + } cc; + struct { + u_int8_t link_id; /* RSL Link ID to be used for this trans */ + int is_mt; /* is this a MO (0) or MT (1) transfer */ + enum gsm411_cp_state cp_state; + struct timer_list cp_timer; + + enum gsm411_rp_state rp_state; + + struct gsm_sms *sms; + } sms; + }; +}; + + + +struct gsm_trans *trans_find_by_id(struct gsm_subscriber *subscr, + u_int8_t proto, u_int8_t trans_id); +struct gsm_trans *trans_find_by_callref(struct gsm_network *net, + u_int32_t callref); + +struct gsm_trans *trans_alloc(struct gsm_subscriber *subscr, + u_int8_t protocol, u_int8_t trans_id, + u_int32_t callref); +void trans_free(struct gsm_trans *trans); + +int trans_assign_trans_id(struct gsm_subscriber *subscr, + u_int8_t protocol, u_int8_t ti_flag); + +#endif diff --git a/include/openbsc/trau_frame.h b/include/openbsc/trau_frame.h new file mode 100644 index 000000000..c594c38cb --- /dev/null +++ b/include/openbsc/trau_frame.h @@ -0,0 +1,64 @@ +#ifndef _TRAU_FRAME_H +#define _TRAU_FRAME_H +/* TRAU frame handling according to GSM TS 08.60 */ + +/* (C) 2009 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +/* 21 for FR/EFR, 25 for AMR, 15 for OM, 15 for data, 13 for E-data, 21 idle */ +#define MAX_C_BITS 25 +/* 260 for FR/EFR, 256 for AMR, 264 for OM, 288 for E-data */ +#define MAX_D_BITS 288 +/* for all speech frames */ +#define MAX_T_BITS 4 +/* for OM */ +#define MAX_S_BITS 6 +/* for E-data */ +#define MAX_M_BITS 2 + +struct decoded_trau_frame { + u_int8_t c_bits[MAX_C_BITS]; + u_int8_t d_bits[MAX_D_BITS]; + u_int8_t t_bits[MAX_T_BITS]; + u_int8_t s_bits[MAX_S_BITS]; + u_int8_t m_bits[MAX_M_BITS]; +}; + +#define TRAU_FT_FR_UP 0x02 /* 0 0 0 1 0 - 3.5.1.1.1 */ +#define TRAU_FT_FR_DOWN 0x1c /* 1 1 1 0 0 - 3.5.1.1.1 */ +#define TRAU_FT_EFR 0x1a /* 1 1 0 1 0 - 3.5.1.1.1 */ +#define TRAU_FT_AMR 0x06 /* 0 0 1 1 0 - 3.5.1.2 */ +#define TRAU_FT_OM_UP 0x07 /* 0 0 1 0 1 - 3.5.2 */ +#define TRAU_FT_OM_DOWN 0x1b /* 1 1 0 1 1 - 3.5.2 */ +#define TRAU_FT_DATA_UP 0x08 /* 0 1 0 0 0 - 3.5.3 */ +#define TRAU_FT_DATA_DOWN 0x16 /* 1 0 1 1 0 - 3.5.3 */ +#define TRAU_FT_D145_SYNC 0x14 /* 1 0 1 0 0 - 3.5.3 */ +#define TRAU_FT_EDATA 0x1f /* 1 1 1 1 1 - 3.5.4 */ +#define TRAU_FT_IDLE_UP 0x10 /* 1 0 0 0 0 - 3.5.5 */ +#define TRAU_FT_IDLE_DOWN 0x0e /* 0 1 1 1 0 - 3.5.5 */ + + +int decode_trau_frame(struct decoded_trau_frame *fr, const u_int8_t *trau_bits); +int encode_trau_frame(u_int8_t *trau_bits, const struct decoded_trau_frame *fr); +int trau_frame_up2down(struct decoded_trau_frame *fr); +u_int8_t *trau_idle_frame(void); + + +#endif /* _TRAU_FRAME_H */ diff --git a/include/openbsc/trau_mux.h b/include/openbsc/trau_mux.h new file mode 100644 index 000000000..dcf33ee8f --- /dev/null +++ b/include/openbsc/trau_mux.h @@ -0,0 +1,48 @@ +/* Simple TRAU frame reflector to route voice calls */ + +/* (C) 2009 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +/* The "TRAU mux map" defines which particular 16kbit sub-slot (in which E1 + * timeslot on which E1 interface) should be directly muxed to which other + * sub-slot. Entries in the mux map are always bi-directional. + * + * The idea of all this is to directly switch voice channels in the BSC + * from one phone to another. We do this right now since we don't support + * any external interface for voice channels, and in the future as an + * optimization to routing them externally. + */ + +/* map a TRAU mux map entry */ +int trau_mux_map(const struct gsm_e1_subslot *src, + const struct gsm_e1_subslot *dst); +int trau_mux_map_lchan(const struct gsm_lchan *src, + const struct gsm_lchan *dst); + +/* unmap a TRAU mux map entry */ +int trau_mux_unmap(const struct gsm_e1_subslot *ss, u_int32_t callref); + +/* we get called by subchan_demux */ +int trau_mux_input(struct gsm_e1_subslot *src_e1_ss, + const u_int8_t *trau_bits, int num_bits); + +/* add a trau receiver */ +int trau_recv_lchan(struct gsm_lchan *lchan, u_int32_t callref); + +/* send trau from application */ +int trau_send_frame(struct gsm_lchan *lchan, struct gsm_data_frame *frame); diff --git a/include/openbsc/ussd.h b/include/openbsc/ussd.h new file mode 100644 index 000000000..6f80d23d8 --- /dev/null +++ b/include/openbsc/ussd.h @@ -0,0 +1,10 @@ +#ifndef _USSD_H +#define _USSD_H + +/* Handler function for mobile-originated USSD messages */ + +#include + +int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg); + +#endif diff --git a/include/openbsc/vty.h b/include/openbsc/vty.h new file mode 100644 index 000000000..516c8c2a0 --- /dev/null +++ b/include/openbsc/vty.h @@ -0,0 +1,46 @@ +#ifndef OPENBSC_VTY_H +#define OPENBSC_VTY_H + +#include +#include +#include + +struct gsm_network; +struct vty; + +void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *); + +struct buffer *vty_argv_to_buffer(int argc, const char *argv[], int base); + +extern struct cmd_element cfg_description_cmd; +extern struct cmd_element cfg_no_description_cmd; +extern struct cmd_element ournode_exit_cmd; +extern struct cmd_element ournode_end_cmd; + +enum bsc_vty_node { + GSMNET_NODE = _LAST_OSMOVTY_NODE + 1, + BTS_NODE, + TRX_NODE, + TS_NODE, + SUBSCR_NODE, + MGCP_NODE, + GBPROXY_NODE, + SGSN_NODE, + NS_NODE, + BSSGP_NODE, + OML_NODE, + E1INP_NODE, + NAT_NODE, + NAT_BSC_NODE, + MSC_NODE, + OM2K_NODE, + TRUNK_NODE, +}; + +extern int bsc_vty_is_config_node(struct vty *vty, int node); +extern void bsc_replace_string(void *ctx, char **dst, const char *newstr); + +int bsc_vty_init(void); +int bsc_vty_init_extra(void); + +#endif diff --git a/install-sh b/install-sh new file mode 100755 index 000000000..6781b987b --- /dev/null +++ b/install-sh @@ -0,0 +1,520 @@ +#!/bin/sh +# install - install a program, script, or datafile + +scriptversion=2009-04-28.21; # UTC + +# This originates from X11R5 (mit/util/scripts/install.sh), which was +# later released in X11R6 (xc/config/util/install.sh) with the +# following copyright and license. +# +# Copyright (C) 1994 X Consortium +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- +# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the name of the X Consortium shall not +# be used in advertising or otherwise to promote the sale, use or other deal- +# ings in this Software without prior written authorization from the X Consor- +# tium. +# +# +# FSF changes to this file are in the public domain. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. + +nl=' +' +IFS=" "" $nl" + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit=${DOITPROG-} +if test -z "$doit"; then + doit_exec=exec +else + doit_exec=$doit +fi + +# Put in absolute file names if you don't have them in your path; +# or use environment vars. + +chgrpprog=${CHGRPPROG-chgrp} +chmodprog=${CHMODPROG-chmod} +chownprog=${CHOWNPROG-chown} +cmpprog=${CMPPROG-cmp} +cpprog=${CPPROG-cp} +mkdirprog=${MKDIRPROG-mkdir} +mvprog=${MVPROG-mv} +rmprog=${RMPROG-rm} +stripprog=${STRIPPROG-strip} + +posix_glob='?' +initialize_posix_glob=' + test "$posix_glob" != "?" || { + if (set -f) 2>/dev/null; then + posix_glob= + else + posix_glob=: + fi + } +' + +posix_mkdir= + +# Desired mode of installed file. +mode=0755 + +chgrpcmd= +chmodcmd=$chmodprog +chowncmd= +mvcmd=$mvprog +rmcmd="$rmprog -f" +stripcmd= + +src= +dst= +dir_arg= +dst_arg= + +copy_on_change=false +no_target_directory= + +usage="\ +Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE + or: $0 [OPTION]... SRCFILES... DIRECTORY + or: $0 [OPTION]... -t DIRECTORY SRCFILES... + or: $0 [OPTION]... -d DIRECTORIES... + +In the 1st form, copy SRCFILE to DSTFILE. +In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. +In the 4th, create DIRECTORIES. + +Options: + --help display this help and exit. + --version display version info and exit. + + -c (ignored) + -C install only if different (preserve the last data modification time) + -d create directories instead of installing files. + -g GROUP $chgrpprog installed files to GROUP. + -m MODE $chmodprog installed files to MODE. + -o USER $chownprog installed files to USER. + -s $stripprog installed files. + -t DIRECTORY install into DIRECTORY. + -T report an error if DSTFILE is a directory. + +Environment variables override the default commands: + CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG + RMPROG STRIPPROG +" + +while test $# -ne 0; do + case $1 in + -c) ;; + + -C) copy_on_change=true;; + + -d) dir_arg=true;; + + -g) chgrpcmd="$chgrpprog $2" + shift;; + + --help) echo "$usage"; exit $?;; + + -m) mode=$2 + case $mode in + *' '* | *' '* | *' +'* | *'*'* | *'?'* | *'['*) + echo "$0: invalid mode: $mode" >&2 + exit 1;; + esac + shift;; + + -o) chowncmd="$chownprog $2" + shift;; + + -s) stripcmd=$stripprog;; + + -t) dst_arg=$2 + shift;; + + -T) no_target_directory=true;; + + --version) echo "$0 $scriptversion"; exit $?;; + + --) shift + break;; + + -*) echo "$0: invalid option: $1" >&2 + exit 1;; + + *) break;; + esac + shift +done + +if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then + # When -d is used, all remaining arguments are directories to create. + # When -t is used, the destination is already specified. + # Otherwise, the last argument is the destination. Remove it from $@. + for arg + do + if test -n "$dst_arg"; then + # $@ is not empty: it contains at least $arg. + set fnord "$@" "$dst_arg" + shift # fnord + fi + shift # arg + dst_arg=$arg + done +fi + +if test $# -eq 0; then + if test -z "$dir_arg"; then + echo "$0: no input file specified." >&2 + exit 1 + fi + # It's OK to call `install-sh -d' without argument. + # This can happen when creating conditional directories. + exit 0 +fi + +if test -z "$dir_arg"; then + trap '(exit $?); exit' 1 2 13 15 + + # Set umask so as not to create temps with too-generous modes. + # However, 'strip' requires both read and write access to temps. + case $mode in + # Optimize common cases. + *644) cp_umask=133;; + *755) cp_umask=22;; + + *[0-7]) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw='% 200' + fi + cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; + *) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw=,u+rw + fi + cp_umask=$mode$u_plus_rw;; + esac +fi + +for src +do + # Protect names starting with `-'. + case $src in + -*) src=./$src;; + esac + + if test -n "$dir_arg"; then + dst=$src + dstdir=$dst + test -d "$dstdir" + dstdir_status=$? + else + + # Waiting for this to be detected by the "$cpprog $src $dsttmp" command + # might cause directories to be created, which would be especially bad + # if $src (and thus $dsttmp) contains '*'. + if test ! -f "$src" && test ! -d "$src"; then + echo "$0: $src does not exist." >&2 + exit 1 + fi + + if test -z "$dst_arg"; then + echo "$0: no destination specified." >&2 + exit 1 + fi + + dst=$dst_arg + # Protect names starting with `-'. + case $dst in + -*) dst=./$dst;; + esac + + # If destination is a directory, append the input filename; won't work + # if double slashes aren't ignored. + if test -d "$dst"; then + if test -n "$no_target_directory"; then + echo "$0: $dst_arg: Is a directory" >&2 + exit 1 + fi + dstdir=$dst + dst=$dstdir/`basename "$src"` + dstdir_status=0 + else + # Prefer dirname, but fall back on a substitute if dirname fails. + dstdir=` + (dirname "$dst") 2>/dev/null || + expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$dst" : 'X\(//\)[^/]' \| \ + X"$dst" : 'X\(//\)$' \| \ + X"$dst" : 'X\(/\)' \| . 2>/dev/null || + echo X"$dst" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q' + ` + + test -d "$dstdir" + dstdir_status=$? + fi + fi + + obsolete_mkdir_used=false + + if test $dstdir_status != 0; then + case $posix_mkdir in + '') + # Create intermediate dirs using mode 755 as modified by the umask. + # This is like FreeBSD 'install' as of 1997-10-28. + umask=`umask` + case $stripcmd.$umask in + # Optimize common cases. + *[2367][2367]) mkdir_umask=$umask;; + .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; + + *[0-7]) + mkdir_umask=`expr $umask + 22 \ + - $umask % 100 % 40 + $umask % 20 \ + - $umask % 10 % 4 + $umask % 2 + `;; + *) mkdir_umask=$umask,go-w;; + esac + + # With -d, create the new directory with the user-specified mode. + # Otherwise, rely on $mkdir_umask. + if test -n "$dir_arg"; then + mkdir_mode=-m$mode + else + mkdir_mode= + fi + + posix_mkdir=false + case $umask in + *[123567][0-7][0-7]) + # POSIX mkdir -p sets u+wx bits regardless of umask, which + # is incompatible with FreeBSD 'install' when (umask & 300) != 0. + ;; + *) + tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ + trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0 + + if (umask $mkdir_umask && + exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1 + then + if test -z "$dir_arg" || { + # Check for POSIX incompatibilities with -m. + # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or + # other-writeable bit of parent directory when it shouldn't. + # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. + ls_ld_tmpdir=`ls -ld "$tmpdir"` + case $ls_ld_tmpdir in + d????-?r-*) different_mode=700;; + d????-?--*) different_mode=755;; + *) false;; + esac && + $mkdirprog -m$different_mode -p -- "$tmpdir" && { + ls_ld_tmpdir_1=`ls -ld "$tmpdir"` + test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" + } + } + then posix_mkdir=: + fi + rmdir "$tmpdir/d" "$tmpdir" + else + # Remove any dirs left behind by ancient mkdir implementations. + rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null + fi + trap '' 0;; + esac;; + esac + + if + $posix_mkdir && ( + umask $mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" + ) + then : + else + + # The umask is ridiculous, or mkdir does not conform to POSIX, + # or it failed possibly due to a race condition. Create the + # directory the slow way, step by step, checking for races as we go. + + case $dstdir in + /*) prefix='/';; + -*) prefix='./';; + *) prefix='';; + esac + + eval "$initialize_posix_glob" + + oIFS=$IFS + IFS=/ + $posix_glob set -f + set fnord $dstdir + shift + $posix_glob set +f + IFS=$oIFS + + prefixes= + + for d + do + test -z "$d" && continue + + prefix=$prefix$d + if test -d "$prefix"; then + prefixes= + else + if $posix_mkdir; then + (umask=$mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break + # Don't fail if two instances are running concurrently. + test -d "$prefix" || exit 1 + else + case $prefix in + *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; + *) qprefix=$prefix;; + esac + prefixes="$prefixes '$qprefix'" + fi + fi + prefix=$prefix/ + done + + if test -n "$prefixes"; then + # Don't fail if two instances are running concurrently. + (umask $mkdir_umask && + eval "\$doit_exec \$mkdirprog $prefixes") || + test -d "$dstdir" || exit 1 + obsolete_mkdir_used=true + fi + fi + fi + + if test -n "$dir_arg"; then + { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && + { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || + test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 + else + + # Make a couple of temp file names in the proper directory. + dsttmp=$dstdir/_inst.$$_ + rmtmp=$dstdir/_rm.$$_ + + # Trap to clean up those temp files at exit. + trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 + + # Copy the file name to the temp name. + (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && + + # and set any options; do chmod last to preserve setuid bits. + # + # If any of these fail, we abort the whole thing. If we want to + # ignore errors from any of these, just make sure not to ignore + # errors from the above "$doit $cpprog $src $dsttmp" command. + # + { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && + { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && + { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && + + # If -C, don't bother to copy if it wouldn't change the file. + if $copy_on_change && + old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && + new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && + + eval "$initialize_posix_glob" && + $posix_glob set -f && + set X $old && old=:$2:$4:$5:$6 && + set X $new && new=:$2:$4:$5:$6 && + $posix_glob set +f && + + test "$old" = "$new" && + $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 + then + rm -f "$dsttmp" + else + # Rename the file to the real destination. + $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || + + # The rename failed, perhaps because mv can't rename something else + # to itself, or perhaps because mv is so ancient that it does not + # support -f. + { + # Now remove or move aside any old file at destination location. + # We try this two ways since rm can't unlink itself on some + # systems and the destination file might be busy for other + # reasons. In this case, the final cleanup might fail but the new + # file should still install successfully. + { + test ! -f "$dst" || + $doit $rmcmd -f "$dst" 2>/dev/null || + { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && + { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } + } || + { echo "$0: cannot unlink or rename $dst" >&2 + (exit 1); exit 1 + } + } && + + # Now rename the file to the real destination. + $doit $mvcmd "$dsttmp" "$dst" + } + fi || exit 1 + + trap '' 0 + fi +done + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/missing b/missing new file mode 100755 index 000000000..28055d2ae --- /dev/null +++ b/missing @@ -0,0 +1,376 @@ +#! /bin/sh +# Common stub for a few missing GNU programs while installing. + +scriptversion=2009-04-28.21; # UTC + +# Copyright (C) 1996, 1997, 1999, 2000, 2002, 2003, 2004, 2005, 2006, +# 2008, 2009 Free Software Foundation, Inc. +# Originally by Fran,cois Pinard , 1996. + +# 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, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +if test $# -eq 0; then + echo 1>&2 "Try \`$0 --help' for more information" + exit 1 +fi + +run=: +sed_output='s/.* --output[ =]\([^ ]*\).*/\1/p' +sed_minuso='s/.* -o \([^ ]*\).*/\1/p' + +# In the cases where this matters, `missing' is being run in the +# srcdir already. +if test -f configure.ac; then + configure_ac=configure.ac +else + configure_ac=configure.in +fi + +msg="missing on your system" + +case $1 in +--run) + # Try to run requested program, and just exit if it succeeds. + run= + shift + "$@" && exit 0 + # Exit code 63 means version mismatch. This often happens + # when the user try to use an ancient version of a tool on + # a file that requires a minimum version. In this case we + # we should proceed has if the program had been absent, or + # if --run hadn't been passed. + if test $? = 63; then + run=: + msg="probably too old" + fi + ;; + + -h|--h|--he|--hel|--help) + echo "\ +$0 [OPTION]... PROGRAM [ARGUMENT]... + +Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an +error status if there is no known handling for PROGRAM. + +Options: + -h, --help display this help and exit + -v, --version output version information and exit + --run try to run the given command, and emulate it if it fails + +Supported PROGRAM values: + aclocal touch file \`aclocal.m4' + autoconf touch file \`configure' + autoheader touch file \`config.h.in' + autom4te touch the output file, or create a stub one + automake touch all \`Makefile.in' files + bison create \`y.tab.[ch]', if possible, from existing .[ch] + flex create \`lex.yy.c', if possible, from existing .c + help2man touch the output file + lex create \`lex.yy.c', if possible, from existing .c + makeinfo touch the output file + tar try tar, gnutar, gtar, then tar without non-portable flags + yacc create \`y.tab.[ch]', if possible, from existing .[ch] + +Version suffixes to PROGRAM as well as the prefixes \`gnu-', \`gnu', and +\`g' are ignored when checking the name. + +Send bug reports to ." + exit $? + ;; + + -v|--v|--ve|--ver|--vers|--versi|--versio|--version) + echo "missing $scriptversion (GNU Automake)" + exit $? + ;; + + -*) + echo 1>&2 "$0: Unknown \`$1' option" + echo 1>&2 "Try \`$0 --help' for more information" + exit 1 + ;; + +esac + +# normalize program name to check for. +program=`echo "$1" | sed ' + s/^gnu-//; t + s/^gnu//; t + s/^g//; t'` + +# Now exit if we have it, but it failed. Also exit now if we +# don't have it and --version was passed (most likely to detect +# the program). This is about non-GNU programs, so use $1 not +# $program. +case $1 in + lex*|yacc*) + # Not GNU programs, they don't have --version. + ;; + + tar*) + if test -n "$run"; then + echo 1>&2 "ERROR: \`tar' requires --run" + exit 1 + elif test "x$2" = "x--version" || test "x$2" = "x--help"; then + exit 1 + fi + ;; + + *) + if test -z "$run" && ($1 --version) > /dev/null 2>&1; then + # We have it, but it failed. + exit 1 + elif test "x$2" = "x--version" || test "x$2" = "x--help"; then + # Could not run --version or --help. This is probably someone + # running `$TOOL --version' or `$TOOL --help' to check whether + # $TOOL exists and not knowing $TOOL uses missing. + exit 1 + fi + ;; +esac + +# If it does not exist, or fails to run (possibly an outdated version), +# try to emulate it. +case $program in + aclocal*) + echo 1>&2 "\ +WARNING: \`$1' is $msg. You should only need it if + you modified \`acinclude.m4' or \`${configure_ac}'. You might want + to install the \`Automake' and \`Perl' packages. Grab them from + any GNU archive site." + touch aclocal.m4 + ;; + + autoconf*) + echo 1>&2 "\ +WARNING: \`$1' is $msg. You should only need it if + you modified \`${configure_ac}'. You might want to install the + \`Autoconf' and \`GNU m4' packages. Grab them from any GNU + archive site." + touch configure + ;; + + autoheader*) + echo 1>&2 "\ +WARNING: \`$1' is $msg. You should only need it if + you modified \`acconfig.h' or \`${configure_ac}'. You might want + to install the \`Autoconf' and \`GNU m4' packages. Grab them + from any GNU archive site." + files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}` + test -z "$files" && files="config.h" + touch_files= + for f in $files; do + case $f in + *:*) touch_files="$touch_files "`echo "$f" | + sed -e 's/^[^:]*://' -e 's/:.*//'`;; + *) touch_files="$touch_files $f.in";; + esac + done + touch $touch_files + ;; + + automake*) + echo 1>&2 "\ +WARNING: \`$1' is $msg. You should only need it if + you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'. + You might want to install the \`Automake' and \`Perl' packages. + Grab them from any GNU archive site." + find . -type f -name Makefile.am -print | + sed 's/\.am$/.in/' | + while read f; do touch "$f"; done + ;; + + autom4te*) + echo 1>&2 "\ +WARNING: \`$1' is needed, but is $msg. + You might have modified some files without having the + proper tools for further handling them. + You can get \`$1' as part of \`Autoconf' from any GNU + archive site." + + file=`echo "$*" | sed -n "$sed_output"` + test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"` + if test -f "$file"; then + touch $file + else + test -z "$file" || exec >$file + echo "#! /bin/sh" + echo "# Created by GNU Automake missing as a replacement of" + echo "# $ $@" + echo "exit 0" + chmod +x $file + exit 1 + fi + ;; + + bison*|yacc*) + echo 1>&2 "\ +WARNING: \`$1' $msg. You should only need it if + you modified a \`.y' file. You may need the \`Bison' package + in order for those modifications to take effect. You can get + \`Bison' from any GNU archive site." + rm -f y.tab.c y.tab.h + if test $# -ne 1; then + eval LASTARG="\${$#}" + case $LASTARG in + *.y) + SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'` + if test -f "$SRCFILE"; then + cp "$SRCFILE" y.tab.c + fi + SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'` + if test -f "$SRCFILE"; then + cp "$SRCFILE" y.tab.h + fi + ;; + esac + fi + if test ! -f y.tab.h; then + echo >y.tab.h + fi + if test ! -f y.tab.c; then + echo 'main() { return 0; }' >y.tab.c + fi + ;; + + lex*|flex*) + echo 1>&2 "\ +WARNING: \`$1' is $msg. You should only need it if + you modified a \`.l' file. You may need the \`Flex' package + in order for those modifications to take effect. You can get + \`Flex' from any GNU archive site." + rm -f lex.yy.c + if test $# -ne 1; then + eval LASTARG="\${$#}" + case $LASTARG in + *.l) + SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'` + if test -f "$SRCFILE"; then + cp "$SRCFILE" lex.yy.c + fi + ;; + esac + fi + if test ! -f lex.yy.c; then + echo 'main() { return 0; }' >lex.yy.c + fi + ;; + + help2man*) + echo 1>&2 "\ +WARNING: \`$1' is $msg. You should only need it if + you modified a dependency of a manual page. You may need the + \`Help2man' package in order for those modifications to take + effect. You can get \`Help2man' from any GNU archive site." + + file=`echo "$*" | sed -n "$sed_output"` + test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"` + if test -f "$file"; then + touch $file + else + test -z "$file" || exec >$file + echo ".ab help2man is required to generate this page" + exit $? + fi + ;; + + makeinfo*) + echo 1>&2 "\ +WARNING: \`$1' is $msg. You should only need it if + you modified a \`.texi' or \`.texinfo' file, or any other file + indirectly affecting the aspect of the manual. The spurious + call might also be the consequence of using a buggy \`make' (AIX, + DU, IRIX). You might want to install the \`Texinfo' package or + the \`GNU make' package. Grab either from any GNU archive site." + # The file to touch is that specified with -o ... + file=`echo "$*" | sed -n "$sed_output"` + test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"` + if test -z "$file"; then + # ... or it is the one specified with @setfilename ... + infile=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'` + file=`sed -n ' + /^@setfilename/{ + s/.* \([^ ]*\) *$/\1/ + p + q + }' $infile` + # ... or it is derived from the source name (dir/f.texi becomes f.info) + test -z "$file" && file=`echo "$infile" | sed 's,.*/,,;s,.[^.]*$,,'`.info + fi + # If the file does not exist, the user really needs makeinfo; + # let's fail without touching anything. + test -f $file || exit 1 + touch $file + ;; + + tar*) + shift + + # We have already tried tar in the generic part. + # Look for gnutar/gtar before invocation to avoid ugly error + # messages. + if (gnutar --version > /dev/null 2>&1); then + gnutar "$@" && exit 0 + fi + if (gtar --version > /dev/null 2>&1); then + gtar "$@" && exit 0 + fi + firstarg="$1" + if shift; then + case $firstarg in + *o*) + firstarg=`echo "$firstarg" | sed s/o//` + tar "$firstarg" "$@" && exit 0 + ;; + esac + case $firstarg in + *h*) + firstarg=`echo "$firstarg" | sed s/h//` + tar "$firstarg" "$@" && exit 0 + ;; + esac + fi + + echo 1>&2 "\ +WARNING: I can't seem to be able to run \`tar' with the given arguments. + You may want to install GNU tar or Free paxutils, or check the + command line arguments." + exit 1 + ;; + + *) + echo 1>&2 "\ +WARNING: \`$1' is needed, and is $msg. + You might have modified some files without having the + proper tools for further handling them. Check the \`README' file, + it often tells you about the needed prerequisites for installing + this package. You may also peek at any GNU archive site, in case + some other package would contain this missing \`$1' program." + exit 1 + ;; +esac + +exit 0 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/openbsc.pc.in b/openbsc.pc.in new file mode 100644 index 000000000..aba07e296 --- /dev/null +++ b/openbsc.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@/ + +Name: OpenBSC +Description: OpenBSC base station controller +Requires: +Version: @VERSION@ +Libs: -L${libdir} -lopenbsc +Cflags: -I${includedir} diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 000000000..1573563f0 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,13 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) + +SUBDIRS = libcommon libabis libmgcp libbsc libmsc libtrau osmo-nitb osmo-bsc_mgcp utils ipaccess libgb gprs + +# Conditional modules +if BUILD_NAT +SUBDIRS += osmo-bsc_nat +endif +if BUILD_BSC +SUBDIRS += osmo-bsc +endif diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 000000000..b1c2f0884 --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,548 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : + +# Conditional modules +@BUILD_NAT_TRUE@am__append_1 = osmo-bsc_nat +@BUILD_BSC_TRUE@am__append_2 = osmo-bsc +subdir = src +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \ + html-recursive info-recursive install-data-recursive \ + install-dvi-recursive install-exec-recursive \ + install-html-recursive install-info-recursive \ + install-pdf-recursive install-ps-recursive install-recursive \ + installcheck-recursive installdirs-recursive pdf-recursive \ + ps-recursive uninstall-recursive +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \ + $(RECURSIVE_CLEAN_TARGETS:-recursive=) tags TAGS ctags CTAGS \ + distdir +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = libcommon libabis libmgcp libbsc libmsc libtrau \ + osmo-nitb osmo-bsc_mgcp utils ipaccess libgb gprs osmo-bsc_nat \ + osmo-bsc +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) +SUBDIRS = libcommon libabis libmgcp libbsc libmsc libtrau osmo-nitb \ + osmo-bsc_mgcp utils ipaccess libgb gprs $(am__append_1) \ + $(am__append_2) +all: all-recursive + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +# This directory's subdirectories are mostly independent; you can cd +# into them and run `make' without going through this Makefile. +# To change the values of `make' variables: instead of editing Makefiles, +# (1) if the variable is set in `config.status', edit `config.status' +# (which will cause the Makefiles to be regenerated when you run `make'); +# (2) otherwise, pass the desired values on the `make' command line. +$(RECURSIVE_TARGETS): + @fail= failcom='exit 1'; \ + for f in x $$MAKEFLAGS; do \ + case $$f in \ + *=* | --[!k]*);; \ + *k*) failcom='fail=yes';; \ + esac; \ + done; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +$(RECURSIVE_CLEAN_TARGETS): + @fail= failcom='exit 1'; \ + for f in x $$MAKEFLAGS; do \ + case $$f in \ + *=* | --[!k]*);; \ + *k*) failcom='fail=yes';; \ + esac; \ + done; \ + dot_seen=no; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + rev=''; for subdir in $$list; do \ + if test "$$subdir" = "."; then :; else \ + rev="$$subdir $$rev"; \ + fi; \ + done; \ + rev="$$rev ."; \ + target=`echo $@ | sed s/-recursive//`; \ + for subdir in $$rev; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done && test -z "$$fail" +tags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \ + done +ctags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \ + done + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: tags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: ctags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) ctags-recursive \ + install-am install-strip tags-recursive + +.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \ + all all-am check check-am clean clean-generic ctags \ + ctags-recursive distclean distclean-generic distclean-tags \ + distdir dvi dvi-am html html-am info info-am install \ + install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + installdirs-am maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \ + tags-recursive uninstall uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/gprs/Makefile.am b/src/gprs/Makefile.am new file mode 100644 index 000000000..16c2200e5 --- /dev/null +++ b/src/gprs/Makefile.am @@ -0,0 +1,22 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS) + +noinst_HEADERS = gprs_sndcp.h + +if HAVE_LIBGTP +bin_PROGRAMS = osmo-gbproxy osmo-sgsn +else +bin_PROGRAMS = osmo-gbproxy +endif + +osmo_gbproxy_SOURCES = gb_proxy.c gb_proxy_main.c gb_proxy_vty.c +osmo_gbproxy_LDADD = $(top_builddir)/src/libgb/libgb.a \ + $(top_builddir)/src/libcommon/libcommon.a + +osmo_sgsn_SOURCES = gprs_gmm.c gprs_sgsn.c gprs_sndcp.c gprs_sndcp_vty.c \ + sgsn_main.c sgsn_vty.c sgsn_libgtp.c \ + gprs_llc.c gprs_llc_vty.c crc24.c +osmo_sgsn_LDADD = $(top_builddir)/src/libgb/libgb.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + -lgtp diff --git a/src/gprs/Makefile.in b/src/gprs/Makefile.in new file mode 100644 index 000000000..99ea7391a --- /dev/null +++ b/src/gprs/Makefile.in @@ -0,0 +1,522 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +@HAVE_LIBGTP_FALSE@bin_PROGRAMS = osmo-gbproxy$(EXEEXT) +@HAVE_LIBGTP_TRUE@bin_PROGRAMS = osmo-gbproxy$(EXEEXT) \ +@HAVE_LIBGTP_TRUE@ osmo-sgsn$(EXEEXT) +subdir = src/gprs +DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_osmo_gbproxy_OBJECTS = gb_proxy.$(OBJEXT) gb_proxy_main.$(OBJEXT) \ + gb_proxy_vty.$(OBJEXT) +osmo_gbproxy_OBJECTS = $(am_osmo_gbproxy_OBJECTS) +osmo_gbproxy_DEPENDENCIES = $(top_builddir)/src/libgb/libgb.a \ + $(top_builddir)/src/libcommon/libcommon.a +am_osmo_sgsn_OBJECTS = gprs_gmm.$(OBJEXT) gprs_sgsn.$(OBJEXT) \ + gprs_sndcp.$(OBJEXT) gprs_sndcp_vty.$(OBJEXT) \ + sgsn_main.$(OBJEXT) sgsn_vty.$(OBJEXT) sgsn_libgtp.$(OBJEXT) \ + gprs_llc.$(OBJEXT) gprs_llc_vty.$(OBJEXT) crc24.$(OBJEXT) +osmo_sgsn_OBJECTS = $(am_osmo_sgsn_OBJECTS) +osmo_sgsn_DEPENDENCIES = $(top_builddir)/src/libgb/libgb.a \ + $(top_builddir)/src/libcommon/libcommon.a +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(osmo_gbproxy_SOURCES) $(osmo_sgsn_SOURCES) +DIST_SOURCES = $(osmo_gbproxy_SOURCES) $(osmo_sgsn_SOURCES) +HEADERS = $(noinst_HEADERS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS) +noinst_HEADERS = gprs_sndcp.h +osmo_gbproxy_SOURCES = gb_proxy.c gb_proxy_main.c gb_proxy_vty.c +osmo_gbproxy_LDADD = $(top_builddir)/src/libgb/libgb.a \ + $(top_builddir)/src/libcommon/libcommon.a + +osmo_sgsn_SOURCES = gprs_gmm.c gprs_sgsn.c gprs_sndcp.c gprs_sndcp_vty.c \ + sgsn_main.c sgsn_vty.c sgsn_libgtp.c \ + gprs_llc.c gprs_llc_vty.c crc24.c + +osmo_sgsn_LDADD = $(top_builddir)/src/libgb/libgb.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + -lgtp + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/gprs/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/gprs/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +osmo-gbproxy$(EXEEXT): $(osmo_gbproxy_OBJECTS) $(osmo_gbproxy_DEPENDENCIES) + @rm -f osmo-gbproxy$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(osmo_gbproxy_OBJECTS) $(osmo_gbproxy_LDADD) $(LIBS) +osmo-sgsn$(EXEEXT): $(osmo_sgsn_OBJECTS) $(osmo_sgsn_DEPENDENCIES) + @rm -f osmo-sgsn$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(osmo_sgsn_OBJECTS) $(osmo_sgsn_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc24.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gb_proxy.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gb_proxy_main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gb_proxy_vty.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_gmm.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_llc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_llc_vty.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_sgsn.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_sndcp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_sndcp_vty.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sgsn_libgtp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sgsn_main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sgsn_vty.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-binPROGRAMS + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/gprs/crc24.c b/src/gprs/crc24.c new file mode 100644 index 000000000..4d65e6ee1 --- /dev/null +++ b/src/gprs/crc24.c @@ -0,0 +1,68 @@ +/* GPRS LLC CRC-24 Implementation */ + +/* (C) 2008-2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +/* CRC24 table - FCS */ +static const u_int32_t tbl_crc24[256] = { + 0x00000000, 0x00d6a776, 0x00f64557, 0x0020e221, 0x00b78115, 0x00612663, 0x0041c442, 0x00976334, + 0x00340991, 0x00e2aee7, 0x00c24cc6, 0x0014ebb0, 0x00838884, 0x00552ff2, 0x0075cdd3, 0x00a36aa5, + 0x00681322, 0x00beb454, 0x009e5675, 0x0048f103, 0x00df9237, 0x00093541, 0x0029d760, 0x00ff7016, + 0x005c1ab3, 0x008abdc5, 0x00aa5fe4, 0x007cf892, 0x00eb9ba6, 0x003d3cd0, 0x001ddef1, 0x00cb7987, + 0x00d02644, 0x00068132, 0x00266313, 0x00f0c465, 0x0067a751, 0x00b10027, 0x0091e206, 0x00474570, + 0x00e42fd5, 0x003288a3, 0x00126a82, 0x00c4cdf4, 0x0053aec0, 0x008509b6, 0x00a5eb97, 0x00734ce1, + 0x00b83566, 0x006e9210, 0x004e7031, 0x0098d747, 0x000fb473, 0x00d91305, 0x00f9f124, 0x002f5652, + 0x008c3cf7, 0x005a9b81, 0x007a79a0, 0x00acded6, 0x003bbde2, 0x00ed1a94, 0x00cdf8b5, 0x001b5fc3, + 0x00fb4733, 0x002de045, 0x000d0264, 0x00dba512, 0x004cc626, 0x009a6150, 0x00ba8371, 0x006c2407, + 0x00cf4ea2, 0x0019e9d4, 0x00390bf5, 0x00efac83, 0x0078cfb7, 0x00ae68c1, 0x008e8ae0, 0x00582d96, + 0x00935411, 0x0045f367, 0x00651146, 0x00b3b630, 0x0024d504, 0x00f27272, 0x00d29053, 0x00043725, + 0x00a75d80, 0x0071faf6, 0x005118d7, 0x0087bfa1, 0x0010dc95, 0x00c67be3, 0x00e699c2, 0x00303eb4, + 0x002b6177, 0x00fdc601, 0x00dd2420, 0x000b8356, 0x009ce062, 0x004a4714, 0x006aa535, 0x00bc0243, + 0x001f68e6, 0x00c9cf90, 0x00e92db1, 0x003f8ac7, 0x00a8e9f3, 0x007e4e85, 0x005eaca4, 0x00880bd2, + 0x00437255, 0x0095d523, 0x00b53702, 0x00639074, 0x00f4f340, 0x00225436, 0x0002b617, 0x00d41161, + 0x00777bc4, 0x00a1dcb2, 0x00813e93, 0x005799e5, 0x00c0fad1, 0x00165da7, 0x0036bf86, 0x00e018f0, + 0x00ad85dd, 0x007b22ab, 0x005bc08a, 0x008d67fc, 0x001a04c8, 0x00cca3be, 0x00ec419f, 0x003ae6e9, + 0x00998c4c, 0x004f2b3a, 0x006fc91b, 0x00b96e6d, 0x002e0d59, 0x00f8aa2f, 0x00d8480e, 0x000eef78, + 0x00c596ff, 0x00133189, 0x0033d3a8, 0x00e574de, 0x007217ea, 0x00a4b09c, 0x008452bd, 0x0052f5cb, + 0x00f19f6e, 0x00273818, 0x0007da39, 0x00d17d4f, 0x00461e7b, 0x0090b90d, 0x00b05b2c, 0x0066fc5a, + 0x007da399, 0x00ab04ef, 0x008be6ce, 0x005d41b8, 0x00ca228c, 0x001c85fa, 0x003c67db, 0x00eac0ad, + 0x0049aa08, 0x009f0d7e, 0x00bfef5f, 0x00694829, 0x00fe2b1d, 0x00288c6b, 0x00086e4a, 0x00dec93c, + 0x0015b0bb, 0x00c317cd, 0x00e3f5ec, 0x0035529a, 0x00a231ae, 0x007496d8, 0x005474f9, 0x0082d38f, + 0x0021b92a, 0x00f71e5c, 0x00d7fc7d, 0x00015b0b, 0x0096383f, 0x00409f49, 0x00607d68, 0x00b6da1e, + 0x0056c2ee, 0x00806598, 0x00a087b9, 0x007620cf, 0x00e143fb, 0x0037e48d, 0x001706ac, 0x00c1a1da, + 0x0062cb7f, 0x00b46c09, 0x00948e28, 0x0042295e, 0x00d54a6a, 0x0003ed1c, 0x00230f3d, 0x00f5a84b, + 0x003ed1cc, 0x00e876ba, 0x00c8949b, 0x001e33ed, 0x008950d9, 0x005ff7af, 0x007f158e, 0x00a9b2f8, + 0x000ad85d, 0x00dc7f2b, 0x00fc9d0a, 0x002a3a7c, 0x00bd5948, 0x006bfe3e, 0x004b1c1f, 0x009dbb69, + 0x0086e4aa, 0x005043dc, 0x0070a1fd, 0x00a6068b, 0x003165bf, 0x00e7c2c9, 0x00c720e8, 0x0011879e, + 0x00b2ed3b, 0x00644a4d, 0x0044a86c, 0x00920f1a, 0x00056c2e, 0x00d3cb58, 0x00f32979, 0x00258e0f, + 0x00eef788, 0x003850fe, 0x0018b2df, 0x00ce15a9, 0x0059769d, 0x008fd1eb, 0x00af33ca, 0x007994bc, + 0x00dafe19, 0x000c596f, 0x002cbb4e, 0x00fa1c38, 0x006d7f0c, 0x00bbd87a, 0x009b3a5b, 0x004d9d2d +}; + +#define INIT_CRC24 0xffffff + +u_int32_t crc24_calc(u_int32_t fcs, u_int8_t *cp, unsigned int len) +{ + while (len--) + fcs = (fcs >> 8) ^ tbl_crc24[(fcs ^ *cp++) & 0xff]; + return fcs; +} diff --git a/src/gprs/gb_proxy.c b/src/gprs/gb_proxy.c new file mode 100644 index 000000000..8df93a9ce --- /dev/null +++ b/src/gprs/gb_proxy.c @@ -0,0 +1,684 @@ +/* NS-over-IP proxy */ + +/* (C) 2010 by Harald Welte + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +struct gbprox_peer { + struct llist_head list; + + /* NS-VC over which we send/receive data to this BVC */ + struct gprs_nsvc *nsvc; + + /* BVCI used for Point-to-Point to this peer */ + uint16_t bvci; + int blocked; + + /* Routeing Area that this peer is part of (raw 04.08 encoding) */ + uint8_t ra[6]; +}; + +/* Linked list of all Gb peers (except SGSN) */ +static LLIST_HEAD(gbprox_bts_peers); + +/* Find the gbprox_peer by its BVCI */ +static struct gbprox_peer *peer_by_bvci(uint16_t bvci) +{ + struct gbprox_peer *peer; + llist_for_each_entry(peer, &gbprox_bts_peers, list) { + if (peer->bvci == bvci) + return peer; + } + return NULL; +} + +static struct gbprox_peer *peer_by_nsvc(struct gprs_nsvc *nsvc) +{ + struct gbprox_peer *peer; + llist_for_each_entry(peer, &gbprox_bts_peers, list) { + if (peer->nsvc == nsvc) + return peer; + } + return NULL; +} + +/* look-up a peer by its Routeing Area Code (RAC) */ +static struct gbprox_peer *peer_by_rac(const uint8_t *ra) +{ + struct gbprox_peer *peer; + llist_for_each_entry(peer, &gbprox_bts_peers, list) { + if (!memcmp(peer->ra, ra, 6)) + return peer; + } + return NULL; +} + +/* look-up a peer by its Location Area Code (LAC) */ +static struct gbprox_peer *peer_by_lac(const uint8_t *la) +{ + struct gbprox_peer *peer; + llist_for_each_entry(peer, &gbprox_bts_peers, list) { + if (!memcmp(peer->ra, la, 5)) + return peer; + } + return NULL; +} + +static struct gbprox_peer *peer_alloc(uint16_t bvci) +{ + struct gbprox_peer *peer; + + peer = talloc_zero(tall_bsc_ctx, struct gbprox_peer); + if (!peer) + return NULL; + + peer->bvci = bvci; + llist_add(&peer->list, &gbprox_bts_peers); + + return peer; +} + +static void peer_free(struct gbprox_peer *peer) +{ + llist_del(&peer->list); + talloc_free(peer); +} + +/* FIXME: this needs to go to libosmocore/msgb.c */ +static struct msgb *msgb_copy(const struct msgb *msg, const char *name) +{ + struct openbsc_msgb_cb *old_cb, *new_cb; + struct msgb *new_msg; + + new_msg = msgb_alloc(msg->data_len, name); + if (!new_msg) + return NULL; + + /* copy data */ + memcpy(new_msg->_data, msg->_data, new_msg->data_len); + + /* copy header */ + new_msg->len = msg->len; + new_msg->data += msg->data - msg->_data; + new_msg->head += msg->head - msg->_data; + new_msg->tail += msg->tail - msg->_data; + + new_msg->l1h = new_msg->_data + (msg->l1h - msg->_data); + new_msg->l2h = new_msg->_data + (msg->l2h - msg->_data); + new_msg->l3h = new_msg->_data + (msg->l3h - msg->_data); + new_msg->l4h = new_msg->_data + (msg->l4h - msg->_data); + + /* copy GB specific data */ + old_cb = OBSC_MSGB_CB(msg); + new_cb = OBSC_MSGB_CB(new_msg); + + new_cb->bssgph = new_msg->_data + (old_cb->bssgph - msg->_data); + new_cb->llch = new_msg->_data + (old_cb->llch - msg->_data); + + /* bssgp_cell_id is a pointer into the old msgb, so we need to make + * it a pointer into the new msgb */ + new_cb->bssgp_cell_id = new_msg->_data + (old_cb->bssgp_cell_id - msg->_data); + new_cb->nsei = old_cb->nsei; + new_cb->bvci = old_cb->bvci; + new_cb->tlli = old_cb->tlli; + + return new_msg; +} + +/* strip off the NS header */ +static void strip_ns_hdr(struct msgb *msg) +{ + int strip_len = msgb_bssgph(msg) - msg->data; + msgb_pull(msg, strip_len); +} + +/* feed a message down the NS-VC associated with the specified peer */ +static int gbprox_relay2sgsn(struct msgb *old_msg, uint16_t ns_bvci) +{ + /* create a copy of the message so the old one can + * be free()d safely when we return from gbprox_rcvmsg() */ + struct msgb *msg = msgb_copy(old_msg, "msgb_relay2sgsn"); + + DEBUGP(DGPRS, "NSEI=%u proxying BTS->SGSN (NS_BVCI=%u, NSEI=%u)\n", + msgb_nsei(msg), ns_bvci, gbcfg.nsip_sgsn_nsei); + + msgb_bvci(msg) = ns_bvci; + msgb_nsei(msg) = gbcfg.nsip_sgsn_nsei; + + strip_ns_hdr(msg); + + return gprs_ns_sendmsg(bssgp_nsi, msg); +} + +/* feed a message down the NS-VC associated with the specified peer */ +static int gbprox_relay2peer(struct msgb *old_msg, struct gbprox_peer *peer, + uint16_t ns_bvci) +{ + /* create a copy of the message so the old one can + * be free()d safely when we return from gbprox_rcvmsg() */ + struct msgb *msg = msgb_copy(old_msg, "msgb_relay2peer"); + + DEBUGP(DGPRS, "NSEI=%u proxying SGSN->BSS (NS_BVCI=%u, NSEI=%u)\n", + msgb_nsei(msg), ns_bvci, peer->nsvc->nsei); + + msgb_bvci(msg) = ns_bvci; + msgb_nsei(msg) = peer->nsvc->nsei; + + /* Strip the old NS header, it will be replaced with a new one */ + strip_ns_hdr(msg); + + return gprs_ns_sendmsg(bssgp_nsi, msg); +} + +static int block_unblock_peer(uint16_t ptp_bvci, uint8_t pdu_type) +{ + struct gbprox_peer *peer; + + peer = peer_by_bvci(ptp_bvci); + if (!peer) { + LOGP(DGPRS, LOGL_ERROR, "BVCI=%u: Cannot find BSS\n", + ptp_bvci); + return -ENOENT; + } + + switch (pdu_type) { + case BSSGP_PDUT_BVC_BLOCK_ACK: + peer->blocked = 1; + break; + case BSSGP_PDUT_BVC_UNBLOCK_ACK: + peer->blocked = 0; + break; + default: + break; + } + return 0; +} + +/* Send a message to a peer identified by ptp_bvci but using ns_bvci + * in the NS hdr */ +static int gbprox_relay2bvci(struct msgb *msg, uint16_t ptp_bvci, + uint16_t ns_bvci) +{ + struct gbprox_peer *peer; + + peer = peer_by_bvci(ptp_bvci); + if (!peer) { + LOGP(DGPRS, LOGL_ERROR, "BVCI=%u: Cannot find BSS\n", + ptp_bvci); + return -ENOENT; + } + + return gbprox_relay2peer(msg, peer, ns_bvci); +} + +/* Receive an incoming signalling message from a BSS-side NS-VC */ +static int gbprox_rx_sig_from_bss(struct msgb *msg, struct gprs_nsvc *nsvc, + uint16_t ns_bvci) +{ + struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg); + struct tlv_parsed tp; + uint8_t pdu_type = bgph->pdu_type; + int data_len = msgb_bssgp_len(msg) - sizeof(*bgph); + struct gbprox_peer *from_peer; + struct gprs_ra_id raid; + + if (ns_bvci != 0 && ns_bvci != 1) { + LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u BVCI=%u is not signalling\n", + nsvc->nsei, ns_bvci); + return -EINVAL; + } + + /* we actually should never see those two for BVCI == 0, but double-check + * just to make sure */ + if (pdu_type == BSSGP_PDUT_UL_UNITDATA || + pdu_type == BSSGP_PDUT_DL_UNITDATA) { + LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u UNITDATA not allowed in " + "signalling\n", nsvc->nsei); + return -EINVAL; + } + + bssgp_tlv_parse(&tp, bgph->data, data_len); + + switch (pdu_type) { + case BSSGP_PDUT_SUSPEND: + case BSSGP_PDUT_RESUME: + /* We implement RAC snooping during SUSPEND/RESUME, since + * it establishes a relationsip between BVCI/peer and the + * routeing area code. The snooped information is then + * used for routing the {SUSPEND,RESUME}_[N]ACK back to + * the correct BSSGP */ + if (!TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA)) + goto err_mand_ie; + from_peer = peer_by_nsvc(nsvc); + if (!from_peer) + goto err_no_peer; + memcpy(from_peer->ra, TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA), + sizeof(from_peer->ra)); + gsm48_parse_ra(&raid, from_peer->ra); + LOGP(DGPRS, LOGL_INFO, "NSEI=%u BSSGP SUSPEND/RESUME " + "RAC snooping: RAC %u-%u-%u-%u behind BVCI=%u, " + "NSVCI=%u\n",nsvc->nsei, raid.mcc, raid.mnc, raid.lac, + raid.rac , from_peer->bvci, nsvc->nsvci); + /* FIXME: This only supports one BSS per RA */ + break; + case BSSGP_PDUT_BVC_RESET: + /* If we receive a BVC reset on the signalling endpoint, we + * don't want the SGSN to reset, as the signalling endpoint + * is common for all point-to-point BVCs (and thus all BTS) */ + if (TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) { + uint16_t bvci = ntohs(*(uint16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI)); + LOGP(DGPRS, LOGL_INFO, "NSEI=%u Rx BVC RESET (BVCI=%u)\n", + nsvc->nsei, bvci); + if (bvci == 0) { + /* FIXME: only do this if SGSN is alive! */ + LOGP(DGPRS, LOGL_INFO, "NSEI=%u Tx fake " + "BVC RESET ACK of BVCI=0\n", nsvc->nsei); + return bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK, + nsvc->nsei, 0, ns_bvci); + } + from_peer = peer_by_bvci(bvci); + if (!from_peer) { + /* if a PTP-BVC is reset, and we don't know that + * PTP-BVCI yet, we should allocate a new peer */ + LOGP(DGPRS, LOGL_INFO, "Allocationg new peer for " + "BVCI=%u via NSVCI=%u/NSEI=%u\n", bvci, + nsvc->nsvci, nsvc->nsei); + from_peer = peer_alloc(bvci); + from_peer->nsvc = nsvc; + } + if (TLVP_PRESENT(&tp, BSSGP_IE_CELL_ID)) { + struct gprs_ra_id raid; + /* We have a Cell Identifier present in this + * PDU, this means we can extend our local + * state information about this particular cell + * */ + memcpy(from_peer->ra, + TLVP_VAL(&tp, BSSGP_IE_CELL_ID), + sizeof(from_peer->ra)); + gsm48_parse_ra(&raid, from_peer->ra); + LOGP(DGPRS, LOGL_INFO, "NSEI=%u/BVCI=%u " + "Cell ID %u-%u-%u-%u\n", nsvc->nsei, + bvci, raid.mcc, raid.mnc, raid.lac, + raid.rac); + } + } + break; + } + + /* Normally, we can simply pass on all signalling messages from BSS to + * SGSN */ + return gbprox_relay2sgsn(msg, ns_bvci); +err_no_peer: + LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(BSS) cannot find peer based on RAC\n", + nsvc->nsei); + return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, NULL, msg); +err_mand_ie: + LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(BSS) missing mandatory RA IE\n", + nsvc->nsei); + return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); +} + +/* Receive paging request from SGSN, we need to relay to proper BSS */ +static int gbprox_rx_paging(struct msgb *msg, struct tlv_parsed *tp, + struct gprs_nsvc *nsvc, uint16_t ns_bvci) +{ + struct gbprox_peer *peer = NULL; + + LOGP(DGPRS, LOGL_INFO, "NSEI=%u(SGSN) BSSGP PAGING ", + nsvc->nsei); + if (TLVP_PRESENT(tp, BSSGP_IE_BVCI)) { + uint16_t bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI)); + LOGPC(DGPRS, LOGL_INFO, "routing by BVCI to peer BVCI=%u\n", + bvci); + } else if (TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA)) { + peer = peer_by_rac(TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA)); + LOGPC(DGPRS, LOGL_INFO, "routing by RAC to peer BVCI=%u\n", + peer ? peer->bvci : -1); + } else if (TLVP_PRESENT(tp, BSSGP_IE_LOCATION_AREA)) { + peer = peer_by_lac(TLVP_VAL(tp, BSSGP_IE_LOCATION_AREA)); + LOGPC(DGPRS, LOGL_INFO, "routing by LAC to peer BVCI=%u\n", + peer ? peer->bvci : -1); + } else + LOGPC(DGPRS, LOGL_INFO, "\n"); + + if (!peer) { + LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(SGSN) BSSGP PAGING: " + "unable to route, missing IE\n", nsvc->nsei); + return -EINVAL; + } + return gbprox_relay2peer(msg, peer, ns_bvci); +} + +/* Receive an incoming BVC-RESET message from the SGSN */ +static int rx_reset_from_sgsn(struct msgb *msg, struct tlv_parsed *tp, + struct gprs_nsvc *nsvc, uint16_t ns_bvci) +{ + struct gbprox_peer *peer; + uint16_t ptp_bvci; + + if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI)) { + return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, + NULL, msg); + } + ptp_bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI)); + + if (ptp_bvci >= 2) { + /* A reset for a PTP BVC was received, forward it to its + * respective peer */ + peer = peer_by_bvci(ptp_bvci); + if (!peer) { + LOGP(DGPRS, LOGL_ERROR, "NSEI=%u BVCI=%u: Cannot find BSS\n", + nsvc->nsei, ptp_bvci); + return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, + NULL, msg); + } + return gbprox_relay2peer(msg, peer, ns_bvci); + } + + /* A reset for the Signalling entity has been received + * from the SGSN. As the signalling BVCI is shared + * among all the BSS's that we multiplex, it needs to + * be relayed */ + llist_for_each_entry(peer, &gbprox_bts_peers, list) + gbprox_relay2peer(msg, peer, ns_bvci); + + return 0; +} + +/* Receive an incoming signalling message from the SGSN-side NS-VC */ +static int gbprox_rx_sig_from_sgsn(struct msgb *msg, struct gprs_nsvc *nsvc, + uint16_t ns_bvci) +{ + struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg); + struct tlv_parsed tp; + uint8_t pdu_type = bgph->pdu_type; + int data_len = msgb_bssgp_len(msg) - sizeof(*bgph); + struct gbprox_peer *peer; + uint16_t bvci; + int rc = 0; + + if (ns_bvci != 0 && ns_bvci != 1) { + LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u(SGSN) BVCI=%u is not " + "signalling\n", nsvc->nsei, ns_bvci); + /* FIXME: Send proper error message */ + return -EINVAL; + } + + /* we actually should never see those two for BVCI == 0, but double-check + * just to make sure */ + if (pdu_type == BSSGP_PDUT_UL_UNITDATA || + pdu_type == BSSGP_PDUT_DL_UNITDATA) { + LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u(SGSN) UNITDATA not allowed in " + "signalling\n", nsvc->nsei); + return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); + } + + rc = bssgp_tlv_parse(&tp, bgph->data, data_len); + + switch (pdu_type) { + case BSSGP_PDUT_BVC_RESET: + rc = rx_reset_from_sgsn(msg, &tp, nsvc, ns_bvci); + break; + case BSSGP_PDUT_FLUSH_LL: + case BSSGP_PDUT_BVC_RESET_ACK: + /* simple case: BVCI IE is mandatory */ + if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) + goto err_mand_ie; + bvci = ntohs(*(uint16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI)); + rc = gbprox_relay2bvci(msg, bvci, ns_bvci); + break; + case BSSGP_PDUT_PAGING_PS: + case BSSGP_PDUT_PAGING_CS: + /* process the paging request (LAC/RAC lookup) */ + rc = gbprox_rx_paging(msg, &tp, nsvc, ns_bvci); + break; + case BSSGP_PDUT_STATUS: + /* Some exception has occurred */ + LOGP(DGPRS, LOGL_NOTICE, + "NSEI=%u(SGSN) BSSGP STATUS ", nsvc->nsei); + if (!TLVP_PRESENT(&tp, BSSGP_IE_CAUSE)) { + LOGPC(DGPRS, LOGL_NOTICE, "\n"); + goto err_mand_ie; + } + LOGPC(DGPRS, LOGL_NOTICE, + "cause=0x%02x(%s) ", *TLVP_VAL(&tp, BSSGP_IE_CAUSE), + bssgp_cause_str(*TLVP_VAL(&tp, BSSGP_IE_CAUSE))); + if (TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) { + uint16_t *bvci = (uint16_t *) + TLVP_VAL(&tp, BSSGP_IE_BVCI); + LOGPC(DGPRS, LOGL_NOTICE, + "BVCI=%u\n", ntohs(*bvci)); + } else + LOGPC(DGPRS, LOGL_NOTICE, "\n"); + break; + /* those only exist in the SGSN -> BSS direction */ + case BSSGP_PDUT_SUSPEND_ACK: + case BSSGP_PDUT_SUSPEND_NACK: + case BSSGP_PDUT_RESUME_ACK: + case BSSGP_PDUT_RESUME_NACK: + /* RAC IE is mandatory */ + if (!TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA)) + goto err_mand_ie; + peer = peer_by_rac(TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA)); + if (!peer) + goto err_no_peer; + rc = gbprox_relay2peer(msg, peer, ns_bvci); + break; + case BSSGP_PDUT_BVC_BLOCK_ACK: + case BSSGP_PDUT_BVC_UNBLOCK_ACK: + if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) + goto err_mand_ie; + bvci = ntohs(*(uint16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI)); + if (bvci == 0) { + LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u(SGSN) BSSGP " + "%sBLOCK_ACK for signalling BVCI ?!?\n", nsvc->nsei, + pdu_type == BSSGP_PDUT_BVC_UNBLOCK_ACK ? "UN":""); + /* should we send STATUS ? */ + } else { + /* Mark BVC as (un)blocked */ + block_unblock_peer(bvci, pdu_type); + } + rc = gbprox_relay2bvci(msg, bvci, ns_bvci); + break; + case BSSGP_PDUT_SGSN_INVOKE_TRACE: + LOGP(DGPRS, LOGL_ERROR, + "NSEI=%u(SGSN) BSSGP INVOKE TRACE not supported\n",nsvc->nsei); + rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg); + break; + default: + LOGP(DGPRS, LOGL_NOTICE, "BSSGP PDU type 0x%02x unknown\n", + pdu_type); + rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); + break; + } + + return rc; +err_mand_ie: + LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(SGSN) missing mandatory IE\n", + nsvc->nsei); + return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); +err_no_peer: + LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(SGSN) cannot find peer based on RAC\n", + nsvc->nsei); + return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, NULL, msg); +} + +/* Main input function for Gb proxy */ +int gbprox_rcvmsg(struct msgb *msg, struct gprs_nsvc *nsvc, uint16_t ns_bvci) +{ + int rc; + struct gbprox_peer *peer; + + /* Only BVCI=0 messages need special treatment */ + if (ns_bvci == 0 || ns_bvci == 1) { + if (nsvc->remote_end_is_sgsn) + rc = gbprox_rx_sig_from_sgsn(msg, nsvc, ns_bvci); + else + rc = gbprox_rx_sig_from_bss(msg, nsvc, ns_bvci); + } else { + /* All other BVCI are PTP and thus can be simply forwarded */ + if (!nsvc->remote_end_is_sgsn) { + return gbprox_relay2sgsn(msg, ns_bvci); + } + /* else: SGSN -> BSS direction */ + peer = peer_by_bvci(ns_bvci); + if (!peer) { + LOGP(DGPRS, LOGL_INFO, "Allocationg new peer for " + "BVCI=%u via NSVC=%u/NSEI=%u\n", ns_bvci, + nsvc->nsvci, nsvc->nsei); + peer = peer_alloc(ns_bvci); + peer->nsvc = nsvc; + } + if (peer->blocked) { + LOGP(DGPRS, LOGL_NOTICE, "Dropping PDU for " + "blocked BVCI=%u via NSVC=%u/NSEI=%u\n", + ns_bvci, nsvc->nsvci, nsvc->nsei); + return bssgp_tx_status(BSSGP_CAUSE_BVCI_BLOCKED, NULL, msg); + } + rc = gbprox_relay2peer(msg, peer, ns_bvci); + } + + return rc; +} + +int gbprox_reset_persistent_nsvcs(struct gprs_ns_inst *nsi) +{ + struct gprs_nsvc *nsvc; + + llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) { + if (!nsvc->persistent) + continue; + gprs_nsvc_reset(nsvc, NS_CAUSE_OM_INTERVENTION); + } + return 0; +} + +/* Signal handler for signals from NS layer */ +int gbprox_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct ns_signal_data *nssd = signal_data; + struct gprs_nsvc *nsvc = nssd->nsvc; + struct gbprox_peer *peer; + + if (subsys != SS_NS) + return 0; + + if (signal == S_NS_RESET && nsvc->nsei == gbcfg.nsip_sgsn_nsei) { + /* We have received a NS-RESET from the NSEI and NSVC + * of the SGSN. This might happen with SGSN that start + * their own NS-RESET procedure without waiting for our + * NS-RESET */ + nsvc->remote_end_is_sgsn = 1; + } + + if (signal == S_NS_ALIVE_EXP && nsvc->remote_end_is_sgsn) { + LOGP(DGPRS, LOGL_NOTICE, "Tns alive expired too often, " + "re-starting RESET procedure\n"); + nsip_connect(nsvc->nsi, &nsvc->ip.bts_addr, nsvc->nsei, + nsvc->nsvci); + } + + if (!nsvc->remote_end_is_sgsn) { + /* from BSS to SGSN */ + peer = peer_by_nsvc(nsvc); + if (!peer) { + LOGP(DGPRS, LOGL_NOTICE, "signal %u for unknown peer " + "NSEI=%u/NSVCI=%u\n", signal, nsvc->nsei, + nsvc->nsvci); + return 0; + } + switch (signal) { + case S_NS_RESET: + case S_NS_BLOCK: + if (!peer->blocked) + break; + LOGP(DGPRS, LOGL_NOTICE, "Converting NS_RESET from " + "NSEI=%u/NSVCI=%u into BSSGP_BVC_BLOCK to SGSN\n", + nsvc->nsei, nsvc->nsvci); + bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_BLOCK, nsvc->nsei, + peer->bvci, 0); + break; + } + } else { + /* iterate over all BTS peers and send the respective PDU */ + llist_for_each_entry(peer, &gbprox_bts_peers, list) { + switch (signal) { + case S_NS_RESET: + gprs_ns_tx_reset(peer->nsvc, nssd->cause); + break; + case S_NS_BLOCK: + gprs_ns_tx_block(peer->nsvc, nssd->cause); + break; + case S_NS_UNBLOCK: + gprs_ns_tx_unblock(peer->nsvc); + break; + } + } + } + return 0; +} + + +#include + +gDEFUN(show_gbproxy, show_gbproxy_cmd, "show gbproxy", + SHOW_STR "Display information about the Gb proxy") +{ + struct gbprox_peer *peer; + + llist_for_each_entry(peer, &gbprox_bts_peers, list) { + struct gprs_nsvc *nsvc = peer->nsvc; + struct gprs_ra_id raid; + gsm48_parse_ra(&raid, peer->ra); + + vty_out(vty, "NSEI %5u, NS-VC %5u, PTP-BVCI %5u, " + "RAC %u-%u-%u-%u", + nsvc->nsei, nsvc->nsvci, peer->bvci, + raid.mcc, raid.mnc, raid.lac, raid.rac); + if (nsvc->ll == GPRS_NS_LL_UDP || nsvc->ll == GPRS_NS_LL_FR_GRE) + vty_out(vty, " %s:%u", + inet_ntoa(nsvc->ip.bts_addr.sin_addr), + ntohs(nsvc->ip.bts_addr.sin_port)); + if (peer->blocked) + vty_out(vty, " [BVC-BLOCKED]"); + + vty_out(vty, "%s", VTY_NEWLINE); + } + return CMD_SUCCESS; +} diff --git a/src/gprs/gb_proxy_main.c b/src/gprs/gb_proxy_main.c new file mode 100644 index 000000000..b53e98579 --- /dev/null +++ b/src/gprs/gb_proxy_main.c @@ -0,0 +1,288 @@ +/* NS-over-IP proxy */ + +/* (C) 2010 by Harald Welte + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../../bscconfig.h" + +/* this is here for the vty... it will never be called */ +void subscr_put() { abort(); } + +#define _GNU_SOURCE +#include + +void *tall_bsc_ctx; + +const char *openbsc_copyright = + "Copyright (C) 2010 Harald Welte and On-Waves\r\n" + "License AGPLv3+: GNU AGPL version 3 or later \r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +static struct log_target *stderr_target; +static char *config_file = "osmo_gbproxy.cfg"; +struct gbproxy_config gbcfg; +static int daemonize = 0; + +/* Pointer to the SGSN peer */ +extern struct gbprox_peer *gbprox_peer_sgsn; + +/* call-back function for the NS protocol */ +static int proxy_ns_cb(enum gprs_ns_evt event, struct gprs_nsvc *nsvc, + struct msgb *msg, u_int16_t bvci) +{ + int rc = 0; + + switch (event) { + case GPRS_NS_EVT_UNIT_DATA: + rc = gbprox_rcvmsg(msg, nsvc, bvci); + break; + default: + LOGP(DGPRS, LOGL_ERROR, "SGSN: Unknown event %u from NS\n", event); + if (msg) + talloc_free(msg); + rc = -EIO; + break; + } + return rc; +} + +static void signal_handler(int signal) +{ + fprintf(stdout, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + dispatch_signal(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL); + sleep(1); + exit(0); + break; + case SIGABRT: + /* in case of abort, we want to obtain a talloc report + * and then return to the caller, who will abort the process */ + case SIGUSR1: + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(tall_bsc_ctx, stderr); + break; + case SIGUSR2: + talloc_report_full(tall_vty_ctx, stderr); + break; + default: + break; + } +} + +static void print_usage() +{ + printf("Usage: bsc_hack\n"); +} + +static void print_help() +{ + printf(" Some useful help...\n"); + printf(" -h --help this text\n"); + printf(" -d option --debug=DNS:DGPRS,0:0 enable debugging\n"); + printf(" -D --daemonize Fork the process into a background daemon\n"); + printf(" -c --config-file filename The config file to use.\n"); + printf(" -s --disable-color\n"); + printf(" -T --timestamp Prefix every log line with a timestamp\n"); + printf(" -V --version. Print the version of OpenBSC.\n"); + printf(" -e --log-level number. Set a global loglevel.\n"); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + { "help", 0, 0, 'h' }, + { "debug", 1, 0, 'd' }, + { "daemonize", 0, 0, 'D' }, + { "config-file", 1, 0, 'c' }, + { "disable-color", 0, 0, 's' }, + { "timestamp", 0, 0, 'T' }, + { "version", 0, 0, 'V' }, + { "log-level", 1, 0, 'e' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "hd:Dc:sTVe:", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage(); + print_help(); + exit(0); + case 's': + log_set_use_color(stderr_target, 0); + break; + case 'd': + log_parse_category_mask(stderr_target, optarg); + break; + case 'D': + daemonize = 1; + break; + case 'c': + config_file = strdup(optarg); + break; + case 'T': + log_set_print_timestamp(stderr_target, 1); + break; + case 'e': + log_set_log_level(stderr_target, atoi(optarg)); + break; + case 'V': + print_version(1); + exit(0); + break; + default: + break; + } + } +} + +extern void *tall_msgb_ctx; + +extern enum node_type bsc_vty_go_parent(struct vty *vty); + +static struct vty_app_info vty_info = { + .name = "OsmoGbProxy", + .version = PACKAGE_VERSION, + .go_parent_cb = bsc_vty_go_parent, + .is_config_node = bsc_vty_is_config_node, +}; + +int main(int argc, char **argv) +{ + struct gsm_network dummy_network; + int rc; + + tall_bsc_ctx = talloc_named_const(NULL, 0, "nsip_proxy"); + tall_msgb_ctx = talloc_named_const(tall_bsc_ctx, 0, "msgb"); + + signal(SIGINT, &signal_handler); + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + signal(SIGPIPE, SIG_IGN); + + log_init(&log_info); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + log_set_all_filter(stderr_target, 1); + + vty_info.copyright = openbsc_copyright; + vty_init(&vty_info); + logging_vty_add_cmds(); + gbproxy_vty_init(); + + handle_options(argc, argv); + + rate_ctr_init(tall_bsc_ctx); + + rc = telnet_init(tall_bsc_ctx, &dummy_network, 4246); + if (rc < 0) + exit(1); + + bssgp_nsi = gprs_ns_instantiate(&proxy_ns_cb); + if (!bssgp_nsi) { + LOGP(DGPRS, LOGL_ERROR, "Unable to instantiate NS\n"); + exit(1); + } + gbcfg.nsi = bssgp_nsi; + gprs_ns_vty_init(bssgp_nsi); + register_signal_handler(SS_NS, &gbprox_signal, NULL); + + rc = gbproxy_parse_config(config_file, &gbcfg); + if (rc < 0) { + LOGP(DGPRS, LOGL_FATAL, "Cannot parse config file\n"); + exit(2); + } + + if (!nsvc_by_nsei(gbcfg.nsi, gbcfg.nsip_sgsn_nsei)) { + LOGP(DGPRS, LOGL_FATAL, "You cannot proxy to NSEI %u " + "without creating that NSEI before\n", + gbcfg.nsip_sgsn_nsei); + exit(2); + } + + rc = gprs_ns_nsip_listen(bssgp_nsi); + if (rc < 0) { + LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen on NSIP socket\n"); + exit(2); + } + + rc = gprs_ns_frgre_listen(bssgp_nsi); + if (rc < 0) { + LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen GRE " + "socket. Do you have CAP_NET_RAW?\n"); + exit(2); + } + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + /* Reset all the persistent NS-VCs that we've read from the config */ + gbprox_reset_persistent_nsvcs(bssgp_nsi); + + while (1) { + rc = bsc_select_main(0); + if (rc < 0) + exit(3); + } + + exit(0); +} diff --git a/src/gprs/gb_proxy_vty.c b/src/gprs/gb_proxy_vty.c new file mode 100644 index 000000000..05f5b1e46 --- /dev/null +++ b/src/gprs/gb_proxy_vty.c @@ -0,0 +1,104 @@ +/* + * (C) 2010 by Harald Welte + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +static struct gbproxy_config *g_cfg = NULL; + +/* + * vty code for mgcp below + */ +static struct cmd_node gbproxy_node = { + GBPROXY_NODE, + "%s(gbproxy)#", + 1, +}; + +static int config_write_gbproxy(struct vty *vty) +{ + vty_out(vty, "gbproxy%s", VTY_NEWLINE); + + vty_out(vty, " sgsn nsei %u%s", g_cfg->nsip_sgsn_nsei, + VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_gbproxy, + cfg_gbproxy_cmd, + "gbproxy", + "Configure the Gb proxy") +{ + vty->node = GBPROXY_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_nsip_sgsn_nsei, + cfg_nsip_sgsn_nsei_cmd, + "sgsn nsei <0-65534>", + "Set the NSEI to be used in the connection with the SGSN") +{ + unsigned int port = atoi(argv[0]); + + g_cfg->nsip_sgsn_nsei = port; + return CMD_SUCCESS; +} + +int gbproxy_vty_init(void) +{ + install_element_ve(&show_gbproxy_cmd); + + install_element(CONFIG_NODE, &cfg_gbproxy_cmd); + install_node(&gbproxy_node, config_write_gbproxy); + install_default(GBPROXY_NODE); + install_element(GBPROXY_NODE, &ournode_exit_cmd); + install_element(GBPROXY_NODE, &ournode_end_cmd); + install_element(GBPROXY_NODE, &cfg_nsip_sgsn_nsei_cmd); + + return 0; +} + +int gbproxy_parse_config(const char *config_file, struct gbproxy_config *cfg) +{ + int rc; + + g_cfg = cfg; + rc = vty_read_config_file(config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file); + return rc; + } + + return 0; +} + diff --git a/src/gprs/gprs_gmm.c b/src/gprs/gprs_gmm.c new file mode 100644 index 000000000..949cd96ea --- /dev/null +++ b/src/gprs/gprs_gmm.c @@ -0,0 +1,1597 @@ +/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface + * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */ + +/* (C) 2009-2010 by Harald Welte + * (C) 2010 by On-Waves + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define PTMSI_ALLOC + +/* Section 11.2.2 / Table 11.4 MM timers netowkr side */ +#define GSM0408_T3322_SECS 6 /* DETACH_REQ -> DETACH_ACC */ +#define GSM0408_T3350_SECS 6 /* waiting for ATT/RAU/TMSI COMPL */ +#define GSM0408_T3360_SECS 6 /* waiting for AUTH/CIPH RESP */ +#define GSM0408_T3370_SECS 6 /* waiting for ID RESP */ + +/* Section 11.2.2 / Table 11.4a MM timers netowkr side */ +#define GSM0408_T3313_SECS 30 /* waiting for paging response */ +#define GSM0408_T3314_SECS 44 /* force to STBY on expiry */ +#define GSM0408_T3316_SECS 44 + +/* Section 11.3 / Table 11.2d Timers of Session Management - network side */ +#define GSM0408_T3385_SECS 8 /* wait for ACT PDP CTX REQ */ +#define GSM0408_T3386_SECS 8 /* wait for MODIFY PDP CTX ACK */ +#define GSM0408_T3395_SECS 8 /* wait for DEACT PDP CTX ACK */ +#define GSM0408_T3397_SECS 8 /* wait for DEACT AA PDP CTX ACK */ + +extern struct sgsn_instance *sgsn; + +/* Protocol related stuff, should go into libosmocore */ + +/* 10.5.5.14 GPRS MM Cause / Table 10.5.147 */ +const struct value_string gmm_cause_names[] = { + { GMM_CAUSE_IMSI_UNKNOWN, "IMSI unknown in HLR" }, + { GMM_CAUSE_ILLEGAL_MS, "Illegal MS" }, + { GMM_CAUSE_ILLEGAL_ME, "Illegal ME" }, + { GMM_CAUSE_GPRS_NOTALLOWED, "GPRS services not allowed" }, + { GMM_CAUSE_GPRS_OTHER_NOTALLOWED, + "GPRS services and non-GPRS services not allowed" }, + { GMM_CAUSE_MS_ID_NOT_DERIVED, + "MS identity cannot be derived by the network" }, + { GMM_CAUSE_IMPL_DETACHED, "Implicitly detached" }, + { GMM_CAUSE_PLMN_NOTALLOWED, "PLMN not allowed" }, + { GMM_CAUSE_LA_NOTALLOWED, "Location Area not allowed" }, + { GMM_CAUSE_ROAMING_NOTALLOWED, + "Roaming not allowed in this location area" }, + { GMM_CAUSE_NO_GPRS_PLMN, + "GPRS services not allowed in this PLMN" }, + { GMM_CAUSE_MSC_TEMP_NOTREACH, "MSC temporarily not reachable" }, + { GMM_CAUSE_NET_FAIL, "Network failure" }, + { GMM_CAUSE_CONGESTION, "Congestion" }, + { GMM_CAUSE_SEM_INCORR_MSG, "Semantically incorrect message" }, + { GMM_CAUSE_INV_MAND_INFO, "Invalid mandatory information" }, + { GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL, + "Message type non-existant or not implemented" }, + { GMM_CAUSE_MSGT_INCOMP_P_STATE, + "Message type not compatible with protocol state" }, + { GMM_CAUSE_IE_NOTEXIST_NOTIMPL, + "Information element non-existent or not implemented" }, + { GMM_CAUSE_COND_IE_ERR, "Conditional IE error" }, + { GMM_CAUSE_MSG_INCOMP_P_STATE, + "Message not compatible with protocol state " }, + { GMM_CAUSE_PROTO_ERR_UNSPEC, "Protocol error, unspecified" }, + { 0, NULL } +}; + +/* 10.5.6.6 SM Cause / Table 10.5.157 */ +const struct value_string gsm_cause_names[] = { + { GSM_CAUSE_INSUFF_RSRC, "Insufficient resources" }, + { GSM_CAUSE_MISSING_APN, "Missing or unknown APN" }, + { GSM_CAUSE_UNKNOWN_PDP, "Unknown PDP address or PDP type" }, + { GSM_CAUSE_AUTH_FAILED, "User Authentication failed" }, + { GSM_CAUSE_ACT_REJ_GGSN, "Activation rejected by GGSN" }, + { GSM_CAUSE_ACT_REJ_UNSPEC, "Activation rejected, unspecified" }, + { GSM_CAUSE_SERV_OPT_NOTSUPP, "Service option not supported" }, + { GSM_CAUSE_REQ_SERV_OPT_NOTSUB, + "Requested service option not subscribed" }, + { GSM_CAUSE_SERV_OPT_TEMP_OOO, + "Service option temporarily out of order" }, + { GSM_CAUSE_NSAPI_IN_USE, "NSAPI already used" }, + { GSM_CAUSE_DEACT_REGULAR, "Regular deactivation" }, + { GSM_CAUSE_QOS_NOT_ACCEPTED, "QoS not accepted" }, + { GSM_CAUSE_NET_FAIL, "Network Failure" }, + { GSM_CAUSE_REACT_RQD, "Reactivation required" }, + { GSM_CAUSE_FEATURE_NOTSUPP, "Feature not supported " }, + { GSM_CAUSE_INVALID_TRANS_ID, "Invalid transaction identifier" }, + { GSM_CAUSE_SEM_INCORR_MSG, "Semantically incorrect message" }, + { GSM_CAUSE_INV_MAND_INFO, "Invalid mandatory information" }, + { GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL, + "Message type non-existant or not implemented" }, + { GSM_CAUSE_MSGT_INCOMP_P_STATE, + "Message type not compatible with protocol state" }, + { GSM_CAUSE_IE_NOTEXIST_NOTIMPL, + "Information element non-existent or not implemented" }, + { GSM_CAUSE_COND_IE_ERR, "Conditional IE error" }, + { GSM_CAUSE_MSG_INCOMP_P_STATE, + "Message not compatible with protocol state " }, + { GSM_CAUSE_PROTO_ERR_UNSPEC, "Protocol error, unspecified" }, + { 0, NULL } +}; + +/* 10.5.5.2 */ +const struct value_string gprs_att_t_strs[] = { + { GPRS_ATT_T_ATTACH, "GPRS attach" }, + { GPRS_ATT_T_ATT_WHILE_IMSI, "GPRS attach while IMSI attached" }, + { GPRS_ATT_T_COMBINED, "Combined GPRS/IMSI attach" }, + { 0, NULL } +}; + +const struct value_string gprs_upd_t_strs[] = { + { GPRS_UPD_T_RA, "RA updating" }, + { GPRS_UPD_T_RA_LA, "combined RA/LA updating" }, + { GPRS_UPD_T_RA_LA_IMSI_ATT, "combined RA/LA updating + IMSI attach" }, + { GPRS_UPD_T_PERIODIC, "periodic updating" }, + { 0, NULL } +}; + +/* 10.5.5.5 */ +const struct value_string gprs_det_t_mo_strs[] = { + { GPRS_DET_T_MO_GPRS, "GPRS detach" }, + { GPRS_DET_T_MO_IMSI, "IMSI detach" }, + { GPRS_DET_T_MO_COMBINED, "Combined GPRS/IMSI detach" }, + { 0, NULL } +}; + +static const struct tlv_definition gsm48_gmm_att_tlvdef = { + .def = { + [GSM48_IE_GMM_CIPH_CKSN] = { TLV_TYPE_FIXED, 1 }, + [GSM48_IE_GMM_TIMER_READY] = { TLV_TYPE_TV, 1 }, + [GSM48_IE_GMM_ALLOC_PTMSI] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GMM_PTMSI_SIG] = { TLV_TYPE_FIXED, 3 }, + [GSM48_IE_GMM_AUTH_RAND] = { TLV_TYPE_FIXED, 16 }, + [GSM48_IE_GMM_AUTH_SRES] = { TLV_TYPE_FIXED, 4 }, + [GSM48_IE_GMM_IMEISV] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GMM_DRX_PARAM] = { TLV_TYPE_FIXED, 2 }, + [GSM48_IE_GMM_MS_NET_CAPA] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GMM_PDP_CTX_STATUS] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GMM_PS_LCS_CAPA] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GMM_GMM_MBMS_CTX_ST] = { TLV_TYPE_TLV, 0 }, + }, +}; + +static const struct tlv_definition gsm48_sm_att_tlvdef = { + .def = { + [GSM48_IE_GSM_APN] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GSM_PROTO_CONF_OPT] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GSM_PDP_ADDR] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GSM_AA_TMR] = { TLV_TYPE_TV, 1 }, + [GSM48_IE_GSM_NAME_FULL] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GSM_NAME_SHORT] = { TLV_TYPE_TLV, 0 }, + [GSM48_IE_GSM_TIMEZONE] = { TLV_TYPE_FIXED, 1 }, + [GSM48_IE_GSM_UTC_AND_TZ] = { TLV_TYPE_FIXED, 7 }, + [GSM48_IE_GSM_LSA_ID] = { TLV_TYPE_TLV, 0 }, + }, +}; + +/* Our implementation, should be kept in SGSN */ + +static void mmctx_timer_cb(void *_mm); + +static void mmctx_timer_start(struct sgsn_mm_ctx *mm, unsigned int T, + unsigned int seconds) +{ + if (bsc_timer_pending(&mm->timer)) + LOGP(DMM, LOGL_ERROR, "Starting MM timer %u while old " + "timer %u pending\n", T, mm->T); + mm->T = T; + mm->num_T_exp = 0; + + /* FIXME: we should do this only once ? */ + mm->timer.data = mm; + mm->timer.cb = &mmctx_timer_cb; + + bsc_schedule_timer(&mm->timer, seconds, 0); +} + +static void mmctx_timer_stop(struct sgsn_mm_ctx *mm, unsigned int T) +{ + if (mm->T != T) + LOGP(DMM, LOGL_ERROR, "Stopping MM timer %u but " + "%u is running\n", T, mm->T); + bsc_del_timer(&mm->timer); +} + +/* Send a message through the underlying layer */ +static int gsm48_gmm_sendmsg(struct msgb *msg, int command, + const struct sgsn_mm_ctx *mm) +{ + if (mm) + rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PKTS_SIG_OUT]); + + /* caller needs to provide TLLI, BVCI and NSEI */ + return gprs_llc_tx_ui(msg, GPRS_SAPI_GMM, command, mm); +} + +/* copy identifiers from old message to new message, this + * is required so lower layers can route it correctly */ +static void gmm_copy_id(struct msgb *msg, const struct msgb *old) +{ + msgb_tlli(msg) = msgb_tlli(old); + msgb_bvci(msg) = msgb_bvci(old); + msgb_nsei(msg) = msgb_nsei(old); +} + +/* Store BVCI/NSEI in MM context */ +static void msgid2mmctx(struct sgsn_mm_ctx *mm, const struct msgb *msg) +{ + mm->bvci = msgb_bvci(msg); + mm->nsei = msgb_nsei(msg); +} + +/* Store BVCI/NSEI in MM context */ +static void mmctx2msgid(struct msgb *msg, const struct sgsn_mm_ctx *mm) +{ + msgb_tlli(msg) = mm->tlli; + msgb_bvci(msg) = mm->bvci; + msgb_nsei(msg) = mm->nsei; +} + +/* Chapter 9.4.18 */ +static int _tx_status(struct msgb *msg, uint8_t cause, + struct sgsn_mm_ctx *mmctx, int sm) +{ + struct gsm48_hdr *gh; + + /* MMCTX might be NULL! */ + + DEBUGP(DMM, "<- GPRS MM STATUS (cause: %s)\n", + get_value_string(gmm_cause_names, cause)); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + if (sm) { + gh->proto_discr = GSM48_PDISC_SM_GPRS; + gh->msg_type = GSM48_MT_GSM_STATUS; + } else { + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_STATUS; + } + gh->data[0] = cause; + + return gsm48_gmm_sendmsg(msg, 0, mmctx); +} +static int gsm48_tx_gmm_status(struct sgsn_mm_ctx *mmctx, uint8_t cause) +{ + struct msgb *msg = gsm48_msgb_alloc(); + + mmctx2msgid(msg, mmctx); + return _tx_status(msg, cause, mmctx, 0); +}; +static int gsm48_tx_gmm_status_oldmsg(struct msgb *oldmsg, uint8_t cause) +{ + struct msgb *msg = gsm48_msgb_alloc(); + + gmm_copy_id(msg, oldmsg); + return _tx_status(msg, cause, NULL, 0); +} +static int gsm48_tx_sm_status(struct sgsn_mm_ctx *mmctx, uint8_t cause) +{ + struct msgb *msg = gsm48_msgb_alloc(); + + mmctx2msgid(msg, mmctx); + return _tx_status(msg, cause, mmctx, 1); +}; +static int gsm48_tx_sm_status_oldmsg(struct msgb *oldmsg, uint8_t cause) +{ + struct msgb *msg = gsm48_msgb_alloc(); + + gmm_copy_id(msg, oldmsg); + return _tx_status(msg, cause, NULL, 1); +} + + +static struct gsm48_qos default_qos = { + .delay_class = 4, /* best effort */ + .reliab_class = GSM48_QOS_RC_LLC_UN_RLC_ACK_DATA_PROT, + .peak_tput = GSM48_QOS_PEAK_TPUT_32000bps, + .preced_class = GSM48_QOS_PC_NORMAL, + .mean_tput = GSM48_QOS_MEAN_TPUT_BEST_EFFORT, + .traf_class = GSM48_QOS_TC_INTERACTIVE, + .deliv_order = GSM48_QOS_DO_UNORDERED, + .deliv_err_sdu = GSM48_QOS_ERRSDU_YES, + .max_sdu_size = GSM48_QOS_MAXSDU_1520, + .max_bitrate_up = GSM48_QOS_MBRATE_63k, + .max_bitrate_down = GSM48_QOS_MBRATE_63k, + .resid_ber = GSM48_QOS_RBER_5e_2, + .sdu_err_ratio = GSM48_QOS_SERR_1e_2, + .handling_prio = 3, + .xfer_delay = 0x10, /* 200ms */ + .guar_bitrate_up = GSM48_QOS_MBRATE_0k, + .guar_bitrate_down = GSM48_QOS_MBRATE_0k, + .sig_ind = 0, /* not optimised for signalling */ + .max_bitrate_down_ext = 0, /* use octet 9 */ + .guar_bitrate_down_ext = 0, /* use octet 13 */ +}; + +/* Chapter 9.4.2: Attach accept */ +static int gsm48_tx_gmm_att_ack(struct sgsn_mm_ctx *mm) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + struct gsm48_attach_ack *aa; + uint8_t *ptsig, *mid; + + DEBUGP(DMM, "<- GPRS ATTACH ACCEPT (new P-TMSI=0x%08x)\n", mm->p_tmsi); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_ATTACH_ACK; + + aa = (struct gsm48_attach_ack *) msgb_put(msg, sizeof(*aa)); + aa->force_stby = 0; /* not indicated */ + aa->att_result = 1; /* GPRS only */ + aa->ra_upd_timer = GPRS_TMR_MINUTE | 10; + aa->radio_prio = 4; /* lowest */ + gsm48_construct_ra(aa->ra_id.digits, &mm->ra); + +#if 0 + /* Optional: P-TMSI signature */ + msgb_v_put(msg, GSM48_IE_GMM_PTMSI_SIG); + ptsig = msgb_put(msg, 3); + ptsig[0] = mm->p_tmsi_sig >> 16; + ptsig[1] = mm->p_tmsi_sig >> 8; + ptsig[2] = mm->p_tmsi_sig & 0xff; + + /* Optional: Negotiated Ready timer value */ +#endif + +#ifdef PTMSI_ALLOC + /* Optional: Allocated P-TMSI */ + mid = msgb_put(msg, GSM48_MID_TMSI_LEN); + gsm48_generate_mid_from_tmsi(mid, mm->p_tmsi); + mid[0] = GSM48_IE_GMM_ALLOC_PTMSI; +#endif + + /* Optional: MS-identity (combined attach) */ + /* Optional: GMM cause (partial attach result for combined attach) */ + + return gsm48_gmm_sendmsg(msg, 0, mm); +} + +/* Chapter 9.4.5: Attach reject */ +static int _tx_gmm_att_rej(struct msgb *msg, uint8_t gmm_cause) +{ + struct gsm48_hdr *gh; + + DEBUGP(DMM, "<- GPRS ATTACH REJECT\n"); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_ATTACH_REJ; + gh->data[0] = gmm_cause; + + return gsm48_gmm_sendmsg(msg, 0, NULL); +} +static int gsm48_tx_gmm_att_rej_oldmsg(const struct msgb *old_msg, + uint8_t gmm_cause) +{ + struct msgb *msg = gsm48_msgb_alloc(); + gmm_copy_id(msg, old_msg); + return _tx_gmm_att_rej(msg, gmm_cause); +} +static int gsm48_tx_gmm_att_rej(struct sgsn_mm_ctx *mm, + uint8_t gmm_cause) +{ + struct msgb *msg = gsm48_msgb_alloc(); + mmctx2msgid(msg, mm); + return _tx_gmm_att_rej(msg, gmm_cause); +} + +/* Chapter 9.4.6.2 Detach accept */ +static int gsm48_tx_gmm_det_ack(struct sgsn_mm_ctx *mm, uint8_t force_stby) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + + DEBUGP(DMM, "<- GPRS DETACH ACCEPT\n"); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_DETACH_ACK; + gh->data[0] = force_stby; + + return gsm48_gmm_sendmsg(msg, 0, mm); +} + +/* Transmit Chapter 9.4.12 Identity Request */ +static int gsm48_tx_gmm_id_req(struct sgsn_mm_ctx *mm, uint8_t id_type) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + + DEBUGP(DMM, "<- GPRS IDENTITY REQUEST: mi_type=%02x\n", id_type); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_ID_REQ; + /* 10.5.5.9 ID type 2 + identity type and 10.5.5.7 'force to standby' IE */ + gh->data[0] = id_type & 0xf; + + return gsm48_gmm_sendmsg(msg, 1, mm); +} + +/* Section 9.4.9: Authentication and Ciphering Request */ +static int gsm48_tx_gmm_auth_ciph_req(struct sgsn_mm_ctx *mm, uint8_t *rand, + uint8_t key_seq, uint8_t algo) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + struct gsm48_auth_ciph_req *acreq; + uint8_t *m_rand, *m_cksn; + + DEBUGP(DMM, "<- GPRS AUTH AND CIPHERING REQ (rand = %s)\n", + hexdump(rand, 16)); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_AUTH_CIPH_REQ; + + acreq = (struct gsm48_auth_ciph_req *) msgb_put(msg, sizeof(*acreq)); + acreq->ciph_alg = algo & 0xf; + acreq->imeisv_req = 0x1; + acreq->force_stby = 0x0; + acreq->ac_ref_nr = 0x0; /* FIXME: increment this? */ + + /* Only if authentication is requested we need to set RAND + CKSN */ + if (rand) { + m_rand = msgb_put(msg, 16+1); + m_rand[0] = GSM48_IE_GMM_AUTH_RAND; + memcpy(m_rand+1, rand, 16); + + m_cksn = msgb_put(msg, 1+1); + m_cksn[0] = GSM48_IE_GMM_CIPH_CKSN; + m_cksn[1] = key_seq; + } + + /* Start T3360 */ + mmctx_timer_start(mm, 3360, GSM0408_T3360_SECS); + + /* FIXME: make sure we don't send any other messages to the MS */ + + return gsm48_gmm_sendmsg(msg, 1, mm); +} + +/* Section 9.4.11: Authentication and Ciphering Reject */ +static int gsm48_tx_gmm_auth_ciph_rej(struct sgsn_mm_ctx *mm) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + + DEBUGP(DMM, "<- GPRS AUTH AND CIPH REJECT\n"); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_AUTH_CIPH_REJ; + + return gsm48_gmm_sendmsg(msg, 0, mm); +} + +/* Section 9.4.10: Authentication and Ciphering Response */ +static int gsm48_rx_gmm_auth_ciph_resp(struct sgsn_mm_ctx *ctx, + struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + struct gsm48_auth_ciph_resp *acr = (struct gsm48_auth_ciph_resp *)gh->data; + struct tlv_parsed tp; + int rc; + + /* FIXME: Stop T3360 */ + + rc = tlv_parse(&tp, &gsm48_gmm_att_tlvdef, acr->data, + (msg->data + msg->len) - acr->data, 0, 0); + + /* FIXME: compare ac_ref? */ + + if (!TLVP_PRESENT(&tp, GSM48_IE_GMM_AUTH_SRES) || + !TLVP_PRESENT(&tp, GSM48_IE_GMM_IMEISV)) { + /* FIXME: missing mandatory IE */ + } + + /* FIXME: compare SRES with what we expected */ + /* FIXME: enable LLC cipheirng */ + return 0; +} + +/* Check if we can already authorize a subscriber */ +static int gsm48_gmm_authorize(struct sgsn_mm_ctx *ctx, + enum gprs_t3350_mode t3350_mode) +{ + if (strlen(ctx->imei) && strlen(ctx->imsi)) { +#ifdef PTMSI_ALLOC + /* Start T3350 and re-transmit up to 5 times until ATTACH COMPLETE */ + ctx->t3350_mode = t3350_mode; + mmctx_timer_start(ctx, 3350, GSM0408_T3350_SECS); +#endif + ctx->mm_state = GMM_REGISTERED_NORMAL; + return gsm48_tx_gmm_att_ack(ctx); + } + if (!strlen(ctx->imei)) { + ctx->mm_state = GMM_COMMON_PROC_INIT; + ctx->t3370_id_type = GSM_MI_TYPE_IMEI; + mmctx_timer_start(ctx, 3370, GSM0408_T3370_SECS); + return gsm48_tx_gmm_id_req(ctx, GSM_MI_TYPE_IMEI); + } + + if (!strlen(ctx->imsi)) { + ctx->mm_state = GMM_COMMON_PROC_INIT; + ctx->t3370_id_type = GSM_MI_TYPE_IMSI; + mmctx_timer_start(ctx, 3370, GSM0408_T3370_SECS); + return gsm48_tx_gmm_id_req(ctx, GSM_MI_TYPE_IMSI); + } + + return 0; +} + +/* Parse Chapter 9.4.13 Identity Response */ +static int gsm48_rx_gmm_id_resp(struct sgsn_mm_ctx *ctx, struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t mi_type = gh->data[1] & GSM_MI_TYPE_MASK; + char mi_string[GSM48_MI_SIZE]; + + gsm48_mi_to_string(mi_string, sizeof(mi_string), &gh->data[1], gh->data[0]); + DEBUGP(DMM, "-> GMM IDENTITY RESPONSE: mi_type=0x%02x MI(%s) ", + mi_type, mi_string); + + if (!ctx) { + DEBUGP(DMM, "from unknown TLLI 0x%08x?!?\n", msgb_tlli(msg)); + return -EINVAL; + } + + if (mi_type == ctx->t3370_id_type) + mmctx_timer_stop(ctx, 3370); + + switch (mi_type) { + case GSM_MI_TYPE_IMSI: + /* we already have a mm context with current TLLI, but no + * P-TMSI / IMSI yet. What we now need to do is to fill + * this initial context with data from the HLR */ + if (strlen(ctx->imsi) == 0) { + /* Check if we already have a MM context for this IMSI */ + struct sgsn_mm_ctx *ictx; + ictx = sgsn_mm_ctx_by_imsi(mi_string); + if (ictx) { + DEBUGP(DMM, "Deleting old MM Context for same IMSI ", + "p_tmsi_old=0x%08x, p_tmsi_new=0x%08x\n", + ictx->p_tmsi, ctx->p_tmsi); + gprs_llgmm_assign(ictx->llme, ictx->tlli, + 0xffffffff, GPRS_ALGO_GEA0, NULL); + sgsn_mm_ctx_free(ictx); + } + } + strncpy(ctx->imsi, mi_string, sizeof(ctx->imei)); + break; + case GSM_MI_TYPE_IMEI: + strncpy(ctx->imei, mi_string, sizeof(ctx->imei)); + break; + case GSM_MI_TYPE_IMEISV: + break; + } + + DEBUGPC(DMM, "\n"); + /* Check if we can let the mobile station enter */ + return gsm48_gmm_authorize(ctx, ctx->t3350_mode); +} + +/* Section 9.4.1 Attach request */ +static int gsm48_rx_gmm_att_req(struct sgsn_mm_ctx *ctx, struct msgb *msg, + struct gprs_llc_llme *llme) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t *cur = gh->data, *msnc, *mi, *old_ra_info, *ms_ra_acc_cap; + uint8_t msnc_len, att_type, mi_len, mi_type, ms_ra_acc_cap_len; + uint16_t drx_par; + uint32_t tmsi; + char mi_string[GSM48_MI_SIZE]; + struct gprs_ra_id ra_id; + uint16_t cid; + + DEBUGP(DMM, "-> GMM ATTACH REQUEST "); + + /* As per TS 04.08 Chapter 4.7.1.4, the attach request arrives either + * with a foreign TLLI (P-TMSI that was allocated to the MS before), + * or with random TLLI. */ + + cid = bssgp_parse_cell_id(&ra_id, msgb_bcid(msg)); + + /* MS network capability 10.5.5.12 */ + msnc_len = *cur++; + msnc = cur; + if (msnc_len > 8) + goto err_inval; + cur += msnc_len; + + /* aTTACH Type 10.5.5.2 */ + att_type = *cur++ & 0x0f; + + /* DRX parameter 10.5.5.6 */ + drx_par = *cur++ << 8; + drx_par |= *cur++; + + /* Mobile Identity (P-TMSI or IMSI) 10.5.1.4 */ + mi_len = *cur++; + mi = cur; + if (mi_len > 8) + goto err_inval; + mi_type = *mi & GSM_MI_TYPE_MASK; + cur += mi_len; + + gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len); + + DEBUGPC(DMM, "MI(%s) type=\"%s\" ", mi_string, + get_value_string(gprs_att_t_strs, att_type)); + + /* Old routing area identification 10.5.5.15 */ + old_ra_info = cur; + cur += 6; + + /* MS Radio Access Capability 10.5.5.12a */ + ms_ra_acc_cap_len = *cur++; + ms_ra_acc_cap = cur; + if (ms_ra_acc_cap_len > 51) + goto err_inval; + + /* Optional: Old P-TMSI Signature, Requested READY timer, TMSI Status */ + + switch (mi_type) { + case GSM_MI_TYPE_IMSI: + /* Try to find MM context based on IMSI */ + if (!ctx) + ctx = sgsn_mm_ctx_by_imsi(mi_string); + if (!ctx) { +#if 0 + return gsm48_tx_gmm_att_rej(msg, GMM_CAUSE_IMSI_UNKNOWN); +#else + /* As a temorary hack, we simply assume that the IMSI exists, + * as long as it is part of 'our' network */ + char mccmnc[16]; + snprintf(mccmnc, sizeof(mccmnc), "%03d%02d", ra_id.mcc, ra_id.mnc); + if (strncmp(mccmnc, mi_string, 5)) { + LOGP(DMM, LOGL_INFO, "Rejecting ATTACH REQUESET IMSI=%s\n", + mi_string); + return gsm48_tx_gmm_att_rej_oldmsg(msg, + GMM_CAUSE_GPRS_NOTALLOWED); + } + ctx = sgsn_mm_ctx_alloc(0, &ra_id); + if (!ctx) + return gsm48_tx_gmm_att_rej_oldmsg(msg, GMM_CAUSE_NET_FAIL); + strncpy(ctx->imsi, mi_string, sizeof(ctx->imsi)); +#endif + } + ctx->tlli = msgb_tlli(msg); + ctx->llme = llme; + msgid2mmctx(ctx, msg); + break; + case GSM_MI_TYPE_TMSI: + memcpy(&tmsi, mi+1, 4); + tmsi = ntohl(tmsi); + /* Try to find MM context based on P-TMSI */ + if (!ctx) + ctx = sgsn_mm_ctx_by_ptmsi(tmsi); + if (!ctx) { + /* Allocate a context as most of our code expects one. + * Context will not have an IMSI ultil ID RESP is received */ + ctx = sgsn_mm_ctx_alloc(msgb_tlli(msg), &ra_id); + ctx->p_tmsi = tmsi; + } + ctx->tlli = msgb_tlli(msg); + ctx->llme = llme; + msgid2mmctx(ctx, msg); + break; + default: + LOGP(DMM, LOGL_NOTICE, "Rejecting ATTACH REQUEST with " + "MI type %u\n", mi_type); + return gsm48_tx_gmm_att_rej_oldmsg(msg, GMM_CAUSE_MS_ID_NOT_DERIVED); + } + /* Update MM Context with currient RA and Cell ID */ + ctx->ra = ra_id; + ctx->cell_id = cid; + /* Update MM Context with other data */ + ctx->drx_parms = drx_par; + ctx->ms_radio_access_capa.len = ms_ra_acc_cap_len; + memcpy(ctx->ms_radio_access_capa.buf, ms_ra_acc_cap, ms_ra_acc_cap_len); + ctx->ms_network_capa.len = msnc_len; + memcpy(ctx->ms_network_capa.buf, msnc, msnc_len); + +#ifdef PTMSI_ALLOC + /* Allocate a new P-TMSI (+ P-TMSI signature) and update TLLI */ + ctx->p_tmsi_old = ctx->p_tmsi; + ctx->p_tmsi = sgsn_alloc_ptmsi(); +#endif + /* Even if there is no P-TMSI allocated, the MS will switch from + * foreign TLLI to local TLLI */ + ctx->tlli_new = gprs_tmsi2tlli(ctx->p_tmsi, TLLI_LOCAL); + + /* Inform LLC layer about new TLLI but keep old active */ + gprs_llgmm_assign(ctx->llme, ctx->tlli, ctx->tlli_new, + GPRS_ALGO_GEA0, NULL); + + DEBUGPC(DMM, "\n"); + return ctx ? gsm48_gmm_authorize(ctx, GMM_T3350_MODE_ATT) : 0; + +err_inval: + DEBUGPC(DMM, "\n"); + return gsm48_tx_gmm_att_rej_oldmsg(msg, GMM_CAUSE_SEM_INCORR_MSG); +} + +/* Section 4.7.4.1 / 9.4.5.2 MO Detach request */ +static int gsm48_rx_gmm_det_req(struct sgsn_mm_ctx *ctx, struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + struct sgsn_pdp_ctx *pdp, *pdp2; + uint8_t detach_type, power_off; + int rc; + + detach_type = gh->data[0] & 0x7; + power_off = gh->data[0] & 0x8; + + /* FIXME: In 24.008 there is an optional P-TMSI and P-TMSI signature IE */ + + DEBUGP(DMM, "-> GMM DETACH REQUEST TLLI=0x%08x type=%s %s\n", + msgb_tlli(msg), get_value_string(gprs_det_t_mo_strs, detach_type), + power_off ? "Power-off" : ""); + + /* Mark MM state as deregistered */ + ctx->mm_state = GMM_DEREGISTERED; + + /* delete all existing PDP contexts for this MS */ + llist_for_each_entry_safe(pdp, pdp2, &ctx->pdp_list, list) { + LOGP(DMM, LOGL_NOTICE, "Dropping PDP context for NSAPI=%u " + "due to GPRS DETACH REQUEST\n", pdp->nsapi); + sgsn_delete_pdp_ctx(pdp); + /* FIXME: the callback wants to transmit a DEACT PDP CTX ACK, + * which is quite stupid for a MS that has just detached.. */ + } + + /* force_stby = 0 */ + rc = gsm48_tx_gmm_det_ack(ctx, 0); + + /* TLLI unassignment */ + gprs_llgmm_assign(ctx->llme, ctx->tlli, 0xffffffff, + GPRS_ALGO_GEA0, NULL); + + return rc; +} + +/* Chapter 9.4.15: Routing area update accept */ +static int gsm48_tx_gmm_ra_upd_ack(struct sgsn_mm_ctx *mm) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + struct gsm48_ra_upd_ack *rua; + uint8_t *mid; + + DEBUGP(DMM, "<- ROUTING AREA UPDATE ACCEPT\n"); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_RA_UPD_ACK; + + rua = (struct gsm48_ra_upd_ack *) msgb_put(msg, sizeof(*rua)); + rua->force_stby = 0; /* not indicated */ + rua->upd_result = 0; /* RA updated */ + rua->ra_upd_timer = GPRS_TMR_MINUTE | 10; + + gsm48_construct_ra(rua->ra_id.digits, &mm->ra); + +#if 0 + /* Optional: P-TMSI signature */ + msgb_v_put(msg, GSM48_IE_GMM_PTMSI_SIG); + ptsig = msgb_put(msg, 3); + ptsig[0] = mm->p_tmsi_sig >> 16; + ptsig[1] = mm->p_tmsi_sig >> 8; + ptsig[2] = mm->p_tmsi_sig & 0xff; +#endif + +#ifdef PTMSI_ALLOC + /* Optional: Allocated P-TMSI */ + mid = msgb_put(msg, GSM48_MID_TMSI_LEN); + gsm48_generate_mid_from_tmsi(mid, mm->p_tmsi); + mid[0] = GSM48_IE_GMM_ALLOC_PTMSI; +#endif + + /* Option: MS ID, ... */ + return gsm48_gmm_sendmsg(msg, 0, mm); +} + +/* Chapter 9.4.17: Routing area update reject */ +static int gsm48_tx_gmm_ra_upd_rej(struct msgb *old_msg, uint8_t cause) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + + DEBUGP(DMM, "<- ROUTING AREA UPDATE REJECT\n"); + + gmm_copy_id(msg, old_msg); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 2); + gh->proto_discr = GSM48_PDISC_MM_GPRS; + gh->msg_type = GSM48_MT_GMM_RA_UPD_REJ; + gh->data[0] = cause; + gh->data[1] = 0; /* ? */ + + /* Option: P-TMSI signature, allocated P-TMSI, MS ID, ... */ + return gsm48_gmm_sendmsg(msg, 0, NULL); +} + +static void process_ms_ctx_status(struct sgsn_mm_ctx *mmctx, + uint16_t pdp_status) +{ + struct sgsn_pdp_ctx *pdp, *pdp2; + /* 24.008 4.7.5.1.3: If the PDP context status information element is + * included in ROUTING AREA UPDATE REQUEST message, then the network + * shall deactivate all those PDP contexts locally (without peer to + * peer signalling between the MS and the network), which are not in SM + * state PDP-INACTIVE on network side but are indicated by the MS as + * being in state PDP-INACTIVE. */ + + llist_for_each_entry_safe(pdp, pdp2, &mmctx->pdp_list, list) { + if (!(pdp_status & (1 << pdp->nsapi))) { + LOGP(DMM, LOGL_NOTICE, "Dropping PDP context for NSAPI=%u " + "due to PDP CTX STATUS IE= 0x%04x\n", + pdp->nsapi, pdp_status); + sgsn_delete_pdp_ctx(pdp); + } + } +} + +/* Chapter 9.4.14: Routing area update request */ +static int gsm48_rx_gmm_ra_upd_req(struct sgsn_mm_ctx *mmctx, struct msgb *msg, + struct gprs_llc_llme *llme) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t *cur = gh->data; + uint8_t *ms_ra_acc_cap; + uint8_t ms_ra_acc_cap_len; + struct gprs_ra_id old_ra_id; + struct tlv_parsed tp; + uint8_t upd_type; + int rc; + + /* Update Type 10.5.5.18 */ + upd_type = *cur++ & 0x0f; + + DEBUGP(DMM, "-> GMM RA UPDATE REQUEST type=\"%s\" ", + get_value_string(gprs_upd_t_strs, upd_type)); + + /* Old routing area identification 10.5.5.15 */ + gsm48_parse_ra(&old_ra_id, cur); + cur += 6; + + /* MS Radio Access Capability 10.5.5.12a */ + ms_ra_acc_cap_len = *cur++; + ms_ra_acc_cap = cur; + + /* Optional: Old P-TMSI Signature, Requested READY timer, TMSI Status, + * DRX parameter, MS network capability */ + rc = tlv_parse(&tp, &gsm48_gmm_att_tlvdef, cur, + (msg->data + msg->len) - cur, 0, 0); + + switch (upd_type) { + case GPRS_UPD_T_RA_LA: + case GPRS_UPD_T_RA_LA_IMSI_ATT: + DEBUGPC(DMM, " unsupported in Mode III, is your SI13 corrupt?\n"); + return gsm48_tx_gmm_ra_upd_rej(msg, GMM_CAUSE_PROTO_ERR_UNSPEC); + break; + case GPRS_UPD_T_RA: + case GPRS_UPD_T_PERIODIC: + break; + } + + /* Look-up the MM context based on old RA-ID and TLLI */ + mmctx = sgsn_mm_ctx_by_tlli(msgb_tlli(msg), &old_ra_id); + if (!mmctx || mmctx->mm_state == GMM_DEREGISTERED) { + /* The MS has to perform GPRS attach */ + DEBUGPC(DMM, " REJECT\n"); + /* Device is still IMSI atached for CS but initiate GPRS ATTACH */ + return gsm48_tx_gmm_ra_upd_rej(msg, GMM_CAUSE_MS_ID_NOT_DERIVED); + } + + /* Store new BVCI/NSEI in MM context (FIXME: delay until we ack?) */ + msgid2mmctx(mmctx, msg); + /* Bump the statistics of received signalling msgs for this MM context */ + rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_SIG_IN]); + + /* Update the MM context with the new RA-ID */ + bssgp_parse_cell_id(&mmctx->ra, msgb_bcid(msg)); + /* Update the MM context with the new (i.e. foreign) TLLI */ + mmctx->tlli = msgb_tlli(msg); + /* FIXME: Update the MM context with the MS radio acc capabilities */ + /* FIXME: Update the MM context with the MS network capabilities */ + + rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_RA_UPDATE]); + + DEBUGPC(DMM, " ACCEPT\n"); +#ifdef PTMSI_ALLOC + mmctx->p_tmsi_old = mmctx->p_tmsi; + mmctx->p_tmsi = sgsn_alloc_ptmsi(); + /* Start T3350 and re-transmit up to 5 times until ATTACH COMPLETE */ + mmctx->t3350_mode = GMM_T3350_MODE_RAU; + mmctx_timer_start(mmctx, 3350, GSM0408_T3350_SECS); +#endif + /* Even if there is no P-TMSI allocated, the MS will switch from + * foreign TLLI to local TLLI */ + mmctx->tlli_new = gprs_tmsi2tlli(mmctx->p_tmsi, TLLI_LOCAL); + + /* Inform LLC layer about new TLLI but keep old active */ + gprs_llgmm_assign(mmctx->llme, mmctx->tlli, mmctx->tlli_new, + GPRS_ALGO_GEA0, NULL); + + /* Look at PDP Context Status IE and see if MS's view of + * activated/deactivated NSAPIs agrees with our view */ + if (TLVP_PRESENT(&tp, GSM48_IE_GMM_PDP_CTX_STATUS)) { + uint16_t pdp_status = ntohs(*(uint16_t *) + TLVP_VAL(&tp, GSM48_IE_GMM_PDP_CTX_STATUS)); + process_ms_ctx_status(mmctx, pdp_status); + } + + /* Send RA UPDATE ACCEPT */ + return gsm48_tx_gmm_ra_upd_ack(mmctx); +} + +static int gsm48_rx_gmm_status(struct sgsn_mm_ctx *mmctx, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + + DEBUGP(DMM, "-> GPRS MM STATUS (cause: %s)\n", + get_value_string(gmm_cause_names, gh->data[0])); + + return 0; +} + +/* GPRS Mobility Management */ +static int gsm0408_rcv_gmm(struct sgsn_mm_ctx *mmctx, struct msgb *msg, + struct gprs_llc_llme *llme) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + int rc; + + /* MMCTX can be NULL when called */ + + if (!mmctx && + gh->msg_type != GSM48_MT_GMM_ATTACH_REQ && + gh->msg_type != GSM48_MT_GMM_RA_UPD_REQ) { + LOGP(DMM, LOGL_NOTICE, "Cannot handle GMM for unknown MM CTX\n"); + return gsm48_tx_gmm_status_oldmsg(msg, GMM_CAUSE_MS_ID_NOT_DERIVED); + } + + switch (gh->msg_type) { + case GSM48_MT_GMM_RA_UPD_REQ: + rc = gsm48_rx_gmm_ra_upd_req(mmctx, msg, llme); + break; + case GSM48_MT_GMM_ATTACH_REQ: + rc = gsm48_rx_gmm_att_req(mmctx, msg, llme); + break; + case GSM48_MT_GMM_ID_RESP: + rc = gsm48_rx_gmm_id_resp(mmctx, msg); + break; + case GSM48_MT_GMM_STATUS: + rc = gsm48_rx_gmm_status(mmctx, msg); + break; + case GSM48_MT_GMM_DETACH_REQ: + rc = gsm48_rx_gmm_det_req(mmctx, msg); + break; + case GSM48_MT_GMM_ATTACH_COMPL: + /* only in case SGSN offered new P-TMSI */ + DEBUGP(DMM, "-> ATTACH COMPLETE\n"); + mmctx_timer_stop(mmctx, 3350); + mmctx->p_tmsi_old = 0; + /* Unassign the old TLLI */ + mmctx->tlli = mmctx->tlli_new; + gprs_llgmm_assign(mmctx->llme, 0xffffffff, mmctx->tlli_new, + GPRS_ALGO_GEA0, NULL); + break; + case GSM48_MT_GMM_RA_UPD_COMPL: + /* only in case SGSN offered new P-TMSI */ + DEBUGP(DMM, "-> ROUTEING AREA UPDATE COMPLETE\n"); + mmctx_timer_stop(mmctx, 3350); + mmctx->p_tmsi_old = 0; + /* Unassign the old TLLI */ + mmctx->tlli = mmctx->tlli_new; + gprs_llgmm_assign(mmctx->llme, 0xffffffff, mmctx->tlli_new, + GPRS_ALGO_GEA0, NULL); + break; + case GSM48_MT_GMM_PTMSI_REALL_COMPL: + DEBUGP(DMM, "-> PTMSI REALLLICATION COMPLETE\n"); + mmctx_timer_stop(mmctx, 3350); + mmctx->p_tmsi_old = 0; + /* Unassign the old TLLI */ + mmctx->tlli = mmctx->tlli_new; + //gprs_llgmm_assign(mmctx->llme, 0xffffffff, mmctx->tlli_new, GPRS_ALGO_GEA0, NULL); + break; + case GSM48_MT_GMM_AUTH_CIPH_RESP: + rc = gsm48_rx_gmm_auth_ciph_resp(mmctx, msg); + break; + default: + DEBUGP(DMM, "Unknown GSM 04.08 GMM msg type 0x%02x\n", + gh->msg_type); + rc = gsm48_tx_gmm_status(mmctx, GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL); + break; + } + + return rc; +} + +static void mmctx_timer_cb(void *_mm) +{ + struct sgsn_mm_ctx *mm = _mm; + + mm->num_T_exp++; + + switch (mm->T) { + case 3350: /* waiting for ATTACH COMPLETE */ + if (mm->num_T_exp >= 5) { + LOGP(DMM, LOGL_NOTICE, "T3350 expired >= 5 times\n"); + mm->mm_state = GMM_DEREGISTERED; + /* FIXME: should we return some error? */ + break; + } + /* re-transmit the respective msg and re-start timer */ + switch (mm->t3350_mode) { + case GMM_T3350_MODE_ATT: + gsm48_tx_gmm_att_ack(mm); + break; + case GMM_T3350_MODE_RAU: + gsm48_tx_gmm_ra_upd_ack(mm); + break; + case GMM_T3350_MODE_PTMSI_REALL: + /* FIXME */ + break; + } + bsc_schedule_timer(&mm->timer, GSM0408_T3350_SECS, 0); + break; + case 3360: /* waiting for AUTH AND CIPH RESP */ + if (mm->num_T_exp >= 5) { + LOGP(DMM, LOGL_NOTICE, "T3360 expired >= 5 times\n"); + mm->mm_state = GMM_DEREGISTERED; + break; + } + /* FIXME: re-transmit the respective msg and re-start timer */ + bsc_schedule_timer(&mm->timer, GSM0408_T3360_SECS, 0); + break; + case 3370: /* waiting for IDENTITY RESPONSE */ + if (mm->num_T_exp >= 5) { + LOGP(DMM, LOGL_NOTICE, "T3370 expired >= 5 times\n"); + gsm48_tx_gmm_att_rej(mm, GMM_CAUSE_MS_ID_NOT_DERIVED); + mm->mm_state = GMM_DEREGISTERED; + break; + } + /* re-tranmit IDENTITY REQUEST and re-start timer */ + gsm48_tx_gmm_id_req(mm, mm->t3370_id_type); + bsc_schedule_timer(&mm->timer, GSM0408_T3370_SECS, 0); + break; + default: + LOGP(DMM, LOGL_ERROR, "timer expired in unknown mode %u\n", + mm->T); + } +} + +/* GPRS SESSION MANAGEMENT */ + +static void pdpctx_timer_cb(void *_mm); + +static void pdpctx_timer_start(struct sgsn_pdp_ctx *pdp, unsigned int T, + unsigned int seconds) +{ + if (bsc_timer_pending(&pdp->timer)) + LOGP(DMM, LOGL_ERROR, "Starting MM timer %u while old " + "timer %u pending\n", T, pdp->T); + pdp->T = T; + pdp->num_T_exp = 0; + + /* FIXME: we should do this only once ? */ + pdp->timer.data = pdp; + pdp->timer.cb = &pdpctx_timer_cb; + + bsc_schedule_timer(&pdp->timer, seconds, 0); +} + + +static void msgb_put_pdp_addr_ipv4(struct msgb *msg, uint32_t ipaddr) +{ + uint8_t v[6]; + + v[0] = PDP_TYPE_ORG_IETF; + v[1] = PDP_TYPE_N_IETF_IPv4; + *(uint32_t *)(v+2) = htonl(ipaddr); + + msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR, sizeof(v), v); +} + +static void msgb_put_pdp_addr_ppp(struct msgb *msg) +{ + uint8_t v[2]; + + v[0] = PDP_TYPE_ORG_ETSI; + v[1] = PDP_TYPE_N_ETSI_PPP; + + msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR, sizeof(v), v); +} + +/* Section 9.5.2: Ativate PDP Context Accept */ +int gsm48_tx_gsm_act_pdp_acc(struct sgsn_pdp_ctx *pdp) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + uint8_t transaction_id = pdp->ti ^ 0x8; /* flip */ + + DEBUGP(DMM, "<- ACTIVATE PDP CONTEXT ACK\n"); + + mmctx2msgid(msg, pdp->mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4); + gh->msg_type = GSM48_MT_GSM_ACT_PDP_ACK; + + /* Negotiated LLC SAPI */ + msgb_v_put(msg, pdp->sapi); + + /* FIXME: copy QoS parameters from original request */ + //msgb_lv_put(msg, pdp->lib->qos_neg.l, pdp->lib->qos_neg.v); + msgb_lv_put(msg, sizeof(default_qos), (uint8_t *)&default_qos); + + /* Radio priority 10.5.7.2 */ + msgb_v_put(msg, pdp->lib->radio_pri); + + /* PDP address */ + /* Highest 4 bits of first byte need to be set to 1, otherwise + * the IE is identical with the 04.08 PDP Address IE */ + pdp->lib->eua.v[0] &= ~0xf0; + msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR, + pdp->lib->eua.l, pdp->lib->eua.v); + pdp->lib->eua.v[0] |= 0xf0; + + /* Optional: Protocol configuration options (FIXME: why 'req') */ + if (pdp->lib->pco_req.l && pdp->lib->pco_req.v) + msgb_tlv_put(msg, GSM48_IE_GSM_PROTO_CONF_OPT, + pdp->lib->pco_req.l, pdp->lib->pco_req.v); + + /* Optional: Packet Flow Identifier */ + + return gsm48_gmm_sendmsg(msg, 0, pdp->mm); +} + +/* Section 9.5.3: Activate PDP Context reject */ +int gsm48_tx_gsm_act_pdp_rej(struct sgsn_mm_ctx *mm, uint8_t tid, + uint8_t cause, uint8_t pco_len, uint8_t *pco_v) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + uint8_t transaction_id = tid ^ 0x8; /* flip */ + + DEBUGP(DMM, "<- ACTIVATE PDP CONTEXT REJ(cause=%u)\n", cause); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4); + gh->msg_type = GSM48_MT_GSM_ACT_PDP_REJ; + + msgb_v_put(msg, cause); + if (pco_len && pco_v) + msgb_tlv_put(msg, GSM48_IE_GSM_PROTO_CONF_OPT, pco_len, pco_v); + + return gsm48_gmm_sendmsg(msg, 0, mm); +} + +/* Section 9.5.8: Deactivate PDP Context Request */ +static int _gsm48_tx_gsm_deact_pdp_req(struct sgsn_mm_ctx *mm, uint8_t tid, + uint8_t sm_cause) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + uint8_t transaction_id = tid ^ 0x8; /* flip */ + + DEBUGP(DMM, "<- DEACTIVATE PDP CONTEXT REQ\n"); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4); + gh->msg_type = GSM48_MT_GSM_DEACT_PDP_REQ; + + msgb_v_put(msg, sm_cause); + + return gsm48_gmm_sendmsg(msg, 0, mm); +} +int gsm48_tx_gsm_deact_pdp_req(struct sgsn_pdp_ctx *pdp, uint8_t sm_cause) +{ + pdpctx_timer_start(pdp, 3395, GSM0408_T3395_SECS); + + return _gsm48_tx_gsm_deact_pdp_req(pdp->mm, pdp->ti, sm_cause); +} + +/* Section 9.5.9: Deactivate PDP Context Accept */ +static int _gsm48_tx_gsm_deact_pdp_acc(struct sgsn_mm_ctx *mm, uint8_t tid) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + uint8_t transaction_id = tid ^ 0x8; /* flip */ + + DEBUGP(DMM, "<- DEACTIVATE PDP CONTEXT ACK\n"); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4); + gh->msg_type = GSM48_MT_GSM_DEACT_PDP_ACK; + + return gsm48_gmm_sendmsg(msg, 0, mm); +} +int gsm48_tx_gsm_deact_pdp_acc(struct sgsn_pdp_ctx *pdp) +{ + return _gsm48_tx_gsm_deact_pdp_acc(pdp->mm, pdp->ti); +} + +/* Section 9.5.1: Activate PDP Context Request */ +static int gsm48_rx_gsm_act_pdp_req(struct sgsn_mm_ctx *mmctx, + struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + struct gsm48_act_pdp_ctx_req *act_req = (struct gsm48_act_pdp_ctx_req *) gh->data; + uint8_t req_qos_len, req_pdpa_len; + uint8_t *req_qos, *req_pdpa; + struct tlv_parsed tp; + uint8_t transaction_id = (gh->proto_discr >> 4); + struct sgsn_ggsn_ctx *ggsn; + struct sgsn_pdp_ctx *pdp; + + DEBUGP(DMM, "-> ACTIVATE PDP CONTEXT REQ: SAPI=%u NSAPI=%u ", + act_req->req_llc_sapi, act_req->req_nsapi); + + /* FIXME: length checks! */ + req_qos_len = act_req->data[0]; + req_qos = act_req->data + 1; /* 10.5.6.5 */ + req_pdpa_len = act_req->data[1 + req_qos_len]; + req_pdpa = act_req->data + 1 + req_qos_len + 1; /* 10.5.6.4 */ + + /* Optional: Access Point Name, Protocol Config Options */ + if (req_pdpa + req_pdpa_len < msg->data + msg->len) + tlv_parse(&tp, &gsm48_sm_att_tlvdef, req_pdpa + req_pdpa_len, + (msg->data + msg->len) - (req_pdpa + req_pdpa_len), 0, 0); + else + memset(&tp, 0, sizeof(tp)); + + switch (req_pdpa[0] & 0xf) { + case 0x0: + DEBUGPC(DMM, "ETSI "); + break; + case 0x1: + DEBUGPC(DMM, "IETF "); + break; + case 0xf: + DEBUGPC(DMM, "Empty "); + break; + } + + switch (req_pdpa[1]) { + case 0x21: + DEBUGPC(DMM, "IPv4 "); + if (req_pdpa_len >= 6) { + struct in_addr ia; + ia.s_addr = ntohl(*((uint32_t *) (req_pdpa+2))); + DEBUGPC(DMM, "%s ", inet_ntoa(ia)); + } + break; + case 0x57: + DEBUGPC(DMM, "IPv6 "); + if (req_pdpa_len >= 18) { + /* FIXME: print IPv6 address */ + } + break; + default: + DEBUGPC(DMM, "0x%02x ", req_pdpa[1]); + break; + } + + DEBUGPC(DMM, "\n"); + + /* put the non-TLV elements in the TLV parser structure to + * pass them on to the SGSN / GTP code */ + tp.lv[OSMO_IE_GSM_REQ_QOS].len = req_qos_len; + tp.lv[OSMO_IE_GSM_REQ_QOS].val = req_qos; + tp.lv[OSMO_IE_GSM_REQ_PDP_ADDR].len = req_pdpa_len; + tp.lv[OSMO_IE_GSM_REQ_PDP_ADDR].val = req_pdpa; + + /* FIXME: determine GGSN based on APN and subscription options */ + if (TLVP_PRESENT(&tp, GSM48_IE_GSM_APN)) {} + + /* Check if NSAPI is out of range (TS 04.65 / 7.2) */ + if (act_req->req_nsapi < 5 || act_req->req_nsapi > 15) { + /* Send reject with GSM_CAUSE_INV_MAND_INFO */ + return gsm48_tx_gsm_act_pdp_rej(mmctx, transaction_id, + GSM_CAUSE_INV_MAND_INFO, + 0, NULL); + } + + /* Check if NSAPI is already in use */ + pdp = sgsn_pdp_ctx_by_nsapi(mmctx, act_req->req_nsapi); + if (pdp) { + /* We already have a PDP context for this TLLI + NSAPI tuple */ + if (pdp->sapi == act_req->req_llc_sapi && + pdp->ti == transaction_id) { + /* This apparently is a re-transmission of a PDP CTX + * ACT REQ (our ACT ACK must have got dropped) */ + return gsm48_tx_gsm_act_pdp_acc(pdp); + } + + /* Send reject with GSM_CAUSE_NSAPI_IN_USE */ + return gsm48_tx_gsm_act_pdp_rej(mmctx, transaction_id, + GSM_CAUSE_NSAPI_IN_USE, + 0, NULL); + } + + /* Only increment counter for a real activation, after we checked + * for re-transmissions */ + rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PDP_CTX_ACT]); + + ggsn = sgsn_ggsn_ctx_by_id(0); + if (!ggsn) { + LOGP(DGPRS, LOGL_ERROR, "No GGSN context 0 found!\n"); + return -EIO; + } + ggsn->gsn = sgsn->gsn; + pdp = sgsn_create_pdp_ctx(ggsn, mmctx, act_req->req_nsapi, &tp); + if (!pdp) + return -1; + + /* Store SAPI and Transaction Identifier */ + pdp->sapi = act_req->req_llc_sapi; + pdp->ti = transaction_id; + + return 0; +} + +/* Section 9.5.8: Deactivate PDP Context Request */ +static int gsm48_rx_gsm_deact_pdp_req(struct sgsn_mm_ctx *mm, struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t transaction_id = (gh->proto_discr >> 4); + struct sgsn_pdp_ctx *pdp; + + DEBUGP(DMM, "-> DEACTIVATE PDP CONTEXT REQ (cause: %s)\n", + get_value_string(gsm_cause_names, gh->data[0])); + + pdp = sgsn_pdp_ctx_by_tid(mm, transaction_id); + if (!pdp) { + LOGP(DMM, LOGL_NOTICE, "Deactivate PDP Context Request for " + "non-existing PDP Context (IMSI=%s, TI=%u)\n", + mm->imsi, transaction_id); + return _gsm48_tx_gsm_deact_pdp_acc(mm, transaction_id); + } + + return sgsn_delete_pdp_ctx(pdp); +} + +/* Section 9.5.9: Deactivate PDP Context Accept */ +static int gsm48_rx_gsm_deact_pdp_ack(struct sgsn_mm_ctx *mm, struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t transaction_id = (gh->proto_discr >> 4); + struct sgsn_pdp_ctx *pdp; + + DEBUGP(DMM, "-> DEACTIVATE PDP CONTEXT ACK\n"); + + pdp = sgsn_pdp_ctx_by_tid(mm, transaction_id); + if (!pdp) { + LOGP(DMM, LOGL_NOTICE, "Deactivate PDP Context Accept for " + "non-existing PDP Context (IMSI=%s, TI=%u)\n", + mm->imsi, transaction_id); + return 0; + } + + return sgsn_delete_pdp_ctx(pdp); +} + +static int gsm48_rx_gsm_status(struct sgsn_mm_ctx *ctx, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + + DEBUGP(DMM, "-> GPRS SM STATUS (cause: %s)\n", + get_value_string(gsm_cause_names, gh->data[0])); + + return 0; +} + +static void pdpctx_timer_cb(void *_pdp) +{ + struct sgsn_pdp_ctx *pdp = _pdp; + + pdp->num_T_exp++; + + switch (pdp->T) { + case 3395: /* waiting for PDP CTX DEACT ACK */ + if (pdp->num_T_exp >= 4) { + LOGP(DMM, LOGL_NOTICE, "T3395 expired >= 5 times\n"); + pdp->state = PDP_STATE_INACTIVE; + sgsn_delete_pdp_ctx(pdp); + break; + } + gsm48_tx_gsm_deact_pdp_req(pdp, GSM_CAUSE_NET_FAIL); + bsc_schedule_timer(&pdp->timer, GSM0408_T3395_SECS, 0); + break; + default: + LOGP(DMM, LOGL_ERROR, "timer expired in unknown mode %u\n", + pdp->T); + } +} + + +/* GPRS Session Management */ +static int gsm0408_rcv_gsm(struct sgsn_mm_ctx *mmctx, struct msgb *msg, + struct gprs_llc_llme *llme) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + int rc; + + /* MMCTX can be NULL when called */ + + if (!mmctx) { + LOGP(DMM, LOGL_NOTICE, "Cannot handle SM for unknown MM CTX\n"); + gsm48_tx_gmm_status_oldmsg(msg, GMM_CAUSE_IMPL_DETACHED); + return gsm48_tx_sm_status_oldmsg(msg, GSM_CAUSE_PROTO_ERR_UNSPEC); + } + + switch (gh->msg_type) { + case GSM48_MT_GSM_ACT_PDP_REQ: + rc = gsm48_rx_gsm_act_pdp_req(mmctx, msg); + break; + case GSM48_MT_GSM_DEACT_PDP_REQ: + rc = gsm48_rx_gsm_deact_pdp_req(mmctx, msg); + break; + case GSM48_MT_GSM_DEACT_PDP_ACK: + rc = gsm48_rx_gsm_deact_pdp_ack(mmctx, msg); + break; + case GSM48_MT_GSM_STATUS: + rc = gsm48_rx_gsm_status(mmctx, msg); + break; + case GSM48_MT_GSM_REQ_PDP_ACT_REJ: + case GSM48_MT_GSM_ACT_AA_PDP_REQ: + case GSM48_MT_GSM_DEACT_AA_PDP_REQ: + DEBUGP(DMM, "Unimplemented GSM 04.08 GSM msg type 0x%02x\n", + gh->msg_type); + rc = gsm48_tx_sm_status(mmctx, GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL); + break; + default: + DEBUGP(DMM, "Unknown GSM 04.08 GSM msg type 0x%02x\n", + gh->msg_type); + rc = gsm48_tx_sm_status(mmctx, GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL); + break; + + } + + return rc; +} + +/* Main entry point for incoming 04.08 GPRS messages */ +int gsm0408_gprs_rcvmsg(struct msgb *msg, struct gprs_llc_llme *llme) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t pdisc = gh->proto_discr & 0x0f; + struct sgsn_mm_ctx *mmctx; + struct gprs_ra_id ra_id; + int rc = -EINVAL; + + bssgp_parse_cell_id(&ra_id, msgb_bcid(msg)); + mmctx = sgsn_mm_ctx_by_tlli(msgb_tlli(msg), &ra_id); + if (mmctx) { + msgid2mmctx(mmctx, msg); + rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_SIG_IN]); + mmctx->llme = llme; + } + + /* MMCTX can be NULL */ + + switch (pdisc) { + case GSM48_PDISC_MM_GPRS: + rc = gsm0408_rcv_gmm(mmctx, msg, llme); + break; + case GSM48_PDISC_SM_GPRS: + rc = gsm0408_rcv_gsm(mmctx, msg, llme); + break; + default: + DEBUGP(DMM, "Unknown GSM 04.08 discriminator 0x%02x\n", + pdisc); + /* FIXME: return status message */ + break; + } + + return rc; +} + +int gprs_gmm_rx_suspend(struct gprs_ra_id *raid, uint32_t tlli) +{ + struct sgsn_mm_ctx *mmctx; + + mmctx = sgsn_mm_ctx_by_tlli(tlli, raid); + if (!mmctx) { + LOGP(DMM, LOGL_NOTICE, "SUSPEND request for unknown " + "TLLI=%08x\n", tlli); + return -EINVAL; + } + + if (mmctx->mm_state != GMM_REGISTERED_NORMAL) { + LOGP(DMM, LOGL_NOTICE, "SUSPEND request while state " + "!= REGISTERED (TLLI=%08x)\n", tlli); + return -EINVAL; + } + + /* Transition from REGISTERED_NORMAL to REGISTERED_SUSPENDED */ + mmctx->mm_state = GMM_REGISTERED_SUSPENDED; + return 0; +} + +int gprs_gmm_rx_resume(struct gprs_ra_id *raid, uint32_t tlli, + uint8_t suspend_ref) +{ + struct sgsn_mm_ctx *mmctx; + + /* FIXME: make use of suspend reference? */ + + mmctx = sgsn_mm_ctx_by_tlli(tlli, raid); + if (!mmctx) { + LOGP(DMM, LOGL_NOTICE, "RESUME request for unknown " + "TLLI=%08x\n", tlli); + return -EINVAL; + } + + if (mmctx->mm_state != GMM_REGISTERED_SUSPENDED) { + LOGP(DMM, LOGL_NOTICE, "RESUME request while state " + "!= SUSPENDED (TLLI=%08x)\n", tlli); + /* FIXME: should we not simply ignore it? */ + return -EINVAL; + } + + /* Transition from SUSPENDED to NORMAL */ + mmctx->mm_state = GMM_REGISTERED_NORMAL; + return 0; +} diff --git a/src/gprs/gprs_llc.c b/src/gprs/gprs_llc.c new file mode 100644 index 000000000..7991f4c1e --- /dev/null +++ b/src/gprs/gprs_llc.c @@ -0,0 +1,852 @@ +/* GPRS LLC protocol implementation as per 3GPP TS 04.64 */ + +/* (C) 2009-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* Section 8.9.9 LLC layer parameter default values */ +static const struct gprs_llc_params llc_default_params[] = { + [1] = { + .t200_201 = 5, + .n200 = 3, + .n201_u = 400, + }, + [2] = { + .t200_201 = 5, + .n200 = 3, + .n201_u = 270, + }, + [3] = { + .iov_i_exp = 27, + .t200_201 = 5, + .n200 = 3, + .n201_u = 500, + .n201_i = 1503, + .mD = 1520, + .mU = 1520, + .kD = 16, + .kU = 16, + }, + [5] = { + .iov_i_exp = 27, + .t200_201 = 10, + .n200 = 3, + .n201_u = 500, + .n201_i = 1503, + .mD = 760, + .mU = 760, + .kD = 8, + .kU = 8, + }, + [7] = { + .t200_201 = 20, + .n200 = 3, + .n201_u = 270, + }, + [8] = { + .t200_201 = 20, + .n200 = 3, + .n201_u = 270, + }, + [9] = { + .iov_i_exp = 27, + .t200_201 = 20, + .n200 = 3, + .n201_u = 500, + .n201_i = 1503, + .mD = 380, + .mU = 380, + .kD = 4, + .kU = 4, + }, + [11] = { + .iov_i_exp = 27, + .t200_201 = 40, + .n200 = 3, + .n201_u = 500, + .n201_i = 1503, + .mD = 190, + .mU = 190, + .kD = 2, + .kU = 2, + }, +}; + +LLIST_HEAD(gprs_llc_llmes); +void *llc_tall_ctx; + +/* If the TLLI is foreign, return its local version */ +static inline uint32_t tlli_foreign2local(uint32_t tlli) +{ + uint32_t new_tlli; + + if (gprs_tlli_type(tlli) == TLLI_FOREIGN) { + new_tlli = tlli | 0x40000000; + DEBUGP(DLLC, "TLLI 0x%08x is foreign, converting to " + "local TLLI 0x%08x\n", tlli, new_tlli); + } else + new_tlli = tlli; + + return new_tlli; +} + +/* lookup LLC Entity based on DLCI (TLLI+SAPI tuple) */ +static struct gprs_llc_lle *lle_by_tlli_sapi(uint32_t tlli, uint8_t sapi) +{ + struct gprs_llc_llme *llme; + + tlli = tlli_foreign2local(tlli); + + llist_for_each_entry(llme, &gprs_llc_llmes, list) { + if (llme->tlli == tlli || llme->old_tlli == tlli) + return &llme->lle[sapi]; + } + return NULL; +} + +static void lle_init(struct gprs_llc_llme *llme, uint8_t sapi) +{ + struct gprs_llc_lle *lle = &llme->lle[sapi]; + + lle->llme = llme; + lle->sapi = sapi; + lle->state = GPRS_LLES_UNASSIGNED; + + /* Initialize according to parameters */ + memcpy(&lle->params, &llc_default_params[sapi], sizeof(lle->params)); +} + +static struct gprs_llc_llme *llme_alloc(uint32_t tlli) +{ + struct gprs_llc_llme *llme; + uint32_t i; + + llme = talloc_zero(llc_tall_ctx, struct gprs_llc_llme); + if (!llme) + return NULL; + + llme->tlli = tlli; + llme->old_tlli = 0xffffffff; + llme->state = GPRS_LLMS_UNASSIGNED; + + for (i = 0; i < ARRAY_SIZE(llme->lle); i++) + lle_init(llme, i); + + llist_add(&llme->list, &gprs_llc_llmes); + + return llme; +} + +static void llme_free(struct gprs_llc_llme *llme) +{ + llist_del(&llme->list); + talloc_free(llme); +} + +enum gprs_llc_cmd { + GPRS_LLC_NULL, + GPRS_LLC_RR, + GPRS_LLC_ACK, + GPRS_LLC_RNR, + GPRS_LLC_SACK, + GPRS_LLC_DM, + GPRS_LLC_DISC, + GPRS_LLC_UA, + GPRS_LLC_SABM, + GPRS_LLC_FRMR, + GPRS_LLC_XID, + GPRS_LLC_UI, +}; + +static const struct value_string llc_cmd_strs[] = { + { GPRS_LLC_NULL, "NULL" }, + { GPRS_LLC_RR, "RR" }, + { GPRS_LLC_ACK, "ACK" }, + { GPRS_LLC_RNR, "RNR" }, + { GPRS_LLC_SACK, "SACK" }, + { GPRS_LLC_DM, "DM" }, + { GPRS_LLC_DISC, "DISC" }, + { GPRS_LLC_UA, "UA" }, + { GPRS_LLC_SABM, "SABM" }, + { GPRS_LLC_FRMR, "FRMR" }, + { GPRS_LLC_XID, "XID" }, + { GPRS_LLC_UI, "UI" }, + { 0, NULL } +}; + +struct gprs_llc_hdr_parsed { + uint8_t sapi; + uint8_t is_cmd:1, + ack_req:1, + is_encrypted:1; + uint32_t seq_rx; + uint32_t seq_tx; + uint32_t fcs; + uint32_t fcs_calc; + uint8_t *data; + uint16_t data_len; + uint16_t crc_length; + enum gprs_llc_cmd cmd; +}; + +#define LLC_ALLOC_SIZE 16384 +#define UI_HDR_LEN 3 +#define N202 4 +#define CRC24_LENGTH 3 + +static int gprs_llc_fcs(uint8_t *data, unsigned int len) +{ + uint32_t fcs_calc; + + fcs_calc = crc24_calc(INIT_CRC24, data, len); + fcs_calc = ~fcs_calc; + fcs_calc &= 0xffffff; + + return fcs_calc; +} + +static void t200_expired(void *data) +{ + struct gprs_llc_lle *lle = data; + + /* 8.5.1.3: Expiry of T200 */ + + if (lle->retrans_ctr >= lle->params.n200) { + /* FIXME: LLGM-STATUS-IND, LL-RELEASE-IND/CNF */ + lle->state = GPRS_LLES_ASSIGNED_ADM; + } + + switch (lle->state) { + case GPRS_LLES_LOCAL_EST: + /* FIXME: retransmit SABM */ + /* FIXME: re-start T200 */ + lle->retrans_ctr++; + break; + case GPRS_LLES_LOCAL_REL: + /* FIXME: retransmit DISC */ + /* FIXME: re-start T200 */ + lle->retrans_ctr++; + break; + } + +} + +static void t201_expired(void *data) +{ + struct gprs_llc_lle *lle = data; + + if (lle->retrans_ctr < lle->params.n200) { + /* FIXME: transmit apropriate supervisory frame (8.6.4.1) */ + /* FIXME: set timer T201 */ + lle->retrans_ctr++; + } +} + +int gprs_llc_tx_u(struct msgb *msg, uint8_t sapi, int command, + enum gprs_llc_u_cmd u_cmd, int pf_bit) +{ + uint8_t *fcs, *llch; + uint8_t addr, ctrl; + uint32_t fcs_calc; + + /* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */ + + /* Address Field */ + addr = sapi & 0xf; + if (command) + addr |= 0x40; + + /* 6.3 Figure 8 */ + ctrl = 0xe0 | u_cmd; + if (pf_bit) + ctrl |= 0x10; + + /* prepend LLC UI header */ + llch = msgb_push(msg, 2); + llch[0] = addr; + llch[1] = ctrl; + + /* append FCS to end of frame */ + fcs = msgb_put(msg, 3); + fcs_calc = gprs_llc_fcs(llch, fcs - llch); + fcs[0] = fcs_calc & 0xff; + fcs[1] = (fcs_calc >> 8) & 0xff; + fcs[2] = (fcs_calc >> 16) & 0xff; + + /* Identifiers passed down: (BVCI, NSEI) */ + + /* Send BSSGP-DL-UNITDATA.req */ + return gprs_bssgp_tx_dl_ud(msg, NULL); +} + +/* Send XID response to LLE */ +static int gprs_llc_tx_xid(struct gprs_llc_lle *lle, struct msgb *msg) +{ + /* copy identifiers from LLE to ensure lower layers can route */ + msgb_tlli(msg) = lle->llme->tlli; + msgb_bvci(msg) = lle->llme->bvci; + msgb_nsei(msg) = lle->llme->nsei; + + return gprs_llc_tx_u(msg, lle->sapi, 0, GPRS_LLC_U_XID, 1); +} + +/* Transmit a UI frame over the given SAPI */ +int gprs_llc_tx_ui(struct msgb *msg, uint8_t sapi, int command, + void *mmctx) +{ + struct gprs_llc_lle *lle; + uint8_t *fcs, *llch; + uint8_t addr, ctrl[2]; + uint32_t fcs_calc; + uint16_t nu = 0; + uint32_t oc; + + /* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */ + + /* look-up or create the LL Entity for this (TLLI, SAPI) tuple */ + lle = lle_by_tlli_sapi(msgb_tlli(msg), sapi); + if (!lle) { + struct gprs_llc_llme *llme; + LOGP(DLLC, LOGL_ERROR, "LLC TX: unknown TLLI 0x%08x, " + "creating LLME on the fly\n", msgb_tlli(msg)); + llme = llme_alloc(msgb_tlli(msg)); + lle = &llme->lle[sapi]; + } + + if (msg->len > lle->params.n201_u) { + LOGP(DLLC, LOGL_ERROR, "Cannot Tx %u bytes (N201-U=%u)\n", + msg->len, lle->params.n201_u); + return -EFBIG; + } + + /* Update LLE's (BVCI, NSEI) tuple */ + lle->llme->bvci = msgb_bvci(msg); + lle->llme->nsei = msgb_nsei(msg); + + /* Obtain current values for N(u) and OC */ + nu = lle->vu_send; + oc = lle->oc_ui_send; + /* Increment V(U) */ + lle->vu_send = (lle->vu_send + 1) % 512; + /* Increment Overflow Counter, if needed */ + if ((lle->vu_send + 1) / 512) + lle->oc_ui_send += 512; + + /* Address Field */ + addr = sapi & 0xf; + if (command) + addr |= 0x40; + + /* Control Field */ + ctrl[0] = 0xc0; + ctrl[0] |= nu >> 6; + ctrl[1] = (nu << 2) & 0xfc; + ctrl[1] |= 0x01; /* Protected Mode */ + + /* prepend LLC UI header */ + llch = msgb_push(msg, 3); + llch[0] = addr; + llch[1] = ctrl[0]; + llch[2] = ctrl[1]; + + /* append FCS to end of frame */ + fcs = msgb_put(msg, 3); + fcs_calc = gprs_llc_fcs(llch, fcs - llch); + fcs[0] = fcs_calc & 0xff; + fcs[1] = (fcs_calc >> 8) & 0xff; + fcs[2] = (fcs_calc >> 16) & 0xff; + + /* encrypt information field + FCS, if needed! */ + if (lle->llme->algo != GPRS_ALGO_GEA0) { + uint32_t iov_ui = 0; /* FIXME: randomly select for TLLI */ + uint16_t crypt_len = (fcs + 3) - (llch + 3); + uint8_t cipher_out[GSM0464_CIPH_MAX_BLOCK]; + uint32_t iv; + int rc, i; + uint64_t kc = *(uint64_t *)&lle->llme->kc; + + /* Compute the 'Input' Paraemeter */ + iv = gprs_cipher_gen_input_ui(iov_ui, sapi, nu, oc); + + /* Compute the keystream that we need to XOR with the data */ + rc = gprs_cipher_run(cipher_out, crypt_len, lle->llme->algo, + kc, iv, GPRS_CIPH_SGSN2MS); + if (rc < 0) { + LOGP(DLLC, LOGL_ERROR, "Error crypting UI frame: %d\n", rc); + return rc; + } + + /* XOR the cipher output with the information field + FCS */ + for (i = 0; i < crypt_len; i++) + *(llch + 3 + i) ^= cipher_out[i]; + + /* Mark frame as encrypted */ + ctrl[1] |= 0x02; + } + + /* Identifiers passed down: (BVCI, NSEI) */ + + /* Send BSSGP-DL-UNITDATA.req */ + return gprs_bssgp_tx_dl_ud(msg, mmctx); +} + +static void gprs_llc_hdr_dump(struct gprs_llc_hdr_parsed *gph) +{ + DEBUGP(DLLC, "LLC SAPI=%u %c %c FCS=0x%06x", + gph->sapi, gph->is_cmd ? 'C' : 'R', gph->ack_req ? 'A' : ' ', + gph->fcs); + + if (gph->cmd) + DEBUGPC(DLLC, "CMD=%s ", get_value_string(llc_cmd_strs, gph->cmd)); + + if (gph->data) + DEBUGPC(DLLC, "DATA "); + + DEBUGPC(DLLC, "\n"); +} +static int gprs_llc_hdr_rx(struct gprs_llc_hdr_parsed *gph, + struct gprs_llc_lle *lle) +{ + switch (gph->cmd) { + case GPRS_LLC_SABM: /* Section 6.4.1.1 */ + lle->v_sent = lle->v_ack = lle->v_recv = 0; + if (lle->state == GPRS_LLES_ASSIGNED_ADM) { + /* start re-establishment (8.7.1) */ + } + lle->state = GPRS_LLES_REMOTE_EST; + /* FIXME: Send UA */ + lle->state = GPRS_LLES_ABM; + /* FIXME: process data */ + break; + case GPRS_LLC_DISC: /* Section 6.4.1.2 */ + /* FIXME: Send UA */ + /* terminate ABM */ + lle->state = GPRS_LLES_ASSIGNED_ADM; + break; + case GPRS_LLC_UA: /* Section 6.4.1.3 */ + if (lle->state == GPRS_LLES_LOCAL_EST) + lle->state = GPRS_LLES_ABM; + break; + case GPRS_LLC_DM: /* Section 6.4.1.4: ABM cannot be performed */ + if (lle->state == GPRS_LLES_LOCAL_EST) + lle->state = GPRS_LLES_ASSIGNED_ADM; + break; + case GPRS_LLC_FRMR: /* Section 6.4.1.5 */ + break; + case GPRS_LLC_XID: /* Section 6.4.1.6 */ + /* FIXME: implement XID negotiation using SNDCP */ + { + struct msgb *resp; + uint8_t *xid; + resp = msgb_alloc_headroom(4096, 1024, "LLC_XID"); + xid = msgb_put(resp, gph->data_len); + memcpy(xid, gph->data, gph->data_len); + gprs_llc_tx_xid(lle, resp); + } + break; + case GPRS_LLC_UI: + if (gph->seq_tx < lle->vu_recv) { + LOGP(DLLC, LOGL_NOTICE, "TLLI=%08x dropping UI, vurecv %u <= %u\n", + lle->llme ? lle->llme->tlli : -1, + gph->seq_tx, lle->vu_recv); + return -EIO; + } + /* Increment the sequence number that we expect in the next frame */ + lle->vu_recv = (gph->seq_tx + 1) % 512; + /* Increment Overflow Counter */ + if ((gph->seq_tx + 1) / 512) + lle->oc_ui_recv += 512; + break; + } + + return 0; +} + +/* parse a GPRS LLC header, also check for invalid frames */ +static int gprs_llc_hdr_parse(struct gprs_llc_hdr_parsed *ghp, + uint8_t *llc_hdr, int len) +{ + uint8_t *ctrl = llc_hdr+1; + int is_sack = 0; + + if (len <= CRC24_LENGTH) + return -EIO; + + ghp->crc_length = len - CRC24_LENGTH; + + ghp->ack_req = 0; + + /* Section 5.5: FCS */ + ghp->fcs = *(llc_hdr + len - 3); + ghp->fcs |= *(llc_hdr + len - 2) << 8; + ghp->fcs |= *(llc_hdr + len - 1) << 16; + + /* Section 6.2.1: invalid PD field */ + if (llc_hdr[0] & 0x80) + return -EIO; + + /* This only works for the MS->SGSN direction */ + if (llc_hdr[0] & 0x40) + ghp->is_cmd = 0; + else + ghp->is_cmd = 1; + + ghp->sapi = llc_hdr[0] & 0xf; + + /* Section 6.2.3: check for reserved SAPI */ + switch (ghp->sapi) { + case 0: + case 4: + case 6: + case 0xa: + case 0xc: + case 0xd: + case 0xf: + return -EINVAL; + } + + if ((ctrl[0] & 0x80) == 0) { + /* I (Information transfer + Supervisory) format */ + uint8_t k; + + ghp->data = ctrl + 3; + + if (ctrl[0] & 0x40) + ghp->ack_req = 1; + + ghp->seq_tx = (ctrl[0] & 0x1f) << 4; + ghp->seq_tx |= (ctrl[1] >> 4); + + ghp->seq_rx = (ctrl[1] & 0x7) << 6; + ghp->seq_rx |= (ctrl[2] >> 2); + + switch (ctrl[2] & 0x03) { + case 0: + ghp->cmd = GPRS_LLC_RR; + break; + case 1: + ghp->cmd = GPRS_LLC_ACK; + break; + case 2: + ghp->cmd = GPRS_LLC_RNR; + break; + case 3: + ghp->cmd = GPRS_LLC_SACK; + k = ctrl[3] & 0x1f; + ghp->data += 1 + k; + break; + } + ghp->data_len = (llc_hdr + len - 3) - ghp->data; + } else if ((ctrl[0] & 0xc0) == 0x80) { + /* S (Supervisory) format */ + ghp->data = NULL; + ghp->data_len = 0; + + if (ctrl[0] & 0x20) + ghp->ack_req = 1; + ghp->seq_rx = (ctrl[0] & 0x7) << 6; + ghp->seq_rx |= (ctrl[1] >> 2); + + switch (ctrl[1] & 0x03) { + case 0: + ghp->cmd = GPRS_LLC_RR; + break; + case 1: + ghp->cmd = GPRS_LLC_ACK; + break; + case 2: + ghp->cmd = GPRS_LLC_RNR; + break; + case 3: + ghp->cmd = GPRS_LLC_SACK; + break; + } + } else if ((ctrl[0] & 0xe0) == 0xc0) { + /* UI (Unconfirmed Inforamtion) format */ + ghp->cmd = GPRS_LLC_UI; + ghp->data = ctrl + 2; + ghp->data_len = (llc_hdr + len - 3) - ghp->data; + + ghp->seq_tx = (ctrl[0] & 0x7) << 6; + ghp->seq_tx |= (ctrl[1] >> 2); + if (ctrl[1] & 0x02) { + ghp->is_encrypted = 1; + /* FIXME: encryption */ + } + if (ctrl[1] & 0x01) { + /* FCS over hdr + all inf fields */ + } else { + /* FCS over hdr + N202 octets (4) */ + if (ghp->crc_length > UI_HDR_LEN + N202) + ghp->crc_length = UI_HDR_LEN + N202; + } + } else { + /* U (Unnumbered) format: 1 1 1 P/F M4 M3 M2 M1 */ + ghp->data = NULL; + ghp->data_len = 0; + + switch (ctrl[0] & 0xf) { + case GPRS_LLC_U_NULL_CMD: + ghp->cmd = GPRS_LLC_NULL; + break; + case GPRS_LLC_U_DM_RESP: + ghp->cmd = GPRS_LLC_DM; + break; + case GPRS_LLC_U_DISC_CMD: + ghp->cmd = GPRS_LLC_DISC; + break; + case GPRS_LLC_U_UA_RESP: + ghp->cmd = GPRS_LLC_UA; + break; + case GPRS_LLC_U_SABM_CMD: + ghp->cmd = GPRS_LLC_SABM; + break; + case GPRS_LLC_U_FRMR_RESP: + ghp->cmd = GPRS_LLC_FRMR; + break; + case GPRS_LLC_U_XID: + ghp->cmd = GPRS_LLC_XID; + ghp->data = ctrl + 1; + ghp->data_len = (llc_hdr + len - 3) - ghp->data; + break; + default: + return -EIO; + } + } + + /* FIXME: parse sack frame */ + if (ghp->cmd == GPRS_LLC_SACK) { + LOGP(DLLC, LOGL_NOTICE, "Unsupported SACK frame\n"); + return -EIO; + } + + return 0; +} + +/* receive an incoming LLC PDU (BSSGP-UL-UNITDATA-IND, 7.2.4.2) */ +int gprs_llc_rcvmsg(struct msgb *msg, struct tlv_parsed *tv) +{ + struct bssgp_ud_hdr *udh = (struct bssgp_ud_hdr *) msgb_bssgph(msg); + struct gprs_llc_hdr *lh = msgb_llch(msg); + struct gprs_llc_hdr_parsed llhp; + struct gprs_llc_lle *lle; + int rc = 0; + + /* Identifiers from DOWN: NSEI, BVCI, TLLI */ + + memset(&llhp, 0, sizeof(llhp)); + rc = gprs_llc_hdr_parse(&llhp, (uint8_t *) lh, TLVP_LEN(tv, BSSGP_IE_LLC_PDU)); + gprs_llc_hdr_dump(&llhp); + if (rc < 0) { + LOGP(DLLC, LOGL_NOTICE, "Error during LLC header parsing\n"); + return rc; + } + + switch (gprs_tlli_type(msgb_tlli(msg))) { + case TLLI_LOCAL: + case TLLI_FOREIGN: + case TLLI_RANDOM: + case TLLI_AUXILIARY: + break; + default: + LOGP(DLLC, LOGL_ERROR, + "Discarding frame with strange TLLI type\n"); + break; + } + + /* find the LLC Entity for this TLLI+SAPI tuple */ + lle = lle_by_tlli_sapi(msgb_tlli(msg), llhp.sapi); + + /* 7.2.1.1 LLC belonging to unassigned TLLI+SAPI shall be discarded, + * except UID and XID frames with SAPI=1 */ + if (!lle) { + if (llhp.sapi == GPRS_SAPI_GMM && + (llhp.cmd == GPRS_LLC_XID || llhp.cmd == GPRS_LLC_UI)) { + struct gprs_llc_llme *llme; + /* FIXME: don't use the TLLI but the 0xFFFF unassigned? */ + llme = llme_alloc(msgb_tlli(msg)); + LOGP(DLLC, LOGL_DEBUG, "LLC RX: unknown TLLI 0x08x, " + "creating LLME on the fly\n", msgb_tlli(msg)); + lle = &llme->lle[llhp.sapi]; + } else { + LOGP(DLLC, LOGL_NOTICE, + "unknown TLLI/SAPI: Silently dropping\n"); + return 0; + } + } + + /* decrypt information field + FCS, if needed! */ + if (llhp.is_encrypted) { + uint32_t iov_ui = 0; /* FIXME: randomly select for TLLI */ + uint16_t crypt_len = llhp.data_len + 3; + uint8_t cipher_out[GSM0464_CIPH_MAX_BLOCK]; + uint32_t iv; + uint64_t kc = *(uint64_t *)&lle->llme->kc; + int rc, i; + + if (lle->llme->algo == GPRS_ALGO_GEA0) { + LOGP(DLLC, LOGL_NOTICE, "encrypted frame for LLC that " + "has no KC/Algo! Dropping.\n"); + return 0; + } + + iv = gprs_cipher_gen_input_ui(iov_ui, lle->sapi, llhp.seq_tx, + lle->oc_ui_recv); + rc = gprs_cipher_run(cipher_out, crypt_len, lle->llme->algo, + kc, iv, GPRS_CIPH_MS2SGSN); + if (rc < 0) { + LOGP(DLLC, LOGL_ERROR, "Error decrypting frame: %d\n", + rc); + return rc; + } + + /* XOR the cipher output with the information field + FCS */ + for (i = 0; i < crypt_len; i++) + *(llhp.data + i) ^= cipher_out[i]; + } else { + if (lle->llme->algo != GPRS_ALGO_GEA0) { + LOGP(DLLC, LOGL_NOTICE, "unencrypted frame for LLC " + "that is supposed to be encrypted. Dropping.\n"); + return 0; + } + } + + /* We have to do the FCS check _after_ decryption */ + llhp.fcs_calc = gprs_llc_fcs((uint8_t *)lh, llhp.crc_length); + if (llhp.fcs != llhp.fcs_calc) { + LOGP(DLLC, LOGL_INFO, "Dropping frame with invalid FCS\n"); + return -EIO; + } + + /* Update LLE's (BVCI, NSEI) tuple */ + lle->llme->bvci = msgb_bvci(msg); + lle->llme->nsei = msgb_nsei(msg); + + /* Receive and Process the actual LLC frame */ + rc = gprs_llc_hdr_rx(&llhp, lle); + if (rc < 0) + return rc; + + /* llhp.data is only set when we need to send LL_[UNIT]DATA_IND up */ + if (llhp.data) { + msgb_gmmh(msg) = llhp.data; + switch (llhp.sapi) { + case GPRS_SAPI_GMM: + /* send LL_UNITDATA_IND to GMM */ + rc = gsm0408_gprs_rcvmsg(msg, lle->llme); + break; + case GPRS_SAPI_SNDCP3: + case GPRS_SAPI_SNDCP5: + case GPRS_SAPI_SNDCP9: + case GPRS_SAPI_SNDCP11: + /* send LL_DATA_IND/LL_UNITDATA_IND to SNDCP */ + rc = sndcp_llunitdata_ind(msg, lle, llhp.data, llhp.data_len); + break; + case GPRS_SAPI_SMS: + /* FIXME */ + case GPRS_SAPI_TOM2: + case GPRS_SAPI_TOM8: + /* FIXME: send LL_DATA_IND/LL_UNITDATA_IND to TOM */ + default: + LOGP(DLLC, LOGL_NOTICE, "Unsupported SAPI %u\n", llhp.sapi); + rc = -EINVAL; + break; + } + } + + return rc; +} + +/* 04.64 Chapter 7.2.1.1 LLGMM-ASSIGN */ +int gprs_llgmm_assign(struct gprs_llc_llme *llme, + uint32_t old_tlli, uint32_t new_tlli, + enum gprs_ciph_algo alg, const uint8_t *kc) +{ + unsigned int i; + + /* Update the crypto parameters */ + llme->algo = alg; + if (alg != GPRS_ALGO_GEA0) + memcpy(llme->kc, kc, sizeof(llme->kc)); + + if (old_tlli == 0xffffffff && new_tlli != 0xffffffff) { + /* TLLI Assignment 8.3.1 */ + /* New TLLI shall be assigned and used when (re)transmitting LLC frames */ + /* If old TLLI != 0xffffffff was assigned to LLME, then TLLI + * old is unassigned. Only TLLI new shall be accepted when + * received from peer. */ + if (llme->old_tlli != 0xffffffff) { + llme->old_tlli = 0xffffffff; + llme->tlli = new_tlli; + } else { + /* If TLLI old == 0xffffffff was assigned to LLME, then this is + * TLLI assignmemt according to 8.3.1 */ + llme->old_tlli = 0xffffffff; + llme->tlli = new_tlli; + llme->state = GPRS_LLMS_ASSIGNED; + /* 8.5.3.1 For all LLE's */ + for (i = 0; i < ARRAY_SIZE(llme->lle); i++) { + struct gprs_llc_lle *l = &llme->lle[i]; + l->vu_send = l->vu_recv = 0; + l->retrans_ctr = 0; + l->state = GPRS_LLES_ASSIGNED_ADM; + /* FIXME Set parameters according to table 9 */ + } + } + } else if (old_tlli != 0xffffffff && new_tlli != 0xffffffff) { + /* TLLI Change 8.3.2 */ + /* Both TLLI Old and TLLI New are assigned; use New when + * (re)transmitting. Accept toth Old and New on Rx */ + llme->old_tlli = llme->tlli; + llme->tlli = new_tlli; + llme->state = GPRS_LLMS_ASSIGNED; + } else if (old_tlli != 0xffffffff && new_tlli == 0xffffffff) { + /* TLLI Unassignment 8.3.3) */ + llme->tlli = llme->old_tlli = 0; + llme->state = GPRS_LLMS_UNASSIGNED; + for (i = 0; i < ARRAY_SIZE(llme->lle); i++) { + struct gprs_llc_lle *l = &llme->lle[i]; + l->state = GPRS_LLES_UNASSIGNED; + } + llme_free(llme); + } else + return -EINVAL; + + return 0; +} + +int gprs_llc_init(const char *cipher_plugin_path) +{ + return gprs_cipher_load(cipher_plugin_path); +} diff --git a/src/gprs/gprs_llc_vty.c b/src/gprs/gprs_llc_vty.c new file mode 100644 index 000000000..d4f743b01 --- /dev/null +++ b/src/gprs/gprs_llc_vty.c @@ -0,0 +1,108 @@ +/* VTY interface for our GPRS LLC implementation */ + +/* (C) 2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct value_string gprs_llc_state_strs[] = { + { GPRS_LLES_UNASSIGNED, "TLLI Unassigned" }, + { GPRS_LLES_ASSIGNED_ADM, "TLLI Assigned" }, + { GPRS_LLES_LOCAL_EST, "Local Establishment" }, + { GPRS_LLES_REMOTE_EST, "Remote Establishment" }, + { GPRS_LLES_ABM, "Asynchronous Balanced Mode" }, + { GPRS_LLES_LOCAL_REL, "Local Release" }, + { GPRS_LLES_TIMER_REC, "Timer Recovery" }, +}; + +static void vty_dump_lle(struct vty *vty, struct gprs_llc_lle *lle) +{ + struct gprs_llc_params *par = &lle->params; + vty_out(vty, " SAPI %2u State %s VUsend=%u, VUrecv=%u", lle->sapi, + get_value_string(gprs_llc_state_strs, lle->state), + lle->vu_send, lle->vu_recv); + vty_out(vty, " Vsent=%u Vack=%u Vrecv=%u, RetransCtr=%u%s", + lle->v_sent, lle->v_ack, lle->v_recv, + lle->retrans_ctr, VTY_NEWLINE); + vty_out(vty, " T200=%u, N200=%u, N201-U=%u, N201-I=%u, mD=%u, " + "mU=%u, kD=%u, kU=%u%s", par->t200_201, par->n200, + par->n201_u, par->n201_i, par->mD, par->mU, par->kD, + par->kU, VTY_NEWLINE); +} + +static uint8_t valid_sapis[] = { 1, 2, 3, 5, 7, 8, 9, 11 }; + +static void vty_dump_llme(struct vty *vty, struct gprs_llc_llme *llme) +{ + unsigned int i; + + vty_out(vty, "TLLI %08x (Old TLLI %08x) BVCI=%u NSEI=%u: State %s%s", + llme->tlli, llme->old_tlli, llme->bvci, llme->nsei, + get_value_string(gprs_llc_state_strs, llme->state), VTY_NEWLINE); + + for (i = 0; i < ARRAY_SIZE(valid_sapis); i++) { + struct gprs_llc_lle *lle; + uint8_t sapi = valid_sapis[i]; + + if (sapi >= ARRAY_SIZE(llme->lle)) + continue; + + lle = &llme->lle[sapi]; + vty_dump_lle(vty, lle); + } +} + + +DEFUN(show_llc, show_llc_cmd, + "show llc", + SHOW_STR "Display information about the LLC protocol") +{ + struct gprs_llc_llme *llme; + + vty_out(vty, "State of LLC Entities%s", VTY_NEWLINE); + llist_for_each_entry(llme, &gprs_llc_llmes, list) { + vty_dump_llme(vty, llme); + } + return CMD_SUCCESS; +} + +int gprs_llc_vty_init(void) +{ + install_element_ve(&show_llc_cmd); + + return 0; +} diff --git a/src/gprs/gprs_sgsn.c b/src/gprs/gprs_sgsn.c new file mode 100644 index 000000000..443655418 --- /dev/null +++ b/src/gprs/gprs_sgsn.c @@ -0,0 +1,383 @@ +/* GPRS SGSN functionality */ + +/* (C) 2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern struct sgsn_instance *sgsn; + +LLIST_HEAD(sgsn_mm_ctxts); +LLIST_HEAD(sgsn_ggsn_ctxts); +LLIST_HEAD(sgsn_apn_ctxts); +LLIST_HEAD(sgsn_pdp_ctxts); + +static const struct rate_ctr_desc mmctx_ctr_description[] = { + { "sign.packets.in", "Signalling Messages ( In)" }, + { "sign.packets.out", "Signalling Messages (Out)" }, + { "udata.packets.in", "User Data Messages ( In)" }, + { "udata.packets.out", "User Data Messages (Out)" }, + { "udata.bytes.in", "User Data Bytes ( In)" }, + { "udata.bytes.out", "User Data Bytes (Out)" }, + { "pdp_ctx_act", "PDP Context Activations " }, + { "suspend", "SUSPEND Count " }, + { "paging.ps", "Paging Packet Switched " }, + { "paging.cs", "Paging Circuit Switched " }, + { "ra_update", "Routing Area Update " }, +}; + +static const struct rate_ctr_group_desc mmctx_ctrg_desc = { + .group_name_prefix = "sgsn.mmctx", + .group_description = "SGSN MM Context Statistics", + .num_ctr = ARRAY_SIZE(mmctx_ctr_description), + .ctr_desc = mmctx_ctr_description, +}; + +static const struct rate_ctr_desc pdpctx_ctr_description[] = { + { "udata.packets.in", "User Data Messages ( In)" }, + { "udata.packets.out", "User Data Messages (Out)" }, + { "udata.bytes.in", "User Data Bytes ( In)" }, + { "udata.bytes.out", "User Data Bytes (Out)" }, +}; + +static const struct rate_ctr_group_desc pdpctx_ctrg_desc = { + .group_name_prefix = "sgsn.pdpctx", + .group_description = "SGSN PDP Context Statistics", + .num_ctr = ARRAY_SIZE(pdpctx_ctr_description), + .ctr_desc = pdpctx_ctr_description, +}; + +static int ra_id_equals(const struct gprs_ra_id *id1, + const struct gprs_ra_id *id2) +{ + return (id1->mcc == id2->mcc && id1->mnc == id2->mnc && + id1->lac == id2->lac && id1->rac == id2->rac); +} + +/* See 03.02 Chapter 2.6 */ +static inline uint32_t tlli_foreign(uint32_t tlli) +{ + return ((tlli | 0x80000000) & ~0x40000000); +} + +/* look-up a SGSN MM context based on TLLI + RAI */ +struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli(uint32_t tlli, + const struct gprs_ra_id *raid) +{ + struct sgsn_mm_ctx *ctx; + int tlli_type; + + llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) { + if (tlli == ctx->tlli && + ra_id_equals(raid, &ctx->ra)) + return ctx; + } + + tlli_type = gprs_tlli_type(tlli); + switch (tlli_type) { + case TLLI_LOCAL: + llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) { + if ((ctx->p_tmsi | 0xC0000000) == tlli || + (ctx->p_tmsi_old && (ctx->p_tmsi_old | 0xC0000000) == tlli)) { + ctx->tlli = tlli; + return ctx; + } + } + break; + case TLLI_FOREIGN: + llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) { + if (tlli == tlli_foreign(ctx->tlli) && + ra_id_equals(raid, &ctx->ra)) + return ctx; + } + break; + default: + break; + } + + return NULL; +} + +struct sgsn_mm_ctx *sgsn_mm_ctx_by_ptmsi(uint32_t p_tmsi) +{ + struct sgsn_mm_ctx *ctx; + + llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) { + if (p_tmsi == ctx->p_tmsi || + (ctx->p_tmsi_old && ctx->p_tmsi_old == p_tmsi)) + return ctx; + } + return NULL; +} + +struct sgsn_mm_ctx *sgsn_mm_ctx_by_imsi(const char *imsi) +{ + struct sgsn_mm_ctx *ctx; + + llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) { + if (!strcmp(imsi, ctx->imsi)) + return ctx; + } + return NULL; + +} + +/* Allocate a new SGSN MM context */ +struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t tlli, + const struct gprs_ra_id *raid) +{ + struct sgsn_mm_ctx *ctx; + + ctx = talloc_zero(tall_bsc_ctx, struct sgsn_mm_ctx); + if (!ctx) + return NULL; + + memcpy(&ctx->ra, raid, sizeof(ctx->ra)); + ctx->tlli = tlli; + ctx->mm_state = GMM_DEREGISTERED; + ctx->ctrg = rate_ctr_group_alloc(ctx, &mmctx_ctrg_desc, tlli); + INIT_LLIST_HEAD(&ctx->pdp_list); + + llist_add(&ctx->list, &sgsn_mm_ctxts); + + return ctx; +} + +void sgsn_mm_ctx_free(struct sgsn_mm_ctx *mm) +{ + struct sgsn_pdp_ctx *pdp, *pdp2; + + /* Unlink from global list of MM contexts */ + llist_del(&mm->list); + + /* Free all PDP contexts */ + llist_for_each_entry_safe(pdp, pdp2, &mm->pdp_list, list) + sgsn_pdp_ctx_free(pdp); + + rate_ctr_group_free(mm->ctrg); + + talloc_free(mm); +} + +/* look up PDP context by MM context and NSAPI */ +struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_nsapi(const struct sgsn_mm_ctx *mm, + uint8_t nsapi) +{ + struct sgsn_pdp_ctx *pdp; + + llist_for_each_entry(pdp, &mm->pdp_list, list) { + if (pdp->nsapi == nsapi) + return pdp; + } + return NULL; +} + +/* look up PDP context by MM context and transaction ID */ +struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_tid(const struct sgsn_mm_ctx *mm, + uint8_t tid) +{ + struct sgsn_pdp_ctx *pdp; + + llist_for_each_entry(pdp, &mm->pdp_list, list) { + if (pdp->ti == tid) + return pdp; + } + return NULL; +} + +struct sgsn_pdp_ctx *sgsn_pdp_ctx_alloc(struct sgsn_mm_ctx *mm, + uint8_t nsapi) +{ + struct sgsn_pdp_ctx *pdp; + + pdp = sgsn_pdp_ctx_by_nsapi(mm, nsapi); + if (pdp) + return NULL; + + pdp = talloc_zero(tall_bsc_ctx, struct sgsn_pdp_ctx); + if (!pdp) + return NULL; + + pdp->mm = mm; + pdp->nsapi = nsapi; + pdp->ctrg = rate_ctr_group_alloc(pdp, &pdpctx_ctrg_desc, nsapi); + llist_add(&pdp->list, &mm->pdp_list); + llist_add(&pdp->g_list, &sgsn_pdp_ctxts); + + return pdp; +} + +void sgsn_pdp_ctx_free(struct sgsn_pdp_ctx *pdp) +{ + rate_ctr_group_free(pdp->ctrg); + llist_del(&pdp->list); + llist_del(&pdp->g_list); + talloc_free(pdp); +} + +/* GGSN contexts */ + +struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_alloc(uint32_t id) +{ + struct sgsn_ggsn_ctx *ggc; + + ggc = talloc_zero(tall_bsc_ctx, struct sgsn_ggsn_ctx); + if (!ggc) + return NULL; + + ggc->id = id; + ggc->gtp_version = 1; + ggc->remote_restart_ctr = -1; + /* if we are called from config file parse, this gsn doesn't exist yet */ + ggc->gsn = sgsn->gsn; + llist_add(&ggc->list, &sgsn_ggsn_ctxts); + + return ggc; +} + +struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_id(uint32_t id) +{ + struct sgsn_ggsn_ctx *ggc; + + llist_for_each_entry(ggc, &sgsn_ggsn_ctxts, list) { + if (id == ggc->id) + return ggc; + } + return NULL; +} + +struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_addr(struct in_addr *addr) +{ + struct sgsn_ggsn_ctx *ggc; + + llist_for_each_entry(ggc, &sgsn_ggsn_ctxts, list) { + if (!memcmp(addr, &ggc->remote_addr, sizeof(*addr))) + return ggc; + } + return NULL; +} + + +struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_find_alloc(uint32_t id) +{ + struct sgsn_ggsn_ctx *ggc; + + ggc = sgsn_ggsn_ctx_by_id(id); + if (!ggc) + ggc = sgsn_ggsn_ctx_alloc(id); + return ggc; +} + +/* APN contexts */ + +#if 0 +struct apn_ctx *apn_ctx_alloc(const char *ap_name) +{ + struct apn_ctx *actx; + + actx = talloc_zero(talloc_bsc_ctx, struct apn_ctx); + if (!actx) + return NULL; + actx->name = talloc_strdup(actx, ap_name); + + return actx; +} + +struct apn_ctx *apn_ctx_by_name(const char *name) +{ + struct apn_ctx *actx; + + llist_for_each_entry(actx, &sgsn_apn_ctxts, list) { + if (!strcmp(name, actx->name)) + return actx; + } + return NULL; +} + +struct apn_ctx *apn_ctx_find_alloc(const char *name) +{ + struct apn_ctx *actx; + + actx = apn_ctx_by_name(name); + if (!actx) + actx = apn_ctx_alloc(name); + + return actx; +} +#endif + +uint32_t sgsn_alloc_ptmsi(void) +{ + struct sgsn_mm_ctx *mm; + uint32_t ptmsi; + +restart: + ptmsi = rand(); + llist_for_each_entry(mm, &sgsn_mm_ctxts, list) { + if (mm->p_tmsi == ptmsi) + goto restart; + } + + return ptmsi; +} + +static void drop_one_pdp(struct sgsn_pdp_ctx *pdp) +{ + if (pdp->mm->mm_state == GMM_REGISTERED_NORMAL) + gsm48_tx_gsm_deact_pdp_req(pdp, GSM_CAUSE_NET_FAIL); + else { + /* FIXME: GPRS paging in case MS is SUSPENDED */ + LOGP(DGPRS, LOGL_NOTICE, "Hard-dropping PDP ctx due to GGSN " + "recovery\n"); + sgsn_pdp_ctx_free(pdp); + } +} + +/* High-level function to be called in case a GGSN has disappeared or + * ottherwise lost state (recovery procedure) */ +int drop_all_pdp_for_ggsn(struct sgsn_ggsn_ctx *ggsn) +{ + struct sgsn_mm_ctx *mm; + int num = 0; + + llist_for_each_entry(mm, &sgsn_mm_ctxts, list) { + struct sgsn_pdp_ctx *pdp; + llist_for_each_entry(pdp, &mm->pdp_list, list) { + if (pdp->ggsn == ggsn) { + drop_one_pdp(pdp); + num++; + } + } + } + + return num; +} diff --git a/src/gprs/gprs_sndcp.c b/src/gprs/gprs_sndcp.c new file mode 100644 index 000000000..4f421e451 --- /dev/null +++ b/src/gprs/gprs_sndcp.c @@ -0,0 +1,616 @@ +/* GPRS SNDCP protocol implementation as per 3GPP TS 04.65 */ + +/* (C) 2010 by Harald Welte + * (C) 2010 by On-Waves + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gprs_sndcp.h" + +/* Chapter 7.2: SN-PDU Formats */ +struct sndcp_common_hdr { + /* octet 1 */ + uint8_t nsapi:4; + uint8_t more:1; + uint8_t type:1; + uint8_t first:1; + uint8_t spare:1; +} __attribute__((packed)); + +/* PCOMP / DCOMP only exist in first fragment */ +struct sndcp_comp_hdr { + /* octet 2 */ + uint8_t pcomp:4; + uint8_t dcomp:4; +} __attribute__((packed)); + +struct sndcp_udata_hdr { + /* octet 3 */ + uint8_t npdu_high:4; + uint8_t seg_nr:4; + /* octet 4 */ + uint8_t npdu_low; +} __attribute__((packed)); + + +static void *tall_sndcp_ctx; + +/* A fragment queue entry, containing one framgent of a N-PDU */ +struct defrag_queue_entry { + struct llist_head list; + /* segment number of this fragment */ + uint32_t seg_nr; + /* length of the data area of this fragment */ + uint32_t data_len; + /* pointer to the data of this fragment */ + uint8_t *data; +}; + +LLIST_HEAD(gprs_sndcp_entities); + +/* Enqueue a fragment into the defragment queue */ +static int defrag_enqueue(struct gprs_sndcp_entity *sne, uint8_t seg_nr, + uint8_t *data, uint32_t data_len) +{ + struct defrag_queue_entry *dqe; + + dqe = talloc_zero(tall_sndcp_ctx, struct defrag_queue_entry); + if (!dqe) + return -ENOMEM; + dqe->data = talloc_zero_size(dqe, data_len); + if (!dqe->data) { + talloc_free(dqe); + return -ENOMEM; + } + dqe->seg_nr = seg_nr; + dqe->data_len = data_len; + + llist_add(&dqe->list, &sne->defrag.frag_list); + + if (seg_nr > sne->defrag.highest_seg) + sne->defrag.highest_seg = seg_nr; + + sne->defrag.seg_have |= (1 << seg_nr); + sne->defrag.tot_len += data_len; + + memcpy(dqe->data, data, data_len); + + return 0; +} + +/* return if we have all segments of this N-PDU */ +static int defrag_have_all_segments(struct gprs_sndcp_entity *sne) +{ + uint32_t seg_needed = 0; + unsigned int i; + + /* create a bitmask of needed segments */ + for (i = 0; i <= sne->defrag.highest_seg; i++) + seg_needed |= (1 << i); + + if (seg_needed == sne->defrag.seg_have) + return 1; + + return 0; +} + +static struct defrag_queue_entry *defrag_get_seg(struct gprs_sndcp_entity *sne, + uint32_t seg_nr) +{ + struct defrag_queue_entry *dqe; + + llist_for_each_entry(dqe, &sne->defrag.frag_list, list) { + if (dqe->seg_nr == seg_nr) { + llist_del(&dqe->list); + return dqe; + } + } + return NULL; +} + +/* Perform actual defragmentation and create an output packet */ +static int defrag_segments(struct gprs_sndcp_entity *sne) +{ + struct msgb *msg; + unsigned int seg_nr; + uint8_t *npdu; + + LOGP(DSNDCP, LOGL_DEBUG, "TLLI=0x%08x NSAPI=%u: Defragment output PDU %u " + "num_seg=%u tot_len=%u\n", sne->lle->llme->tlli, sne->nsapi, + sne->defrag.npdu, sne->defrag.highest_seg, sne->defrag.tot_len); + msg = msgb_alloc_headroom(sne->defrag.tot_len+256, 128, "SNDCP Defrag"); + if (!msg) + return -ENOMEM; + + /* FIXME: message headers + identifiers */ + + npdu = msg->data; + + for (seg_nr = 0; seg_nr <= sne->defrag.highest_seg; seg_nr++) { + struct defrag_queue_entry *dqe; + uint8_t *data; + + dqe = defrag_get_seg(sne, seg_nr); + if (!dqe) { + LOGP(DSNDCP, LOGL_ERROR, "Segment %u missing\n", seg_nr); + talloc_free(msg); + return -EIO; + } + /* actually append the segment to the N-PDU */ + data = msgb_put(msg, dqe->data_len); + memcpy(data, dqe->data, dqe->data_len); + + /* release memory for the fragment queue entry */ + talloc_free(dqe); + } + + /* FIXME: cancel timer */ + + /* actually send the N-PDU to the SGSN core code, which then + * hands it off to the correct GTP tunnel + GGSN via gtp_data_req() */ + return sgsn_rx_sndcp_ud_ind(&sne->ra_id, sne->lle->llme->tlli, + sne->nsapi, msg, sne->defrag.tot_len, npdu); +} + +static int defrag_input(struct gprs_sndcp_entity *sne, struct msgb *msg, uint8_t *hdr, + unsigned int len) +{ + struct sndcp_common_hdr *sch; + struct sndcp_comp_hdr *scomph = NULL; + struct sndcp_udata_hdr *suh; + uint16_t npdu_num; + uint8_t *data; + int rc; + + sch = (struct sndcp_common_hdr *) hdr; + if (sch->first) { + scomph = (struct sndcp_comp_hdr *) (hdr + 1); + suh = (struct sndcp_udata_hdr *) (hdr + 1 + sizeof(struct sndcp_common_hdr)); + } else + suh = (struct sndcp_udata_hdr *) (hdr + sizeof(struct sndcp_common_hdr)); + + data = (uint8_t *)suh + sizeof(struct sndcp_udata_hdr); + + npdu_num = (suh->npdu_high << 8) | suh->npdu_low; + + LOGP(DSNDCP, LOGL_DEBUG, "TLLI=0x%08x NSAPI=%u: Input PDU %u Segment %u " + "Length %u %s %s\n", sne->lle->llme->tlli, sne->nsapi, npdu_num, + suh->seg_nr, len, sch->first ? "F " : "", sch->more ? "M" : ""); + + if (sch->first) { + /* first segment of a new packet. Discard all leftover fragments of + * previous packet */ + if (!llist_empty(&sne->defrag.frag_list)) { + struct defrag_queue_entry *dqe, *dqe2; + LOGP(DSNDCP, LOGL_INFO, "TLLI=0x%08x NSAPI=%u: Dropping " + "SN-PDU %u due to insufficient segments (%04x)\n", + sne->lle->llme->tlli, sne->nsapi, sne->defrag.npdu, + sne->defrag.seg_have); + llist_for_each_entry_safe(dqe, dqe2, &sne->defrag.frag_list, list) { + llist_del(&dqe->list); + talloc_free(dqe); + } + } + /* store the currently de-fragmented PDU number */ + sne->defrag.npdu = npdu_num; + + /* Re-set fragmentation state */ + sne->defrag.no_more = sne->defrag.highest_seg = sne->defrag.seg_have = 0; + sne->defrag.tot_len = 0; + /* FIXME: (re)start timer */ + } + + if (sne->defrag.npdu != npdu_num) { + LOGP(DSNDCP, LOGL_INFO, "Segment for different SN-PDU " + "(%u != %u)\n", npdu_num, sne->defrag.npdu); + /* FIXME */ + } + + /* FIXME: check if seg_nr already exists */ + /* make sure to subtract length of SNDCP header from 'len' */ + rc = defrag_enqueue(sne, suh->seg_nr, data, len - (data - hdr)); + if (rc < 0) + return rc; + + if (!sch->more) { + /* this is suppsed to be the last segment of the N-PDU, but it + * might well be not the last to arrive */ + sne->defrag.no_more = 1; + } + + if (sne->defrag.no_more) { + /* we have already received the last segment before, let's check + * if all the previous segments exist */ + if (defrag_have_all_segments(sne)) + return defrag_segments(sne); + } + + return 0; +} + +static struct gprs_sndcp_entity *gprs_sndcp_entity_by_lle(const struct gprs_llc_lle *lle, + uint8_t nsapi) +{ + struct gprs_sndcp_entity *sne; + + llist_for_each_entry(sne, &gprs_sndcp_entities, list) { + if (sne->lle == lle && sne->nsapi == nsapi) + return sne; + } + return NULL; +} + +static struct gprs_sndcp_entity *gprs_sndcp_entity_alloc(struct gprs_llc_lle *lle, + uint8_t nsapi) +{ + struct gprs_sndcp_entity *sne; + + sne = talloc_zero(tall_sndcp_ctx, struct gprs_sndcp_entity); + if (!sne) + return NULL; + + sne->lle = lle; + sne->nsapi = nsapi; + sne->defrag.timer.data = sne; + //sne->fqueue.timer.cb = FIXME; + sne->rx_state = SNDCP_RX_S_FIRST; + INIT_LLIST_HEAD(&sne->defrag.frag_list); + + llist_add(&sne->list, &gprs_sndcp_entities); + + return sne; +} + +/* Entry point for the SNSM-ACTIVATE.indication */ +int sndcp_sm_activate_ind(struct gprs_llc_lle *lle, uint8_t nsapi) +{ + LOGP(DSNDCP, LOGL_INFO, "SNSM-ACTIVATE.ind (lle=%p TLLI=%08x, " + "SAPI=%u, NSAPI=%u)\n", lle, lle->llme->tlli, lle->sapi, nsapi); + + if (gprs_sndcp_entity_by_lle(lle, nsapi)) { + LOGP(DSNDCP, LOGL_ERROR, "Trying to ACTIVATE " + "already-existing entity (TLLI=%08x, NSAPI=%u)\n", + lle->llme->tlli, nsapi); + return -EEXIST; + } + + if (!gprs_sndcp_entity_alloc(lle, nsapi)) { + LOGP(DSNDCP, LOGL_ERROR, "Out of memory during ACTIVATE\n"); + return -ENOMEM; + } + + return 0; +} + +/* Entry point for the SNSM-DEACTIVATE.indication */ +int sndcp_sm_deactivate_ind(struct gprs_llc_lle *lle, uint8_t nsapi) +{ + struct gprs_sndcp_entity *sne; + + LOGP(DSNDCP, LOGL_INFO, "SNSM-DEACTIVATE.ind (lle=%p, TLLI=%08x, " + "SAPI=%u, NSAPI=%u)\n", lle, lle->llme->tlli, lle->sapi, nsapi); + + sne = gprs_sndcp_entity_by_lle(lle, nsapi); + if (!sne) { + LOGP(DSNDCP, LOGL_ERROR, "SNSM-DEACTIVATE.ind for non-" + "existing TLLI=%08x SAPI=%u NSAPI=%u\n", lle->llme->tlli, + lle->sapi, nsapi); + return -ENOENT; + } + llist_del(&sne->list); + /* frag queue entries are hierarchically allocated, so no need to + * free them explicitly here */ + talloc_free(sne); + + return 0; +} + +/* Fragmenter state */ +struct sndcp_frag_state { + uint8_t frag_nr; + struct msgb *msg; /* original message */ + uint8_t *next_byte; /* first byte of next fragment */ + + struct gprs_sndcp_entity *sne; + void *mmcontext; +}; + +/* returns '1' if there are more fragments to send, '0' if none */ +static int sndcp_send_ud_frag(struct sndcp_frag_state *fs) +{ + struct gprs_sndcp_entity *sne = fs->sne; + struct gprs_llc_lle *lle = sne->lle; + struct sndcp_common_hdr *sch; + struct sndcp_comp_hdr *scomph; + struct sndcp_udata_hdr *suh; + struct msgb *fmsg; + unsigned int max_payload_len; + unsigned int len; + uint8_t *data; + int rc, more; + + fmsg = msgb_alloc_headroom(fs->sne->lle->params.n201_u+256, 128, + "SNDCP Frag"); + if (!fmsg) + return -ENOMEM; + + /* make sure lower layers route the fragment like the original */ + msgb_tlli(fmsg) = msgb_tlli(fs->msg); + msgb_bvci(fmsg) = msgb_bvci(fs->msg); + msgb_nsei(fmsg) = msgb_nsei(fs->msg); + + /* prepend common SNDCP header */ + sch = (struct sndcp_common_hdr *) msgb_put(fmsg, sizeof(*sch)); + sch->nsapi = sne->nsapi; + /* Set FIRST bit if we are the first fragment in a series */ + if (fs->frag_nr == 0) + sch->first = 1; + sch->type = 1; + + /* append the compression header for first fragment */ + if (sch->first) { + scomph = (struct sndcp_comp_hdr *) + msgb_put(fmsg, sizeof(*scomph)); + scomph->pcomp = 0; + scomph->dcomp = 0; + } + + /* append the user-data header */ + suh = (struct sndcp_udata_hdr *) msgb_put(fmsg, sizeof(*suh)); + suh->npdu_low = sne->tx_npdu_nr & 0xff; + suh->npdu_high = (sne->tx_npdu_nr >> 8) & 0xf; + suh->seg_nr = fs->frag_nr % 0xf; + + /* calculate remaining length to be sent */ + len = (fs->msg->data + fs->msg->len) - fs->next_byte; + /* how much payload can we actually send via LLC? */ + max_payload_len = lle->params.n201_u - (sizeof(*sch) + sizeof(*suh)); + if (sch->first) + max_payload_len -= sizeof(*scomph); + /* check if we're exceeding the max */ + if (len > max_payload_len) + len = max_payload_len; + + /* copy the actual fragment data into our fmsg */ + data = msgb_put(fmsg, len); + memcpy(data, fs->next_byte, len); + + /* Increment fragment number and data pointer to next fragment */ + fs->frag_nr++; + fs->next_byte += len; + + /* determine if we have more fragemnts to send */ + if ((fs->msg->data + fs->msg->len) <= fs->next_byte) + more = 0; + else + more = 1; + + /* set the MORE bit of the SNDCP header accordingly */ + sch->more = more; + + rc = gprs_llc_tx_ui(fmsg, lle->sapi, 0, fs->mmcontext); + if (rc < 0) { + /* abort in case of error, do not advance frag_nr / next_byte */ + msgb_free(fmsg); + return rc; + } + + if (!more) { + /* we've sent all fragments */ + msgb_free(fs->msg); + memset(fs, 0, sizeof(*fs)); + /* increment NPDU number for next frame */ + sne->tx_npdu_nr = (sne->tx_npdu_nr + 1) % 0xfff; + return 0; + } + + /* default: more fragments to send */ + return 1; +} + +/* Request transmission of a SN-PDU over specified LLC Entity + SAPI */ +int sndcp_unitdata_req(struct msgb *msg, struct gprs_llc_lle *lle, uint8_t nsapi, + void *mmcontext) +{ + struct gprs_sndcp_entity *sne; + struct sndcp_common_hdr *sch; + struct sndcp_comp_hdr *scomph; + struct sndcp_udata_hdr *suh; + struct sndcp_frag_state fs; + + /* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */ + + sne = gprs_sndcp_entity_by_lle(lle, nsapi); + if (!sne) { + LOGP(DSNDCP, LOGL_ERROR, "Cannot find SNDCP Entity\n"); + return -EIO; + } + + /* Check if we need to fragment this N-PDU into multiple SN-PDUs */ + if (msg->len > lle->params.n201_u - + (sizeof(*sch) + sizeof(*suh) + sizeof(*scomph))) { + /* initialize the fragmenter state */ + fs.msg = msg; + fs.frag_nr = 0; + fs.next_byte = msg->data; + fs.sne = sne; + fs.mmcontext = mmcontext; + + /* call function to generate and send fragments until all + * of the N-PDU has been sent */ + while (1) { + int rc = sndcp_send_ud_frag(&fs); + if (rc == 0) + return 0; + if (rc < 0) + return rc; + } + /* not reached */ + return 0; + } + + /* this is the non-fragmenting case where we only build 1 SN-PDU */ + + /* prepend the user-data header */ + suh = (struct sndcp_udata_hdr *) msgb_push(msg, sizeof(*suh)); + suh->npdu_low = sne->tx_npdu_nr & 0xff; + suh->npdu_high = (sne->tx_npdu_nr >> 8) & 0xf; + suh->seg_nr = 0; + sne->tx_npdu_nr = (sne->tx_npdu_nr + 1) % 0xfff; + + scomph = (struct sndcp_comp_hdr *) msgb_push(msg, sizeof(*scomph)); + scomph->pcomp = 0; + scomph->dcomp = 0; + + /* prepend common SNDCP header */ + sch = (struct sndcp_common_hdr *) msgb_push(msg, sizeof(*sch)); + sch->first = 1; + sch->type = 1; + sch->nsapi = nsapi; + + return gprs_llc_tx_ui(msg, lle->sapi, 0, mmcontext); +} + +/* Section 5.1.2.17 LL-UNITDATA.ind */ +int sndcp_llunitdata_ind(struct msgb *msg, struct gprs_llc_lle *lle, + uint8_t *hdr, uint16_t len) +{ + struct gprs_sndcp_entity *sne; + struct sndcp_common_hdr *sch = (struct sndcp_common_hdr *)hdr; + struct sndcp_comp_hdr *scomph = NULL; + struct sndcp_udata_hdr *suh; + uint8_t *npdu; + uint16_t npdu_num; + int npdu_len; + + sch = (struct sndcp_common_hdr *) hdr; + if (sch->first) { + scomph = (struct sndcp_comp_hdr *) (hdr + 1); + suh = (struct sndcp_udata_hdr *) (hdr + 1 + sizeof(struct sndcp_common_hdr)); + } else + suh = (struct sndcp_udata_hdr *) (hdr + sizeof(struct sndcp_common_hdr)); + + if (sch->type == 0) { + LOGP(DSNDCP, LOGL_ERROR, "SN-DATA PDU at unitdata_ind() function\n"); + return -EINVAL; + } + + if (len < sizeof(*sch) + sizeof(*suh)) { + LOGP(DSNDCP, LOGL_ERROR, "SN-UNITDATA PDU too short (%u)\n", len); + return -EIO; + } + + sne = gprs_sndcp_entity_by_lle(lle, sch->nsapi); + if (!sne) { + LOGP(DSNDCP, LOGL_ERROR, "Message for non-existing SNDCP Entity " + "(lle=%p, TLLI=%08x, SAPI=%u, NSAPI=%u)\n", lle, + lle->llme->tlli, lle->sapi, sch->nsapi); + return -EIO; + } + /* FIXME: move this RA_ID up to the LLME or even higher */ + bssgp_parse_cell_id(&sne->ra_id, msgb_bcid(msg)); + + /* any non-first segment is by definition something to defragment + * as is any segment that tells us there are more segments */ + if (!sch->first || sch->more) + return defrag_input(sne, msg, hdr, len); + + if (scomph && (scomph->pcomp || scomph->dcomp)) { + LOGP(DSNDCP, LOGL_ERROR, "We don't support compression yet\n"); + return -EIO; + } + + npdu_num = (suh->npdu_high << 8) | suh->npdu_low; + npdu = (uint8_t *)suh + sizeof(*suh); + npdu_len = (msg->data + msg->len) - npdu; + if (npdu_len <= 0) { + LOGP(DSNDCP, LOGL_ERROR, "Short SNDCP N-PDU: %d\n", npdu_len); + return -EIO; + } + /* actually send the N-PDU to the SGSN core code, which then + * hands it off to the correct GTP tunnel + GGSN via gtp_data_req() */ + return sgsn_rx_sndcp_ud_ind(&sne->ra_id, lle->llme->tlli, sne->nsapi, msg, npdu_len, npdu); +} + +/* Section 5.1.2.1 LL-RESET.ind */ +static int sndcp_ll_reset_ind(struct gprs_sndcp_entity *se) +{ + /* treat all outstanding SNDCP-LLC request type primitives as not sent */ + /* reset all SNDCP XID parameters to default values */ +} + +static int sndcp_ll_status_ind() +{ + /* inform the SM sub-layer by means of SNSM-STATUS.req */ +} + +#if 0 +static struct sndcp_state_list {{ + uint32_t states; + unsigned int type; + int (*rout)(struct gprs_sndcp_entity *se, struct msgb *msg); +} sndcp_state_list[] = { + { ALL_STATES, + LL_RESET_IND, sndcp_ll_reset_ind }, + { ALL_STATES, + LL_ESTABLISH_IND, sndcp_ll_est_ind }, + { SBIT(SNDCP_S_EST_RQD), + LL_ESTABLISH_RESP, sndcp_ll_est_ind }, + { SBIT(SNDCP_S_EST_RQD), + LL_ESTABLISH_CONF, sndcp_ll_est_conf }, + { SBIT(SNDCP_S_ +}; + +static int sndcp_rx_llc_prim() +{ + case LL_ESTABLISH_REQ: + case LL_RELEASE_REQ: + case LL_XID_REQ: + case LL_DATA_REQ: + LL_UNITDATA_REQ, /* TLLI, SN-PDU, Ref, QoS, Radio Prio, Ciph */ + + switch (prim) { + case LL_RESET_IND: + case LL_ESTABLISH_IND: + case LL_ESTABLISH_RESP: + case LL_ESTABLISH_CONF: + case LL_RELEASE_IND: + case LL_RELEASE_CONF: + case LL_XID_IND: + case LL_XID_RESP: + case LL_XID_CONF: + case LL_DATA_IND: + case LL_DATA_CONF: + case LL_UNITDATA_IND: + case LL_STATUS_IND: +} +#endif diff --git a/src/gprs/gprs_sndcp.h b/src/gprs/gprs_sndcp.h new file mode 100644 index 000000000..e9a50be1c --- /dev/null +++ b/src/gprs/gprs_sndcp.h @@ -0,0 +1,53 @@ +#ifndef _INT_SNDCP_H +#define _INT_SNDCP_H + +#include +#include + +/* A fragment queue header, maintaining list of fragments for one N-PDU */ +struct defrag_state { + /* PDU number for which the defragmentation state applies */ + uint16_t npdu; + /* highest segment number we have received so far */ + uint8_t highest_seg; + /* bitmask of the segments we already have */ + uint32_t seg_have; + /* do we still expect more segments? */ + unsigned int no_more; + /* total length of all segments together */ + unsigned int tot_len; + + /* linked list of defrag_queue_entry: one for each fragment */ + struct llist_head frag_list; + + struct timer_list timer; +}; + +/* See 6.7.1.2 Reassembly */ +enum sndcp_rx_state { + SNDCP_RX_S_FIRST, + SNDCP_RX_S_SUBSEQ, + SNDCP_RX_S_DISCARD, +}; + +struct gprs_sndcp_entity { + struct llist_head list; + + /* FIXME: move this RA_ID up to the LLME or even higher */ + struct gprs_ra_id ra_id; + /* reference to the LLC Entity below this SNDCP entity */ + struct gprs_llc_lle *lle; + /* The NSAPI we shall use on top of LLC */ + uint8_t nsapi; + + /* NPDU number for the GTP->SNDCP side */ + uint16_t tx_npdu_nr; + /* SNDCP eeceiver state */ + enum sndcp_rx_state rx_state; + /* The defragmentation queue */ + struct defrag_state defrag; +}; + +extern struct llist_head gprs_sndcp_entities; + +#endif /* INT_SNDCP_H */ diff --git a/src/gprs/gprs_sndcp_vty.c b/src/gprs/gprs_sndcp_vty.c new file mode 100644 index 000000000..5a755d5f7 --- /dev/null +++ b/src/gprs/gprs_sndcp_vty.c @@ -0,0 +1,74 @@ +/* VTY interface for our GPRS SNDCP implementation */ + +/* (C) 2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gprs_sndcp.h" + +#include +#include + +static void vty_dump_sne(struct vty *vty, struct gprs_sndcp_entity *sne) +{ + unsigned int i; + + vty_out(vty, " TLLI %08x SAPI=%u NSAPI=%u:%s", + sne->lle->llme->tlli, sne->lle->sapi, sne->nsapi, VTY_NEWLINE); + vty_out(vty, " Defrag: npdu=%u highest_seg=%u seg_have=0x%08x tot_len=%u%s", + sne->defrag.npdu, sne->defrag.highest_seg, sne->defrag.seg_have, + sne->defrag.tot_len, VTY_NEWLINE); +} + + +DEFUN(show_sndcp, show_sndcp_cmd, + "show sndcp", + SHOW_STR "Display information about the SNDCP protocol") +{ + struct gprs_sndcp_entity *sne; + + vty_out(vty, "State of SNDCP Entities%s", VTY_NEWLINE); + llist_for_each_entry(sne, &gprs_sndcp_entities, list) + vty_dump_sne(vty, sne); + + return CMD_SUCCESS; +} + +int gprs_sndcp_vty_init(void) +{ + install_element_ve(&show_sndcp_cmd); + + return 0; +} diff --git a/src/gprs/sgsn_libgtp.c b/src/gprs/sgsn_libgtp.c new file mode 100644 index 000000000..7b10a45a3 --- /dev/null +++ b/src/gprs/sgsn_libgtp.c @@ -0,0 +1,610 @@ +/* GPRS SGSN integration with libgtp of OpenGGSN */ +/* libgtp implements the GPRS Tunelling Protocol GTP per TS 09.60 / 29.060 */ + +/* (C) 2010 by Harald Welte + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +const struct value_string gtp_cause_strs[] = { + { GTPCAUSE_REQ_IMSI, "Request IMSI" }, + { GTPCAUSE_REQ_IMEI, "Request IMEI" }, + { GTPCAUSE_REQ_IMSI_IMEI, "Request IMSI and IMEI" }, + { GTPCAUSE_NO_ID_NEEDED, "No identity needed" }, + { GTPCAUSE_MS_REFUSES_X, "MS refuses" }, + { GTPCAUSE_MS_NOT_RESP_X, "MS is not GPRS responding" }, + { GTPCAUSE_ACC_REQ, "Request accepted" }, + { GTPCAUSE_NON_EXIST, "Non-existent" }, + { GTPCAUSE_INVALID_MESSAGE, "Invalid message format" }, + { GTPCAUSE_IMSI_NOT_KNOWN, "IMSI not known" }, + { GTPCAUSE_MS_DETACHED, "MS is GPRS detached" }, + { GTPCAUSE_MS_NOT_RESP, "MS is not GPRS responding" }, + { GTPCAUSE_MS_REFUSES, "MS refuses" }, + { GTPCAUSE_NO_RESOURCES, "No resources available" }, + { GTPCAUSE_NOT_SUPPORTED, "Service not supported" }, + { GTPCAUSE_MAN_IE_INCORRECT, "Mandatory IE incorrect" }, + { GTPCAUSE_MAN_IE_MISSING, "Mandatory IE missing" }, + { GTPCAUSE_OPT_IE_INCORRECT, "Optional IE incorrect" }, + { GTPCAUSE_SYS_FAIL, "System failure" }, + { GTPCAUSE_ROAMING_REST, "Roaming restrictions" }, + { GTPCAUSE_PTIMSI_MISMATCH, "P-TMSI Signature mismatch" }, + { GTPCAUSE_CONN_SUSP, "GPRS connection suspended" }, + { GTPCAUSE_AUTH_FAIL, "Authentication failure" }, + { GTPCAUSE_USER_AUTH_FAIL, "User authentication failed" }, + { GTPCAUSE_CONTEXT_NOT_FOUND, "Context not found" }, + { GTPCAUSE_ADDR_OCCUPIED, "All dynamic PDP addresses occupied" }, + { GTPCAUSE_NO_MEMORY, "No memory is available" }, + { GTPCAUSE_RELOC_FAIL, "Relocation failure" }, + { GTPCAUSE_UNKNOWN_MAN_EXTHEADER, "Unknown mandatory ext. header" }, + { GTPCAUSE_SEM_ERR_TFT, "Semantic error in TFT operation" }, + { GTPCAUSE_SYN_ERR_TFT, "Syntactic error in TFT operation" }, + { GTPCAUSE_SEM_ERR_FILTER, "Semantic errors in packet filter" }, + { GTPCAUSE_SYN_ERR_FILTER, "Syntactic errors in packet filter" }, + { GTPCAUSE_MISSING_APN, "Missing or unknown APN" }, + { GTPCAUSE_UNKNOWN_PDP, "Unknown PDP address or PDP type" }, + { 0, NULL } +}; + +/* Generate the GTP IMSI IE according to 09.60 Section 7.9.2 */ +static uint64_t imsi_str2gtp(char *str) +{ + uint64_t imsi64 = 0; + unsigned int n; + unsigned int imsi_len = strlen(str); + + if (imsi_len > 16) { + LOGP(DGPRS, LOGL_NOTICE, "IMSI length > 16 not supported!\n"); + return 0; + } + + for (n = 0; n < 16; n++) { + uint64_t val; + if (n < imsi_len) + val = (str[n]-'0') & 0xf; + else + val = 0xf; + imsi64 |= (val << (n*4)); + } + return imsi64; +} + +/* generate a PDP context based on the IE's from the 04.08 message, + * and send the GTP create pdp context request to the GGSN */ +struct sgsn_pdp_ctx *sgsn_create_pdp_ctx(struct sgsn_ggsn_ctx *ggsn, + struct sgsn_mm_ctx *mmctx, + uint16_t nsapi, + struct tlv_parsed *tp) +{ + struct sgsn_pdp_ctx *pctx; + struct pdp_t *pdp; + uint64_t imsi_ui64; + int rc; + + LOGP(DGPRS, LOGL_ERROR, "Create PDP Context\n"); + pctx = sgsn_pdp_ctx_alloc(mmctx, nsapi); + if (!pctx) { + LOGP(DGPRS, LOGL_ERROR, "Couldn't allocate PDP Ctx\n"); + return NULL; + } + + imsi_ui64 = imsi_str2gtp(mmctx->imsi); + + rc = pdp_newpdp(&pdp, imsi_ui64, nsapi, NULL); + if (rc) { + LOGP(DGPRS, LOGL_ERROR, "Out of libgtp PDP Contexts\n"); + return NULL; + } + pdp->priv = pctx; + pctx->lib = pdp; + pctx->ggsn = ggsn; + + //pdp->peer = /* sockaddr_in of GGSN (receive) */ + //pdp->ipif = /* not used by library */ + pdp->version = ggsn->gtp_version; + pdp->hisaddr0 = ggsn->remote_addr; + pdp->hisaddr1 = ggsn->remote_addr; + //pdp->cch_pdp = 512; /* Charging Flat Rate */ + + /* MS provided APN, subscription not verified */ + pdp->selmode = 0x01; + + /* IMSI, TEID/TEIC, FLLU/FLLC, TID, NSAPI set in pdp_newpdp */ + + /* FIXME: MSISDN in BCD format from mmctx */ + //pdp->msisdn.l/.v + + /* End User Address from GMM requested PDP address */ + pdp->eua.l = TLVP_LEN(tp, OSMO_IE_GSM_REQ_PDP_ADDR); + if (pdp->eua.l > sizeof(pdp->eua.v)) + pdp->eua.l = sizeof(pdp->eua.v); + memcpy(pdp->eua.v, TLVP_VAL(tp, OSMO_IE_GSM_REQ_PDP_ADDR), + pdp->eua.l); + /* Highest 4 bits of first byte need to be set to 1, otherwise + * the IE is identical with the 04.08 PDP Address IE */ + pdp->eua.v[0] |= 0xf0; + + /* APN name from GMM */ + pdp->apn_use.l = TLVP_LEN(tp, GSM48_IE_GSM_APN); + if (pdp->apn_use.l > sizeof(pdp->apn_use.v)) + pdp->apn_use.l = sizeof(pdp->apn_use.v); + memcpy(pdp->apn_use.v, TLVP_VAL(tp, GSM48_IE_GSM_APN), + pdp->apn_use.l); + + /* Protocol Configuration Options from GMM */ + pdp->pco_req.l = TLVP_LEN(tp, GSM48_IE_GSM_PROTO_CONF_OPT); + if (pdp->pco_req.l > sizeof(pdp->pco_req.v)) + pdp->pco_req.l = sizeof(pdp->pco_req.v); + memcpy(pdp->pco_req.v, TLVP_VAL(tp, GSM48_IE_GSM_PROTO_CONF_OPT), + pdp->pco_req.l); + + /* QoS options from GMM */ + pdp->qos_req.l = TLVP_LEN(tp, OSMO_IE_GSM_REQ_QOS); + if (pdp->qos_req.l > sizeof(pdp->qos_req.v)) + pdp->qos_req.l = sizeof(pdp->qos_req.v); + memcpy(pdp->qos_req.v, TLVP_VAL(tp, OSMO_IE_GSM_REQ_QOS), + pdp->qos_req.l); + + /* SGSN address for control plane */ + pdp->gsnlc.l = sizeof(sgsn->cfg.gtp_listenaddr.sin_addr); + memcpy(pdp->gsnlc.v, &sgsn->cfg.gtp_listenaddr.sin_addr, + sizeof(sgsn->cfg.gtp_listenaddr.sin_addr)); + + /* SGSN address for user plane */ + pdp->gsnlu.l = sizeof(sgsn->cfg.gtp_listenaddr.sin_addr); + memcpy(pdp->gsnlu.v, &sgsn->cfg.gtp_listenaddr.sin_addr, + sizeof(sgsn->cfg.gtp_listenaddr.sin_addr)); + + /* change pdp state to 'requested' */ + pctx->state = PDP_STATE_CR_REQ; + + rc = gtp_create_context_req(ggsn->gsn, pdp, pctx); + /* FIXME */ + + return pctx; +} + +/* SGSN wants to delete a PDP context */ +int sgsn_delete_pdp_ctx(struct sgsn_pdp_ctx *pctx) +{ + LOGP(DGPRS, LOGL_ERROR, "Delete PDP Context\n"); + + /* FIXME: decide if we need teardown or not ! */ + return gtp_delete_context_req(pctx->ggsn->gsn, pctx->lib, pctx, 1); +} + +struct cause_map { + uint8_t cause_in; + uint8_t cause_out; +}; + +static uint8_t cause_map(const struct cause_map *map, uint8_t in, uint8_t deflt) +{ + const struct cause_map *m; + + for (m = map; m->cause_in && m->cause_out; m++) { + if (m->cause_in == in) + return m->cause_out; + } + return deflt; +} + +/* how do we map from gtp cause to SM cause */ +static const struct cause_map gtp2sm_cause_map[] = { + { GTPCAUSE_NO_RESOURCES, GSM_CAUSE_INSUFF_RSRC }, + { GTPCAUSE_NOT_SUPPORTED, GSM_CAUSE_SERV_OPT_NOTSUPP }, + { GTPCAUSE_MAN_IE_INCORRECT, GSM_CAUSE_INV_MAND_INFO }, + { GTPCAUSE_MAN_IE_MISSING, GSM_CAUSE_INV_MAND_INFO }, + { GTPCAUSE_OPT_IE_INCORRECT, GSM_CAUSE_PROTO_ERR_UNSPEC }, + { GTPCAUSE_SYS_FAIL, GSM_CAUSE_NET_FAIL }, + { GTPCAUSE_ROAMING_REST, GSM_CAUSE_REQ_SERV_OPT_NOTSUB }, + { GTPCAUSE_PTIMSI_MISMATCH, GSM_CAUSE_PROTO_ERR_UNSPEC }, + { GTPCAUSE_CONN_SUSP, GSM_CAUSE_PROTO_ERR_UNSPEC }, + { GTPCAUSE_AUTH_FAIL, GSM_CAUSE_AUTH_FAILED }, + { GTPCAUSE_USER_AUTH_FAIL, GSM_CAUSE_ACT_REJ_GGSN }, + { GTPCAUSE_CONTEXT_NOT_FOUND, GSM_CAUSE_PROTO_ERR_UNSPEC }, + { GTPCAUSE_ADDR_OCCUPIED, GSM_CAUSE_INSUFF_RSRC }, + { GTPCAUSE_NO_MEMORY, GSM_CAUSE_INSUFF_RSRC }, + { GTPCAUSE_RELOC_FAIL, GSM_CAUSE_PROTO_ERR_UNSPEC }, + { GTPCAUSE_UNKNOWN_MAN_EXTHEADER, GSM_CAUSE_PROTO_ERR_UNSPEC }, + { GTPCAUSE_MISSING_APN, GSM_CAUSE_MISSING_APN }, + { GTPCAUSE_UNKNOWN_PDP, GSM_CAUSE_UNKNOWN_PDP }, + { 0, 0 } +}; + +/* The GGSN has confirmed the creation of a PDP Context */ +static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause) +{ + struct sgsn_pdp_ctx *pctx = cbp; + uint8_t reject_cause; + int rc; + + DEBUGP(DGPRS, "Received CREATE PDP CTX CONF, cause=%d(%s)\n", + cause, get_value_string(gtp_cause_strs, cause)); + + /* Check for cause value if it was really successful */ + if (cause < 0) { + LOGP(DGPRS, LOGL_NOTICE, "Create PDP ctx req timed out\n"); + if (pdp && pdp->version == 1) { + pdp->version = 0; + gtp_create_context_req(sgsn->gsn, pdp, cbp); + return 0; + } else { + reject_cause = GSM_CAUSE_NET_FAIL; + goto reject; + } + } + + /* Check for cause value if it was really successful */ + if (cause != GTPCAUSE_ACC_REQ) { + reject_cause = cause_map(gtp2sm_cause_map, cause, + GSM_CAUSE_ACT_REJ_GGSN); + goto reject; + } + + /* Activate the SNDCP layer */ + sndcp_sm_activate_ind(&pctx->mm->llme->lle[pctx->sapi], pctx->nsapi); + + /* Send PDP CTX ACT to MS */ + return gsm48_tx_gsm_act_pdp_acc(pctx); + +reject: + pctx->state = PDP_STATE_NONE; + if (pdp) + pdp_freepdp(pdp); + /* Send PDP CTX ACT REJ to MS */ + rc = gsm48_tx_gsm_act_pdp_rej(pctx->mm, pctx->ti, reject_cause, + 0, NULL); + sgsn_pdp_ctx_free(pctx); + + return EOF; +} + +/* Confirmation of a PDP Context Delete */ +static int delete_pdp_conf(struct pdp_t *pdp, void *cbp, int cause) +{ + struct sgsn_pdp_ctx *pctx = cbp; + int rc; + + DEBUGP(DGPRS, "Received DELETE PDP CTX CONF, cause=%d(%s)\n", + cause, get_value_string(gtp_cause_strs, cause)); + + /* Deactivate the SNDCP layer */ + sndcp_sm_deactivate_ind(&pctx->mm->llme->lle[pctx->sapi], pctx->nsapi); + + /* Confirm deactivation of PDP context to MS */ + rc = gsm48_tx_gsm_deact_pdp_acc(pctx); + + sgsn_pdp_ctx_free(pctx); + + return rc; +} + +/* Confirmation of an GTP ECHO request */ +static int echo_conf(struct pdp_t *pdp, void *cbp, int recovery) +{ + if (recovery < 0) { + DEBUGP(DGPRS, "GTP Echo Request timed out\n"); + /* FIXME: if version == 1, retry with version 0 */ + } else { + DEBUGP(DGPRS, "GTP Rx Echo Response\n"); + } + return 0; +} + +/* Any message received by GGSN contains a recovery IE */ +static int cb_recovery(struct sockaddr_in *peer, uint8_t recovery) +{ + struct sgsn_ggsn_ctx *ggsn; + + ggsn = sgsn_ggsn_ctx_by_addr(&peer->sin_addr); + if (!ggsn) { + DEBUGP(DGPRS, "Received Recovery IE for unknown GGSN\n"); + return -EINVAL; + } + + if (ggsn->remote_restart_ctr == -1) { + /* First received ECHO RESPONSE, note the restart ctr */ + ggsn->remote_restart_ctr = recovery; + } else if (ggsn->remote_restart_ctr != recovery) { + /* counter has changed (GGSN restart): release all PDP */ + LOGP(DGPRS, LOGL_NOTICE, "GGSN recovery (%u->%u), " + "releasing all PDP contexts\n", + ggsn->remote_restart_ctr, recovery); + ggsn->remote_restart_ctr = recovery; + drop_all_pdp_for_ggsn(ggsn); + } + return 0; +} + +/* libgtp callback for confirmations */ +static int cb_conf(int type, int cause, struct pdp_t *pdp, void *cbp) +{ + DEBUGP(DGPRS, "libgtp cb_conf(type=%d, cause=%d, pdp=%p, cbp=%p)\n", + type, cause, pdp, cbp); + + if (cause == EOF) + LOGP(DGPRS, LOGL_ERROR, "libgtp EOF (type=%u, pdp=%p, cbp=%p)\n", + type, pdp, cbp); + + switch (type) { + case GTP_ECHO_REQ: + /* libgtp hands us the RECOVERY number instead of a cause */ + return echo_conf(pdp, cbp, cause); + case GTP_CREATE_PDP_REQ: + return create_pdp_conf(pdp, cbp, cause); + case GTP_DELETE_PDP_REQ: + return delete_pdp_conf(pdp, cbp, cause); + default: + break; + } + return 0; +} + +/* Called whenever a PDP context is deleted for any reason */ +static int cb_delete_context(struct pdp_t *pdp) +{ + LOGP(DGPRS, LOGL_INFO, "PDP Context was deleted\n"); + return 0; +} + +/* Called when we receive a Version Not Supported message */ +static int cb_unsup_ind(struct sockaddr_in *peer) +{ + LOGP(DGPRS, LOGL_INFO, "GTP Version not supported Indication " + "from %s:%u\n", inet_ntoa(peer->sin_addr), + ntohs(peer->sin_port)); + return 0; +} + +/* Called when we receive a Supported Ext Headers Notification */ +static int cb_extheader_ind(struct sockaddr_in *peer) +{ + LOGP(DGPRS, LOGL_INFO, "GTP Supported Ext Headers Noficiation " + "from %s:%u\n", inet_ntoa(peer->sin_addr), + ntohs(peer->sin_port)); + return 0; +} + +/* Called whenever we recive a DATA packet */ +static int cb_data_ind(struct pdp_t *lib, void *packet, unsigned int len) +{ + struct bssgp_paging_info pinfo; + struct sgsn_pdp_ctx *pdp; + struct sgsn_mm_ctx *mm; + struct msgb *msg; + uint8_t *ud; + int rc; + + DEBUGP(DGPRS, "GTP DATA IND from GGSN, length=%u\n", len); + + pdp = lib->priv; + if (!pdp) { + DEBUGP(DGPRS, "GTP DATA IND from GGSN for unknown PDP\n"); + return -EIO; + } + mm = pdp->mm; + + msg = msgb_alloc_headroom(len+256, 128, "GTP->SNDCP"); + ud = msgb_put(msg, len); + memcpy(ud, packet, len); + + msgb_tlli(msg) = mm->tlli; + msgb_bvci(msg) = mm->bvci; + msgb_nsei(msg) = mm->nsei; + + switch (mm->mm_state) { + case GMM_REGISTERED_SUSPENDED: + /* initiate PS PAGING procedure */ + memset(&pinfo, 0, sizeof(pinfo)); + pinfo.mode = BSSGP_PAGING_PS; + pinfo.scope = BSSGP_PAGING_BVCI; + pinfo.bvci = mm->bvci; + pinfo.imsi = mm->imsi; + pinfo.ptmsi = &mm->p_tmsi; + pinfo.drx_params = mm->drx_parms; + pinfo.qos[0] = 0; // FIXME + rc = gprs_bssgp_tx_paging(mm->nsei, 0, &pinfo); + rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PAGING_PS]); + /* FIXME: queue the packet we received from GTP */ + break; + case GMM_REGISTERED_NORMAL: + break; + default: + LOGP(DGPRS, LOGL_ERROR, "GTP DATA IND for TLLI %08X in state " + "%u\n", mm->tlli, mm->mm_state); + msgb_free(msg); + return -1; + } + + rate_ctr_inc(&pdp->ctrg->ctr[PDP_CTR_PKTS_UDATA_OUT]); + rate_ctr_add(&pdp->ctrg->ctr[PDP_CTR_BYTES_UDATA_OUT], len); + rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PKTS_UDATA_OUT]); + rate_ctr_add(&mm->ctrg->ctr[GMM_CTR_BYTES_UDATA_OUT], len); + + return sndcp_unitdata_req(msg, &mm->llme->lle[pdp->sapi], + pdp->nsapi, mm); +} + +/* Called by SNDCP when it has received/re-assembled a N-PDU */ +int sgsn_rx_sndcp_ud_ind(struct gprs_ra_id *ra_id, int32_t tlli, uint8_t nsapi, + struct msgb *msg, uint32_t npdu_len, uint8_t *npdu) +{ + struct sgsn_mm_ctx *mmctx; + struct sgsn_pdp_ctx *pdp; + + /* look-up the MM context for this message */ + mmctx = sgsn_mm_ctx_by_tlli(tlli, ra_id); + if (!mmctx) { + LOGP(DGPRS, LOGL_ERROR, + "Cannot find MM CTX for TLLI %08x\n", tlli); + return -EIO; + } + /* look-up the PDP context for this message */ + pdp = sgsn_pdp_ctx_by_nsapi(mmctx, nsapi); + if (!pdp) { + LOGP(DGPRS, LOGL_ERROR, "Cannot find PDP CTX for " + "TLLI=%08x, NSAPI=%u\n", tlli, nsapi); + return -EIO; + } + if (!pdp->lib) { + LOGP(DGPRS, LOGL_ERROR, "PDP CTX without libgtp\n"); + return -EIO; + } + + rate_ctr_inc(&pdp->ctrg->ctr[PDP_CTR_PKTS_UDATA_IN]); + rate_ctr_add(&pdp->ctrg->ctr[PDP_CTR_BYTES_UDATA_IN], npdu_len); + rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_UDATA_IN]); + rate_ctr_add(&mmctx->ctrg->ctr[GMM_CTR_BYTES_UDATA_IN], npdu_len); + + return gtp_data_req(pdp->ggsn->gsn, pdp->lib, npdu, npdu_len); + + return gtp_data_req(pdp->ggsn->gsn, pdp->lib, npdu, npdu_len); +} + +/* libgtp select loop integration */ +static int sgsn_gtp_fd_cb(struct bsc_fd *fd, unsigned int what) +{ + struct sgsn_instance *sgi = fd->data; + int rc; + + if (!(what & BSC_FD_READ)) + return 0; + + switch (fd->priv_nr) { + case 0: + rc = gtp_decaps0(sgi->gsn); + break; + case 1: + rc = gtp_decaps1c(sgi->gsn); + break; + case 2: + rc = gtp_decaps1u(sgi->gsn); + break; + default: + rc = -EINVAL; + break; + } + return rc; +} + +static void sgsn_gtp_tmr_start(struct sgsn_instance *sgi) +{ + struct timeval next; + + /* Retrieve next retransmission as struct timeval */ + gtp_retranstimeout(sgi->gsn, &next); + + /* re-schedule the timer */ + bsc_schedule_timer(&sgi->gtp_timer, next.tv_sec, next.tv_usec/1000); +} + +/* timer callback for libgtp retransmissions and ping */ +static void sgsn_gtp_tmr_cb(void *data) +{ + struct sgsn_instance *sgi = data; + + /* Do all the retransmissions as needed */ + gtp_retrans(sgi->gsn); + + sgsn_gtp_tmr_start(sgi); +} + +int sgsn_gtp_init(struct sgsn_instance *sgi) +{ + int rc; + struct gsn_t *gsn; + + rc = gtp_new(&sgi->gsn, sgi->cfg.gtp_statedir, + &sgi->cfg.gtp_listenaddr.sin_addr, GTP_MODE_SGSN); + if (rc) { + LOGP(DGPRS, LOGL_ERROR, "Failed to create GTP: %d\n", rc); + return rc; + } + gsn = sgi->gsn; + + sgi->gtp_fd0.fd = gsn->fd0; + sgi->gtp_fd0.priv_nr = 0; + sgi->gtp_fd0.data = sgi; + sgi->gtp_fd0.when = BSC_FD_READ; + sgi->gtp_fd0.cb = sgsn_gtp_fd_cb; + rc = bsc_register_fd(&sgi->gtp_fd0); + if (rc < 0) + return rc; + + sgi->gtp_fd1c.fd = gsn->fd1c; + sgi->gtp_fd1c.priv_nr = 1; + sgi->gtp_fd1c.data = sgi; + sgi->gtp_fd1c.when = BSC_FD_READ; + sgi->gtp_fd1c.cb = sgsn_gtp_fd_cb; + bsc_register_fd(&sgi->gtp_fd1c); + if (rc < 0) + return rc; + + sgi->gtp_fd1u.fd = gsn->fd1u; + sgi->gtp_fd1u.priv_nr = 2; + sgi->gtp_fd1u.data = sgi; + sgi->gtp_fd1u.when = BSC_FD_READ; + sgi->gtp_fd1u.cb = sgsn_gtp_fd_cb; + bsc_register_fd(&sgi->gtp_fd1u); + if (rc < 0) + return rc; + + /* Start GTP re-transmission timer */ + sgi->gtp_timer.cb = sgsn_gtp_tmr_cb; + sgi->gtp_timer.data = sgi; + sgsn_gtp_tmr_start(sgi); + + /* Register callbackcs with libgtp */ + gtp_set_cb_delete_context(gsn, cb_delete_context); + gtp_set_cb_conf(gsn, cb_conf); + gtp_set_cb_recovery(gsn, cb_recovery); + gtp_set_cb_data_ind(gsn, cb_data_ind); + gtp_set_cb_unsup_ind(gsn, cb_unsup_ind); + gtp_set_cb_extheader_ind(gsn, cb_extheader_ind); + + return 0; +} diff --git a/src/gprs/sgsn_main.c b/src/gprs/sgsn_main.c new file mode 100644 index 000000000..c59265fb4 --- /dev/null +++ b/src/gprs/sgsn_main.c @@ -0,0 +1,288 @@ +/* GPRS SGSN Implementation */ + +/* (C) 2010 by Harald Welte + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../../bscconfig.h" + +/* this is here for the vty... it will never be called */ +void subscr_put() { abort(); } + +#define _GNU_SOURCE +#include + +void *tall_bsc_ctx; + +struct gprs_ns_inst *sgsn_nsi; +static struct log_target *stderr_target; +static int daemonize = 0; +const char *openbsc_copyright = + "Copyright (C) 2010 Harald Welte and On-Waves\r\n" + "License AGPLv3+: GNU AGPL version 2 or later \r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +static struct sgsn_instance sgsn_inst = { + .config_file = "osmo_sgsn.cfg", + .cfg = { + .gtp_statedir = "./", + }, +}; +struct sgsn_instance *sgsn = &sgsn_inst; + +/* call-back function for the NS protocol */ +static int sgsn_ns_cb(enum gprs_ns_evt event, struct gprs_nsvc *nsvc, + struct msgb *msg, u_int16_t bvci) +{ + int rc = 0; + + switch (event) { + case GPRS_NS_EVT_UNIT_DATA: + /* hand the message into the BSSGP implementation */ + rc = gprs_bssgp_rcvmsg(msg); + break; + default: + LOGP(DGPRS, LOGL_ERROR, "SGSN: Unknown event %u from NS\n", event); + if (msg) + talloc_free(msg); + rc = -EIO; + break; + } + return rc; +} + +static void signal_handler(int signal) +{ + fprintf(stdout, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + dispatch_signal(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL); + sleep(1); + exit(0); + break; + case SIGABRT: + /* in case of abort, we want to obtain a talloc report + * and then return to the caller, who will abort the process */ + case SIGUSR1: + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(tall_bsc_ctx, stderr); + break; + case SIGUSR2: + talloc_report_full(tall_vty_ctx, stderr); + break; + default: + break; + } +} + +/* NSI that BSSGP uses when transmitting on NS */ +extern struct gprs_ns_inst *bssgp_nsi; +extern void *tall_msgb_ctx; + +extern enum node_type bsc_vty_go_parent(struct vty *vty); + +static struct vty_app_info vty_info = { + .name = "OsmoSGSN", + .version = PACKAGE_VERSION, + .go_parent_cb = bsc_vty_go_parent, + .is_config_node = bsc_vty_is_config_node, +}; + +static void print_help(void) +{ + printf("Some useful help...\n"); + printf(" -h --help\tthis text\n"); + printf(" -D --daemonize\tFork the process into a background daemon\n"); + printf(" -d option --debug\tenable Debugging\n"); + printf(" -s --disable-color\n"); + printf(" -c --config-file\tThe config file to use\n"); + printf(" -e --log-level number\tSet a global log level\n"); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"debug", 1, 0, 'd'}, + {"daemonize", 0, 0, 'D'}, + {"config-file", 1, 0, 'c'}, + {"disable-color", 0, 0, 's'}, + {"timestamp", 0, 0, 'T'}, + {"log-level", 1, 0, 'e'}, + {NULL, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hd:Dc:sTe:", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + //print_usage(); + print_help(); + exit(0); + case 's': + log_set_use_color(stderr_target, 0); + break; + case 'd': + log_parse_category_mask(stderr_target, optarg); + break; + case 'D': + daemonize = 1; + break; + case 'c': + sgsn_inst.config_file = strdup(optarg); + break; + case 'T': + log_set_print_timestamp(stderr_target, 1); + break; + case 'e': + log_set_log_level(stderr_target, atoi(optarg)); + break; + default: + /* ignore */ + break; + } + } +} + +int main(int argc, char **argv) +{ + struct gsm_network dummy_network; + struct sockaddr_in sin; + int rc; + + tall_bsc_ctx = talloc_named_const(NULL, 0, "osmo_sgsn"); + tall_msgb_ctx = talloc_named_const(tall_bsc_ctx, 0, "msgb"); + + signal(SIGINT, &signal_handler); + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + signal(SIGPIPE, SIG_IGN); + + log_init(&log_info); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + log_set_all_filter(stderr_target, 1); + + vty_info.copyright = openbsc_copyright; + vty_init(&vty_info); + logging_vty_add_cmds(); + sgsn_vty_init(); + + handle_options(argc, argv); + + rate_ctr_init(tall_bsc_ctx); + rc = telnet_init(tall_bsc_ctx, &dummy_network, 4245); + if (rc < 0) + exit(1); + + sgsn_nsi = gprs_ns_instantiate(&sgsn_ns_cb); + if (!sgsn_nsi) { + LOGP(DGPRS, LOGL_ERROR, "Unable to instantiate NS\n"); + exit(1); + } + bssgp_nsi = sgsn_inst.cfg.nsi = sgsn_nsi; + + gprs_llc_init("/usr/local/lib/osmocom/crypt/"); + + gprs_ns_vty_init(bssgp_nsi); + gprs_bssgp_vty_init(); + gprs_llc_vty_init(); + gprs_sndcp_vty_init(); + /* FIXME: register signal handler for SS_NS */ + + rc = sgsn_parse_config(sgsn_inst.config_file, &sgsn_inst.cfg); + if (rc < 0) { + LOGP(DGPRS, LOGL_FATAL, "Cannot parse config file\n"); + exit(2); + } + + rc = sgsn_gtp_init(&sgsn_inst); + if (rc) { + LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen on GTP socket\n"); + exit(2); + } + + rc = gprs_ns_nsip_listen(sgsn_nsi); + if (rc < 0) { + LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen on NSIP socket\n"); + exit(2); + } + + rc = gprs_ns_frgre_listen(sgsn_nsi); + if (rc < 0) { + LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen GRE " + "socket. Do you have CAP_NET_RAW?\n"); + exit(2); + } + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + while (1) { + rc = bsc_select_main(0); + if (rc < 0) + exit(3); + } + + exit(0); +} diff --git a/src/gprs/sgsn_vty.c b/src/gprs/sgsn_vty.c new file mode 100644 index 000000000..74669ffb7 --- /dev/null +++ b/src/gprs/sgsn_vty.c @@ -0,0 +1,357 @@ +/* + * (C) 2010 by Harald Welte + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +static struct sgsn_config *g_cfg = NULL; + + +#define GSM48_MAX_APN_LEN 102 /* 10.5.6.1 */ +static char *gprs_apn2str(uint8_t *apn, unsigned int len) +{ + static char apnbuf[GSM48_MAX_APN_LEN+1]; + unsigned int i; + + if (!apn) + return ""; + + if (len > sizeof(apnbuf)-1) + len = sizeof(apnbuf)-1; + + memcpy(apnbuf, apn, len); + apnbuf[len] = '\0'; + + /* replace the domain name step sizes with dots */ + while (i < len) { + unsigned int step = apnbuf[i]; + apnbuf[i] = '.'; + i += step+1; + } + + return apnbuf+1; +} + +static char *gprs_pdpaddr2str(uint8_t *pdpa, uint8_t len) +{ + static char str[INET6_ADDRSTRLEN + 10]; + + if (!pdpa || len < 2) + return "none"; + + switch (pdpa[0] & 0x0f) { + case PDP_TYPE_ORG_IETF: + switch (pdpa[1]) { + case PDP_TYPE_N_IETF_IPv4: + if (len < 2 + 4) + break; + strcpy(str, "IPv4 "); + inet_ntop(AF_INET, pdpa+2, str+5, sizeof(str)-5); + return str; + case PDP_TYPE_N_IETF_IPv6: + if (len < 2 + 8) + break; + strcpy(str, "IPv6 "); + inet_ntop(AF_INET6, pdpa+2, str+5, sizeof(str)-5); + return str; + default: + break; + } + break; + case PDP_TYPE_ORG_ETSI: + if (pdpa[1] == PDP_TYPE_N_ETSI_PPP) + return "PPP"; + break; + default: + break; + } + + return "invalid"; +} + +static struct cmd_node sgsn_node = { + SGSN_NODE, + "%s(sgsn)#", + 1, +}; + +static int config_write_sgsn(struct vty *vty) +{ + struct sgsn_ggsn_ctx *gctx; + + vty_out(vty, "sgsn%s", VTY_NEWLINE); + + vty_out(vty, " gtp local-ip %s%s", + inet_ntoa(g_cfg->gtp_listenaddr.sin_addr), VTY_NEWLINE); + + llist_for_each_entry(gctx, &sgsn_ggsn_ctxts, list) { + vty_out(vty, " ggsn %u remote-ip %s%s", gctx->id, + inet_ntoa(gctx->remote_addr), VTY_NEWLINE); + vty_out(vty, " ggsn %u gtp-version %u%s", gctx->id, + gctx->gtp_version, VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +#define SGSN_STR "Configure the SGSN" + +DEFUN(cfg_sgsn, cfg_sgsn_cmd, + "sgsn", + SGSN_STR) +{ + vty->node = SGSN_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_sgsn_bind_addr, cfg_sgsn_bind_addr_cmd, + "gtp local-ip A.B.C.D", + "GTP Parameters\n" + "Set the IP address for the local GTP bind\n") +{ + inet_aton(argv[0], &g_cfg->gtp_listenaddr.sin_addr); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ggsn_remote_ip, cfg_ggsn_remote_ip_cmd, + "ggsn <0-255> remote-ip A.B.C.D", + "") +{ + uint32_t id = atoi(argv[0]); + struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id); + + inet_aton(argv[1], &ggc->remote_addr); + + return CMD_SUCCESS; +} + +#if 0 +DEFUN(cfg_ggsn_remote_port, cfg_ggsn_remote_port_cmd, + "ggsn <0-255> remote-port <0-65535>", + "") +{ + uint32_t id = atoi(argv[0]); + struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id); + uint16_t port = atoi(argv[1]); + +} +#endif + +DEFUN(cfg_ggsn_gtp_version, cfg_ggsn_gtp_version_cmd, + "ggsn <0-255> gtp-version (0|1)", + "") +{ + uint32_t id = atoi(argv[0]); + struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id); + + if (atoi(argv[1])) + ggc->gtp_version = 1; + else + ggc->gtp_version = 0; + + return CMD_SUCCESS; +} + +#if 0 +DEFUN(cfg_apn_ggsn, cfg_apn_ggsn_cmd, + "apn APNAME ggsn <0-255>", + "") +{ + struct apn_ctx ** +} +#endif + +const struct value_string gprs_mm_st_strs[] = { + { GMM_DEREGISTERED, "DEREGISTERED" }, + { GMM_COMMON_PROC_INIT, "COMMON PROCEDURE (INIT)" }, + { GMM_REGISTERED_NORMAL, "REGISTERED (NORMAL)" }, + { GMM_REGISTERED_SUSPENDED, "REGISTERED (SUSPENDED)" }, + { GMM_DEREGISTERED_INIT, "DEREGISTERED (INIT)" }, + { 0, NULL } +}; + +static void vty_dump_pdp(struct vty *vty, const char *pfx, + struct sgsn_pdp_ctx *pdp) +{ + vty_out(vty, "%sPDP Context IMSI: %s, SAPI: %u, NSAPI: %u%s", + pfx, pdp->mm->imsi, pdp->sapi, pdp->nsapi, VTY_NEWLINE); + vty_out(vty, "%s APN: %s%s", pfx, + gprs_apn2str(pdp->lib->apn_use.v, pdp->lib->apn_use.l), + VTY_NEWLINE); + vty_out(vty, "%s PDP Address: %s%s", pfx, + gprs_pdpaddr2str(pdp->lib->eua.v, pdp->lib->eua.l), + VTY_NEWLINE); + vty_out_rate_ctr_group(vty, " ", pdp->ctrg); +} + +static void vty_dump_mmctx(struct vty *vty, const char *pfx, + struct sgsn_mm_ctx *mm, int pdp) +{ + vty_out(vty, "%sMM Context for IMSI %s, IMEI %s, P-TMSI %08x%s", + pfx, mm->imsi, mm->imei, mm->p_tmsi, VTY_NEWLINE); + vty_out(vty, "%s MSISDN: %s, TLLI: %08x%s", pfx, mm->msisdn, + mm->tlli, VTY_NEWLINE); + vty_out(vty, "%s MM State: %s, Routeing Area: %u-%u-%u-%u, " + "Cell ID: %u%s", pfx, + get_value_string(gprs_mm_st_strs, mm->mm_state), + mm->ra.mcc, mm->ra.mnc, mm->ra.lac, mm->ra.rac, + mm->cell_id, VTY_NEWLINE); + + vty_out_rate_ctr_group(vty, " ", mm->ctrg); + + if (pdp) { + struct sgsn_pdp_ctx *pdp; + + llist_for_each_entry(pdp, &mm->pdp_list, list) + vty_dump_pdp(vty, " ", pdp); + } +} + +DEFUN(show_sgsn, show_sgsn_cmd, "show sgsn", + SHOW_STR "Display information about the SGSN") +{ + /* FIXME: statistics */ + return CMD_SUCCESS; +} + +#define MMCTX_STR "MM Context\n" +#define INCLUDE_PDP_STR "Include PDP Context Information\n" + +#if 0 +DEFUN(show_mmctx_tlli, show_mmctx_tlli_cmd, + "show mm-context tlli HEX [pdp]", + SHOW_STR MMCTX_STR "Identify by TLLI\n" "TLLI\n" INCLUDE_PDP_STR) +{ + uint32_t tlli; + struct sgsn_mm_ctx *mm; + + tlli = strtoul(argv[0], NULL, 16); + mm = sgsn_mm_ctx_by_tlli(tlli); + if (!mm) { + vty_out(vty, "No MM context for TLLI %08x%s", + tlli, VTY_NEWLINE); + return CMD_WARNING; + } + vty_dump_mmctx(vty, "", mm, argv[1] ? 1 : 0); + return CMD_SUCCESS; +} +#endif + +DEFUN(swow_mmctx_imsi, show_mmctx_imsi_cmd, + "show mm-context imsi IMSI [pdp]", + SHOW_STR MMCTX_STR "Identify by IMSI\n" "IMSI of the MM Context\n" + INCLUDE_PDP_STR) +{ + struct sgsn_mm_ctx *mm; + + mm = sgsn_mm_ctx_by_imsi(argv[0]); + if (!mm) { + vty_out(vty, "No MM context for IMSI %s%s", + argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + vty_dump_mmctx(vty, "", mm, argv[1] ? 1 : 0); + return CMD_SUCCESS; +} + +DEFUN(swow_mmctx_all, show_mmctx_all_cmd, + "show mm-context all [pdp]", + SHOW_STR MMCTX_STR "All MM Contexts\n" INCLUDE_PDP_STR) +{ + struct sgsn_mm_ctx *mm; + + llist_for_each_entry(mm, &sgsn_mm_ctxts, list) + vty_dump_mmctx(vty, "", mm, argv[0] ? 1 : 0); + + return CMD_SUCCESS; +} + +DEFUN(show_ggsn, show_ggsn_cmd, + "show ggsn", + "") +{ + +} + +DEFUN(show_pdpctx_all, show_pdpctx_all_cmd, + "show pdp-context all", + SHOW_STR "Display information on PDP Context\n") +{ + struct sgsn_pdp_ctx *pdp; + + llist_for_each_entry(pdp, &sgsn_pdp_ctxts, g_list) + vty_dump_pdp(vty, "", pdp); + + return CMD_SUCCESS; +} + +int sgsn_vty_init(void) +{ + install_element_ve(&show_sgsn_cmd); + //install_element_ve(&show_mmctx_tlli_cmd); + install_element_ve(&show_mmctx_imsi_cmd); + install_element_ve(&show_mmctx_all_cmd); + install_element_ve(&show_pdpctx_all_cmd); + + install_element(CONFIG_NODE, &cfg_sgsn_cmd); + install_node(&sgsn_node, config_write_sgsn); + install_default(SGSN_NODE); + install_element(SGSN_NODE, &ournode_exit_cmd); + install_element(SGSN_NODE, &ournode_end_cmd); + install_element(SGSN_NODE, &cfg_sgsn_bind_addr_cmd); + install_element(SGSN_NODE, &cfg_ggsn_remote_ip_cmd); + //install_element(SGSN_NODE, &cfg_ggsn_remote_port_cmd); + install_element(SGSN_NODE, &cfg_ggsn_gtp_version_cmd); + + return 0; +} + +int sgsn_parse_config(const char *config_file, struct sgsn_config *cfg) +{ + int rc; + + g_cfg = cfg; + rc = vty_read_config_file(config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file); + return rc; + } + + return 0; +} diff --git a/src/ipaccess/Makefile.am b/src/ipaccess/Makefile.am new file mode 100644 index 000000000..144cca1ee --- /dev/null +++ b/src/ipaccess/Makefile.am @@ -0,0 +1,21 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) + +bin_PROGRAMS = ipaccess-find ipaccess-config ipaccess-proxy + +ipaccess_find_SOURCES = ipaccess-find.c + +ipaccess_config_SOURCES = ipaccess-config.c ipaccess-firmware.c network_listen.c + +# FIXME: resolve the bogus dependencies patched around here: +ipaccess_config_LDADD = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + -ldl -ldbi $(LIBCRYPT) + +ipaccess_proxy_SOURCES = ipaccess-proxy.c +ipaccess_proxy_LDADD = $(top_builddir)/src/libcommon/libcommon.a diff --git a/src/ipaccess/Makefile.in b/src/ipaccess/Makefile.in new file mode 100644 index 000000000..864fa316d --- /dev/null +++ b/src/ipaccess/Makefile.in @@ -0,0 +1,520 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +bin_PROGRAMS = ipaccess-find$(EXEEXT) ipaccess-config$(EXEEXT) \ + ipaccess-proxy$(EXEEXT) +subdir = src/ipaccess +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_ipaccess_config_OBJECTS = ipaccess-config.$(OBJEXT) \ + ipaccess-firmware.$(OBJEXT) network_listen.$(OBJEXT) +ipaccess_config_OBJECTS = $(am_ipaccess_config_OBJECTS) +ipaccess_config_DEPENDENCIES = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + $(top_builddir)/src/libcommon/libcommon.a +am_ipaccess_find_OBJECTS = ipaccess-find.$(OBJEXT) +ipaccess_find_OBJECTS = $(am_ipaccess_find_OBJECTS) +ipaccess_find_LDADD = $(LDADD) +am_ipaccess_proxy_OBJECTS = ipaccess-proxy.$(OBJEXT) +ipaccess_proxy_OBJECTS = $(am_ipaccess_proxy_OBJECTS) +ipaccess_proxy_DEPENDENCIES = \ + $(top_builddir)/src/libcommon/libcommon.a +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(ipaccess_config_SOURCES) $(ipaccess_find_SOURCES) \ + $(ipaccess_proxy_SOURCES) +DIST_SOURCES = $(ipaccess_config_SOURCES) $(ipaccess_find_SOURCES) \ + $(ipaccess_proxy_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) +ipaccess_find_SOURCES = ipaccess-find.c +ipaccess_config_SOURCES = ipaccess-config.c ipaccess-firmware.c network_listen.c + +# FIXME: resolve the bogus dependencies patched around here: +ipaccess_config_LDADD = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + -ldl -ldbi $(LIBCRYPT) + +ipaccess_proxy_SOURCES = ipaccess-proxy.c +ipaccess_proxy_LDADD = $(top_builddir)/src/libcommon/libcommon.a +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/ipaccess/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/ipaccess/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +ipaccess-config$(EXEEXT): $(ipaccess_config_OBJECTS) $(ipaccess_config_DEPENDENCIES) + @rm -f ipaccess-config$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(ipaccess_config_OBJECTS) $(ipaccess_config_LDADD) $(LIBS) +ipaccess-find$(EXEEXT): $(ipaccess_find_OBJECTS) $(ipaccess_find_DEPENDENCIES) + @rm -f ipaccess-find$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(ipaccess_find_OBJECTS) $(ipaccess_find_LDADD) $(LIBS) +ipaccess-proxy$(EXEEXT): $(ipaccess_proxy_OBJECTS) $(ipaccess_proxy_DEPENDENCIES) + @rm -f ipaccess-proxy$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(ipaccess_proxy_OBJECTS) $(ipaccess_proxy_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipaccess-config.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipaccess-find.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipaccess-firmware.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipaccess-proxy.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network_listen.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-binPROGRAMS + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/ipaccess/ipaccess-config.c b/src/ipaccess/ipaccess-config.c new file mode 100644 index 000000000..d02faea3a --- /dev/null +++ b/src/ipaccess/ipaccess-config.c @@ -0,0 +1,865 @@ +/* ip.access nanoBTS configuration tool */ + +/* (C) 2009-2010 by Harald Welte + * (C) 2009-2010 by Holger Hans Peter Freyther + * (C) 2009-2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct gsm_network *gsmnet; + +static int net_listen_testnr; +static int restart; +static char *prim_oml_ip; +static char *bts_ip_addr, *bts_ip_mask, *bts_ip_gw; +static char *unit_id; +static u_int16_t nv_flags; +static u_int16_t nv_mask; +static char *software = NULL; +static int sw_load_state = 0; +static int oml_state = 0; +static int dump_files = 0; +static char *firmware_analysis = NULL; +static int found_trx = 0; + +struct sw_load { + u_int8_t file_id[255]; + u_int8_t file_id_len; + + u_int8_t file_version[255]; + u_int8_t file_version_len; +}; + +static void *tall_ctx_config = NULL; +static struct sw_load *sw_load1 = NULL; +static struct sw_load *sw_load2 = NULL; + +/* +static u_int8_t prim_oml_attr[] = { 0x95, 0x00, 7, 0x88, 192, 168, 100, 11, 0x00, 0x00 }; +static u_int8_t unit_id_attr[] = { 0x91, 0x00, 9, '2', '3', '4', '2', '/' , '0', '/', '0', 0x00 }; +*/ + +/* + * Callback function for NACK on the OML NM + * + * Currently we send the config requests but don't check the + * result. The nanoBTS will send us a NACK when we did something the + * BTS didn't like. + */ +static int ipacc_msg_nack(u_int8_t mt) +{ + fprintf(stderr, "Failure to set attribute. This seems fatal\n"); + exit(-1); + return 0; +} + +static void check_restart_or_exit(struct gsm_bts_trx *trx) +{ + if (restart) { + abis_nm_ipaccess_restart(trx); + } else { + exit(0); + } +} + +static int ipacc_msg_ack(u_int8_t mt, struct gsm_bts_trx *trx) +{ + if (sw_load_state == 1) { + fprintf(stderr, "The new software is activaed.\n"); + check_restart_or_exit(trx); + } else if (oml_state == 1) { + fprintf(stderr, "Set the NV Attributes.\n"); + check_restart_or_exit(trx); + } + + return 0; +} + +static const uint8_t phys_conf_min[] = { 0x02 }; + +static uint16_t build_physconf(uint8_t *physconf_buf, const struct rxlev_stats *st) +{ + uint16_t *whitelist = (uint16_t *) (physconf_buf + 4); + int num_arfcn; + unsigned int arfcnlist_size; + + /* Create whitelist from rxlevels */ + physconf_buf[0] = phys_conf_min[0]; + physconf_buf[1] = NM_IPAC_EIE_ARFCN_WHITE; + num_arfcn = ipac_rxlevstat2whitelist(whitelist, st, 0, 100); + arfcnlist_size = num_arfcn * 2; + *((uint16_t *) (physconf_buf+2)) = htons(arfcnlist_size); + DEBUGP(DNM, "physconf_buf (%s)\n", hexdump(physconf_buf, arfcnlist_size+4)); + return arfcnlist_size+4; +} + +static int nwl_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_bts_trx *trx; + uint8_t physconf_buf[2*NUM_ARFCNS+16]; + uint16_t physconf_len; + + switch (signal) { + case S_IPAC_NWL_COMPLETE: + trx = signal_data; + DEBUGP(DNM, "received S_IPAC_NWL_COMPLETE signal\n"); + switch (trx->ipaccess.test_nr) { + case NM_IPACC_TESTNO_CHAN_USAGE: + /* Dump RxLev results */ + //rxlev_stat_dump(&trx->ipaccess.rxlev_stat); + /* Create whitelist from results */ + physconf_len = build_physconf(physconf_buf, + &trx->ipaccess.rxlev_stat); + /* Start next test abbout BCCH channel usage */ + ipac_nwl_test_start(trx, NM_IPACC_TESTNO_BCCH_CHAN_USAGE, + physconf_buf, physconf_len); + break; + case NM_IPACC_TESTNO_BCCH_CHAN_USAGE: + /* Dump BCCH RxLev results */ + //rxlev_stat_dump(&trx->ipaccess.rxlev_stat); + /* Create whitelist from results */ + physconf_len = build_physconf(physconf_buf, + &trx->ipaccess.rxlev_stat); + /* Start next test about BCCH info */ + ipac_nwl_test_start(trx, NM_IPACC_TESTNO_BCCH_INFO, + physconf_buf, physconf_len); + break; + case NM_IPACC_TESTNO_BCCH_INFO: +#if 0 + /* re-start full process with CHAN_USAGE */ + DEBUGP(DNM, "starting next test cycle\n"); + ipac_nwl_test_start(trx, net_listen_testnr, phys_conf_min, + sizeof(phys_conf_min)); +#else + exit(0); +#endif + break; + } + break; + } + return 0; +} + +static int nm_state_event(int evt, u_int8_t obj_class, void *obj, + struct gsm_nm_state *old_state, struct gsm_nm_state *new_state, + struct abis_om_obj_inst *obj_inst); + +static int nm_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct ipacc_ack_signal_data *ipacc_data; + struct nm_statechg_signal_data *nsd; + + switch (signal) { + case S_NM_IPACC_NACK: + ipacc_data = signal_data; + return ipacc_msg_nack(ipacc_data->msg_type); + case S_NM_IPACC_ACK: + ipacc_data = signal_data; + return ipacc_msg_ack(ipacc_data->msg_type, ipacc_data->trx); + case S_NM_IPACC_RESTART_ACK: + printf("The BTS has acked the restart. Exiting.\n"); + exit(0); + break; + case S_NM_IPACC_RESTART_NACK: + printf("The BTS has nacked the restart. Exiting.\n"); + exit(0); + break; + case S_NM_STATECHG_OPER: + case S_NM_STATECHG_ADM: + nsd = signal_data; + nm_state_event(signal, nsd->obj_class, nsd->obj, nsd->old_state, + nsd->new_state, nsd->obj_inst); + break; + default: + break; + } + + return 0; +} + +/* callback function passed to the ABIS OML code */ +static int percent; +static int percent_old; +static int swload_cbfn(unsigned int hook, unsigned int event, struct msgb *_msg, + void *data, void *param) +{ + struct msgb *msg; + struct gsm_bts_trx *trx; + + if (hook != GSM_HOOK_NM_SWLOAD) + return 0; + + trx = (struct gsm_bts_trx *) data; + + switch (event) { + case NM_MT_LOAD_INIT_ACK: + fprintf(stdout, "Software Load Initiate ACK\n"); + break; + case NM_MT_LOAD_INIT_NACK: + fprintf(stderr, "ERROR: Software Load Initiate NACK\n"); + exit(5); + break; + case NM_MT_LOAD_END_ACK: + fprintf(stderr, "LOAD END ACK..."); + /* now make it the default */ + sw_load_state = 1; + + msg = msgb_alloc(1024, "sw: nvattr"); + msg->l2h = msgb_put(msg, 3); + msg->l3h = &msg->l2h[3]; + + /* activate software */ + if (sw_load1) { + msgb_v_put(msg, NM_ATT_SW_DESCR); + msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw_load1->file_id_len, sw_load1->file_id); + msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw_load1->file_version_len, + sw_load1->file_version); + } + + if (sw_load2) { + msgb_v_put(msg, NM_ATT_SW_DESCR); + msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw_load2->file_id_len, sw_load2->file_id); + msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw_load2->file_version_len, + sw_load2->file_version); + } + + /* fill in the data */ + msg->l2h[0] = NM_ATT_IPACC_CUR_SW_CFG; + msg->l2h[1] = msgb_l3len(msg) >> 8; + msg->l2h[2] = msgb_l3len(msg) & 0xff; + printf("Foo l2h: %p l3h: %p... length l2: %u l3: %u\n", msg->l2h, msg->l3h, msgb_l2len(msg), msgb_l3len(msg)); + abis_nm_ipaccess_set_nvattr(trx, msg->l2h, msgb_l2len(msg)); + msgb_free(msg); + break; + case NM_MT_LOAD_END_NACK: + fprintf(stderr, "ERROR: Software Load End NACK\n"); + exit(3); + break; + case NM_MT_ACTIVATE_SW_NACK: + fprintf(stderr, "ERROR: Activate Software NACK\n"); + exit(4); + break; + case NM_MT_ACTIVATE_SW_ACK: + break; + case NM_MT_LOAD_SEG_ACK: + percent = abis_nm_software_load_status(trx->bts); + if (percent > percent_old) + printf("Software Download Progress: %d%%\n", percent); + percent_old = percent; + break; + case NM_MT_LOAD_ABORT: + fprintf(stderr, "ERROR: Load aborted by the BTS.\n"); + exit(6); + break; + } + return 0; +} + +static void nv_put_ip_if_cfg(struct msgb *nmsg, uint32_t ip, uint32_t mask) +{ + msgb_put_u8(nmsg, NM_ATT_IPACC_IP_IF_CFG); + + msgb_put_u32(nmsg, ip); + msgb_put_u32(nmsg, mask); +} + +static void nv_put_gw_cfg(struct msgb *nmsg, uint32_t addr, uint32_t mask, uint32_t gw) +{ + msgb_put_u8(nmsg, NM_ATT_IPACC_IP_GW_CFG); + msgb_put_u32(nmsg, addr); + msgb_put_u32(nmsg, mask); + msgb_put_u32(nmsg, gw); +} + +static void nv_put_unit_id(struct msgb *nmsg, const char *unit_id) +{ + msgb_tl16v_put(nmsg, NM_ATT_IPACC_UNIT_ID, strlen(unit_id)+1, + (const uint8_t *)unit_id); +} + +static void nv_put_prim_oml(struct msgb *nmsg, uint32_t ip, uint16_t port) +{ + int len; + + /* 0x88 + IP + port */ + len = 1 + sizeof(ip) + sizeof(port); + + msgb_put_u8(nmsg, NM_ATT_IPACC_PRIM_OML_CFG_LIST); + msgb_put_u16(nmsg, len); + + msgb_put_u8(nmsg, 0x88); + + /* IP address */ + msgb_put_u32(nmsg, ip); + + /* port number */ + msgb_put_u16(nmsg, port); +} + +static void nv_put_flags(struct msgb *nmsg, uint16_t nv_flags, uint16_t nv_mask) +{ + msgb_put_u8(nmsg, NM_ATT_IPACC_NV_FLAGS); + msgb_put_u16(nmsg, sizeof(nv_flags) + sizeof(nv_mask)); + msgb_put_u8(nmsg, nv_flags & 0xff); + msgb_put_u8(nmsg, nv_mask & 0xff); + msgb_put_u8(nmsg, nv_flags >> 8); + msgb_put_u8(nmsg, nv_mask >> 8); +} + +/* human-readable names for the ip.access nanoBTS NVRAM Flags */ +static const struct value_string ipa_nvflag_strs[] = { + { 0x0001, "static-ip" }, + { 0x0002, "static-gw" }, + { 0x0004, "no-dhcp-vsi" }, + { 0x0008, "dhcp-enabled" }, + { 0x0040, "led-disabled" }, + { 0x0100, "secondary-oml-enabled" }, + { 0x0200, "diag-enabled" }, + { 0x0400, "cli-enabled" }, + { 0x0800, "http-enabled" }, + { 0x1000, "post-enabled" }, + { 0x2000, "snmp-enabled" }, + { 0, NULL } +}; + +/* set the flags in flags/mask according to a string-identified flag and 'enable' */ +static int ipa_nvflag_set(uint16_t *flags, uint16_t *mask, const char *name, int en) +{ + int rc; + rc = get_string_value(ipa_nvflag_strs, name); + if (rc < 0) + return rc; + + *mask |= rc; + if (en) + *flags |= rc; + else + *flags &= ~rc; + + return 0; +} + +static void bootstrap_om(struct gsm_bts_trx *trx) +{ + struct msgb *nmsg = msgb_alloc(1024, "nested msgb"); + int need_to_set_attr = 0; + int len; + + printf("OML link established using TRX %d\n", trx->nr); + + if (unit_id) { + len = strlen(unit_id); + if (len > nmsg->data_len-10) + goto out_err; + printf("setting Unit ID to '%s'\n", unit_id); + nv_put_unit_id(nmsg, unit_id); + need_to_set_attr = 1; + } + if (prim_oml_ip) { + struct in_addr ia; + + if (!inet_aton(prim_oml_ip, &ia)) { + fprintf(stderr, "invalid IP address: %s\n", + prim_oml_ip); + goto out_err; + } + + printf("setting primary OML link IP to '%s'\n", inet_ntoa(ia)); + nv_put_prim_oml(nmsg, ntohl(ia.s_addr), 0); + need_to_set_attr = 1; + } + if (nv_mask) { + printf("setting NV Flags/Mask to 0x%04x/0x%04x\n", + nv_flags, nv_mask); + nv_put_flags(nmsg, nv_flags, nv_mask); + need_to_set_attr = 1; + } + if (bts_ip_addr && bts_ip_mask) { + struct in_addr ia_addr, ia_mask; + + if (!inet_aton(bts_ip_addr, &ia_addr)) { + fprintf(stderr, "invalid IP address: %s\n", + bts_ip_addr); + goto out_err; + } + + if (!inet_aton(bts_ip_mask, &ia_mask)) { + fprintf(stderr, "invalid IP address: %s\n", + bts_ip_mask); + goto out_err; + } + + printf("setting static IP Address/Mask\n"); + nv_put_ip_if_cfg(nmsg, ntohl(ia_addr.s_addr), ntohl(ia_mask.s_addr)); + need_to_set_attr = 1; + } + if (bts_ip_gw) { + struct in_addr ia_gw; + + if (!inet_aton(bts_ip_gw, &ia_gw)) { + fprintf(stderr, "invalid IP address: %s\n", + bts_ip_gw); + goto out_err; + } + + printf("setting static IP Gateway\n"); + /* we only set the default gateway with zero addr/mask */ + nv_put_gw_cfg(nmsg, 0, 0, ntohl(ia_gw.s_addr)); + need_to_set_attr = 1; + } + + if (need_to_set_attr) { + abis_nm_ipaccess_set_nvattr(trx, nmsg->head, nmsg->len); + oml_state = 1; + } + + if (restart && !prim_oml_ip && !software) { + printf("restarting BTS\n"); + abis_nm_ipaccess_restart(trx); + } + +out_err: + msgb_free(nmsg); +} + +static int nm_state_event(int evt, u_int8_t obj_class, void *obj, + struct gsm_nm_state *old_state, struct gsm_nm_state *new_state, + struct abis_om_obj_inst *obj_inst) +{ + if (obj_class == NM_OC_BASEB_TRANSC) { + if (!found_trx && obj_inst->trx_nr != 0xff) { + struct gsm_bts_trx *trx = container_of(obj, struct gsm_bts_trx, bb_transc); + bootstrap_om(trx); + found_trx = 1; + } + } else if (evt == S_NM_STATECHG_OPER && + obj_class == NM_OC_RADIO_CARRIER && + new_state->availability == 3) { + struct gsm_bts_trx *trx = obj; + + if (net_listen_testnr) + ipac_nwl_test_start(trx, net_listen_testnr, + phys_conf_min, sizeof(phys_conf_min)); + else if (software) { + int rc; + printf("Attempting software upload with '%s'\n", software); + rc = abis_nm_software_load(trx->bts, trx->nr, software, 19, 0, swload_cbfn, trx); + if (rc < 0) { + fprintf(stderr, "Failed to start software load\n"); + exit(-3); + } + } + } + return 0; +} + +static struct sw_load *create_swload(struct sdp_header *header) +{ + struct sw_load *load; + + load = talloc_zero(tall_ctx_config, struct sw_load); + + strncpy((char *)load->file_id, header->firmware_info.sw_part, 20); + load->file_id_len = strlen(header->firmware_info.sw_part) + 1; + + strncpy((char *)load->file_version, header->firmware_info.version, 20); + load->file_version_len = strlen(header->firmware_info.version) + 1; + + return load; +} + +static int find_sw_load_params(const char *filename) +{ + struct stat stat; + struct sdp_header *header; + struct llist_head *entry; + int fd; + void *tall_firm_ctx = 0; + + entry = talloc_zero(tall_firm_ctx, struct llist_head); + INIT_LLIST_HEAD(entry); + + fd = open(filename, O_RDONLY); + if (!fd) { + perror("nada"); + return -1; + } + + /* verify the file */ + if (fstat(fd, &stat) == -1) { + perror("Can not stat the file"); + return -1; + } + + ipaccess_analyze_file(fd, stat.st_size, 0, entry); + if (close(fd) != 0) { + perror("Close failed.\n"); + return -1; + } + + /* try to find what we are looking for */ + llist_for_each_entry(header, entry, entry) { + if (ntohs(header->firmware_info.more_more_magic) == 0x1000) { + sw_load1 = create_swload(header); + } else if (ntohs(header->firmware_info.more_more_magic) == 0x2001) { + sw_load2 = create_swload(header); + } + } + + if (!sw_load1 || !sw_load2) { + fprintf(stderr, "Did not find data.\n"); + talloc_free(tall_firm_ctx); + return -1; + } + + talloc_free(tall_firm_ctx); + return 0; +} + +static void dump_entry(struct sdp_header_item *sub_entry, int part, int fd) +{ + int out_fd; + int copied; + char filename[4096]; + off_t target; + + if (!dump_files) + return; + + if (sub_entry->header_entry.something1 == 0) + return; + + snprintf(filename, sizeof(filename), "part.%d", part++); + out_fd = open(filename, O_WRONLY | O_CREAT, 0660); + if (out_fd < 0) { + perror("Can not dump firmware"); + return; + } + + target = sub_entry->absolute_offset + ntohl(sub_entry->header_entry.start) + 4; + if (lseek(fd, target, SEEK_SET) != target) { + perror("seek failed"); + close(out_fd); + return; + } + + for (copied = 0; copied < ntohl(sub_entry->header_entry.length); ++copied) { + char c; + if (read(fd, &c, sizeof(c)) != sizeof(c)) { + perror("copy failed"); + break; + } + + if (write(out_fd, &c, sizeof(c)) != sizeof(c)) { + perror("write failed"); + break; + } + } + + close(out_fd); +} + +static void analyze_firmware(const char *filename) +{ + struct stat stat; + struct sdp_header *header; + struct sdp_header_item *sub_entry; + struct llist_head *entry; + int fd; + void *tall_firm_ctx = 0; + int part = 0; + + entry = talloc_zero(tall_firm_ctx, struct llist_head); + INIT_LLIST_HEAD(entry); + + printf("Opening possible firmware '%s'\n", filename); + fd = open(filename, O_RDONLY); + if (!fd) { + perror("nada"); + return; + } + + /* verify the file */ + if (fstat(fd, &stat) == -1) { + perror("Can not stat the file"); + return; + } + + ipaccess_analyze_file(fd, stat.st_size, 0, entry); + + llist_for_each_entry(header, entry, entry) { + printf("Printing header information:\n"); + printf("more_more_magic: 0x%x\n", ntohs(header->firmware_info.more_more_magic)); + printf("header_length: %u\n", ntohl(header->firmware_info.header_length)); + printf("file_length: %u\n", ntohl(header->firmware_info.file_length)); + printf("sw_part: %.20s\n", header->firmware_info.sw_part); + printf("text1: %.64s\n", header->firmware_info.text1); + printf("time: %.12s\n", header->firmware_info.time); + printf("date: %.14s\n", header->firmware_info.date); + printf("text2: %.10s\n", header->firmware_info.text2); + printf("version: %.20s\n", header->firmware_info.version); + printf("subitems...\n"); + + llist_for_each_entry(sub_entry, &header->header_list, entry) { + printf("\tsomething1: %u\n", sub_entry->header_entry.something1); + printf("\ttext1: %.64s\n", sub_entry->header_entry.text1); + printf("\ttime: %.12s\n", sub_entry->header_entry.time); + printf("\tdate: %.14s\n", sub_entry->header_entry.date); + printf("\ttext2: %.10s\n", sub_entry->header_entry.text2); + printf("\tversion: %.20s\n", sub_entry->header_entry.version); + printf("\tlength: %u\n", ntohl(sub_entry->header_entry.length)); + printf("\taddr1: 0x%x\n", ntohl(sub_entry->header_entry.addr1)); + printf("\taddr2: 0x%x\n", ntohl(sub_entry->header_entry.addr2)); + printf("\tstart: 0x%x\n", ntohl(sub_entry->header_entry.start)); + printf("\tabs. offset: 0x%lx\n", sub_entry->absolute_offset); + printf("\n\n"); + + dump_entry(sub_entry, part++, fd); + } + printf("\n\n"); + } + + if (close(fd) != 0) { + perror("Close failed.\n"); + return; + } + + talloc_free(tall_firm_ctx); +} + +static void print_usage(void) +{ + printf("Usage: ipaccess-config\n"); +} + +static void print_help(void) +{ +#if 0 + printf("Commmands for reading from the BTS:\n"); + printf(" -D --dump\t\t\tDump the BTS configuration\n"); + printf("\n"); +#endif + printf("Commmands for writing to the BTS:\n"); + printf(" -u --unit-id UNIT_ID\t\tSet the Unit ID of the BTS\n"); + printf(" -o --oml-ip IP\t\tSet primary OML IP (IP of your BSC)\n"); + printf(" -i --ip-address IP/MASK\tSet static IP address + netmask of BTS\n"); + printf(" -g --ip-gateway IP\t\tSet static IP gateway of BTS\n"); + printf(" -r --restart\t\t\tRestart the BTS (after other operations)\n"); + printf(" -n --nvram-flags FLAGS/MASK\tSet NVRAM attributes\n"); + printf(" -S --nvattr-set FLAG\tSet one additional NVRAM attribute\n"); + printf(" -U --nvattr-unset FLAG\tSet one additional NVRAM attribute\n"); + printf(" -l --listen TESTNR\t\tPerform specified test number\n"); + printf(" -s --stream-id ID\t\tSet the IPA Stream Identifier for OML\n"); + printf(" -d --software FIRMWARE\tDownload firmware into BTS\n"); + printf("\n"); + printf("Miscellaneous commands:\n"); + printf(" -h --help\t\t\tthis text\n"); + printf(" -f --firmware FIRMWARE\tProvide firmware information\n"); + printf(" -w --write-firmware\t\tThis will dump the firmware parts to the filesystem. Use with -f.\n"); +} + +extern void bts_model_nanobts_init(); + +int main(int argc, char **argv) +{ + struct gsm_bts *bts; + struct sockaddr_in sin; + int rc, option_index = 0, stream_id = 0xff; + struct log_target *stderr_target; + + log_init(&log_info); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + log_set_all_filter(stderr_target, 1); + log_set_log_level(stderr_target, 0); + log_parse_category_mask(stderr_target, "DNM,0"); + bts_model_nanobts_init(); + + printf("ipaccess-config (C) 2009-2010 by Harald Welte and others\n"); + printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n"); + + while (1) { + int c; + unsigned long ul; + char *slash; + static struct option long_options[] = { + { "unit-id", 1, 0, 'u' }, + { "oml-ip", 1, 0, 'o' }, + { "ip-address", 1, 0, 'i' }, + { "ip-gateway", 1, 0, 'g' }, + { "restart", 0, 0, 'r' }, + { "nvram-flags", 1, 0, 'n' }, + { "nvattr-set", 1, 0, 'S' }, + { "nvattr-unset", 1, 0, 'U' }, + { "help", 0, 0, 'h' }, + { "listen", 1, 0, 'l' }, + { "stream-id", 1, 0, 's' }, + { "software", 1, 0, 'd' }, + { "firmware", 1, 0, 'f' }, + { "write-firmware", 0, 0, 'w' }, + { "disable-color", 0, 0, 'c'}, + { 0, 0, 0, 0 }, + }; + + c = getopt_long(argc, argv, "u:o:i:g:rn:S:U:l:hs:d:f:wc", long_options, + &option_index); + + if (c == -1) + break; + + switch (c) { + case 'u': + unit_id = optarg; + break; + case 'o': + prim_oml_ip = optarg; + break; + case 'i': + slash = strchr(optarg, '/'); + if (!slash) + exit(2); + bts_ip_addr = optarg; + *slash = 0; + bts_ip_mask = slash+1; + break; + case 'g': + bts_ip_gw = optarg; + break; + case 'r': + restart = 1; + break; + case 'n': + slash = strchr(optarg, '/'); + if (!slash) + exit(2); + ul = strtoul(optarg, NULL, 16); + nv_flags = ul & 0xffff; + ul = strtoul(slash+1, NULL, 16); + nv_mask = ul & 0xffff; + break; + case 'S': + if (ipa_nvflag_set(&nv_flags, &nv_mask, optarg, 1) < 0) + exit(2); + break; + case 'U': + if (ipa_nvflag_set(&nv_flags, &nv_mask, optarg, 0) < 0) + exit(2); + break; + case 'l': + net_listen_testnr = atoi(optarg); + break; + case 's': + stream_id = atoi(optarg); + break; + case 'd': + software = strdup(optarg); + if (find_sw_load_params(optarg) != 0) + exit(0); + break; + case 'f': + firmware_analysis = optarg; + break; + case 'w': + dump_files = 1; + break; + case 'c': + log_set_use_color(stderr_target, 0); + break; + case 'h': + print_usage(); + print_help(); + exit(0); + } + }; + + if (firmware_analysis) + analyze_firmware(firmware_analysis); + + if (optind >= argc) { + /* only warn if we have not done anything else */ + if (!firmware_analysis) + fprintf(stderr, "you have to specify the IP address of the BTS. Use --help for more information\n"); + exit(2); + } + + gsmnet = gsm_network_init(1, 1, NULL); + if (!gsmnet) + exit(1); + + bts = gsm_bts_alloc(gsmnet, GSM_BTS_TYPE_NANOBTS, HARDCODED_TSC, + HARDCODED_BSIC); + /* ip.access supports up to 4 chained TRX */ + gsm_bts_trx_alloc(bts); + gsm_bts_trx_alloc(bts); + gsm_bts_trx_alloc(bts); + bts->oml_tei = stream_id; + + register_signal_handler(SS_NM, nm_sig_cb, NULL); + register_signal_handler(SS_IPAC_NWL, nwl_sig_cb, NULL); + + ipac_nwl_init(); + + printf("Trying to connect to ip.access BTS ...\n"); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + inet_aton(argv[optind], &sin.sin_addr); + rc = ia_config_connect(bts, &sin); + if (rc < 0) { + perror("Error connecting to the BTS"); + exit(1); + } + + bts->oml_link->ts->sign.delay = 10; + bts->c0->rsl_link->ts->sign.delay = 10; + while (1) { + rc = bsc_select_main(0); + if (rc < 0) + exit(3); + } + + exit(0); +} + diff --git a/src/ipaccess/ipaccess-find.c b/src/ipaccess/ipaccess-find.c new file mode 100644 index 000000000..bea4b77ad --- /dev/null +++ b/src/ipaccess/ipaccess-find.c @@ -0,0 +1,227 @@ +/* ip.access nanoBTS configuration tool */ + +/* (C) 2009-2010 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include + +static const char *idtag_names[] = { + [IPAC_IDTAG_SERNR] = "Serial Number", + [IPAC_IDTAG_UNITNAME] = "Unit Name", + [IPAC_IDTAG_LOCATION1] = "Location 1", + [IPAC_IDTAG_LOCATION2] = "Location 2", + [IPAC_IDTAG_EQUIPVERS] = "Equipment Version", + [IPAC_IDTAG_SWVERSION] = "Software Version", + [IPAC_IDTAG_IPADDR] = "IP Address", + [IPAC_IDTAG_MACADDR] = "MAC Address", + [IPAC_IDTAG_UNIT] = "Unit ID", +}; + +static const char *ipac_idtag_name(int tag) +{ + if (tag >= ARRAY_SIZE(idtag_names)) + return "unknown"; + + return idtag_names[tag]; +} + +static int udp_sock(const char *ifname) +{ + int fd, rc, bc = 1; + struct sockaddr_in sa; + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) + return fd; + + if (ifname) { + rc = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, + strlen(ifname)); + if (rc < 0) + goto err; + } + + sa.sin_family = AF_INET; + sa.sin_port = htons(3006); + sa.sin_addr.s_addr = INADDR_ANY; + + rc = bind(fd, (struct sockaddr *)&sa, sizeof(sa)); + if (rc < 0) + goto err; + + rc = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &bc, sizeof(bc)); + if (rc < 0) + goto err; + +#if 0 + /* we cannot bind, since the response packets don't come from + * the broadcast address */ + sa.sin_family = AF_INET; + sa.sin_port = htons(3006); + inet_aton("255.255.255.255", &sa.sin_addr); + + rc = connect(fd, (struct sockaddr *)&sa, sizeof(sa)); + if (rc < 0) + goto err; +#endif + return fd; + +err: + close(fd); + return rc; +} + +const unsigned char find_pkt[] = { 0x00, 0x0b+8, IPAC_PROTO_IPACCESS, 0x00, + IPAC_MSGT_ID_GET, + 0x01, IPAC_IDTAG_MACADDR, + 0x01, IPAC_IDTAG_IPADDR, + 0x01, IPAC_IDTAG_UNIT, + 0x01, IPAC_IDTAG_LOCATION1, + 0x01, IPAC_IDTAG_LOCATION2, + 0x01, IPAC_IDTAG_EQUIPVERS, + 0x01, IPAC_IDTAG_SWVERSION, + 0x01, IPAC_IDTAG_UNITNAME, + 0x01, IPAC_IDTAG_SERNR, + }; + + +static int bcast_find(int fd) +{ + struct sockaddr_in sa; + + sa.sin_family = AF_INET; + sa.sin_port = htons(3006); + inet_aton("255.255.255.255", &sa.sin_addr); + + return sendto(fd, find_pkt, sizeof(find_pkt), 0, (struct sockaddr *) &sa, sizeof(sa)); +} + +static int parse_response(unsigned char *buf, int len) +{ + u_int8_t t_len; + u_int8_t t_tag; + u_int8_t *cur = buf; + + while (cur < buf + len) { + t_len = *cur++; + t_tag = *cur++; + + printf("%s='%s' ", ipac_idtag_name(t_tag), cur); + + cur += t_len; + } + printf("\n"); + return 0; +} + +static int read_response(int fd) +{ + unsigned char buf[255]; + struct sockaddr_in sa; + int len; + socklen_t sa_len = sizeof(sa); + + len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sa, &sa_len); + if (len < 0) + return len; + + /* 2 bytes length, 1 byte protocol (0xfe) */ + if (buf[2] != 0xfe) + return 0; + + if (buf[4] != IPAC_MSGT_ID_RESP) + return 0; + + return parse_response(buf+6, len-6); +} + +static int bfd_cb(struct bsc_fd *bfd, unsigned int flags) +{ + if (flags & BSC_FD_READ) + return read_response(bfd->fd); + if (flags & BSC_FD_WRITE) { + bfd->when &= ~BSC_FD_WRITE; + return bcast_find(bfd->fd); + } + return 0; +} + +static struct timer_list timer; + +static void timer_cb(void *_data) +{ + struct bsc_fd *bfd = _data; + + bfd->when |= BSC_FD_WRITE; + + bsc_schedule_timer(&timer, 5, 0); +} + +int main(int argc, char **argv) +{ + struct bsc_fd bfd; + char *ifname; + int rc; + + printf("ipaccess-find (C) 2009 by Harald Welte\n"); + printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n"); + + if (argc < 2) { + fprintf(stdout, "you might need to specify the outgoing\n" + " network interface, e.g. ``%s eth0''\n", argv[0]); + } + + ifname = argv[1]; + bfd.cb = bfd_cb; + bfd.when = BSC_FD_READ | BSC_FD_WRITE; + bfd.fd = udp_sock(ifname); + if (bfd.fd < 0) { + perror("Cannot create local socket for broadcast udp"); + exit(1); + } + + bsc_register_fd(&bfd); + + timer.cb = timer_cb; + timer.data = &bfd; + + bsc_schedule_timer(&timer, 5, 0); + + printf("Trying to find ip.access BTS by broadcast UDP...\n"); + + while (1) { + rc = bsc_select_main(0); + if (rc < 0) + exit(3); + } + + exit(0); +} + diff --git a/src/ipaccess/ipaccess-firmware.c b/src/ipaccess/ipaccess-firmware.c new file mode 100644 index 000000000..7fdd0f825 --- /dev/null +++ b/src/ipaccess/ipaccess-firmware.c @@ -0,0 +1,135 @@ +/* Routines for parsing an ipacces SDP firmware file */ + +/* (C) 2009 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include +#include +#include + +#define PART_LENGTH 138 + +static_assert(sizeof(struct sdp_header_entry) == 138, right_entry); +static_assert(sizeof(struct sdp_firmware) == 158, _right_header_length); + +/* more magic, the second "int" in the header */ +static char more_magic[] = { 0x10, 0x02 }; + +int ipaccess_analyze_file(int fd, const unsigned int st_size, const unsigned int base_offset, struct llist_head *list) +{ + struct sdp_firmware *firmware_header = 0; + struct sdp_header *header; + char buf[4096]; + int rc, i; + u_int16_t table_size; + u_int16_t table_offset; + off_t table_start; + + + rc = read(fd, buf, sizeof(*firmware_header)); + if (rc < 0) { + perror("Can not read header start."); + return -1; + } + + firmware_header = (struct sdp_firmware *) &buf[0]; + if (strncmp(firmware_header->magic, " SDP", 4) != 0) { + fprintf(stderr, "Wrong magic.\n"); + return -1; + } + + if (memcmp(firmware_header->more_magic, more_magic, 2) != 0) { + fprintf(stderr, "Wrong more magic. Got: 0x%x %x %x %x\n", + firmware_header->more_magic[0] & 0xff, firmware_header->more_magic[1] & 0xff, + firmware_header->more_magic[2] & 0xff, firmware_header->more_magic[3] & 0xff); + return -1; + } + + + if (ntohl(firmware_header->file_length) != st_size) { + fprintf(stderr, "The filesize and the header do not match.\n"); + return -1; + } + + /* add the firmware */ + header = talloc_zero(list, struct sdp_header); + header->firmware_info = *firmware_header; + INIT_LLIST_HEAD(&header->header_list); + llist_add(&header->entry, list); + + table_offset = ntohs(firmware_header->table_offset); + table_start = lseek(fd, table_offset, SEEK_CUR); + if (table_start == -1) { + fprintf(stderr, "Failed to seek to the rel position: 0x%x\n", table_offset); + return -1; + } + + if (read(fd, &table_size, sizeof(table_size)) != sizeof(table_size)) { + fprintf(stderr, "The table size could not be read.\n"); + return -1; + } + + table_size = ntohs(table_size); + + if (table_size % PART_LENGTH != 0) { + fprintf(stderr, "The part length seems to be wrong: 0x%x\n", table_size); + return -1; + } + + /* look into each firmware now */ + for (i = 0; i < table_size / PART_LENGTH; ++i) { + struct sdp_header_entry entry; + struct sdp_header_item *header_entry; + unsigned int offset = table_start + 2; + offset += i * 138; + + if (lseek(fd, offset, SEEK_SET) != offset) { + fprintf(stderr, "Can not seek to the offset: %u.\n", offset); + return -1; + } + + rc = read(fd, &entry, sizeof(entry)); + if (rc != sizeof(entry)) { + fprintf(stderr, "Can not read the header entry.\n"); + return -1; + } + + header_entry = talloc_zero(header, struct sdp_header_item); + header_entry->header_entry = entry; + header_entry->absolute_offset = base_offset; + llist_add(&header_entry->entry, &header->header_list); + + /* now we need to find the SDP file... */ + offset = ntohl(entry.start) + 4 + base_offset; + if (lseek(fd, offset, SEEK_SET) != offset) { + perror("can't seek to sdp"); + return -1; + } + + + ipaccess_analyze_file(fd, ntohl(entry.length), offset, list); + } + + return 0; +} + diff --git a/src/ipaccess/ipaccess-proxy.c b/src/ipaccess/ipaccess-proxy.c new file mode 100644 index 000000000..2dc1b2f62 --- /dev/null +++ b/src/ipaccess/ipaccess-proxy.c @@ -0,0 +1,1373 @@ +/* OpenBSC Abis/IP proxy ip.access nanoBTS */ + +/* (C) 2009 by Harald Welte + * (C) 2010 by On-Waves + * (C) 2010 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define _GNU_SOURCE +#include + +#include +#include +#include +#include +#include +#include +#include + +static struct log_target *stderr_target; + +/* one instance of an ip.access protocol proxy */ +struct ipa_proxy { + /* socket where we listen for incoming OML from BTS */ + struct bsc_fd oml_listen_fd; + /* socket where we listen for incoming RSL from BTS */ + struct bsc_fd rsl_listen_fd; + /* list of BTS's (struct ipa_bts_conn */ + struct llist_head bts_list; + /* the BSC reconnect timer */ + struct timer_list reconn_timer; + /* global GPRS NS data */ + struct in_addr gprs_addr; + struct in_addr listen_addr; +}; + +/* global pointer to the proxy structure */ +static struct ipa_proxy *ipp; + +struct ipa_proxy_conn { + struct bsc_fd fd; + struct llist_head tx_queue; + struct ipa_bts_conn *bts_conn; +}; +#define MAX_TRX 4 + +/* represents a particular BTS in our proxy */ +struct ipa_bts_conn { + /* list of BTS's (ipa_proxy->bts_list) */ + struct llist_head list; + /* back pointer to the proxy which we belong to */ + struct ipa_proxy *ipp; + /* the unit ID as determined by CCM */ + struct { + u_int16_t site_id; + u_int16_t bts_id; + } unit_id; + + /* incoming connections from BTS */ + struct ipa_proxy_conn *oml_conn; + struct ipa_proxy_conn *rsl_conn[MAX_TRX]; + + /* outgoing connections to BSC */ + struct ipa_proxy_conn *bsc_oml_conn; + struct ipa_proxy_conn *bsc_rsl_conn[MAX_TRX]; + + /* UDP sockets for BTS and BSC injection */ + struct bsc_fd udp_bts_fd; + struct bsc_fd udp_bsc_fd; + + /* NS data */ + struct in_addr bts_addr; + struct bsc_fd gprs_ns_fd; + int gprs_local_port; + uint16_t gprs_orig_port; + uint32_t gprs_orig_ip; + + char *id_tags[0xff]; + u_int8_t *id_resp; + unsigned int id_resp_len; +}; + +enum ipp_fd_type { + OML_FROM_BTS = 1, + RSL_FROM_BTS = 2, + OML_TO_BSC = 3, + RSL_TO_BSC = 4, + UDP_TO_BTS = 5, + UDP_TO_BSC = 6, +}; + +/* some of the code against we link from OpenBSC needs this */ +void *tall_bsc_ctx; + +static char *listen_ipaddr; +static char *bsc_ipaddr; +static char *gprs_ns_ipaddr; + +static int make_gprs_sock(struct bsc_fd *bfd, int (*cb)(struct bsc_fd*,unsigned int), void *); +static int gprs_ns_cb(struct bsc_fd *bfd, unsigned int what); + +#define PROXY_ALLOC_SIZE 1200 + +static const u_int8_t pong[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_PONG }; +static const u_int8_t id_ack[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_ACK }; +static const u_int8_t id_req[] = { 0, 17, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_GET, + 0x01, IPAC_IDTAG_UNIT, + 0x01, IPAC_IDTAG_MACADDR, + 0x01, IPAC_IDTAG_LOCATION1, + 0x01, IPAC_IDTAG_LOCATION2, + 0x01, IPAC_IDTAG_EQUIPVERS, + 0x01, IPAC_IDTAG_SWVERSION, + 0x01, IPAC_IDTAG_UNITNAME, + 0x01, IPAC_IDTAG_SERNR, + }; + +static const char *idtag_names[] = { + [IPAC_IDTAG_SERNR] = "Serial_Number", + [IPAC_IDTAG_UNITNAME] = "Unit_Name", + [IPAC_IDTAG_LOCATION1] = "Location_1", + [IPAC_IDTAG_LOCATION2] = "Location_2", + [IPAC_IDTAG_EQUIPVERS] = "Equipment_Version", + [IPAC_IDTAG_SWVERSION] = "Software_Version", + [IPAC_IDTAG_IPADDR] = "IP_Address", + [IPAC_IDTAG_MACADDR] = "MAC_Address", + [IPAC_IDTAG_UNIT] = "Unit_ID", +}; + +static const char *ipac_idtag_name(int tag) +{ + if (tag >= ARRAY_SIZE(idtag_names)) + return "unknown"; + + return idtag_names[tag]; +} + +static int ipac_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len) +{ + u_int8_t t_len; + u_int8_t t_tag; + u_int8_t *cur = buf; + + while (cur < buf + len) { + t_len = *cur++; + t_tag = *cur++; + + DEBUGPC(DMI, "%s='%s' ", ipac_idtag_name(t_tag), cur); + + dec->lv[t_tag].len = t_len; + dec->lv[t_tag].val = cur; + + cur += t_len; + } + return 0; +} + +static int parse_unitid(const char *str, u_int16_t *site_id, u_int16_t *bts_id, + u_int16_t *trx_id) +{ + unsigned long ul; + char *endptr; + const char *nptr; + + nptr = str; + ul = strtoul(nptr, &endptr, 10); + if (endptr <= nptr) + return -EINVAL; + if (site_id) + *site_id = ul & 0xffff; + + if (*endptr++ != '/') + return -EINVAL; + + nptr = endptr; + ul = strtoul(nptr, &endptr, 10); + if (endptr <= nptr) + return -EINVAL; + if (bts_id) + *bts_id = ul & 0xffff; + + if (*endptr++ != '/') + return -EINVAL; + + nptr = endptr; + ul = strtoul(nptr, &endptr, 10); + if (endptr <= nptr) + return -EINVAL; + if (trx_id) + *trx_id = ul & 0xffff; + + return 0; +} + +static struct ipa_bts_conn *find_bts_by_unitid(struct ipa_proxy *ipp, + u_int16_t site_id, + u_int16_t bts_id) +{ + struct ipa_bts_conn *ipbc; + + llist_for_each_entry(ipbc, &ipp->bts_list, list) { + if (ipbc->unit_id.site_id == site_id && + ipbc->unit_id.bts_id == bts_id) + return ipbc; + } + + return NULL; +} + +struct ipa_proxy_conn *alloc_conn(void) +{ + struct ipa_proxy_conn *ipc; + + ipc = talloc_zero(tall_bsc_ctx, struct ipa_proxy_conn); + if (!ipc) + return NULL; + + INIT_LLIST_HEAD(&ipc->tx_queue); + + return ipc; +} + +static int store_idtags(struct ipa_bts_conn *ipbc, struct tlv_parsed *tlvp) +{ + unsigned int i, len; + + for (i = 0; i <= 0xff; i++) { + if (!TLVP_PRESENT(tlvp, i)) + continue; + + len = TLVP_LEN(tlvp, i); +#if 0 + if (!ipbc->id_tags[i]) + ipbc->id_tags[i] = talloc_size(tall_bsc_ctx, len); + else +#endif + ipbc->id_tags[i] = talloc_realloc_size(ipbc, + ipbc->id_tags[i], len); + if (!ipbc->id_tags[i]) + return -ENOMEM; + + memset(ipbc->id_tags[i], 0, len); + //memcpy(ipbc->id_tags[i], TLVP_VAL(tlvp, i), len); + } + return 0; +} + + +static struct ipa_proxy_conn *connect_bsc(struct sockaddr_in *sa, int priv_nr, void *data); + +#define logp_ipbc_uid(ss, lvl, ipbc, trx_id) _logp_ipbc_uid(ss, lvl, __FILE__, __LINE__, ipbc, trx_id) + +static void _logp_ipbc_uid(unsigned int ss, unsigned int lvl, char *file, int line, + struct ipa_bts_conn *ipbc, u_int8_t trx_id) +{ + if (ipbc) + logp2(ss, lvl, file, line, 0, "(%u/%u/%u) ", ipbc->unit_id.site_id, + ipbc->unit_id.bts_id, trx_id); + else + logp2(ss, lvl, file, line, 0, "unknown "); +} + +/* UDP socket handling */ + +static int make_sock(struct bsc_fd *bfd, u_int16_t port, int proto, int priv_nr, + int (*cb)(struct bsc_fd *fd, unsigned int what), + void *data) +{ + struct sockaddr_in addr; + int ret, on = 1; + + bfd->fd = socket(AF_INET, SOCK_DGRAM, proto); + bfd->cb = cb; + bfd->when = BSC_FD_READ; + bfd->data = data; + bfd->priv_nr = priv_nr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + + setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr)); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "could not bind socket: %s\n", + strerror(errno)); + return -EIO; + } + + ret = bsc_register_fd(bfd); + if (ret < 0) { + perror("register UDP fd"); + return ret; + } + return 0; +} + +static int handle_udp_read(struct bsc_fd *bfd) +{ + struct ipa_bts_conn *ipbc = bfd->data; + struct ipa_proxy_conn *other_conn = NULL; + struct msgb *msg = msgb_alloc(PROXY_ALLOC_SIZE, "Abis/IP UDP"); + struct ipaccess_head *hh; + int ret; + + /* with UDP sockets, we cannot read partial packets but have to read + * all of it in one go */ + hh = (struct ipaccess_head *) msg->data; + ret = recv(bfd->fd, msg->data, msg->data_len, 0); + if (ret < 0) { + if (errno != EAGAIN) + LOGP(DINP, LOGL_ERROR, "recv error %s\n", strerror(errno)); + msgb_free(msg); + return ret; + } + if (ret == 0) { + DEBUGP(DINP, "UDP peer disappeared, dead socket\n"); + bsc_unregister_fd(bfd); + close(bfd->fd); + bfd->fd = -1; + msgb_free(msg); + return -EIO; + } + if (ret < sizeof(*hh)) { + DEBUGP(DINP, "could not even read header!?!\n"); + msgb_free(msg); + return -EIO; + } + msgb_put(msg, ret); + msg->l2h = msg->data + sizeof(*hh); + DEBUGP(DMI, "UDP RX: %s\n", hexdump(msg->data, msg->len)); + + if (hh->len != msg->len - sizeof(*hh)) { + DEBUGP(DINP, "length (%u/%u) disagrees with header(%u)\n", + msg->len, msg->len - 3, hh->len); + msgb_free(msg); + return -EIO; + } + + switch (bfd->priv_nr & 0xff) { + case UDP_TO_BTS: + /* injection towards BTS */ + switch (hh->proto) { + case IPAC_PROTO_RSL: + /* FIXME: what to do about TRX > 0 */ + other_conn = ipbc->rsl_conn[0]; + break; + default: + DEBUGP(DINP, "Unknown protocol 0x%02x, sending to " + "OML FD\n", hh->proto); + /* fall through */ + case IPAC_PROTO_IPACCESS: + case IPAC_PROTO_OML: + other_conn = ipbc->oml_conn; + break; + } + break; + case UDP_TO_BSC: + /* injection towards BSC */ + switch (hh->proto) { + case IPAC_PROTO_RSL: + /* FIXME: what to do about TRX > 0 */ + other_conn = ipbc->bsc_rsl_conn[0]; + break; + default: + DEBUGP(DINP, "Unknown protocol 0x%02x, sending to " + "OML FD\n", hh->proto); + case IPAC_PROTO_IPACCESS: + case IPAC_PROTO_OML: + other_conn = ipbc->bsc_oml_conn; + break; + } + break; + default: + DEBUGP(DINP, "Unknown filedescriptor priv_nr=%04x\n", bfd->priv_nr); + break; + } + + if (other_conn) { + /* enqueue the message for TX on the respective FD */ + msgb_enqueue(&other_conn->tx_queue, msg); + other_conn->fd.when |= BSC_FD_WRITE; + } else + msgb_free(msg); + + return 0; +} + +static int handle_udp_write(struct bsc_fd *bfd) +{ + /* not implemented yet */ + bfd->when &= ~BSC_FD_WRITE; + + return -EIO; +} + +/* callback from select.c in case one of the fd's can be read/written */ +static int udp_fd_cb(struct bsc_fd *bfd, unsigned int what) +{ + int rc = 0; + + if (what & BSC_FD_READ) + rc = handle_udp_read(bfd); + if (what & BSC_FD_WRITE) + rc = handle_udp_write(bfd); + + return rc; +} + + +static int ipbc_alloc_connect(struct ipa_proxy_conn *ipc, struct bsc_fd *bfd, + u_int16_t site_id, u_int16_t bts_id, + u_int16_t trx_id, struct tlv_parsed *tlvp, + struct msgb *msg) +{ + struct ipa_bts_conn *ipbc; + u_int16_t udp_port; + int ret = 0; + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + inet_aton(bsc_ipaddr, &sin.sin_addr); + + DEBUGP(DINP, "(%u/%u/%u) New BTS connection: ", + site_id, bts_id, trx_id); + + /* OML needs to be established before RSL */ + if ((bfd->priv_nr & 0xff) != OML_FROM_BTS) { + DEBUGPC(DINP, "Not a OML connection ?!?\n"); + return -EIO; + } + + /* allocate new BTS connection data structure */ + ipbc = talloc_zero(tall_bsc_ctx, struct ipa_bts_conn); + if (!ipbc) { + ret = -ENOMEM; + goto err_out; + } + + DEBUGPC(DINP, "Created BTS Conn data structure\n"); + ipbc->ipp = ipp; + ipbc->unit_id.site_id = site_id; + ipbc->unit_id.bts_id = bts_id; + ipbc->oml_conn = ipc; + ipc->bts_conn = ipbc; + + /* store the content of the ID TAGS for later reference */ + store_idtags(ipbc, tlvp); + ipbc->id_resp_len = msg->len; + ipbc->id_resp = talloc_size(tall_bsc_ctx, ipbc->id_resp_len); + memcpy(ipbc->id_resp, msg->data, ipbc->id_resp_len); + + /* Create OML TCP connection towards BSC */ + sin.sin_port = htons(IPA_TCP_PORT_OML); + ipbc->bsc_oml_conn = connect_bsc(&sin, OML_TO_BSC, ipbc); + if (!ipbc->bsc_oml_conn) { + ret = -EIO; + goto err_bsc_conn; + } + + DEBUGP(DINP, "(%u/%u/%u) OML Connected to BSC\n", + site_id, bts_id, trx_id); + + /* Create UDP socket for BTS packet injection */ + udp_port = 10000 + (site_id % 1000)*100 + (bts_id % 100); + ret = make_sock(&ipbc->udp_bts_fd, udp_port, IPPROTO_UDP, + UDP_TO_BTS, udp_fd_cb, ipbc); + if (ret < 0) + goto err_udp_bts; + DEBUGP(DINP, "(%u/%u/%u) Created UDP socket for injection " + "towards BTS at port %u\n", site_id, bts_id, trx_id, udp_port); + + /* Create UDP socket for BSC packet injection */ + udp_port = 20000 + (site_id % 1000)*100 + (bts_id % 100); + ret = make_sock(&ipbc->udp_bsc_fd, udp_port, IPPROTO_UDP, + UDP_TO_BSC, udp_fd_cb, ipbc); + if (ret < 0) + goto err_udp_bsc; + DEBUGP(DINP, "(%u/%u/%u) Created UDP socket for injection " + "towards BSC at port %u\n", site_id, bts_id, trx_id, udp_port); + + + /* GPRS NS related code */ + if (gprs_ns_ipaddr) { + struct sockaddr_in sock; + socklen_t len = sizeof(sock); + ret = make_gprs_sock(&ipbc->gprs_ns_fd, gprs_ns_cb, ipbc); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "Creating the GPRS socket failed.\n"); + goto err_udp_bsc; + } + + ret = getsockname(ipbc->gprs_ns_fd.fd, (struct sockaddr* ) &sock, &len); + ipbc->gprs_local_port = ntohs(sock.sin_port); + LOGP(DINP, LOGL_NOTICE, + "Created GPRS NS Socket. Listening on: %s:%d\n", + inet_ntoa(sock.sin_addr), ipbc->gprs_local_port); + + ret = getpeername(bfd->fd, (struct sockaddr* ) &sock, &len); + ipbc->bts_addr = sock.sin_addr; + } + + llist_add(&ipbc->list, &ipp->bts_list); + + return 0; + +err_udp_bsc: + bsc_unregister_fd(&ipbc->udp_bts_fd); +err_udp_bts: + bsc_unregister_fd(&ipbc->bsc_oml_conn->fd); + close(ipbc->bsc_oml_conn->fd.fd); + talloc_free(ipbc->bsc_oml_conn); + ipbc->bsc_oml_conn = NULL; +err_bsc_conn: + talloc_free(ipbc->id_resp); + talloc_free(ipbc); +#if 0 + bsc_unregister_fd(bfd); + close(bfd->fd); + talloc_free(bfd); +#endif +err_out: + return ret; +} + +static int ipaccess_rcvmsg(struct ipa_proxy_conn *ipc, struct msgb *msg, + struct bsc_fd *bfd) +{ + struct tlv_parsed tlvp; + u_int8_t msg_type = *(msg->l2h); + u_int16_t site_id, bts_id, trx_id; + struct ipa_bts_conn *ipbc; + int ret = 0; + + switch (msg_type) { + case IPAC_MSGT_PING: + ret = write(bfd->fd, pong, sizeof(pong)); + if (ret < 0) + return ret; + if (ret < sizeof(pong)) { + DEBUGP(DINP, "short write\n"); + return -EIO; + } + break; + case IPAC_MSGT_PONG: + DEBUGP(DMI, "PONG!\n"); + break; + case IPAC_MSGT_ID_RESP: + DEBUGP(DMI, "ID_RESP "); + /* parse tags, search for Unit ID */ + ipac_idtag_parse(&tlvp, (u_int8_t *)msg->l2h + 2, + msgb_l2len(msg)-2); + DEBUGP(DMI, "\n"); + + if (!TLVP_PRESENT(&tlvp, IPAC_IDTAG_UNIT)) { + LOGP(DINP, LOGL_ERROR, "No Unit ID in ID RESPONSE !?!\n"); + return -EIO; + } + + /* lookup BTS, create sign_link, ... */ + site_id = bts_id = trx_id = 0; + parse_unitid((char *)TLVP_VAL(&tlvp, IPAC_IDTAG_UNIT), + &site_id, &bts_id, &trx_id); + ipbc = find_bts_by_unitid(ipp, site_id, bts_id); + if (!ipbc) { + /* We have not found an ipbc (per-bts proxy instance) + * for this BTS yet. The first connection of a new BTS must + * be a OML connection. We allocate the associated data structures, + * and try to connect to the remote end */ + + return ipbc_alloc_connect(ipc, bfd, site_id, bts_id, + trx_id, &tlvp, msg); + /* if this fails, the caller will clean up bfd */ + } else { + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + inet_aton(bsc_ipaddr, &sin.sin_addr); + + DEBUGP(DINP, "Identified BTS %u/%u/%u\n", + site_id, bts_id, trx_id); + + if ((bfd->priv_nr & 0xff) != RSL_FROM_BTS) { + LOGP(DINP, LOGL_ERROR, "Second OML connection from " + "same BTS ?!?\n"); + return 0; + } + + if (trx_id > MAX_TRX) { + LOGP(DINP, LOGL_ERROR, "We don't support more " + "than %u TRX\n", MAX_TRX); + return -EINVAL; + } + + ipc->bts_conn = ipbc; + /* store TRX number in higher 8 bit of the bfd private number */ + bfd->priv_nr |= trx_id << 8; + ipbc->rsl_conn[trx_id] = ipc; + + /* Create RSL TCP connection towards BSC */ + sin.sin_port = htons(IPA_TCP_PORT_RSL); + ipbc->bsc_rsl_conn[trx_id] = + connect_bsc(&sin, RSL_TO_BSC | (trx_id << 8), ipbc); + if (!ipbc->bsc_oml_conn) + return -EIO; + DEBUGP(DINP, "(%u/%u/%u) Connected RSL to BSC\n", + site_id, bts_id, trx_id); + } + break; + case IPAC_MSGT_ID_GET: + DEBUGP(DMI, "ID_GET\n"); + if ((bfd->priv_nr & 0xff) != OML_TO_BSC && + (bfd->priv_nr & 0xff) != RSL_TO_BSC) { + DEBUGP(DINP, "IDentity REQuest from BTS ?!?\n"); + return -EIO; + } + ipbc = ipc->bts_conn; + if (!ipbc) { + DEBUGP(DINP, "ID_GET from BSC before we have ID_RESP from BTS\n"); + return -EIO; + } + ret = write(bfd->fd, ipbc->id_resp, ipbc->id_resp_len); + break; + case IPAC_MSGT_ID_ACK: + DEBUGP(DMI, "ID_ACK? -> ACK!\n"); + ret = write(bfd->fd, id_ack, sizeof(id_ack)); + break; + default: + LOGP(DMI, LOGL_ERROR, "Unhandled IPA type; %d\n", msg_type); + return 1; + break; + } + return 0; +} + +struct msgb *ipaccess_read_msg(struct bsc_fd *bfd, int *error) +{ + struct msgb *msg = msgb_alloc(PROXY_ALLOC_SIZE, "Abis/IP"); + struct ipaccess_head *hh; + int len, ret = 0; + + if (!msg) { + *error = -ENOMEM; + return NULL; + } + + /* first read our 3-byte header */ + hh = (struct ipaccess_head *) msg->data; + ret = recv(bfd->fd, msg->data, 3, 0); + if (ret < 0) { + if (errno != EAGAIN) + LOGP(DINP, LOGL_ERROR, "recv error: %s\n", strerror(errno)); + msgb_free(msg); + *error = ret; + return NULL; + } else if (ret == 0) { + msgb_free(msg); + *error = ret; + return NULL; + } + + msgb_put(msg, ret); + + /* then read te length as specified in header */ + msg->l2h = msg->data + sizeof(*hh); + len = ntohs(hh->len); + ret = recv(bfd->fd, msg->l2h, len, 0); + if (ret < len) { + LOGP(DINP, LOGL_ERROR, "short read!\n"); + msgb_free(msg); + *error = -EIO; + return NULL; + } + msgb_put(msg, ret); + + return msg; +} + +static struct ipa_proxy_conn *ipc_by_priv_nr(struct ipa_bts_conn *ipbc, + unsigned int priv_nr) +{ + struct ipa_proxy_conn *bsc_conn; + unsigned int trx_id = priv_nr >> 8; + + switch (priv_nr & 0xff) { + case OML_FROM_BTS: /* incoming OML data from BTS, forward to BSC OML */ + bsc_conn = ipbc->bsc_oml_conn; + break; + case RSL_FROM_BTS: /* incoming RSL data from BTS, forward to BSC RSL */ + bsc_conn = ipbc->bsc_rsl_conn[trx_id]; + break; + case OML_TO_BSC: /* incoming OML data from BSC, forward to BTS OML */ + bsc_conn = ipbc->oml_conn; + break; + case RSL_TO_BSC: /* incoming RSL data from BSC, forward to BTS RSL */ + bsc_conn = ipbc->rsl_conn[trx_id]; + break; + default: + bsc_conn = NULL; + break; + } + return bsc_conn; +} + +static void reconn_tmr_cb(void *data) +{ + struct ipa_proxy *ipp = data; + struct ipa_bts_conn *ipbc; + struct sockaddr_in sin; + int i; + + DEBUGP(DINP, "Running reconnect timer\n"); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + inet_aton(bsc_ipaddr, &sin.sin_addr); + + llist_for_each_entry(ipbc, &ipp->bts_list, list) { + /* if OML to BSC is dead, try to restore it */ + if (ipbc->oml_conn && !ipbc->bsc_oml_conn) { + sin.sin_port = htons(IPA_TCP_PORT_OML); + logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, 0); + LOGPC(DINP, LOGL_NOTICE, "OML Trying to reconnect\n"); + ipbc->bsc_oml_conn = connect_bsc(&sin, OML_TO_BSC, ipbc); + if (!ipbc->bsc_oml_conn) + goto reschedule; + logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, 0); + LOGPC(DINP, LOGL_NOTICE, "OML Reconnected\n"); + } + /* if we (still) don't have a OML connection, skip RSL */ + if (!ipbc->oml_conn || !ipbc->bsc_oml_conn) + continue; + + for (i = 0; i < ARRAY_SIZE(ipbc->rsl_conn); i++) { + unsigned int priv_nr; + /* don't establish RSL links which we don't have */ + if (!ipbc->rsl_conn[i]) + continue; + if (ipbc->bsc_rsl_conn[i]) + continue; + priv_nr = ipbc->rsl_conn[i]->fd.priv_nr; + priv_nr &= ~0xff; + priv_nr |= RSL_TO_BSC; + sin.sin_port = htons(IPA_TCP_PORT_RSL); + logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, priv_nr >> 8); + LOGPC(DINP, LOGL_NOTICE, "RSL Trying to reconnect\n"); + ipbc->bsc_rsl_conn[i] = connect_bsc(&sin, priv_nr, ipbc); + if (!ipbc->bsc_rsl_conn) + goto reschedule; + logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, priv_nr >> 8); + LOGPC(DINP, LOGL_NOTICE, "RSL Reconnected\n"); + } + } + return; + +reschedule: + bsc_schedule_timer(&ipp->reconn_timer, 5, 0); +} + +static void handle_dead_socket(struct bsc_fd *bfd) +{ + struct ipa_proxy_conn *ipc = bfd->data; /* local conn */ + struct ipa_proxy_conn *bsc_conn; /* remote conn */ + struct ipa_bts_conn *ipbc = ipc->bts_conn; + unsigned int trx_id = bfd->priv_nr >> 8; + struct msgb *msg, *msg2; + + bsc_unregister_fd(bfd); + close(bfd->fd); + bfd->fd = -1; + + /* FIXME: clear tx_queue, remove all references, etc. */ + llist_for_each_entry_safe(msg, msg2, &ipc->tx_queue, list) + msgb_free(msg); + + switch (bfd->priv_nr & 0xff) { + case OML_FROM_BTS: /* incoming OML data from BTS, forward to BSC OML */ + ipbc->oml_conn = NULL; + bsc_conn = ipbc->bsc_oml_conn; + /* close the connection to the BSC */ + bsc_unregister_fd(&bsc_conn->fd); + close(bsc_conn->fd.fd); + llist_for_each_entry_safe(msg, msg2, &bsc_conn->tx_queue, list) + msgb_free(msg); + talloc_free(bsc_conn); + ipbc->bsc_oml_conn = NULL; + /* FIXME: do we need to delete the entire ipbc ? */ + break; + case RSL_FROM_BTS: /* incoming RSL data from BTS, forward to BSC RSL */ + ipbc->rsl_conn[trx_id] = NULL; + bsc_conn = ipbc->bsc_rsl_conn[trx_id]; + /* close the connection to the BSC */ + bsc_unregister_fd(&bsc_conn->fd); + close(bsc_conn->fd.fd); + llist_for_each_entry_safe(msg, msg2, &bsc_conn->tx_queue, list) + msgb_free(msg); + talloc_free(bsc_conn); + ipbc->bsc_rsl_conn[trx_id] = NULL; + break; + case OML_TO_BSC: /* incoming OML data from BSC, forward to BTS OML */ + ipbc->bsc_oml_conn = NULL; + bsc_conn = ipbc->oml_conn; + /* start reconnect timer */ + bsc_schedule_timer(&ipp->reconn_timer, 5, 0); + break; + case RSL_TO_BSC: /* incoming RSL data from BSC, forward to BTS RSL */ + ipbc->bsc_rsl_conn[trx_id] = NULL; + bsc_conn = ipbc->rsl_conn[trx_id]; + /* start reconnect timer */ + bsc_schedule_timer(&ipp->reconn_timer, 5, 0); + break; + default: + bsc_conn = NULL; + break; + } + + talloc_free(ipc); +} + +static void patch_gprs_msg(struct ipa_bts_conn *ipbc, int priv_nr, struct msgb *msg) +{ + uint8_t *nsvci; + + if ((priv_nr & 0xff) != OML_FROM_BTS && (priv_nr & 0xff) != OML_TO_BSC) + return; + + if (msgb_l2len(msg) != 39) + return; + + /* + * Check if this is a IPA Set Attribute or IPA Set Attribute ACK + * and if the FOM Class is GPRS NSVC0 and then we will patch it. + * + * The patch assumes the message looks like the one from the trace + * but we only match messages with a specific size anyway... So + * this hack should work just fine. + */ + + if (msg->l2h[0] == 0x10 && msg->l2h[1] == 0x80 && + msg->l2h[2] == 0x00 && msg->l2h[3] == 0x15 && + msg->l2h[18] == 0xf5 && msg->l2h[19] == 0xf2) { + nsvci = &msg->l2h[23]; + ipbc->gprs_orig_port = *(u_int16_t *)(nsvci+8); + ipbc->gprs_orig_ip = *(u_int32_t *)(nsvci+10); + *(u_int16_t *)(nsvci+8) = htons(ipbc->gprs_local_port); + *(u_int32_t *)(nsvci+10) = ipbc->ipp->listen_addr.s_addr; + } else if (msg->l2h[0] == 0x10 && msg->l2h[1] == 0x80 && + msg->l2h[2] == 0x00 && msg->l2h[3] == 0x15 && + msg->l2h[18] == 0xf6 && msg->l2h[19] == 0xf2) { + nsvci = &msg->l2h[23]; + *(u_int16_t *)(nsvci+8) = ipbc->gprs_orig_port; + *(u_int32_t *)(nsvci+10) = ipbc->gprs_orig_ip; + } +} + +static int handle_tcp_read(struct bsc_fd *bfd) +{ + struct ipa_proxy_conn *ipc = bfd->data; + struct ipa_bts_conn *ipbc = ipc->bts_conn; + struct ipa_proxy_conn *bsc_conn; + struct msgb *msg; + struct ipaccess_head *hh; + int ret = 0; + char *btsbsc; + + if ((bfd->priv_nr & 0xff) <= 2) + btsbsc = "BTS"; + else + btsbsc = "BSC"; + + msg = ipaccess_read_msg(bfd, &ret); + if (!msg) { + if (ret == 0) { + logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, bfd->priv_nr >> 8); + LOGPC(DINP, LOGL_NOTICE, "%s disappeared, " + "dead socket\n", btsbsc); + handle_dead_socket(bfd); + } + return ret; + } + + msgb_put(msg, ret); + logp_ipbc_uid(DMI, LOGL_DEBUG, ipbc, bfd->priv_nr >> 8); + DEBUGPC(DMI, "RX<-%s: %s\n", btsbsc, hexdump(msg->data, msg->len)); + + hh = (struct ipaccess_head *) msg->data; + if (hh->proto == IPAC_PROTO_IPACCESS) { + ret = ipaccess_rcvmsg(ipc, msg, bfd); + if (ret < 0) { + bsc_unregister_fd(bfd); + close(bfd->fd); + bfd->fd = -1; + talloc_free(bfd); + msgb_free(msg); + return ret; + } else if (ret == 0) { + /* we do not forward parts of the CCM protocol + * through the proxy but rather terminate it ourselves. */ + msgb_free(msg); + return ret; + } + } + + if (!ipbc) { + LOGP(DINP, LOGL_ERROR, + "received %s packet but no ipc->bts_conn?!?\n", btsbsc); + msgb_free(msg); + return -EIO; + } + + bsc_conn = ipc_by_priv_nr(ipbc, bfd->priv_nr); + if (bsc_conn) { + if (gprs_ns_ipaddr) + patch_gprs_msg(ipbc, bfd->priv_nr, msg); + /* enqueue packet towards BSC */ + msgb_enqueue(&bsc_conn->tx_queue, msg); + /* mark respective filedescriptor as 'we want to write' */ + bsc_conn->fd.when |= BSC_FD_WRITE; + } else { + logp_ipbc_uid(DINP, LOGL_INFO, ipbc, bfd->priv_nr >> 8); + LOGPC(DINP, LOGL_INFO, "Dropping packet from %s, " + "since remote connection is dead\n", btsbsc); + msgb_free(msg); + } + + return ret; +} + +/* a TCP socket is ready to be written to */ +static int handle_tcp_write(struct bsc_fd *bfd) +{ + struct ipa_proxy_conn *ipc = bfd->data; + struct ipa_bts_conn *ipbc = ipc->bts_conn; + struct llist_head *lh; + struct msgb *msg; + char *btsbsc; + int ret; + + if ((bfd->priv_nr & 0xff) <= 2) + btsbsc = "BTS"; + else + btsbsc = "BSC"; + + + /* get the next msg for this timeslot */ + if (llist_empty(&ipc->tx_queue)) { + bfd->when &= ~BSC_FD_WRITE; + return 0; + } + lh = ipc->tx_queue.next; + llist_del(lh); + msg = llist_entry(lh, struct msgb, list); + + logp_ipbc_uid(DMI, LOGL_DEBUG, ipbc, bfd->priv_nr >> 8); + DEBUGPC(DMI, "TX %04x: %s\n", bfd->priv_nr, + hexdump(msg->data, msg->len)); + + ret = send(bfd->fd, msg->data, msg->len, 0); + msgb_free(msg); + + if (ret == 0) { + logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, bfd->priv_nr >> 8); + LOGP(DINP, LOGL_NOTICE, "%s disappeared, dead socket\n", btsbsc); + handle_dead_socket(bfd); + } + + return ret; +} + +/* callback from select.c in case one of the fd's can be read/written */ +static int ipaccess_fd_cb(struct bsc_fd *bfd, unsigned int what) +{ + int rc = 0; + + if (what & BSC_FD_READ) { + rc = handle_tcp_read(bfd); + if (rc < 0) + return rc; + } + if (what & BSC_FD_WRITE) + rc = handle_tcp_write(bfd); + + return rc; +} + +/* callback of the listening filedescriptor */ +static int listen_fd_cb(struct bsc_fd *listen_bfd, unsigned int what) +{ + int ret; + struct ipa_proxy_conn *ipc; + struct bsc_fd *bfd; + struct sockaddr_in sa; + socklen_t sa_len = sizeof(sa); + + if (!(what & BSC_FD_READ)) + return 0; + + ret = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len); + if (ret < 0) { + perror("accept"); + return ret; + } + DEBUGP(DINP, "accept()ed new %s link from %s\n", + (listen_bfd->priv_nr & 0xff) == OML_FROM_BTS ? "OML" : "RSL", + inet_ntoa(sa.sin_addr)); + + ipc = alloc_conn(); + if (!ipc) { + close(ret); + return -ENOMEM; + } + + bfd = &ipc->fd; + bfd->fd = ret; + bfd->data = ipc; + bfd->priv_nr = listen_bfd->priv_nr; + bfd->cb = ipaccess_fd_cb; + bfd->when = BSC_FD_READ; + ret = bsc_register_fd(bfd); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "could not register FD\n"); + close(bfd->fd); + talloc_free(ipc); + return ret; + } + + /* Request ID. FIXME: request LOCATION, HW/SW VErsion, Unit Name, Serno */ + ret = write(bfd->fd, id_req, sizeof(id_req)); + + return 0; +} + +static void send_ns(int fd, const char *buf, int size, struct in_addr ip, int port) +{ + int ret; + struct sockaddr_in addr; + socklen_t len = sizeof(addr); + memset(&addr, 0, sizeof(addr)); + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr = ip; + + ret = sendto(fd, buf, size, 0, (struct sockaddr *) &addr, len); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "Failed to forward GPRS message.\n"); + } +} + +static int gprs_ns_cb(struct bsc_fd *bfd, unsigned int what) +{ + struct ipa_bts_conn *bts; + char buf[4096]; + int ret; + struct sockaddr_in sock; + socklen_t len = sizeof(sock); + + /* 1. get the data... */ + ret = recvfrom(bfd->fd, buf, sizeof(buf), 0, (struct sockaddr *) &sock, &len); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "Failed to recv GPRS NS msg: %s.\n", strerror(errno)); + return -1; + } + + bts = bfd->data; + + /* 2. figure out where to send it to */ + if (memcmp(&sock.sin_addr, &ipp->gprs_addr, sizeof(sock.sin_addr)) == 0) { + LOGP(DINP, LOGL_DEBUG, "GPRS NS msg from network.\n"); + send_ns(bfd->fd, buf, ret, bts->bts_addr, 23000); + } else if (memcmp(&sock.sin_addr, &bts->bts_addr, sizeof(sock.sin_addr)) == 0) { + LOGP(DINP, LOGL_DEBUG, "GPRS NS msg from BTS.\n"); + send_ns(bfd->fd, buf, ret, ipp->gprs_addr, 23000); + } else { + LOGP(DINP, LOGL_ERROR, "Unknown GPRS source: %s\n", inet_ntoa(sock.sin_addr)); + } + + return 0; +} + +static int make_listen_sock(struct bsc_fd *bfd, u_int16_t port, int priv_nr, + int (*cb)(struct bsc_fd *fd, unsigned int what)) +{ + struct sockaddr_in addr; + int ret, on = 1; + + bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + bfd->cb = cb; + bfd->when = BSC_FD_READ; + bfd->priv_nr = priv_nr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (!listen_ipaddr) + addr.sin_addr.s_addr = INADDR_ANY; + else + inet_aton(listen_ipaddr, &addr.sin_addr); + + setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr)); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, + "Could not bind listen socket for IP %s with error: %s.\n", + listen_ipaddr, strerror(errno)); + return -EIO; + } + + ret = listen(bfd->fd, 1); + if (ret < 0) { + perror("listen"); + return ret; + } + + ret = bsc_register_fd(bfd); + if (ret < 0) { + perror("register_listen_fd"); + return ret; + } + return 0; +} + +static int make_gprs_sock(struct bsc_fd *bfd, int (*cb)(struct bsc_fd*,unsigned int), void *data) +{ + struct sockaddr_in addr; + int ret; + + bfd->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + bfd->cb = cb; + bfd->data = data; + bfd->when = BSC_FD_READ; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = 0; + inet_aton(listen_ipaddr, &addr.sin_addr); + + ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr)); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, + "Could not bind n socket for IP %s with error: %s.\n", + listen_ipaddr, strerror(errno)); + return -EIO; + } + + ret = bsc_register_fd(bfd); + if (ret < 0) { + perror("register_listen_fd"); + return ret; + } + return 0; +} + +/* Actively connect to a BSC. */ +static struct ipa_proxy_conn *connect_bsc(struct sockaddr_in *sa, int priv_nr, void *data) +{ + struct ipa_proxy_conn *ipc; + struct bsc_fd *bfd; + int ret, on = 1; + + ipc = alloc_conn(); + if (!ipc) + return NULL; + + ipc->bts_conn = data; + + bfd = &ipc->fd; + bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + bfd->cb = ipaccess_fd_cb; + bfd->when = BSC_FD_READ | BSC_FD_WRITE; + bfd->data = ipc; + bfd->priv_nr = priv_nr; + + setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + ret = connect(bfd->fd, (struct sockaddr *) sa, sizeof(*sa)); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "Could not connect socket: %s\n", + inet_ntoa(sa->sin_addr)); + close(bfd->fd); + talloc_free(ipc); + return NULL; + } + + /* pre-fill tx_queue with identity request */ + ret = bsc_register_fd(bfd); + if (ret < 0) { + close(bfd->fd); + talloc_free(ipc); + return NULL; + } + + return ipc; +} + +static int ipaccess_proxy_setup(void) +{ + int ret; + + ipp = talloc_zero(tall_bsc_ctx, struct ipa_proxy); + if (!ipp) + return -ENOMEM; + INIT_LLIST_HEAD(&ipp->bts_list); + ipp->reconn_timer.cb = reconn_tmr_cb; + ipp->reconn_timer.data = ipp; + + /* Listen for OML connections */ + ret = make_listen_sock(&ipp->oml_listen_fd, IPA_TCP_PORT_OML, + OML_FROM_BTS, listen_fd_cb); + if (ret < 0) + return ret; + + /* Listen for RSL connections */ + ret = make_listen_sock(&ipp->rsl_listen_fd, IPA_TCP_PORT_RSL, + RSL_FROM_BTS, listen_fd_cb); + + if (ret < 0) + return ret; + + /* Connect the GPRS NS Socket */ + if (gprs_ns_ipaddr) { + inet_aton(gprs_ns_ipaddr, &ipp->gprs_addr); + inet_aton(listen_ipaddr, &ipp->listen_addr); + } + + return ret; +} + +static void signal_handler(int signal) +{ + fprintf(stdout, "signal %u received\n", signal); + + switch (signal) { + case SIGABRT: + /* in case of abort, we want to obtain a talloc report + * and then return to the caller, who will abort the process */ + case SIGUSR1: + talloc_report_full(tall_bsc_ctx, stderr); + break; + default: + break; + } +} + +static void print_help() +{ + printf(" ipaccess-proxy is a proxy BTS.\n"); + printf(" -h --help. This help text.\n"); + printf(" -l --listen IP. The ip to listen to.\n"); + printf(" -b --bsc IP. The BSC IP address.\n"); + printf(" -g --gprs IP. Take GPRS NS from that IP.\n"); + printf("\n"); + printf(" -s --disable-color. Disable the color inside the logging message.\n"); + printf(" -e --log-level number. Set the global loglevel.\n"); + printf(" -T --timestamp. Prefix every log message with a timestamp.\n"); + printf(" -V --version. Print the version of OpenBSC.\n"); +} + +static void print_usage() +{ + printf("Usage: ipaccess-proxy\n"); +} + +static void handle_options(int argc, char** argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"disable-color", 0, 0, 's'}, + {"timestamp", 0, 0, 'T'}, + {"log-level", 1, 0, 'e'}, + {"listen", 1, 0, 'l'}, + {"bsc", 1, 0, 'b'}, + {"udp", 1, 0, 'u'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hsTe:l:b:g:", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage(); + print_help(); + exit(0); + case 'l': + listen_ipaddr = optarg; + break; + case 'b': + bsc_ipaddr = optarg; + break; + case 'g': + gprs_ns_ipaddr = optarg; + break; + case 's': + log_set_use_color(stderr_target, 0); + break; + case 'T': + log_set_print_timestamp(stderr_target, 1); + break; + case 'e': + log_set_log_level(stderr_target, atoi(optarg)); + break; + default: + /* ignore */ + break; + } + } +} + +int main(int argc, char **argv) +{ + int rc; + + listen_ipaddr = "192.168.100.11"; + bsc_ipaddr = "192.168.100.239"; + + tall_bsc_ctx = talloc_named_const(NULL, 1, "ipaccess-proxy"); + + log_init(&log_info); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + log_set_all_filter(stderr_target, 1); + log_parse_category_mask(stderr_target, "DINP:DMI"); + + handle_options(argc, argv); + + rc = ipaccess_proxy_setup(); + if (rc < 0) + exit(1); + + signal(SIGUSR1, &signal_handler); + signal(SIGABRT, &signal_handler); + + while (1) { + bsc_select_main(0); + } +} diff --git a/src/ipaccess/network_listen.c b/src/ipaccess/network_listen.c new file mode 100644 index 000000000..aaf7c974a --- /dev/null +++ b/src/ipaccess/network_listen.c @@ -0,0 +1,251 @@ +/* ip.access nanoBTS network listen mode */ + +/* (C) 2009-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define WHITELIST_MAX_SIZE ((NUM_ARFCNS*2)+2+1) + +int ipac_rxlevstat2whitelist(uint16_t *buf, const struct rxlev_stats *st, uint8_t min_rxlev, + uint16_t max_num_arfcns) +{ + int i; + unsigned int num_arfcn = 0; + + for (i = NUM_RXLEVS-1; i >= min_rxlev; i--) { + int16_t arfcn = -1; + + while ((arfcn = rxlev_stat_get_next(st, i, arfcn)) >= 0) { + *buf++ = htons(arfcn); + num_arfcn++; + + } + + if (num_arfcn > max_num_arfcns) + break; + } + + return num_arfcn; +} + +enum ipac_test_state { + IPAC_TEST_S_IDLE, + IPAC_TEST_S_RQD, + IPAC_TEST_S_EXEC, + IPAC_TEST_S_PARTIAL, +}; + +int ipac_nwl_test_start(struct gsm_bts_trx *trx, uint8_t testnr, + const uint8_t *phys_conf, unsigned int phys_conf_len) +{ + struct msgb *msg; + + if (trx->ipaccess.test_state != IPAC_TEST_S_IDLE) { + fprintf(stderr, "Cannot start test in state %u\n", trx->ipaccess.test_state); + return -EINVAL; + } + + switch (testnr) { + case NM_IPACC_TESTNO_CHAN_USAGE: + case NM_IPACC_TESTNO_BCCH_CHAN_USAGE: + rxlev_stat_reset(&trx->ipaccess.rxlev_stat); + break; + } + + msg = msgb_alloc_headroom(phys_conf_len+256, 128, "OML"); + + if (phys_conf && phys_conf_len) { + uint8_t *payload; + /* first put the phys conf header */ + msgb_tv16_put(msg, NM_ATT_PHYS_CONF, phys_conf_len); + payload = msgb_put(msg, phys_conf_len); + memcpy(payload, phys_conf, phys_conf_len); + } + + abis_nm_perform_test(trx->bts, NM_OC_RADIO_CARRIER, 0, trx->nr, 0xff, + testnr, 1, msg); + trx->ipaccess.test_nr = testnr; + + /* FIXME: start safety timer until when test is supposed to complete */ + + return 0; +} + +static uint16_t last_arfcn; +static struct gsm_sysinfo_freq nwl_si_freq[1024]; +#define FREQ_TYPE_NCELL_2 0x04 /* sub channel of SI 2 */ +#define FREQ_TYPE_NCELL_2bis 0x08 /* sub channel of SI 2bis */ +#define FREQ_TYPE_NCELL_2ter 0x10 /* sub channel of SI 2ter */ + +struct ipacc_ferr_elem { + int16_t freq_err; + uint8_t freq_qual; + uint8_t arfcn; +} __attribute__((packed)); + +struct ipacc_cusage_elem { + uint16_t arfcn:10, + rxlev:6; +} __attribute__ ((packed)); + +static int test_rep(void *_msg) +{ + struct msgb *msg = _msg; + struct abis_om_fom_hdr *foh = msgb_l3(msg); + uint16_t test_rep_len, ferr_list_len; + struct ipacc_ferr_elem *ife; + struct ipac_bcch_info binfo; + int i, rc; + + DEBUGP(DNM, "TEST REPORT: "); + + if (foh->data[0] != NM_ATT_TEST_NO || + foh->data[2] != NM_ATT_TEST_REPORT) + return -EINVAL; + + DEBUGPC(DNM, "test_no=0x%02x ", foh->data[1]); + /* data[2] == NM_ATT_TEST_REPORT */ + /* data[3..4]: test_rep_len */ + test_rep_len = ntohs(*(uint16_t *) &foh->data[3]); + /* data[5]: ip.access test result */ + DEBUGPC(DNM, "tst_res=%s\n", ipacc_testres_name(foh->data[5])); + + /* data[6]: ip.access nested IE. 3 == freq_err_list */ + switch (foh->data[6]) { + case NM_IPAC_EIE_FREQ_ERR_LIST: + /* data[7..8]: length of ferr_list */ + ferr_list_len = ntohs(*(uint16_t *) &foh->data[7]); + + /* data[9...]: frequency error list elements */ + for (i = 0; i < ferr_list_len; i+= sizeof(*ife)) { + ife = (struct ipacc_ferr_elem *) (foh->data + 9 + i); + DEBUGP(DNM, "==> ARFCN %4u, Frequency Error %6hd\n", + ife->arfcn, ntohs(ife->freq_err)); + } + break; + case NM_IPAC_EIE_CHAN_USE_LIST: + /* data[7..8]: length of ferr_list */ + ferr_list_len = ntohs(*(uint16_t *) &foh->data[7]); + + /* data[9...]: channel usage list elements */ + for (i = 0; i < ferr_list_len; i+= 2) { + uint16_t *cu_ptr = (uint16_t *)(foh->data + 9 + i); + uint16_t cu = ntohs(*cu_ptr); + uint16_t arfcn = cu & 0x3ff; + uint8_t rxlev = cu >> 10; + DEBUGP(DNM, "==> ARFCN %4u, RxLev %2u\n", arfcn, rxlev); + rxlev_stat_input(&msg->trx->ipaccess.rxlev_stat, + arfcn, rxlev); + } + break; + case NM_IPAC_EIE_BCCH_INFO_TYPE: + break; + case NM_IPAC_EIE_BCCH_INFO: + rc = ipac_parse_bcch_info(&binfo, foh->data+6); + if (rc < 0) { + DEBUGP(DNM, "BCCH Info parsing failed\n"); + break; + } + DEBUGP(DNM, "==> ARFCN %u, RxLev %2u, RxQual %2u: %3d-%d, LAC %d CI %d BSIC %u\n", + binfo.arfcn, binfo.rx_lev, binfo.rx_qual, + binfo.cgi.mcc, binfo.cgi.mnc, + binfo.cgi.lac, binfo.cgi.ci, binfo.bsic); + + if (binfo.arfcn != last_arfcn) { + /* report is on a new arfcn, need to clear channel list */ + memset(nwl_si_freq, 0, sizeof(nwl_si_freq)); + last_arfcn = binfo.arfcn; + } + if (binfo.info_type & IPAC_BINF_NEIGH_BA_SI2) { + DEBUGP(DNM, "BA SI2: %s\n", hexdump(binfo.ba_list_si2, sizeof(binfo.ba_list_si2))); + gsm48_decode_freq_list(nwl_si_freq, binfo.ba_list_si2, sizeof(binfo.ba_list_si2), + 0x8c, FREQ_TYPE_NCELL_2); + } + if (binfo.info_type & IPAC_BINF_NEIGH_BA_SI2bis) { + DEBUGP(DNM, "BA SI2bis: %s\n", hexdump(binfo.ba_list_si2bis, sizeof(binfo.ba_list_si2bis))); + gsm48_decode_freq_list(nwl_si_freq, binfo.ba_list_si2bis, sizeof(binfo.ba_list_si2bis), + 0x8e, FREQ_TYPE_NCELL_2bis); + } + if (binfo.info_type & IPAC_BINF_NEIGH_BA_SI2ter) { + DEBUGP(DNM, "BA SI2ter: %s\n", hexdump(binfo.ba_list_si2ter, sizeof(binfo.ba_list_si2ter))); + gsm48_decode_freq_list(nwl_si_freq, binfo.ba_list_si2ter, sizeof(binfo.ba_list_si2ter), + 0x8e, FREQ_TYPE_NCELL_2ter); + } + for (i = 0; i < ARRAY_SIZE(nwl_si_freq); i++) { + if (nwl_si_freq[i].mask) + DEBUGP(DNM, "Neighbor Cell on ARFCN %u\n", i); + } + break; + default: + break; + } + + switch (foh->data[5]) { + case NM_IPACC_TESTRES_SUCCESS: + case NM_IPACC_TESTRES_STOPPED: + case NM_IPACC_TESTRES_TIMEOUT: + case NM_IPACC_TESTRES_NO_CHANS: + msg->trx->ipaccess.test_state = IPAC_TEST_S_IDLE; + /* Send signal to notify higher layers of test completion */ + DEBUGP(DNM, "dispatching S_IPAC_NWL_COMPLETE signal\n"); + dispatch_signal(SS_IPAC_NWL, S_IPAC_NWL_COMPLETE, msg->trx); + break; + case NM_IPACC_TESTRES_PARTIAL: + msg->trx->ipaccess.test_state = IPAC_TEST_S_PARTIAL; + break; + } + + return 0; +} + +static int nwl_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + switch (signal) { + case S_NM_TEST_REP: + return test_rep(signal_data); + default: + break; + } + + return 0; +} + +void ipac_nwl_init(void) +{ + register_signal_handler(SS_NM, nwl_sig_cb, NULL); +} diff --git a/src/libabis/Makefile.am b/src/libabis/Makefile.am new file mode 100644 index 000000000..0df7b5a4a --- /dev/null +++ b/src/libabis/Makefile.am @@ -0,0 +1,14 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) + +noinst_LIBRARIES = libabis.a + +libabis_a_SOURCES = e1_input.c e1_input_vty.c \ + input/misdn.c \ + input/ipaccess.c \ + input/hsl.c \ + input/dahdi.c \ + input/lapd.c + +EXTRA_DIST = input/lapd.h diff --git a/src/libabis/Makefile.in b/src/libabis/Makefile.in new file mode 100644 index 000000000..90d628791 --- /dev/null +++ b/src/libabis/Makefile.in @@ -0,0 +1,548 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = src/libabis +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LIBRARIES = $(noinst_LIBRARIES) +AR = ar +ARFLAGS = cru +AM_V_AR = $(am__v_AR_$(V)) +am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY)) +am__v_AR_0 = @echo " AR " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +libabis_a_AR = $(AR) $(ARFLAGS) +libabis_a_LIBADD = +am_libabis_a_OBJECTS = e1_input.$(OBJEXT) e1_input_vty.$(OBJEXT) \ + misdn.$(OBJEXT) ipaccess.$(OBJEXT) hsl.$(OBJEXT) \ + dahdi.$(OBJEXT) lapd.$(OBJEXT) +libabis_a_OBJECTS = $(am_libabis_a_OBJECTS) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +AM_V_lt = $(am__v_lt_$(V)) +am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY)) +am__v_lt_0 = --silent +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(libabis_a_SOURCES) +DIST_SOURCES = $(libabis_a_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) +noinst_LIBRARIES = libabis.a +libabis_a_SOURCES = e1_input.c e1_input_vty.c \ + input/misdn.c \ + input/ipaccess.c \ + input/hsl.c \ + input/dahdi.c \ + input/lapd.c + +EXTRA_DIST = input/lapd.h +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libabis/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/libabis/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) +libabis.a: $(libabis_a_OBJECTS) $(libabis_a_DEPENDENCIES) + $(AM_V_at)-rm -f libabis.a + $(AM_V_AR)$(libabis_a_AR) libabis.a $(libabis_a_OBJECTS) $(libabis_a_LIBADD) + $(AM_V_at)$(RANLIB) libabis.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dahdi.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/e1_input.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/e1_input_vty.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hsl.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipaccess.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lapd.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/misdn.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +misdn.o: input/misdn.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT misdn.o -MD -MP -MF $(DEPDIR)/misdn.Tpo -c -o misdn.o `test -f 'input/misdn.c' || echo '$(srcdir)/'`input/misdn.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/misdn.Tpo $(DEPDIR)/misdn.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/misdn.c' object='misdn.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o misdn.o `test -f 'input/misdn.c' || echo '$(srcdir)/'`input/misdn.c + +misdn.obj: input/misdn.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT misdn.obj -MD -MP -MF $(DEPDIR)/misdn.Tpo -c -o misdn.obj `if test -f 'input/misdn.c'; then $(CYGPATH_W) 'input/misdn.c'; else $(CYGPATH_W) '$(srcdir)/input/misdn.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/misdn.Tpo $(DEPDIR)/misdn.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/misdn.c' object='misdn.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o misdn.obj `if test -f 'input/misdn.c'; then $(CYGPATH_W) 'input/misdn.c'; else $(CYGPATH_W) '$(srcdir)/input/misdn.c'; fi` + +ipaccess.o: input/ipaccess.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ipaccess.o -MD -MP -MF $(DEPDIR)/ipaccess.Tpo -c -o ipaccess.o `test -f 'input/ipaccess.c' || echo '$(srcdir)/'`input/ipaccess.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ipaccess.Tpo $(DEPDIR)/ipaccess.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/ipaccess.c' object='ipaccess.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ipaccess.o `test -f 'input/ipaccess.c' || echo '$(srcdir)/'`input/ipaccess.c + +ipaccess.obj: input/ipaccess.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ipaccess.obj -MD -MP -MF $(DEPDIR)/ipaccess.Tpo -c -o ipaccess.obj `if test -f 'input/ipaccess.c'; then $(CYGPATH_W) 'input/ipaccess.c'; else $(CYGPATH_W) '$(srcdir)/input/ipaccess.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/ipaccess.Tpo $(DEPDIR)/ipaccess.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/ipaccess.c' object='ipaccess.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ipaccess.obj `if test -f 'input/ipaccess.c'; then $(CYGPATH_W) 'input/ipaccess.c'; else $(CYGPATH_W) '$(srcdir)/input/ipaccess.c'; fi` + +hsl.o: input/hsl.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT hsl.o -MD -MP -MF $(DEPDIR)/hsl.Tpo -c -o hsl.o `test -f 'input/hsl.c' || echo '$(srcdir)/'`input/hsl.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/hsl.Tpo $(DEPDIR)/hsl.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/hsl.c' object='hsl.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o hsl.o `test -f 'input/hsl.c' || echo '$(srcdir)/'`input/hsl.c + +hsl.obj: input/hsl.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT hsl.obj -MD -MP -MF $(DEPDIR)/hsl.Tpo -c -o hsl.obj `if test -f 'input/hsl.c'; then $(CYGPATH_W) 'input/hsl.c'; else $(CYGPATH_W) '$(srcdir)/input/hsl.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/hsl.Tpo $(DEPDIR)/hsl.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/hsl.c' object='hsl.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o hsl.obj `if test -f 'input/hsl.c'; then $(CYGPATH_W) 'input/hsl.c'; else $(CYGPATH_W) '$(srcdir)/input/hsl.c'; fi` + +dahdi.o: input/dahdi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dahdi.o -MD -MP -MF $(DEPDIR)/dahdi.Tpo -c -o dahdi.o `test -f 'input/dahdi.c' || echo '$(srcdir)/'`input/dahdi.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dahdi.Tpo $(DEPDIR)/dahdi.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/dahdi.c' object='dahdi.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dahdi.o `test -f 'input/dahdi.c' || echo '$(srcdir)/'`input/dahdi.c + +dahdi.obj: input/dahdi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dahdi.obj -MD -MP -MF $(DEPDIR)/dahdi.Tpo -c -o dahdi.obj `if test -f 'input/dahdi.c'; then $(CYGPATH_W) 'input/dahdi.c'; else $(CYGPATH_W) '$(srcdir)/input/dahdi.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dahdi.Tpo $(DEPDIR)/dahdi.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/dahdi.c' object='dahdi.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dahdi.obj `if test -f 'input/dahdi.c'; then $(CYGPATH_W) 'input/dahdi.c'; else $(CYGPATH_W) '$(srcdir)/input/dahdi.c'; fi` + +lapd.o: input/lapd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lapd.o -MD -MP -MF $(DEPDIR)/lapd.Tpo -c -o lapd.o `test -f 'input/lapd.c' || echo '$(srcdir)/'`input/lapd.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lapd.Tpo $(DEPDIR)/lapd.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/lapd.c' object='lapd.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lapd.o `test -f 'input/lapd.c' || echo '$(srcdir)/'`input/lapd.c + +lapd.obj: input/lapd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lapd.obj -MD -MP -MF $(DEPDIR)/lapd.Tpo -c -o lapd.obj `if test -f 'input/lapd.c'; then $(CYGPATH_W) 'input/lapd.c'; else $(CYGPATH_W) '$(srcdir)/input/lapd.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lapd.Tpo $(DEPDIR)/lapd.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='input/lapd.c' object='lapd.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lapd.obj `if test -f 'input/lapd.c'; then $(CYGPATH_W) 'input/lapd.c'; else $(CYGPATH_W) '$(srcdir)/input/lapd.c'; fi` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LIBRARIES) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-noinstLIBRARIES ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/libabis/e1_input.c b/src/libabis/e1_input.c new file mode 100644 index 000000000..3b6644eff --- /dev/null +++ b/src/libabis/e1_input.c @@ -0,0 +1,649 @@ +/* OpenBSC Abis interface to E1 */ + +/* (C) 2008-2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define AF_COMPATIBILITY_FUNC +//#include +#ifndef AF_ISDN +#define AF_ISDN 34 +#define PF_ISDN AF_ISDN +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../bscconfig.h" + +#define NUM_E1_TS 32 + +/* list of all E1 drivers */ +LLIST_HEAD(e1inp_driver_list); + +/* list of all E1 lines */ +LLIST_HEAD(e1inp_line_list); + +static void *tall_sigl_ctx; + +/* + * pcap writing of the misdn load + * pcap format is from http://wiki.wireshark.org/Development/LibpcapFileFormat + */ +#define DLT_LINUX_LAPD 177 +#define PCAP_INPUT 0 +#define PCAP_OUTPUT 1 + +struct pcap_hdr { + u_int32_t magic_number; + u_int16_t version_major; + u_int16_t version_minor; + int32_t thiszone; + u_int32_t sigfigs; + u_int32_t snaplen; + u_int32_t network; +} __attribute__((packed)); + +struct pcaprec_hdr { + u_int32_t ts_sec; + u_int32_t ts_usec; + u_int32_t incl_len; + u_int32_t orig_len; +} __attribute__((packed)); + +struct fake_linux_lapd_header { + u_int16_t pkttype; + u_int16_t hatype; + u_int16_t halen; + u_int64_t addr; + int16_t protocol; +} __attribute__((packed)); + +struct lapd_header { + u_int8_t ea1 : 1; + u_int8_t cr : 1; + u_int8_t sapi : 6; + u_int8_t ea2 : 1; + u_int8_t tei : 7; + u_int8_t control_foo; /* fake UM's ... */ +} __attribute__((packed)); + +static_assert(offsetof(struct fake_linux_lapd_header, hatype) == 2, hatype_offset); +static_assert(offsetof(struct fake_linux_lapd_header, halen) == 4, halen_offset); +static_assert(offsetof(struct fake_linux_lapd_header, addr) == 6, addr_offset); +static_assert(offsetof(struct fake_linux_lapd_header, protocol) == 14, proto_offset); +static_assert(sizeof(struct fake_linux_lapd_header) == 16, lapd_header_size); + + +static int pcap_fd = -1; + +void e1_set_pcap_fd(int fd) +{ + int ret; + struct pcap_hdr header = { + .magic_number = 0xa1b2c3d4, + .version_major = 2, + .version_minor = 4, + .thiszone = 0, + .sigfigs = 0, + .snaplen = 65535, + .network = DLT_LINUX_LAPD, + }; + + pcap_fd = fd; + ret = write(pcap_fd, &header, sizeof(header)); +} + +/* This currently only works for the D-Channel */ +static void write_pcap_packet(int direction, int sapi, int tei, + struct msgb *msg) { + if (pcap_fd < 0) + return; + + int ret; + time_t cur_time; + struct tm *tm; + + struct fake_linux_lapd_header header = { + .pkttype = 4, + .hatype = 0, + .halen = 0, + .addr = direction == PCAP_OUTPUT ? 0x0 : 0x1, + .protocol = ntohs(48), + }; + + struct lapd_header lapd_header = { + .ea1 = 0, + .cr = direction == PCAP_OUTPUT ? 1 : 0, + .sapi = sapi & 0x3F, + .ea2 = 1, + .tei = tei & 0x7F, + .control_foo = 0x03 /* UI */, + }; + + struct pcaprec_hdr payload_header = { + .ts_sec = 0, + .ts_usec = 0, + .incl_len = msgb_l2len(msg) + sizeof(struct fake_linux_lapd_header) + + sizeof(struct lapd_header), + .orig_len = msgb_l2len(msg) + sizeof(struct fake_linux_lapd_header) + + sizeof(struct lapd_header), + }; + + + cur_time = time(NULL); + tm = localtime(&cur_time); + payload_header.ts_sec = mktime(tm); + + ret = write(pcap_fd, &payload_header, sizeof(payload_header)); + ret = write(pcap_fd, &header, sizeof(header)); + ret = write(pcap_fd, &lapd_header, sizeof(lapd_header)); + ret = write(pcap_fd, msg->l2h, msgb_l2len(msg)); +} + +static const char *sign_types[] = { + [E1INP_SIGN_NONE] = "None", + [E1INP_SIGN_OML] = "OML", + [E1INP_SIGN_RSL] = "RSL", +}; +const char *e1inp_signtype_name(enum e1inp_sign_type tp) +{ + if (tp >= ARRAY_SIZE(sign_types)) + return "undefined"; + return sign_types[tp]; +} + +static const char *ts_types[] = { + [E1INP_TS_TYPE_NONE] = "None", + [E1INP_TS_TYPE_SIGN] = "Signalling", + [E1INP_TS_TYPE_TRAU] = "TRAU", +}; + +const char *e1inp_tstype_name(enum e1inp_ts_type tp) +{ + if (tp >= ARRAY_SIZE(ts_types)) + return "undefined"; + return ts_types[tp]; +} + +/* callback when a TRAU frame was received */ +static int subch_cb(struct subch_demux *dmx, int ch, u_int8_t *data, int len, + void *_priv) +{ + struct e1inp_ts *e1i_ts = _priv; + struct gsm_e1_subslot src_ss; + + src_ss.e1_nr = e1i_ts->line->num; + src_ss.e1_ts = e1i_ts->num; + src_ss.e1_ts_ss = ch; + + return trau_mux_input(&src_ss, data, len); +} + +int abis_rsl_sendmsg(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link; + struct e1inp_driver *e1inp_driver; + struct e1inp_ts *e1i_ts; + + msg->l2h = msg->data; + + if (!msg->trx) { + LOGP(DRSL, LOGL_ERROR, "rsl_sendmsg: msg->trx == NULL: %s\n", + hexdump(msg->data, msg->len)); + talloc_free(msg); + return -EINVAL; + } else if (!msg->trx->rsl_link) { + LOGP(DRSL, LOGL_ERROR, "rsl_sendmsg: msg->trx->rsl_link == NULL: %s\n", + hexdump(msg->data, msg->len)); + talloc_free(msg); + return -EIO; + } + + sign_link = msg->trx->rsl_link; + e1i_ts = sign_link->ts; + if (!bsc_timer_pending(&e1i_ts->sign.tx_timer)) { + /* notify the driver we have something to write */ + e1inp_driver = sign_link->ts->line->driver; + e1inp_driver->want_write(e1i_ts); + } + msgb_enqueue(&sign_link->tx_list, msg); + + /* dump it */ + write_pcap_packet(PCAP_OUTPUT, sign_link->sapi, sign_link->tei, msg); + + return 0; +} + +int _abis_nm_sendmsg(struct msgb *msg, int to_trx_oml) +{ + struct e1inp_sign_link *sign_link; + struct e1inp_driver *e1inp_driver; + struct e1inp_ts *e1i_ts; + + msg->l2h = msg->data; + + if (!msg->trx || !msg->trx->bts || !msg->trx->bts->oml_link) { + LOGP(DNM, LOGL_ERROR, "nm_sendmsg: msg->trx == NULL\n"); + return -EINVAL; + } + + /* Check for TRX-specific OML link first */ + if (to_trx_oml) { + if (!msg->trx->oml_link) + return -ENODEV; + sign_link = msg->trx->oml_link; + } else + sign_link = msg->trx->bts->oml_link; + + e1i_ts = sign_link->ts; + if (!bsc_timer_pending(&e1i_ts->sign.tx_timer)) { + /* notify the driver we have something to write */ + e1inp_driver = sign_link->ts->line->driver; + e1inp_driver->want_write(e1i_ts); + } + msgb_enqueue(&sign_link->tx_list, msg); + + /* dump it */ + write_pcap_packet(PCAP_OUTPUT, sign_link->sapi, sign_link->tei, msg); + + return 0; +} + +/* Timeslot */ + +/* configure and initialize one e1inp_ts */ +int e1inp_ts_config(struct e1inp_ts *ts, struct e1inp_line *line, + enum e1inp_ts_type type) +{ + if (ts->type == type && ts->line && line) + return 0; + + ts->type = type; + ts->line = line; + + switch (type) { + case E1INP_TS_TYPE_SIGN: + if (line && line->driver) + ts->sign.delay = line->driver->default_delay; + else + ts->sign.delay = 100000; + INIT_LLIST_HEAD(&ts->sign.sign_links); + break; + case E1INP_TS_TYPE_TRAU: + subchan_mux_init(&ts->trau.mux); + ts->trau.demux.out_cb = subch_cb; + ts->trau.demux.data = ts; + subch_demux_init(&ts->trau.demux); + break; + default: + LOGP(DMI, LOGL_ERROR, "unsupported E1 timeslot type %u\n", + ts->type); + return -EINVAL; + } + return 0; +} + +struct e1inp_line *e1inp_line_get(u_int8_t e1_nr) +{ + struct e1inp_line *e1i_line; + + /* iterate over global list of e1 lines */ + llist_for_each_entry(e1i_line, &e1inp_line_list, list) { + if (e1i_line->num == e1_nr) + return e1i_line; + } + return NULL; +} + +struct e1inp_line *e1inp_line_create(u_int8_t e1_nr, const char *driver_name) +{ + struct e1inp_driver *driver; + struct e1inp_line *line; + int i; + + line = e1inp_line_get(e1_nr); + if (line) { + LOGP(DINP, LOGL_ERROR, "E1 Line %u already exists\n", + e1_nr); + return NULL; + } + + driver = e1inp_driver_find(driver_name); + if (!driver) { + LOGP(DINP, LOGL_ERROR, "No such E1 driver '%s'\n", + driver_name); + return NULL; + } + + line = talloc_zero(tall_bsc_ctx, struct e1inp_line); + if (!line) + return NULL; + + line->driver = driver; + + line->num = e1_nr; + for (i = 0; i < NUM_E1_TS; i++) { + line->ts[i].num = i+1; + line->ts[i].line = line; + } + llist_add_tail(&line->list, &e1inp_line_list); + + return line; +} + +#if 0 +struct e1inp_line *e1inp_line_get_create(u_int8_t e1_nr) +{ + struct e1inp_line *line; + int i; + + line = e1inp_line_get(e1_nr); + if (line) + return line; + + line = talloc_zero(tall_bsc_ctx, struct e1inp_line); + if (!line) + return NULL; + + line->num = e1_nr; + for (i = 0; i < NUM_E1_TS; i++) { + line->ts[i].num = i+1; + line->ts[i].line = line; + } + llist_add_tail(&line->list, &e1inp_line_list); + + return line; +} +#endif + +static struct e1inp_ts *e1inp_ts_get(u_int8_t e1_nr, u_int8_t ts_nr) +{ + struct e1inp_line *e1i_line; + + e1i_line = e1inp_line_get(e1_nr); + if (!e1i_line) + return NULL; + + return &e1i_line->ts[ts_nr-1]; +} + +struct subch_mux *e1inp_get_mux(u_int8_t e1_nr, u_int8_t ts_nr) +{ + struct e1inp_ts *e1i_ts = e1inp_ts_get(e1_nr, ts_nr); + + if (!e1i_ts) + return NULL; + + return &e1i_ts->trau.mux; +} + +/* Signalling Link */ + +struct e1inp_sign_link *e1inp_lookup_sign_link(struct e1inp_ts *e1i, + u_int8_t tei, u_int8_t sapi) +{ + struct e1inp_sign_link *link; + + llist_for_each_entry(link, &e1i->sign.sign_links, list) { + if (link->sapi == sapi && link->tei == tei) + return link; + } + + return NULL; +} + +/* create a new signalling link in a E1 timeslot */ + +struct e1inp_sign_link * +e1inp_sign_link_create(struct e1inp_ts *ts, enum e1inp_sign_type type, + struct gsm_bts_trx *trx, u_int8_t tei, + u_int8_t sapi) +{ + struct e1inp_sign_link *link; + + if (ts->type != E1INP_TS_TYPE_SIGN) + return NULL; + + link = talloc_zero(tall_sigl_ctx, struct e1inp_sign_link); + if (!link) + return NULL; + + link->ts = ts; + link->type = type; + INIT_LLIST_HEAD(&link->tx_list); + link->trx = trx; + link->tei = tei; + link->sapi = sapi; + + llist_add_tail(&link->list, &ts->sign.sign_links); + + return link; +} + +void e1inp_sign_link_destroy(struct e1inp_sign_link *link) +{ + struct msgb *msg; + + llist_del(&link->list); + while (!llist_empty(&link->tx_list)) { + msg = msgb_dequeue(&link->tx_list); + msgb_free(msg); + } + + if (link->ts->type == E1INP_TS_TYPE_SIGN) + bsc_del_timer(&link->ts->sign.tx_timer); + + talloc_free(link); +} + +/* the E1 driver tells us he has received something on a TS */ +int e1inp_rx_ts(struct e1inp_ts *ts, struct msgb *msg, + u_int8_t tei, u_int8_t sapi) +{ + struct e1inp_sign_link *link; + struct gsm_bts *bts; + int ret; + + switch (ts->type) { + case E1INP_TS_TYPE_SIGN: + /* consult the list of signalling links */ + write_pcap_packet(PCAP_INPUT, sapi, tei, msg); + link = e1inp_lookup_sign_link(ts, tei, sapi); + if (!link) { + LOGP(DMI, LOGL_ERROR, "didn't find signalling link for " + "tei %d, sapi %d\n", tei, sapi); + return -EINVAL; + } + + log_set_context(BSC_CTX_BTS, link->trx->bts); + switch (link->type) { + case E1INP_SIGN_OML: + msg->trx = link->trx; + bts = msg->trx->bts; + ret = bts->model->oml_rcvmsg(msg); + break; + case E1INP_SIGN_RSL: + msg->trx = link->trx; + ret = abis_rsl_rcvmsg(msg); + break; + default: + ret = -EINVAL; + LOGP(DMI, LOGL_ERROR, "unknown link type %u\n", link->type); + break; + } + break; + case E1INP_TS_TYPE_TRAU: + ret = subch_demux_in(&ts->trau.demux, msg->l2h, msgb_l2len(msg)); + break; + default: + ret = -EINVAL; + LOGP(DMI, LOGL_ERROR, "unknown TS type %u\n", ts->type); + break; + } + + return ret; +} + +#define TSX_ALLOC_SIZE 4096 + +/* called by driver if it wants to transmit on a given TS */ +struct msgb *e1inp_tx_ts(struct e1inp_ts *e1i_ts, + struct e1inp_sign_link **sign_link) +{ + struct e1inp_sign_link *link; + struct msgb *msg = NULL; + int len; + + switch (e1i_ts->type) { + case E1INP_TS_TYPE_SIGN: + /* FIXME: implement this round robin */ + llist_for_each_entry(link, &e1i_ts->sign.sign_links, list) { + msg = msgb_dequeue(&link->tx_list); + if (msg) { + if (sign_link) + *sign_link = link; + break; + } + } + break; + case E1INP_TS_TYPE_TRAU: + msg = msgb_alloc(TSX_ALLOC_SIZE, "TRAU_TX"); + if (!msg) + return NULL; + len = subchan_mux_out(&e1i_ts->trau.mux, msg->data, 40); + msgb_put(msg, 40); + break; + default: + LOGP(DMI, LOGL_ERROR, "unsupported E1 TS type %u\n", e1i_ts->type); + return NULL; + } + return msg; +} + +/* called by driver in case some kind of link state event */ +int e1inp_event(struct e1inp_ts *ts, int evt, u_int8_t tei, u_int8_t sapi) +{ + struct e1inp_sign_link *link; + struct input_signal_data isd; + + link = e1inp_lookup_sign_link(ts, tei, sapi); + if (!link) + return -EINVAL; + + isd.link_type = link->type; + isd.trx = link->trx; + isd.tei = tei; + isd.sapi = sapi; + + /* report further upwards */ + dispatch_signal(SS_INPUT, evt, &isd); + return 0; +} + +/* register a driver with the E1 core */ +int e1inp_driver_register(struct e1inp_driver *drv) +{ + llist_add_tail(&drv->list, &e1inp_driver_list); + return 0; +} + +struct e1inp_driver *e1inp_driver_find(const char *name) +{ + struct e1inp_driver *drv; + + llist_for_each_entry(drv, &e1inp_driver_list, list) { + if (!strcasecmp(name, drv->name)) + return drv; + } + return NULL; +} + +int e1inp_line_update(struct e1inp_line *line) +{ + struct input_signal_data isd; + int rc; + + if (line->driver && line->driver->line_update) + rc = line->driver->line_update(line); + else + rc = 0; + + /* Send a signal to anyone who is interested in new lines being + * configured */ + memset(&isd, 0, sizeof(isd)); + isd.line = line; + dispatch_signal(SS_INPUT, S_INP_LINE_INIT, &isd); + + return rc; +} + +static int e1i_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + if (subsys != SS_GLOBAL || + signal != S_GLOBAL_SHUTDOWN) + return 0; + + if (pcap_fd) { + close(pcap_fd); + pcap_fd = -1; + } + + return 0; +} + +void e1inp_misdn_init(void); +void e1inp_dahdi_init(void); + +void e1inp_init(void) +{ + tall_sigl_ctx = talloc_named_const(tall_bsc_ctx, 1, + "e1inp_sign_link"); + register_signal_handler(SS_GLOBAL, e1i_sig_cb, NULL); + + e1inp_misdn_init(); +#ifdef HAVE_DAHDI_USER_H + e1inp_dahdi_init(); +#endif +} diff --git a/src/libabis/e1_input_vty.c b/src/libabis/e1_input_vty.c new file mode 100644 index 000000000..66bf6555e --- /dev/null +++ b/src/libabis/e1_input_vty.c @@ -0,0 +1,102 @@ +/* OpenBSC E1 vty interface */ +/* (C) 2011 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../bscconfig.h" + +#define E1_DRIVER_NAMES "(misdn|dahdi)" +#define E1_DRIVER_HELP "mISDN supported E1 Card\n" \ + "DAHDI supported E1/T1/J1 Card\n" + +DEFUN(cfg_e1line_driver, cfg_e1_line_driver_cmd, + "e1_line <0-255> driver " E1_DRIVER_NAMES, + "Configure E1/T1/J1 Line\n" "Line Number\n" "Set driver for this line\n" + E1_DRIVER_HELP) +{ + struct e1inp_line *line; + int e1_nr = atoi(argv[0]); + + line = e1inp_line_get(e1_nr); + if (line) { + vty_out(vty, "%% Line %d already exists%s", e1_nr, VTY_NEWLINE); + return CMD_WARNING; + } + line = e1inp_line_create(e1_nr, argv[1]); + if (!line) { + vty_out(vty, "%% Error creating line %d%s", e1_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_e1inp, cfg_e1inp_cmd, + "e1_input", + "Configure E1/T1/J1 TDM input\n") +{ + vty->node = E1INP_NODE; + + return CMD_SUCCESS; +} + +static int e1inp_config_write(struct vty *vty) +{ + struct e1inp_line *line; + + vty_out(vty, "e1_input%s", VTY_NEWLINE); + + llist_for_each_entry(line, &e1inp_line_list, list) { + vty_out(vty, " e1_line %u driver %s%s", line->num, + line->driver->name, VTY_NEWLINE); + } + return CMD_SUCCESS; +} + +struct cmd_node e1inp_node = { + E1INP_NODE, + "%s(e1_input)#", + 1, +}; + +int e1inp_vty_init(void) +{ + install_element(CONFIG_NODE, &cfg_e1inp_cmd); + install_node(&e1inp_node, e1inp_config_write); + install_element(E1INP_NODE, &cfg_e1_line_driver_cmd); + + return 0; +} diff --git a/src/libabis/input/dahdi.c b/src/libabis/input/dahdi.c new file mode 100644 index 000000000..572bb5ae9 --- /dev/null +++ b/src/libabis/input/dahdi.c @@ -0,0 +1,494 @@ +/* OpenBSC Abis input driver for DAHDI */ + +/* (C) 2008-2011 by Harald Welte + * (C) 2009 by Holger Hans Peter Freyther + * (C) 2010 by Digium and Matthew Fredrickson + * + * All Rights Reserved + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "../../../bscconfig.h" + +#ifdef HAVE_DAHDI_USER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lapd.h" + +#define TS1_ALLOC_SIZE 300 + +/* Corresponds to dahdi/user.h, only PRI related events */ +static const struct value_string dahdi_evt_names[] = { + { DAHDI_EVENT_NONE, "NONE" }, + { DAHDI_EVENT_ALARM, "ALARM" }, + { DAHDI_EVENT_NOALARM, "NOALARM" }, + { DAHDI_EVENT_ABORT, "HDLC ABORT" }, + { DAHDI_EVENT_OVERRUN, "HDLC OVERRUN" }, + { DAHDI_EVENT_BADFCS, "HDLC BAD FCS" }, + { DAHDI_EVENT_REMOVED, "REMOVED" }, + { 0, NULL } +}; + +static void handle_dahdi_exception(struct e1inp_ts *ts) +{ + int rc, evt; + struct input_signal_data isd; + + rc = ioctl(ts->driver.dahdi.fd.fd, DAHDI_GETEVENT, &evt); + if (rc < 0) + return; + + LOGP(DMI, LOGL_NOTICE, "Line %u(%s) / TS %u DAHDI EVENT %s\n", + ts->line->num, ts->line->name, ts->num, + get_value_string(dahdi_evt_names, evt)); + + isd.line = ts->line; + + switch (evt) { + case DAHDI_EVENT_ALARM: + /* we should notify the code that the line is gone */ + dispatch_signal(SS_INPUT, S_INP_LINE_ALARM, &isd); + break; + case DAHDI_EVENT_NOALARM: + /* alarm has gone, we should re-start the SABM requests */ + dispatch_signal(SS_INPUT, S_INP_LINE_NOALARM, &isd); + break; + } +} + +static int handle_ts1_read(struct bsc_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct msgb *msg = msgb_alloc(TS1_ALLOC_SIZE, "DAHDI TS1"); + lapd_mph_type prim; + unsigned int sapi, tei; + int ilen, ret; + uint8_t *idata; + + if (!msg) + return -ENOMEM; + + ret = read(bfd->fd, msg->data, TS1_ALLOC_SIZE - 16); + if (ret == -1) + handle_dahdi_exception(e1i_ts); + else if (ret < 0) { + perror("read "); + } + msgb_put(msg, ret - 2); + if (ret <= 3) { + perror("read "); + } + + sapi = msg->data[0] >> 2; + tei = msg->data[1] >> 1; + + DEBUGP(DMI, "<= len = %d, sapi(%d) tei(%d)", ret, sapi, tei); + + idata = lapd_receive(e1i_ts->driver.dahdi.lapd, msg->data, msg->len, &ilen, &prim); + if (!idata && prim == 0) + return -EIO; + + msgb_pull(msg, 2); + + DEBUGP(DMI, "prim %08x\n", prim); + + switch (prim) { + case 0: + break; + case LAPD_MPH_ACTIVATE_IND: + DEBUGP(DMI, "MPH_ACTIVATE_IND: sapi(%d) tei(%d)\n", sapi, tei); + ret = e1inp_event(e1i_ts, S_INP_TEI_UP, tei, sapi); + break; + case LAPD_MPH_DEACTIVATE_IND: + DEBUGP(DMI, "MPH_DEACTIVATE_IND: sapi(%d) tei(%d)\n", sapi, tei); + ret = e1inp_event(e1i_ts, S_INP_TEI_DN, tei, sapi); + break; + case LAPD_DL_DATA_IND: + case LAPD_DL_UNITDATA_IND: + if (prim == LAPD_DL_DATA_IND) + msg->l2h = msg->data + 2; + else + msg->l2h = msg->data + 1; + DEBUGP(DMI, "RX: %s\n", hexdump(msgb_l2(msg), ret)); + ret = e1inp_rx_ts(e1i_ts, msg, tei, sapi); + break; + default: + printf("ERROR: unknown prim\n"); + break; + } + + DEBUGP(DMI, "Returned ok\n"); + return ret; +} + +static int ts_want_write(struct e1inp_ts *e1i_ts) +{ + /* We never include the DAHDI B-Channel FD into the + * writeset, since it doesn't support poll() based + * write flow control */ + if (e1i_ts->type == E1INP_TS_TYPE_TRAU) { + fprintf(stderr, "Trying to write TRAU ts\n"); + return 0; + } + + e1i_ts->driver.dahdi.fd.when |= BSC_FD_WRITE; + + return 0; +} + +static void timeout_ts1_write(void *data) +{ + struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data; + + /* trigger write of ts1, due to tx delay timer */ + ts_want_write(e1i_ts); +} + +static void dahdi_write_msg(uint8_t *data, int len, void *cbdata) +{ + struct bsc_fd *bfd = cbdata; + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + int ret; + + ret = write(bfd->fd, data, len + 2); + if (ret == -1) + handle_dahdi_exception(e1i_ts); + else if (ret < 0) + LOGP(DMI, LOGL_NOTICE, "%s write failed %d\n", __func__, ret); +} + +static int handle_ts1_write(struct bsc_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct e1inp_sign_link *sign_link; + struct msgb *msg; + + bfd->when &= ~BSC_FD_WRITE; + + /* get the next msg for this timeslot */ + msg = e1inp_tx_ts(e1i_ts, &sign_link); + if (!msg) { + /* no message after tx delay timer */ + return 0; + } + + DEBUGP(DMI, "TX: %s\n", hexdump(msg->data, msg->len)); + lapd_transmit(e1i_ts->driver.dahdi.lapd, sign_link->tei, + sign_link->sapi, msg->data, msg->len); + msgb_free(msg); + + /* set tx delay timer for next event */ + e1i_ts->sign.tx_timer.cb = timeout_ts1_write; + e1i_ts->sign.tx_timer.data = e1i_ts; + bsc_schedule_timer(&e1i_ts->sign.tx_timer, 0, 50000); + + return 0; +} + + +static int invertbits = 1; + +static u_int8_t flip_table[256]; + +static void init_flip_bits(void) +{ + int i,k; + + for (i = 0 ; i < 256 ; i++) { + u_int8_t sample = 0 ; + for (k = 0; k<8; k++) { + if ( i & 1 << k ) sample |= 0x80 >> k; + } + flip_table[i] = sample; + } +} + +static u_int8_t * flip_buf_bits ( u_int8_t * buf , int len) +{ + int i; + u_int8_t * start = buf; + + for (i = 0 ; i < len; i++) { + buf[i] = flip_table[(u_int8_t)buf[i]]; + } + + return start; +} + +#define D_BCHAN_TX_GRAN 160 +/* write to a B channel TS */ +static int handle_tsX_write(struct bsc_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + u_int8_t tx_buf[D_BCHAN_TX_GRAN]; + struct subch_mux *mx = &e1i_ts->trau.mux; + int ret; + + ret = subchan_mux_out(mx, tx_buf, D_BCHAN_TX_GRAN); + + if (ret != D_BCHAN_TX_GRAN) { + fprintf(stderr, "Huh, got ret of %d\n", ret); + if (ret < 0) + return ret; + } + + DEBUGP(DMIB, "BCHAN TX: %s\n", + hexdump(tx_buf, D_BCHAN_TX_GRAN)); + + if (invertbits) { + flip_buf_bits(tx_buf, ret); + } + + ret = write(bfd->fd, tx_buf, ret); + if (ret < D_BCHAN_TX_GRAN) + fprintf(stderr, "send returns %d instead of %d\n", ret, + D_BCHAN_TX_GRAN); + + return ret; +} + +#define D_TSX_ALLOC_SIZE (D_BCHAN_TX_GRAN) +/* FIXME: read from a B channel TS */ +static int handle_tsX_read(struct bsc_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct msgb *msg = msgb_alloc(D_TSX_ALLOC_SIZE, "DAHDI TSx"); + int ret; + + if (!msg) + return -ENOMEM; + + ret = read(bfd->fd, msg->data, D_TSX_ALLOC_SIZE); + if (ret < 0 || ret != D_TSX_ALLOC_SIZE) { + fprintf(stderr, "read error %d %s\n", ret, strerror(errno)); + return ret; + } + + if (invertbits) { + flip_buf_bits(msg->data, ret); + } + + msgb_put(msg, ret); + + msg->l2h = msg->data; + DEBUGP(DMIB, "BCHAN RX: %s\n", + hexdump(msgb_l2(msg), ret)); + ret = e1inp_rx_ts(e1i_ts, msg, 0, 0); + /* physical layer indicates that data has been sent, + * we thus can send some more data */ + ret = handle_tsX_write(bfd); + msgb_free(msg); + + return ret; +} + +/* callback from select.c in case one of the fd's can be read/written */ +static int dahdi_fd_cb(struct bsc_fd *bfd, unsigned int what) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + unsigned int idx = ts_nr-1; + struct e1inp_ts *e1i_ts = &line->ts[idx]; + int rc = 0; + + switch (e1i_ts->type) { + case E1INP_TS_TYPE_SIGN: + if (what & BSC_FD_EXCEPT) + handle_dahdi_exception(e1i_ts); + if (what & BSC_FD_READ) + rc = handle_ts1_read(bfd); + if (what & BSC_FD_WRITE) + rc = handle_ts1_write(bfd); + break; + case E1INP_TS_TYPE_TRAU: + if (what & BSC_FD_EXCEPT) + handle_dahdi_exception(e1i_ts); + if (what & BSC_FD_READ) + rc = handle_tsX_read(bfd); + if (what & BSC_FD_WRITE) + rc = handle_tsX_write(bfd); + /* We never include the DAHDI B-Channel FD into the + * writeset, since it doesn't support poll() based + * write flow control */ + break; + default: + fprintf(stderr, "unknown E1 TS type %u\n", e1i_ts->type); + break; + } + + return rc; +} + +static int dahdi_e1_line_update(struct e1inp_line *line); + +struct e1inp_driver dahdi_driver = { + .name = "dahdi", + .want_write = ts_want_write, + .line_update = &dahdi_e1_line_update, +}; + +void dahdi_set_bufinfo(int fd, int as_sigchan) +{ + struct dahdi_bufferinfo bi; + int x = 0; + + if (ioctl(fd, DAHDI_GET_BUFINFO, &bi)) { + fprintf(stderr, "Error getting bufinfo\n"); + exit(-1); + } + + if (as_sigchan) { + bi.numbufs = 4; + bi.bufsize = 512; + } else { + bi.numbufs = 8; + bi.bufsize = D_BCHAN_TX_GRAN; + bi.txbufpolicy = DAHDI_POLICY_WHEN_FULL; + } + + if (ioctl(fd, DAHDI_SET_BUFINFO, &bi)) { + fprintf(stderr, "Error setting bufinfo\n"); + exit(-1); + } + + if (!as_sigchan) { + if (ioctl(fd, DAHDI_AUDIOMODE, &x)) { + fprintf(stderr, "Error setting bufinfo\n"); + exit(-1); + } + } else { + int one = 1; + ioctl(fd, DAHDI_HDLCFCSMODE, &one); + /* we cannot reliably check for the ioctl return value here + * as this command will fail if the slot _already_ was a + * signalling slot before :( */ + } +} + +static int dahdi_e1_setup(struct e1inp_line *line) +{ + int ts, ret; + + /* TS0 is CRC4, don't need any fd for it */ + for (ts = 1; ts < NUM_E1_TS; ts++) { + unsigned int idx = ts-1; + char openstr[128]; + struct e1inp_ts *e1i_ts = &line->ts[idx]; + struct bsc_fd *bfd = &e1i_ts->driver.dahdi.fd; + + bfd->data = line; + bfd->priv_nr = ts; + bfd->cb = dahdi_fd_cb; + snprintf(openstr, sizeof(openstr), "/dev/dahdi/%d", ts); + + switch (e1i_ts->type) { + case E1INP_TS_TYPE_NONE: + continue; + break; + case E1INP_TS_TYPE_SIGN: + bfd->fd = open(openstr, O_RDWR | O_NONBLOCK); + if (bfd->fd == -1) { + fprintf(stderr, "%s could not open %s %s\n", + __func__, openstr, strerror(errno)); + exit(-1); + } + bfd->when = BSC_FD_READ | BSC_FD_EXCEPT; + dahdi_set_bufinfo(bfd->fd, 1); + e1i_ts->driver.dahdi.lapd = lapd_instance_alloc(1, dahdi_write_msg, bfd); + break; + case E1INP_TS_TYPE_TRAU: + bfd->fd = open(openstr, O_RDWR | O_NONBLOCK); + if (bfd->fd == -1) { + fprintf(stderr, "%s could not open %s %s\n", + __func__, openstr, strerror(errno)); + exit(-1); + } + dahdi_set_bufinfo(bfd->fd, 0); + /* We never include the DAHDI B-Channel FD into the + * writeset, since it doesn't support poll() based + * write flow control */ + bfd->when = BSC_FD_READ | BSC_FD_EXCEPT;// | BSC_FD_WRITE; + break; + } + + if (bfd->fd < 0) { + fprintf(stderr, "%s could not open %s %s\n", + __func__, openstr, strerror(errno)); + return bfd->fd; + } + + ret = bsc_register_fd(bfd); + if (ret < 0) { + fprintf(stderr, "could not register FD: %s\n", + strerror(ret)); + return ret; + } + } + + return 0; +} + +static int dahdi_e1_line_update(struct e1inp_line *line) +{ + if (line->driver != &dahdi_driver) + return -EINVAL; + + return dahdi_e1_setup(line); +} + +int e1inp_dahdi_init(void) +{ + init_flip_bits(); + + /* register the driver with the core */ + return e1inp_driver_register(&dahdi_driver); +} + +#endif /* HAVE_DAHDI_USER_H */ diff --git a/src/libabis/input/hsl.c b/src/libabis/input/hsl.c new file mode 100644 index 000000000..1afe82b49 --- /dev/null +++ b/src/libabis/input/hsl.c @@ -0,0 +1,460 @@ +/* OpenBSC Abis input driver for HSL Femto */ + +/* (C) 2011 by Harald Welte + * (C) 2011 by On-Waves + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +/* HSL uses a much more primitive/simplified version of the IPA multiplex. + * + * They have taken out the nice parts like the ID_GET / ID_RESP for resolving + * the UNIT ID, as well as the keepalive ping/pong messages. Furthermore, the + * Stream Identifiers are fixed on the BTS side (RSL always 0, OML always 0xff) + * and both OML+RSL share a single TCP connection. + * + * Other oddities include the encapsulation of BSSGP messages in the L3_INFO IE + * of RSL + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HSL_TCP_PORT 2500 +#define HSL_PROTO_DEBUG 0xdd + +#define PRIV_OML 1 +#define PRIV_RSL 2 + +/* data structure for one E1 interface with A-bis */ +struct hsl_e1_handle { + struct bsc_fd listen_fd; + struct gsm_network *gsmnet; +}; + +static struct hsl_e1_handle *e1h; + + +#define TS1_ALLOC_SIZE 900 + +#define OML_UP 0x0001 +#define RSL_UP 0x0002 + +int hsl_drop_oml(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + struct e1inp_ts *ts; + struct e1inp_line *line; + struct bsc_fd *bfd; + + if (!bts || !bts->oml_link) + return -1; + + /* send OML down */ + ts = bts->oml_link->ts; + line = ts->line; + e1inp_event(ts, S_INP_TEI_DN, bts->oml_link->tei, bts->oml_link->sapi); + + bfd = &ts->driver.ipaccess.fd; + bsc_unregister_fd(bfd); + close(bfd->fd); + bfd->fd = -1; + + /* clean up OML and RSL */ + e1inp_sign_link_destroy(bts->oml_link); + bts->oml_link = NULL; + e1inp_sign_link_destroy(bts->c0->rsl_link); + bts->c0->rsl_link = NULL; + bts->ip_access.flags = 0; + + /* kill the E1 line now... as we have no one left to use it */ + talloc_free(line); + + return -1; +} + +static int hsl_drop_ts_fd(struct e1inp_ts *ts, struct bsc_fd *bfd) +{ + struct e1inp_sign_link *link, *link2; + int bts_nr = -1; + + llist_for_each_entry_safe(link, link2, &ts->sign.sign_links, list) { + bts_nr = link->trx->bts->bts_nr; + e1inp_sign_link_destroy(link); + } + + bsc_unregister_fd(bfd); + close(bfd->fd); + bfd->fd = -1; + + talloc_free(ts->line); + + return bts_nr; +} + +struct gsm_bts *find_bts_by_serno(struct gsm_network *net, unsigned long serno) +{ + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) { + if (bts->type != GSM_BTS_TYPE_HSL_FEMTO) + continue; + + if (serno == bts->hsl.serno) + return bts; + } + + return NULL; +} + + +static int process_hsl_rsl(struct msgb *msg, struct e1inp_line *line) +{ + char serno_buf[16]; + uint8_t serno_len; + unsigned long serno; + struct gsm_bts *bts; + + switch (msg->l2h[1]) { + case 0x80: + /*, contains Serial Number + SW version */ + if (msg->l2h[2] != 0xc0) + break; + serno_len = msg->l2h[3]; + if (serno_len > sizeof(serno_buf)-1) + serno_len = sizeof(serno_buf)-1; + memcpy(serno_buf, msg->l2h+4, serno_len); + serno_buf[serno_len] = '\0'; + serno = strtoul(serno_buf, NULL, 10); + bts = find_bts_by_serno(e1h->gsmnet, serno); + if (!bts) { + LOGP(DINP, LOGL_ERROR, "Unable to find BTS config for " + "serial number %lu(%s)\n", serno, serno_buf); + return -EIO; + } + + DEBUGP(DINP, "Identified HSL BTS Serial Number %lu\n", serno); + + /* we shouldn't hardcode it, but HSL femto also hardcodes it... */ + bts->oml_tei = 255; + bts->c0->rsl_tei = 0; + bts->oml_link = e1inp_sign_link_create(&line->ts[PRIV_OML - 1], + E1INP_SIGN_OML, bts->c0, + bts->oml_tei, 0); + bts->c0->rsl_link = e1inp_sign_link_create(&line->ts[PRIV_OML - 1], + E1INP_SIGN_RSL, bts->c0, + bts->c0->rsl_tei, 0); + e1inp_event(&line->ts[PRIV_OML-1], S_INP_TEI_UP, 255, 0); + e1inp_event(&line->ts[PRIV_OML-1], S_INP_TEI_UP, 0, 0); + bts->ip_access.flags |= OML_UP; + bts->ip_access.flags |= (RSL_UP << 0); + msgb_free(msg); + return 1; /* == we have taken over the msg */ + case 0x82: + /* FIXME: do something with BSSGP, i.e. forward it over + * NSIP to OsmoSGSN */ + msgb_free(msg); + return 1; + } + return 0; +} + +static int handle_ts1_read(struct bsc_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct e1inp_sign_link *link; + struct msgb *msg; + struct ipaccess_head *hh; + int ret = 0, error; + + msg = ipaccess_read_msg(bfd, &error); + if (!msg) { + if (error == 0) { + int ret = hsl_drop_ts_fd(e1i_ts, bfd); + if (ret >= 0) + LOGP(DINP, LOGL_NOTICE, "BTS %u disappeared, dead socket\n", + ret); + else + LOGP(DINP, LOGL_NOTICE, "unknown BTS disappeared, dead socket\n"); + } + return error; + } + + DEBUGP(DMI, "RX %u: %s\n", ts_nr, hexdump(msgb_l2(msg), msgb_l2len(msg))); + + hh = (struct ipaccess_head *) msg->data; + if (hh->proto == HSL_PROTO_DEBUG) { + LOGP(DINP, LOGL_NOTICE, "HSL debug: %s\n", msg->data + sizeof(*hh)); + msgb_free(msg); + return ret; + } + + /* HSL proprietary RSL extension */ + if (hh->proto == 0 && (msg->l2h[0] == 0x81 || msg->l2h[0] == 0x80)) { + ret = process_hsl_rsl(msg, line); + if (ret < 0) { + /* FIXME: close connection */ + hsl_drop_ts_fd(e1i_ts, bfd); + return ret; + } else if (ret == 1) + return 0; + /* else: continue... */ + } +#ifdef HSL_SR_1_0 + /* HSL for whatever reason chose to use 0x81 instead of 0x80 for FOM */ + if (hh->proto == 255 && msg->l2h[0] == (ABIS_OM_MDISC_FOM | 0x01)) + msg->l2h[0] = ABIS_OM_MDISC_FOM; +#endif + link = e1inp_lookup_sign_link(e1i_ts, hh->proto, 0); + if (!link) { + LOGP(DINP, LOGL_ERROR, "no matching signalling link for " + "hh->proto=0x%02x\n", hh->proto); + msgb_free(msg); + return -EIO; + } + msg->trx = link->trx; + + switch (link->type) { + case E1INP_SIGN_RSL: + if (!(msg->trx->bts->ip_access.flags & (RSL_UP << msg->trx->nr))) { + e1inp_event(e1i_ts, S_INP_TEI_UP, link->tei, link->sapi); + msg->trx->bts->ip_access.flags |= (RSL_UP << msg->trx->nr); + } + ret = abis_rsl_rcvmsg(msg); + break; + case E1INP_SIGN_OML: + if (!(msg->trx->bts->ip_access.flags & OML_UP)) { + e1inp_event(e1i_ts, S_INP_TEI_UP, link->tei, link->sapi); + msg->trx->bts->ip_access.flags |= OML_UP; + } + ret = abis_nm_rcvmsg(msg); + break; + default: + LOGP(DINP, LOGL_NOTICE, "Unknown HSL protocol class 0x%02x\n", hh->proto); + msgb_free(msg); + break; + } + return ret; +} + +static int ts_want_write(struct e1inp_ts *e1i_ts) +{ + e1i_ts->driver.ipaccess.fd.when |= BSC_FD_WRITE; + + return 0; +} + +static void timeout_ts1_write(void *data) +{ + struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data; + + /* trigger write of ts1, due to tx delay timer */ + ts_want_write(e1i_ts); +} + +static int handle_ts1_write(struct bsc_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct e1inp_sign_link *sign_link; + struct msgb *msg; + u_int8_t proto; + int ret; + + bfd->when &= ~BSC_FD_WRITE; + + /* get the next msg for this timeslot */ + msg = e1inp_tx_ts(e1i_ts, &sign_link); + if (!msg) { + /* no message after tx delay timer */ + return 0; + } + + switch (sign_link->type) { + case E1INP_SIGN_OML: + proto = IPAC_PROTO_OML; +#ifdef HSL_SR_1_0 + /* HSL uses 0x81 for FOM for some reason */ + if (msg->data[0] == ABIS_OM_MDISC_FOM) + msg->data[0] = ABIS_OM_MDISC_FOM | 0x01; +#endif + break; + case E1INP_SIGN_RSL: + proto = IPAC_PROTO_RSL; + break; + default: + msgb_free(msg); + bfd->when |= BSC_FD_WRITE; /* come back for more msg */ + return -EINVAL; + } + + msg->l2h = msg->data; + ipaccess_prepend_header(msg, sign_link->tei); + + DEBUGP(DMI, "TX %u: %s\n", ts_nr, hexdump(msg->l2h, msgb_l2len(msg))); + + ret = send(bfd->fd, msg->data, msg->len, 0); + msgb_free(msg); + + /* set tx delay timer for next event */ + e1i_ts->sign.tx_timer.cb = timeout_ts1_write; + e1i_ts->sign.tx_timer.data = e1i_ts; + + /* Reducing this might break the nanoBTS 900 init. */ + bsc_schedule_timer(&e1i_ts->sign.tx_timer, 0, e1i_ts->sign.delay); + + return ret; +} + +/* callback from select.c in case one of the fd's can be read/written */ +static int hsl_fd_cb(struct bsc_fd *bfd, unsigned int what) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + unsigned int idx = ts_nr-1; + struct e1inp_ts *e1i_ts; + int rc = 0; + + /* In case of early RSL we might not yet have a line */ + + if (line) + e1i_ts = &line->ts[idx]; + + if (!line || e1i_ts->type == E1INP_TS_TYPE_SIGN) { + if (what & BSC_FD_READ) + rc = handle_ts1_read(bfd); + if (what & BSC_FD_WRITE) + rc = handle_ts1_write(bfd); + } else + LOGP(DINP, LOGL_ERROR, "unknown E1 TS type %u\n", e1i_ts->type); + + return rc; +} + +struct e1inp_driver hsl_driver = { + .name = "HSL", + .want_write = ts_want_write, + .default_delay = 0, +}; + +/* callback of the OML listening filedescriptor */ +static int listen_fd_cb(struct bsc_fd *listen_bfd, unsigned int what) +{ + int ret; + int idx = 0; + int i; + struct e1inp_line *line; + struct e1inp_ts *e1i_ts; + struct bsc_fd *bfd; + struct sockaddr_in sa; + socklen_t sa_len = sizeof(sa); + + if (!(what & BSC_FD_READ)) + return 0; + + ret = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len); + if (ret < 0) { + perror("accept"); + return ret; + } + LOGP(DINP, LOGL_NOTICE, "accept()ed new HSL link from %s\n", + inet_ntoa(sa.sin_addr)); + + line = talloc_zero(tall_bsc_ctx, struct e1inp_line); + if (!line) { + close(ret); + return -ENOMEM; + } + line->driver = &hsl_driver; + //line->driver_data = e1h; + /* create virrtual E1 timeslots for signalling */ + e1inp_ts_config(&line->ts[1-1], line, E1INP_TS_TYPE_SIGN); + + /* initialize the fds */ + for (i = 0; i < ARRAY_SIZE(line->ts); ++i) + line->ts[i].driver.ipaccess.fd.fd = -1; + + e1i_ts = &line->ts[idx]; + + bfd = &e1i_ts->driver.ipaccess.fd; + bfd->fd = ret; + bfd->data = line; + bfd->priv_nr = PRIV_OML; + bfd->cb = hsl_fd_cb; + bfd->when = BSC_FD_READ; + ret = bsc_register_fd(bfd); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "could not register FD\n"); + close(bfd->fd); + talloc_free(line); + return ret; + } + + return ret; + //return e1inp_line_register(line); +} + +int hsl_setup(struct gsm_network *gsmnet) +{ + int ret; + + /* register the driver with the core */ + /* FIXME: do this in the plugin initializer function */ + ret = e1inp_driver_register(&hsl_driver); + if (ret) + return ret; + + e1h = talloc_zero(tall_bsc_ctx, struct hsl_e1_handle); + if (!e1h) + return -ENOMEM; + + e1h->gsmnet = gsmnet; + + /* Listen for connections */ + ret = make_sock(&e1h->listen_fd, IPPROTO_TCP, 0, HSL_TCP_PORT, + listen_fd_cb); + if (ret < 0) + return ret; + + return 0; +} diff --git a/src/libabis/input/ipaccess.c b/src/libabis/input/ipaccess.c new file mode 100644 index 000000000..dcf8d1a53 --- /dev/null +++ b/src/libabis/input/ipaccess.c @@ -0,0 +1,797 @@ +/* OpenBSC Abis input driver for ip.access */ + +/* (C) 2009 by Harald Welte + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PRIV_OML 1 +#define PRIV_RSL 2 + +/* data structure for one E1 interface with A-bis */ +struct ia_e1_handle { + struct bsc_fd listen_fd; + struct bsc_fd rsl_listen_fd; + struct gsm_network *gsmnet; +}; + +static struct ia_e1_handle *e1h; + + +#define TS1_ALLOC_SIZE 900 + +static const u_int8_t pong[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_PONG }; +static const u_int8_t id_ack[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_ACK }; +static const u_int8_t id_req[] = { 0, 17, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_GET, + 0x01, IPAC_IDTAG_UNIT, + 0x01, IPAC_IDTAG_MACADDR, + 0x01, IPAC_IDTAG_LOCATION1, + 0x01, IPAC_IDTAG_LOCATION2, + 0x01, IPAC_IDTAG_EQUIPVERS, + 0x01, IPAC_IDTAG_SWVERSION, + 0x01, IPAC_IDTAG_UNITNAME, + 0x01, IPAC_IDTAG_SERNR, + }; + +static const char *idtag_names[] = { + [IPAC_IDTAG_SERNR] = "Serial_Number", + [IPAC_IDTAG_UNITNAME] = "Unit_Name", + [IPAC_IDTAG_LOCATION1] = "Location_1", + [IPAC_IDTAG_LOCATION2] = "Location_2", + [IPAC_IDTAG_EQUIPVERS] = "Equipment_Version", + [IPAC_IDTAG_SWVERSION] = "Software_Version", + [IPAC_IDTAG_IPADDR] = "IP_Address", + [IPAC_IDTAG_MACADDR] = "MAC_Address", + [IPAC_IDTAG_UNIT] = "Unit_ID", +}; + +static const char *ipac_idtag_name(int tag) +{ + if (tag >= ARRAY_SIZE(idtag_names)) + return "unknown"; + + return idtag_names[tag]; +} + +int ipaccess_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len) +{ + u_int8_t t_len; + u_int8_t t_tag; + u_int8_t *cur = buf; + + memset(dec, 0, sizeof(*dec)); + + while (len >= 2) { + len -= 2; + t_len = *cur++; + t_tag = *cur++; + + if (t_len > len + 1) { + LOGP(DMI, LOGL_ERROR, "The tag does not fit: %d\n", t_len); + return -1; + } + + DEBUGPC(DMI, "%s='%s' ", ipac_idtag_name(t_tag), cur); + + dec->lv[t_tag].len = t_len; + dec->lv[t_tag].val = cur; + + cur += t_len; + len -= t_len; + } + return 0; +} + +struct gsm_bts *find_bts_by_unitid(struct gsm_network *net, + u_int16_t site_id, u_int16_t bts_id) +{ + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) { + + if (!is_ipaccess_bts(bts)) + continue; + + if (bts->ip_access.site_id == site_id && + bts->ip_access.bts_id == bts_id) + return bts; + } + + return NULL; +} + +static int parse_unitid(const char *str, u_int16_t *site_id, u_int16_t *bts_id, + u_int16_t *trx_id) +{ + unsigned long ul; + char *endptr; + const char *nptr; + + nptr = str; + ul = strtoul(nptr, &endptr, 10); + if (endptr <= nptr) + return -EINVAL; + if (site_id) + *site_id = ul & 0xffff; + + if (*endptr++ != '/') + return -EINVAL; + + nptr = endptr; + ul = strtoul(nptr, &endptr, 10); + if (endptr <= nptr) + return -EINVAL; + if (bts_id) + *bts_id = ul & 0xffff; + + if (*endptr++ != '/') + return -EINVAL; + + nptr = endptr; + ul = strtoul(nptr, &endptr, 10); + if (endptr <= nptr) + return -EINVAL; + if (trx_id) + *trx_id = ul & 0xffff; + + return 0; +} + +/* send the id ack */ +int ipaccess_send_id_ack(int fd) +{ + return write(fd, id_ack, sizeof(id_ack)); +} + +int ipaccess_send_id_req(int fd) +{ + return write(fd, id_req, sizeof(id_req)); +} + +/* base handling of the ip.access protocol */ +int ipaccess_rcvmsg_base(struct msgb *msg, + struct bsc_fd *bfd) +{ + u_int8_t msg_type = *(msg->l2h); + int ret = 0; + + switch (msg_type) { + case IPAC_MSGT_PING: + ret = write(bfd->fd, pong, sizeof(pong)); + break; + case IPAC_MSGT_PONG: + DEBUGP(DMI, "PONG!\n"); + break; + case IPAC_MSGT_ID_ACK: + DEBUGP(DMI, "ID_ACK? -> ACK!\n"); + ret = ipaccess_send_id_ack(bfd->fd); + break; + } + return 0; +} + +static int ipaccess_rcvmsg(struct e1inp_line *line, struct msgb *msg, + struct bsc_fd *bfd) +{ + struct tlv_parsed tlvp; + u_int8_t msg_type = *(msg->l2h); + u_int16_t site_id = 0, bts_id = 0, trx_id = 0; + struct gsm_bts *bts; + char *unitid; + int len; + + /* handle base messages */ + ipaccess_rcvmsg_base(msg, bfd); + + switch (msg_type) { + case IPAC_MSGT_ID_RESP: + DEBUGP(DMI, "ID_RESP "); + /* parse tags, search for Unit ID */ + ipaccess_idtag_parse(&tlvp, (u_int8_t *)msg->l2h + 2, + msgb_l2len(msg)-2); + DEBUGP(DMI, "\n"); + + if (!TLVP_PRESENT(&tlvp, IPAC_IDTAG_UNIT)) + break; + + len = TLVP_LEN(&tlvp, IPAC_IDTAG_UNIT); + if (len < 1) + break; + + /* lookup BTS, create sign_link, ... */ + unitid = (char *) TLVP_VAL(&tlvp, IPAC_IDTAG_UNIT); + unitid[len - 1] = '\0'; + parse_unitid(unitid, &site_id, &bts_id, &trx_id); + bts = find_bts_by_unitid(e1h->gsmnet, site_id, bts_id); + if (!bts) { + LOGP(DINP, LOGL_ERROR, "Unable to find BTS configuration for " + " %u/%u/%u, disconnecting\n", site_id, bts_id, + trx_id); + return -EIO; + } + DEBUGP(DINP, "Identified BTS %u/%u/%u\n", site_id, bts_id, trx_id); + if (bfd->priv_nr == PRIV_OML) { + /* drop any old oml connection */ + ipaccess_drop_oml(bts); + bts->oml_link = e1inp_sign_link_create(&line->ts[PRIV_OML - 1], + E1INP_SIGN_OML, bts->c0, + bts->oml_tei, 0); + } else if (bfd->priv_nr == PRIV_RSL) { + struct e1inp_ts *e1i_ts; + struct bsc_fd *newbfd; + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_id); + + /* drop any old rsl connection */ + ipaccess_drop_rsl(trx); + + if (!bts->oml_link) { + bsc_unregister_fd(bfd); + close(bfd->fd); + bfd->fd = -1; + talloc_free(bfd); + return 0; + } + + bfd->data = line = bts->oml_link->ts->line; + e1i_ts = &line->ts[PRIV_RSL + trx_id - 1]; + newbfd = &e1i_ts->driver.ipaccess.fd; + e1inp_ts_config(e1i_ts, line, E1INP_TS_TYPE_SIGN); + + trx->rsl_link = e1inp_sign_link_create(e1i_ts, + E1INP_SIGN_RSL, trx, + trx->rsl_tei, 0); + trx->rsl_link->ts->sign.delay = 0; + + /* get rid of our old temporary bfd */ + memcpy(newbfd, bfd, sizeof(*newbfd)); + newbfd->priv_nr = PRIV_RSL + trx_id; + bsc_unregister_fd(bfd); + bfd->fd = -1; + talloc_free(bfd); + bsc_register_fd(newbfd); + } + break; + } + return 0; +} + +#define OML_UP 0x0001 +#define RSL_UP 0x0002 + +/* + * read one ipa message from the socket + * return NULL in case of error + */ +struct msgb *ipaccess_read_msg(struct bsc_fd *bfd, int *error) +{ + struct msgb *msg = msgb_alloc(TS1_ALLOC_SIZE, "Abis/IP"); + struct ipaccess_head *hh; + int len, ret = 0; + + if (!msg) { + *error = -ENOMEM; + return NULL; + } + + /* first read our 3-byte header */ + hh = (struct ipaccess_head *) msg->data; + ret = recv(bfd->fd, msg->data, sizeof(*hh), 0); + if (ret == 0) { + msgb_free(msg); + *error = ret; + return NULL; + } else if (ret != sizeof(*hh)) { + if (errno != EAGAIN) + LOGP(DINP, LOGL_ERROR, "recv error %d %s\n", ret, strerror(errno)); + msgb_free(msg); + *error = ret; + return NULL; + } + + msgb_put(msg, ret); + + /* then read te length as specified in header */ + msg->l2h = msg->data + sizeof(*hh); + len = ntohs(hh->len); + + if (len < 0 || TS1_ALLOC_SIZE < len + sizeof(*hh)) { + LOGP(DINP, LOGL_ERROR, "Can not read this packet. %d avail\n", len); + msgb_free(msg); + *error = -EIO; + return NULL; + } + + ret = recv(bfd->fd, msg->l2h, len, 0); + if (ret < len) { + LOGP(DINP, LOGL_ERROR, "short read! Got %d from %d\n", ret, len); + msgb_free(msg); + *error = -EIO; + return NULL; + } + msgb_put(msg, ret); + + return msg; +} + +int ipaccess_drop_oml(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + struct e1inp_ts *ts; + struct e1inp_line *line; + struct bsc_fd *bfd; + + if (!bts || !bts->oml_link) + return -1; + + /* send OML down */ + ts = bts->oml_link->ts; + line = ts->line; + e1inp_event(ts, S_INP_TEI_DN, bts->oml_link->tei, bts->oml_link->sapi); + + bfd = &ts->driver.ipaccess.fd; + bsc_unregister_fd(bfd); + close(bfd->fd); + bfd->fd = -1; + + /* clean up OML and RSL */ + e1inp_sign_link_destroy(bts->oml_link); + bts->oml_link = NULL; + bts->ip_access.flags = 0; + + /* drop all RSL connections too */ + llist_for_each_entry(trx, &bts->trx_list, list) + ipaccess_drop_rsl(trx); + + /* kill the E1 line now... as we have no one left to use it */ + talloc_free(line); + + return -1; +} + +static int ipaccess_drop(struct e1inp_ts *ts, struct bsc_fd *bfd) +{ + struct e1inp_sign_link *link; + int bts_nr; + + if (!ts) { + /* + * If we don't have a TS this means that this is a RSL + * connection but we are not past the authentication + * handling yet. So we can safely delete this bfd and + * wait for a reconnect. + */ + bsc_unregister_fd(bfd); + close(bfd->fd); + bfd->fd = -1; + talloc_free(bfd); + return -1; + } + + /* attempt to find a signalling link */ + if (ts->type == E1INP_TS_TYPE_SIGN) { + llist_for_each_entry(link, &ts->sign.sign_links, list) { + bts_nr = link->trx->bts->bts_nr; + /* we have issues just reconnecting RLS so we drop OML */ + ipaccess_drop_oml(link->trx->bts); + return bts_nr; + } + } + + /* error case */ + LOGP(DINP, LOGL_ERROR, "Failed to find a signalling link for ts: %p\n", ts); + bsc_unregister_fd(bfd); + close(bfd->fd); + bfd->fd = -1; + return -1; +} + +int ipaccess_drop_rsl(struct gsm_bts_trx *trx) +{ + struct bsc_fd *bfd; + struct e1inp_ts *ts; + + if (!trx || !trx->rsl_link) + return -1; + + /* send RSL down */ + ts = trx->rsl_link->ts; + e1inp_event(ts, S_INP_TEI_DN, trx->rsl_link->tei, trx->rsl_link->sapi); + + /* close the socket */ + bfd = &ts->driver.ipaccess.fd; + bsc_unregister_fd(bfd); + close(bfd->fd); + bfd->fd = -1; + + /* destroy */ + e1inp_sign_link_destroy(trx->rsl_link); + trx->rsl_link = NULL; + + return -1; +} + +static int handle_ts1_read(struct bsc_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct e1inp_sign_link *link; + struct msgb *msg; + struct ipaccess_head *hh; + int ret = 0, error; + + msg = ipaccess_read_msg(bfd, &error); + if (!msg) { + if (error == 0) { + int ret = ipaccess_drop(e1i_ts, bfd); + if (ret >= 0) + LOGP(DINP, LOGL_NOTICE, "BTS %u disappeared, dead socket\n", + ret); + else + LOGP(DINP, LOGL_NOTICE, "unknown BTS disappeared, dead socket\n"); + } + return error; + } + + DEBUGP(DMI, "RX %u: %s\n", ts_nr, hexdump(msgb_l2(msg), msgb_l2len(msg))); + + hh = (struct ipaccess_head *) msg->data; + if (hh->proto == IPAC_PROTO_IPACCESS) { + ret = ipaccess_rcvmsg(line, msg, bfd); + if (ret < 0) + ipaccess_drop(e1i_ts, bfd); + msgb_free(msg); + return ret; + } + /* BIG FAT WARNING: bfd might no longer exist here, since ipaccess_rcvmsg() + * might have free'd it !!! */ + + link = e1inp_lookup_sign_link(e1i_ts, hh->proto, 0); + if (!link) { + LOGP(DINP, LOGL_ERROR, "no matching signalling link for " + "hh->proto=0x%02x\n", hh->proto); + msgb_free(msg); + return -EIO; + } + msg->trx = link->trx; + + switch (link->type) { + case E1INP_SIGN_RSL: + if (!(msg->trx->bts->ip_access.flags & (RSL_UP << msg->trx->nr))) { + e1inp_event(e1i_ts, S_INP_TEI_UP, link->tei, link->sapi); + msg->trx->bts->ip_access.flags |= (RSL_UP << msg->trx->nr); + } + ret = abis_rsl_rcvmsg(msg); + break; + case E1INP_SIGN_OML: + if (!(msg->trx->bts->ip_access.flags & OML_UP)) { + e1inp_event(e1i_ts, S_INP_TEI_UP, link->tei, link->sapi); + msg->trx->bts->ip_access.flags |= OML_UP; + } + ret = abis_nm_rcvmsg(msg); + break; + default: + LOGP(DINP, LOGL_NOTICE, "Unknown IP.access protocol proto=0x%02x\n", hh->proto); + msgb_free(msg); + break; + } + return ret; +} + +void ipaccess_prepend_header(struct msgb *msg, int proto) +{ + struct ipaccess_head *hh; + + /* prepend the ip.access header */ + hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh)); + hh->len = htons(msg->len - sizeof(*hh)); + hh->proto = proto; +} + +static int ts_want_write(struct e1inp_ts *e1i_ts) +{ + e1i_ts->driver.ipaccess.fd.when |= BSC_FD_WRITE; + + return 0; +} + +static void timeout_ts1_write(void *data) +{ + struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data; + + /* trigger write of ts1, due to tx delay timer */ + ts_want_write(e1i_ts); +} + +static int handle_ts1_write(struct bsc_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct e1inp_sign_link *sign_link; + struct msgb *msg; + u_int8_t proto; + int ret; + + bfd->when &= ~BSC_FD_WRITE; + + /* get the next msg for this timeslot */ + msg = e1inp_tx_ts(e1i_ts, &sign_link); + if (!msg) { + /* no message after tx delay timer */ + return 0; + } + + switch (sign_link->type) { + case E1INP_SIGN_OML: + proto = IPAC_PROTO_OML; + break; + case E1INP_SIGN_RSL: + proto = IPAC_PROTO_RSL; + break; + default: + msgb_free(msg); + bfd->when |= BSC_FD_WRITE; /* come back for more msg */ + return -EINVAL; + } + + msg->l2h = msg->data; + ipaccess_prepend_header(msg, sign_link->tei); + + DEBUGP(DMI, "TX %u: %s\n", ts_nr, hexdump(msg->l2h, msgb_l2len(msg))); + + ret = send(bfd->fd, msg->data, msg->len, 0); + msgb_free(msg); + + /* set tx delay timer for next event */ + e1i_ts->sign.tx_timer.cb = timeout_ts1_write; + e1i_ts->sign.tx_timer.data = e1i_ts; + + /* Reducing this might break the nanoBTS 900 init. */ + bsc_schedule_timer(&e1i_ts->sign.tx_timer, 0, e1i_ts->sign.delay); + + return ret; +} + +/* callback from select.c in case one of the fd's can be read/written */ +static int ipaccess_fd_cb(struct bsc_fd *bfd, unsigned int what) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + unsigned int idx = ts_nr-1; + struct e1inp_ts *e1i_ts; + int rc = 0; + + /* In case of early RSL we might not yet have a line */ + + if (line) + e1i_ts = &line->ts[idx]; + + if (!line || e1i_ts->type == E1INP_TS_TYPE_SIGN) { + if (what & BSC_FD_READ) + rc = handle_ts1_read(bfd); + if (what & BSC_FD_WRITE) + rc = handle_ts1_write(bfd); + } else + LOGP(DINP, LOGL_ERROR, "unknown E1 TS type %u\n", e1i_ts->type); + + return rc; +} + +struct e1inp_driver ipaccess_driver = { + .name = "ip.access", + .want_write = ts_want_write, + .default_delay = 0, +}; + +/* callback of the OML listening filedescriptor */ +static int listen_fd_cb(struct bsc_fd *listen_bfd, unsigned int what) +{ + int ret; + int idx = 0; + int i; + struct e1inp_line *line; + struct e1inp_ts *e1i_ts; + struct bsc_fd *bfd; + struct sockaddr_in sa; + socklen_t sa_len = sizeof(sa); + + if (!(what & BSC_FD_READ)) + return 0; + + ret = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len); + if (ret < 0) { + perror("accept"); + return ret; + } + LOGP(DINP, LOGL_NOTICE, "accept()ed new OML link from %s\n", + inet_ntoa(sa.sin_addr)); + + line = talloc_zero(tall_bsc_ctx, struct e1inp_line); + if (!line) { + close(ret); + return -ENOMEM; + } + line->driver = &ipaccess_driver; + //line->driver_data = e1h; + /* create virrtual E1 timeslots for signalling */ + e1inp_ts_config(&line->ts[1-1], line, E1INP_TS_TYPE_SIGN); + + /* initialize the fds */ + for (i = 0; i < ARRAY_SIZE(line->ts); ++i) + line->ts[i].driver.ipaccess.fd.fd = -1; + + e1i_ts = &line->ts[idx]; + + bfd = &e1i_ts->driver.ipaccess.fd; + bfd->fd = ret; + bfd->data = line; + bfd->priv_nr = PRIV_OML; + bfd->cb = ipaccess_fd_cb; + bfd->when = BSC_FD_READ; + ret = bsc_register_fd(bfd); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "could not register FD\n"); + close(bfd->fd); + talloc_free(line); + return ret; + } + + /* Request ID. FIXME: request LOCATION, HW/SW VErsion, Unit Name, Serno */ + ret = ipaccess_send_id_req(bfd->fd); + + return ret; + //return e1inp_line_register(line); +} + +static int rsl_listen_fd_cb(struct bsc_fd *listen_bfd, unsigned int what) +{ + struct sockaddr_in sa; + socklen_t sa_len = sizeof(sa); + struct bsc_fd *bfd; + int ret; + + if (!(what & BSC_FD_READ)) + return 0; + + bfd = talloc_zero(tall_bsc_ctx, struct bsc_fd); + if (!bfd) + return -ENOMEM; + + /* Some BTS has connected to us, but we don't know yet which line + * (as created by the OML link) to associate it with. Thus, we + * allocate a temporary bfd until we have received ID from BTS */ + + bfd->fd = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len); + if (bfd->fd < 0) { + perror("accept"); + return bfd->fd; + } + LOGP(DINP, LOGL_NOTICE, "accept()ed new RSL link from %s\n", inet_ntoa(sa.sin_addr)); + bfd->priv_nr = PRIV_RSL; + bfd->cb = ipaccess_fd_cb; + bfd->when = BSC_FD_READ; + ret = bsc_register_fd(bfd); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "could not register FD\n"); + close(bfd->fd); + talloc_free(bfd); + return ret; + } + /* Request ID. FIXME: request LOCATION, HW/SW VErsion, Unit Name, Serno */ + ret = write(bfd->fd, id_req, sizeof(id_req)); + + return 0; +} + +/* Actively connect to a BTS. Currently used by ipaccess-config.c */ +int ipaccess_connect(struct e1inp_line *line, struct sockaddr_in *sa) +{ + struct e1inp_ts *e1i_ts = &line->ts[0]; + struct bsc_fd *bfd = &e1i_ts->driver.ipaccess.fd; + int ret, on = 1; + + bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + bfd->cb = ipaccess_fd_cb; + bfd->when = BSC_FD_READ | BSC_FD_WRITE; + bfd->data = line; + bfd->priv_nr = PRIV_OML; + + if (bfd->fd < 0) { + LOGP(DINP, LOGL_ERROR, "could not create TCP socket.\n"); + return -EIO; + } + + setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + ret = connect(bfd->fd, (struct sockaddr *) sa, sizeof(*sa)); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "could not connect socket\n"); + close(bfd->fd); + return ret; + } + + ret = bsc_register_fd(bfd); + if (ret < 0) { + close(bfd->fd); + return ret; + } + + line->driver = &ipaccess_driver; + + return ret; + //return e1inp_line_register(line); +} + +int ipaccess_setup(struct gsm_network *gsmnet) +{ + int ret; + + /* register the driver with the core */ + /* FIXME: do this in the plugin initializer function */ + ret = e1inp_driver_register(&ipaccess_driver); + if (ret) + return ret; + + e1h = talloc_zero(tall_bsc_ctx, struct ia_e1_handle); + if (!e1h) + return -ENOMEM; + + e1h->gsmnet = gsmnet; + + /* Listen for OML connections */ + ret = make_sock(&e1h->listen_fd, IPPROTO_TCP, 0, IPA_TCP_PORT_OML, + listen_fd_cb); + if (ret < 0) + return ret; + + /* Listen for RSL connections */ + ret = make_sock(&e1h->rsl_listen_fd, IPPROTO_TCP, 0, + IPA_TCP_PORT_RSL, rsl_listen_fd_cb); + if (ret < 0) + return ret; + + return ret; +} diff --git a/src/libabis/input/lapd.c b/src/libabis/input/lapd.c new file mode 100644 index 000000000..7bce6cc51 --- /dev/null +++ b/src/libabis/input/lapd.c @@ -0,0 +1,710 @@ +/* OpenBSC minimal LAPD implementation */ + +/* (C) 2009 by oystein@homelien.no + * (C) 2009 by Holger Hans Peter Freyther + * (C) 2010 by Digium and Matthew Fredrickson + * (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +/* TODO: + * detect RR timeout and set SAP state back to SABM_RETRANSMIT + * use of value_string + * further code cleanup (spaghetti) + */ + +#include +#include +#include +#include + +#include "lapd.h" + +#include +#include +#include +#include +#include + +#define SABM_INTERVAL 0, 300000 + +typedef enum { + LAPD_TEI_NONE = 0, + LAPD_TEI_ASSIGNED, + LAPD_TEI_ACTIVE, +} lapd_tei_state; + +const char *lapd_tei_states[] = { + "NONE", + "ASSIGNED", + "ACTIVE", +}; + +typedef enum { + LAPD_TYPE_NONE = 0, + + LAPD_TYPE_I, + LAPD_TYPE_S, + LAPD_TYPE_U, +} lapd_msg_type; + +typedef enum { + /* commands/responses */ + LAPD_CMD_NONE = 0, + + LAPD_CMD_I, + LAPD_CMD_RR, + LAPD_CMD_RNR, + LAPD_CMD_REJ, + + LAPD_CMD_SABME, + LAPD_CMD_DM, + LAPD_CMD_UI, + LAPD_CMD_DISC, + LAPD_CMD_UA, + LAPD_CMD_FRMR, + LAPD_CMD_XID, +} lapd_cmd_type; + +const char *lapd_cmd_types[] = { + "NONE", + + "I", + "RR", + "RNR", + "REJ", + + "SABME", + "DM", + "UI", + "DISC", + "UA", + "FRMR", + "XID", + +}; + +enum lapd_sap_state { + SAP_STATE_INACTIVE, + SAP_STATE_SABM_RETRANS, + SAP_STATE_ACTIVE, +}; + +const char *lapd_sap_states[] = { + "INACTIVE", + "SABM_RETRANS", + "ACTIVE", +}; + +const char *lapd_msg_types = "?ISU"; + +/* structure representing an allocated TEI within a LAPD instance */ +struct lapd_tei { + struct llist_head list; + struct lapd_instance *li; + uint8_t tei; + lapd_tei_state state; + + struct llist_head sap_list; +}; + +/* Structure representing a SAP within a TEI. We use this for TE-mode to + * re-transmit SABM */ +struct lapd_sap { + struct llist_head list; + struct lapd_tei *tei; + uint8_t sapi; + enum lapd_sap_state state; + + /* A valid N(R) value is one that is in the range V(A) ≤ N(R) ≤ V(S). */ + int vs; /* next to be transmitted */ + int va; /* last acked by peer */ + int vr; /* next expected to be received */ + + struct timer_list sabme_timer; /* timer to re-transmit SABM message */ +}; + +/* 3.5.2.2 Send state variable V(S) + * Each point-to-point data link connection endpoint shall have an associated V(S) when using I frame + * commands. V(S) denotes the sequence number of the next I frame to be transmitted. The V(S) can + * take on the value 0 through n minus 1. The value of V(S) shall be incremented by 1 with each + * successive I frame transmission, and shall not exceed V(A) by more than the maximum number of + * outstanding I frames k. The value of k may be in the range of 1 ≤ k ≤ 127. + * + * 3.5.2.3 Acknowledge state variable V(A) + * Each point-to-point data link connection endpoint shall have an associated V(A) when using I frame + * commands and supervisory frame commands/responses. V(A) identifies the last I frame that has been + * acknowledged by its peer [V(A) − 1 equals the N(S) of the last acknowledged I frame]. V(A) can + * take on the value 0 through n minus 1. The value of V(A) shall be updated by the valid N(R) values + * received from its peer (see 3.5.2.6). A valid N(R) value is one that is in the range V(A) ≤ N(R) ≤ + * V(S). + * + * 3.5.2.5 Receive state variable V(R) + * Each point-to-point data link connection endpoint shall have an associated V(R) when using I frame + * commands and supervisory frame commands/responses. V(R) denotes the sequence number of the + * next in-sequence I frame expected to be received. V(R) can take on the value 0 through n minus 1. + * The value of V(R) shall be incremented by one with the receipt of an error-free, in-sequence I frame + * whose N(S) equals V(R). + */ +#define LAPD_NS(sap) (sap->vs) +#define LAPD_NR(sap) (sap->vr) + +/* 3.5.2.4 Send sequence number N(S) + * Only I frames contain N(S), the send sequence number of transmitted I frames. At the time that an in- + * sequence I frame is designated for transmission, the value of N(S) is set equal to V(S). + * + * 3.5.2.6 Receive sequence number N(R) + * All I frames and supervisory frames contain N(R), the expected send sequence number of the next + * received I frame. At the time that a frame of the above types is designated for transmission, the value + * of N(R) is set equal to V(R). N(R) indicates that the data link layer entity transmitting the N(R) has + * correctly received all I frames numbered up to and including N(R) − 1. + */ + +/* Resolve TEI structure from given numeric TEI */ +static struct lapd_tei *teip_from_tei(struct lapd_instance *li, uint8_t tei) +{ + struct lapd_tei *lt; + + llist_for_each_entry(lt, &li->tei_list, list) { + if (lt->tei == tei) + return lt; + } + return NULL; +}; + +static void lapd_tei_set_state(struct lapd_tei *teip, int newstate) +{ + DEBUGP(DMI, "state change on TEI %d: %s -> %s\n", teip->tei, + lapd_tei_states[teip->state], lapd_tei_states[newstate]); + teip->state = newstate; +}; + +/* Allocate a new TEI */ +struct lapd_tei *lapd_tei_alloc(struct lapd_instance *li, uint8_t tei) +{ + struct lapd_tei *teip; + + teip = talloc_zero(li, struct lapd_tei); + if (!teip) + return NULL; + + teip->li = li; + teip->tei = tei; + llist_add(&teip->list, &li->tei_list); + INIT_LLIST_HEAD(&teip->sap_list); + + lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED); + + return teip; +} + +/* Find a SAP within a given TEI */ +static struct lapd_sap *lapd_sap_find(struct lapd_tei *teip, uint8_t sapi) +{ + struct lapd_sap *sap; + + llist_for_each_entry(sap, &teip->sap_list, list) { + if (sap->sapi == sapi) + return sap; + } + + return NULL; +} + +static void sabme_timer_cb(void *_sap); + +/* Allocate a new SAP within a given TEI */ +static struct lapd_sap *lapd_sap_alloc(struct lapd_tei *teip, uint8_t sapi) +{ + struct lapd_sap *sap = talloc_zero(teip, struct lapd_sap); + + LOGP(DMI, LOGL_INFO, "Allocating SAP for SAPI=%u / TEI=%u\n", + sapi, teip->tei); + + sap->sapi = sapi; + sap->tei = teip; + sap->sabme_timer.cb = &sabme_timer_cb; + sap->sabme_timer.data = sap; + + llist_add(&sap->list, &teip->sap_list); + + return sap; +} + +static void lapd_sap_set_state(struct lapd_tei *teip, uint8_t sapi, + enum lapd_sap_state newstate) +{ + struct lapd_sap *sap = lapd_sap_find(teip, sapi); + if (!sap) + return; + + DEBUGP(DMI, "state change on TEI %u / SAPI %u: %s -> %s\n", teip->tei, + sapi, lapd_sap_states[sap->state], lapd_sap_states[newstate]); + switch (sap->state) { + case SAP_STATE_SABM_RETRANS: + if (newstate != SAP_STATE_SABM_RETRANS) + bsc_del_timer(&sap->sabme_timer); + break; + default: + if (newstate == SAP_STATE_SABM_RETRANS) + bsc_schedule_timer(&sap->sabme_timer, SABM_INTERVAL); + break; + } + + sap->state = newstate; +}; + +/* Input function into TEI manager */ +static void lapd_tei_receive(struct lapd_instance *li, uint8_t *data, int len) +{ + uint8_t entity = data[0]; + uint8_t ref = data[1]; + uint8_t mt = data[3]; + uint8_t action = data[4] >> 1; + uint8_t e = data[4] & 1; + uint8_t resp[8]; + struct lapd_tei *teip; + + DEBUGP(DMI, "TEIMGR: entity %x, ref %x, mt %x, action %x, e %x\n", entity, ref, mt, action, e); + + switch (mt) { + case 0x01: /* IDENTITY REQUEST */ + DEBUGP(DMI, "TEIMGR: identity request for TEI %u\n", action); + + teip = teip_from_tei(li, action); + if (!teip) { + LOGP(DMI, LOGL_INFO, "TEI MGR: New TEI %u\n", action); + lapd_tei_alloc(li, action); + } + + /* Send ACCEPT */ + memmove(resp, "\xfe\xff\x03\x0f\x00\x00\x02\x00", 8); + resp[7] = (action << 1) | 1; + li->transmit_cb(resp, 8, li->cbdata); + + if (teip->state == LAPD_TEI_NONE) + lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED); + break; + default: + LOGP(DMI, LOGL_NOTICE, "TEIMGR: unknown mt %x action %x\n", + mt, action); + break; + }; +}; + +/* General input function for any data received for this LAPD instance */ +uint8_t *lapd_receive(struct lapd_instance *li, uint8_t * data, unsigned int len, + int *ilen, lapd_mph_type *prim) +{ + uint8_t sapi, cr, tei, command; + int pf, ns, nr; + uint8_t *contents; + struct lapd_tei *teip; + struct lapd_sap *sap; + + uint8_t resp[8]; + int l = 0; + + *ilen = 0; + *prim = 0; + + if (len < 2) { + DEBUGP(DMI, "len %d < 2\n", len); + return NULL; + }; + + if ((data[0] & 1) != 0 || (data[1] & 1) != 1) { + DEBUGP(DMI, "address field %x/%x not well formed\n", data[0], + data[1]); + return NULL; + }; + + sapi = data[0] >> 2; + cr = (data[0] >> 1) & 1; + tei = data[1] >> 1; + command = li->network_side ^ cr; + //DEBUGP(DMI, " address sapi %x tei %d cmd %d cr %d\n", sapi, tei, command, cr); + + if (len < 3) { + DEBUGP(DMI, "len %d < 3\n", len); + return NULL; + }; + + lapd_msg_type typ = 0; + lapd_cmd_type cmd = 0; + pf = -1; + ns = -1; + nr = -1; + if ((data[2] & 1) == 0) { + typ = LAPD_TYPE_I; + assert(len >= 4); + ns = data[2] >> 1; + nr = data[3] >> 1; + pf = data[3] & 1; + cmd = LAPD_CMD_I; + } else if ((data[2] & 3) == 1) { + typ = LAPD_TYPE_S; + assert(len >= 4); + nr = data[3] >> 1; + pf = data[3] & 1; + switch (data[2]) { + case 0x1: + cmd = LAPD_CMD_RR; + break; + case 0x5: + cmd = LAPD_CMD_RNR; + break; + case 0x9: + cmd = LAPD_CMD_REJ; + break; + default: + LOGP(DMI, LOGL_ERROR, "unknown LAPD S cmd %x\n", data[2]); + return NULL; + }; + } else if ((data[2] & 3) == 3) { + typ = LAPD_TYPE_U; + pf = (data[2] >> 4) & 1; + int val = data[2] & ~(1 << 4); + switch (val) { + case 0x6f: + cmd = LAPD_CMD_SABME; + break; + case 0x0f: + cmd = LAPD_CMD_DM; + break; + case 0x03: + cmd = LAPD_CMD_UI; + break; + case 0x43: + cmd = LAPD_CMD_DISC; + break; + case 0x63: + cmd = LAPD_CMD_UA; + break; + case 0x87: + cmd = LAPD_CMD_FRMR; + break; + case 0xaf: + cmd = LAPD_CMD_XID; + break; + + default: + LOGP(DMI, LOGL_ERROR, "unknown U cmd %x " + "(pf %x data %x)\n", val, pf, data[2]); + return NULL; + }; + }; + + contents = &data[4]; + if (typ == LAPD_TYPE_U) + contents--; + *ilen = len - (contents - data); + + if (tei == 127) + lapd_tei_receive(li, contents, *ilen); + + teip = teip_from_tei(li, tei); + if (!teip) { + LOGP(DMI, LOGL_NOTICE, "Unknown TEI %u\n", tei); + return NULL; + } + + sap = lapd_sap_find(teip, sapi); + if (!sap) { + LOGP(DMI, LOGL_INFO, "No SAP for TEI=%u / SAPI=%u, " + "allocating\n", tei, sapi); + sap = lapd_sap_alloc(teip, sapi); + } + + DEBUGP(DMI, "<- %c %s sapi %x tei %3d cmd %x pf %x ns %3d nr %3d " + "ilen %d teip %p vs %d va %d vr %d len %d\n", + lapd_msg_types[typ], lapd_cmd_types[cmd], sapi, tei, command, pf, + ns, nr, *ilen, teip, sap->vs, sap->va, sap->vr, len); + + switch (cmd) { + case LAPD_CMD_I: + if (ns != sap->vr) { + DEBUGP(DMI, "ns %d != vr %d\n", ns, sap->vr); + if (ns == ((sap->vr - 1) & 0x7f)) { + DEBUGP(DMI, "DOUBLE FRAME, ignoring\n"); + cmd = 0; // ignore + } else { + assert(0); + }; + } else { + //printf("IN SEQUENCE\n"); + sap->vr = (ns + 1) & 0x7f; // FIXME: hack! + }; + + break; + case LAPD_CMD_UI: + break; + case LAPD_CMD_SABME: + sap->vs = 0; + sap->vr = 0; + sap->va = 0; + + // ua + resp[l++] = data[0]; + resp[l++] = (tei << 1) | 1; + resp[l++] = 0x73; + li->transmit_cb(resp, l, li->cbdata); + if (teip->state != LAPD_TEI_ACTIVE) { + if (teip->state == LAPD_TEI_ASSIGNED) { + lapd_tei_set_state(teip, + LAPD_TEI_ACTIVE); + //printf("ASSIGNED and ACTIVE\n"); + } else { +#if 0 + DEBUGP(DMI, "rr in strange state, send rej\n"); + + // rej + resp[l++] = (sap-> sapi << 2) | (li->network_side ? 0 : 2); + resp[l++] = (tei << 1) | 1; + resp[l++] = 0x09; //rej + resp[l++] = ((sap->vr + 1) << 1) | 0; + li->transmit_cb(resp, l, li->cbdata); + pf = 0; // dont reply +#endif + }; + }; + + *prim = LAPD_MPH_ACTIVATE_IND; + break; + case LAPD_CMD_UA: + sap->vs = 0; + sap->vr = 0; + sap->va = 0; + lapd_tei_set_state(teip, LAPD_TEI_ACTIVE); + lapd_sap_set_state(teip, sapi, SAP_STATE_ACTIVE); + *prim = LAPD_MPH_ACTIVATE_IND; + break; + case LAPD_CMD_RR: + sap->va = (nr & 0x7f); +#if 0 + if (teip->state != LAPD_TEI_ACTIVE) { + if (teip->state == LAPD_TEI_ASSIGNED) { + lapd_tei_set_state(teip, LAPD_TEI_ACTIVE); + *prim = LAPD_MPH_ACTIVATE_IND; + //printf("ASSIGNED and ACTIVE\n"); + } else { +#if 0 + DEBUGP(DMI, "rr in strange " "state, send rej\n"); + + // rej + resp[l++] = (sap-> sapi << 2) | (li->network_side ? 0 : 2); + resp[l++] = (tei << 1) | 1; + resp[l++] = 0x09; //rej + resp[l++] = + ((sap->vr + 1) << 1) | 0; + li->transmit_cb(resp, l, li->cbdata); + pf = 0; // dont reply +#endif + }; + }; +#endif + if (pf) { + // interrogating us, send rr + resp[l++] = data[0]; + resp[l++] = (tei << 1) | 1; + resp[l++] = 0x01; // rr + resp[l++] = (LAPD_NR(sap) << 1) | (data[3] & 1); // pf bit from req + + li->transmit_cb(resp, l, li->cbdata); + + }; + break; + case LAPD_CMD_FRMR: + // frame reject +#if 0 + if (teip->state == LAPD_TEI_ACTIVE) + *prim = LAPD_MPH_DEACTIVATE_IND; + lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED); +#endif + LOGP(DMI, LOGL_NOTICE, "frame reject, ignoring\n"); + break; + case LAPD_CMD_DISC: + // disconnect + resp[l++] = data[0]; + resp[l++] = (tei << 1) | 1; + resp[l++] = 0x73; + li->transmit_cb(resp, l, li->cbdata); + lapd_tei_set_state(teip, LAPD_TEI_NONE); + break; + default: + LOGP(DMI, LOGL_NOTICE, "unknown cmd for tei %d (cmd %x)\n", + tei, cmd); + break; + } + + if (typ == LAPD_TYPE_I) { + /* send rr + * Thu Jan 22 19:17:13 2009 <4000> sangoma.c:340 read (62/25) 4: fa 33 01 0a + * lapd <- S RR sapi 3e tei 25 cmd 0 pf 0 ns -1 nr 5 ilen 0 teip 0x613800 vs 7 va 5 vr 2 len 4 + */ + + /* interrogating us, send rr */ + DEBUGP(DMI, "Sending RR response\n"); + resp[l++] = data[0]; + resp[l++] = (tei << 1) | 1; + resp[l++] = 0x01; // rr + resp[l++] = (LAPD_NR(sap) << 1) | (data[3] & 1); // pf bit from req + + li->transmit_cb(resp, l, li->cbdata); + + if (cmd != 0) { + *prim = LAPD_DL_DATA_IND; + return contents; + } + } else if (tei != 127 && typ == LAPD_TYPE_U && cmd == LAPD_CMD_UI) { + *prim = LAPD_DL_UNITDATA_IND; + return contents; + } + + return NULL; +}; + +/* low-level function to send a single SABM message */ +static int lapd_send_sabm(struct lapd_instance *li, uint8_t tei, uint8_t sapi) +{ + struct msgb *msg = msgb_alloc_headroom(1024, 128, "LAPD SABM"); + if (!msg) + return -ENOMEM; + + DEBUGP(DMI, "Sending SABM for TEI=%u, SAPI=%u\n", tei, sapi); + + msgb_put_u8(msg, (sapi << 2) | (li->network_side ? 2 : 0)); + msgb_put_u8(msg, (tei << 1) | 1); + msgb_put_u8(msg, 0x7F); + + li->transmit_cb(msg->data, msg->len, li->cbdata); + + msgb_free(msg); + + return 0; +} + +/* timer call-back function for SABM re-transmission */ +static void sabme_timer_cb(void *_sap) +{ + struct lapd_sap *sap = _sap; + + lapd_send_sabm(sap->tei->li, sap->tei->tei, sap->sapi); + + if (sap->state == SAP_STATE_SABM_RETRANS) + bsc_schedule_timer(&sap->sabme_timer, SABM_INTERVAL); +} + +/* Start a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */ +int lapd_sap_start(struct lapd_instance *li, uint8_t tei, uint8_t sapi) +{ + struct lapd_sap *sap; + struct lapd_tei *teip; + + teip = teip_from_tei(li, tei); + if (!teip) + teip = lapd_tei_alloc(li, tei); + + sap = lapd_sap_find(teip, sapi); + if (sap) + return -EEXIST; + + sap = lapd_sap_alloc(teip, sapi); + + lapd_sap_set_state(teip, sapi, SAP_STATE_SABM_RETRANS); + + return 0; +} + +/* Stop a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */ +int lapd_sap_stop(struct lapd_instance *li, uint8_t tei, uint8_t sapi) +{ + struct lapd_tei *teip; + struct lapd_sap *sap; + + teip = teip_from_tei(li, tei); + if (!teip) + return -ENODEV; + + sap = lapd_sap_find(teip, sapi); + if (!sap) + return -ENODEV; + + lapd_sap_set_state(teip, sapi, SAP_STATE_INACTIVE); + + llist_del(&sap->list); + talloc_free(sap); + + return 0; +} + +/* Transmit Data (I-Frame) on the given LAPD Instance / TEI / SAPI */ +void lapd_transmit(struct lapd_instance *li, uint8_t tei, uint8_t sapi, + uint8_t *data, unsigned int len) +{ + struct lapd_tei *teip = teip_from_tei(li, tei); + struct lapd_sap *sap; + + if (!teip) { + LOGP(DMI, LOGL_ERROR, "Cannot transmit on non-existing " + "TEI %u\n", tei); + return; + } + + sap = lapd_sap_find(teip, sapi); + if (!sap) { + LOGP(DMI, LOGL_INFO, "Tx on unknown SAPI=%u in TEI=%u, " + "allocating\n", sapi, tei); + sap = lapd_sap_alloc(teip, sapi); + } + + /* prepend stuff */ + uint8_t buf[10000]; + memset(buf, 0, sizeof(buf)); + memmove(buf + 4, data, len); + len += 4; + + buf[0] = (sapi << 2) | (li->network_side ? 2 : 0); + buf[1] = (tei << 1) | 1; + buf[2] = (LAPD_NS(sap) << 1); + buf[3] = (LAPD_NR(sap) << 1) | 0; + + sap->vs = (sap->vs + 1) & 0x7f; + + li->transmit_cb(buf, len, li->cbdata); +}; + +/* Allocate a new LAPD instance */ +struct lapd_instance *lapd_instance_alloc(int network_side, + void (*tx_cb)(uint8_t *data, int len, + void *cbdata), void *cbdata) +{ + struct lapd_instance *li; + + li = talloc_zero(NULL, struct lapd_instance); + if (!li) + return NULL; + + li->transmit_cb = tx_cb; + li->cbdata = cbdata; + li->network_side = network_side; + INIT_LLIST_HEAD(&li->tei_list); + + return li; +} diff --git a/src/libabis/input/lapd.h b/src/libabis/input/lapd.h new file mode 100644 index 000000000..fd11edaa3 --- /dev/null +++ b/src/libabis/input/lapd.h @@ -0,0 +1,46 @@ +#ifndef OPENBSC_LAPD_H +#define OPENBSC_LAPD_H + +#include + +#include + +typedef enum { + LAPD_MPH_NONE = 0, + + LAPD_MPH_ACTIVATE_IND, + LAPD_MPH_DEACTIVATE_IND, + + LAPD_DL_DATA_IND, + LAPD_DL_UNITDATA_IND, + +} lapd_mph_type; + +struct lapd_instance { + struct llist_head list; /* list of LAPD instances */ + int network_side; + + void (*transmit_cb)(uint8_t *data, int len, void *cbdata); + void *cbdata; + + struct llist_head tei_list; /* list of TEI in this LAPD instance */ +}; + +extern uint8_t *lapd_receive(struct lapd_instance *li, uint8_t *data, unsigned int len, + int *ilen, lapd_mph_type *prim); + +extern void lapd_transmit(struct lapd_instance *li, uint8_t tei, uint8_t sapi, + uint8_t *data, unsigned int len); + +struct lapd_instance *lapd_instance_alloc(int network_side, + void (*tx_cb)(uint8_t *data, int len, + void *cbdata), void *cbdata); + + +/* Start a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */ +int lapd_sap_start(struct lapd_instance *li, uint8_t tei, uint8_t sapi); + +/* Stop a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */ +int lapd_sap_stop(struct lapd_instance *li, uint8_t tei, uint8_t sapi); + +#endif /* OPENBSC_LAPD_H */ diff --git a/src/libabis/input/misdn.c b/src/libabis/input/misdn.c new file mode 100644 index 000000000..459887917 --- /dev/null +++ b/src/libabis/input/misdn.c @@ -0,0 +1,542 @@ +/* OpenBSC Abis input driver for mISDNuser */ + +/* (C) 2008-2009 by Harald Welte + * (C) 2009 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define AF_COMPATIBILITY_FUNC +//#include +#ifndef AF_ISDN +#define AF_ISDN 34 +#define PF_ISDN AF_ISDN +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TS1_ALLOC_SIZE 300 + +struct prim_name { + unsigned int prim; + const char *name; +}; + +const struct prim_name prim_names[] = { + { PH_CONTROL_IND, "PH_CONTROL_IND" }, + { PH_DATA_IND, "PH_DATA_IND" }, + { PH_DATA_CNF, "PH_DATA_CNF" }, + { PH_ACTIVATE_IND, "PH_ACTIVATE_IND" }, + { DL_ESTABLISH_IND, "DL_ESTABLISH_IND" }, + { DL_ESTABLISH_CNF, "DL_ESTABLISH_CNF" }, + { DL_RELEASE_IND, "DL_RELEASE_IND" }, + { DL_RELEASE_CNF, "DL_RELEASE_CNF" }, + { DL_DATA_IND, "DL_DATA_IND" }, + { DL_UNITDATA_IND, "DL_UNITDATA_IND" }, + { DL_INFORMATION_IND, "DL_INFORMATION_IND" }, + { MPH_ACTIVATE_IND, "MPH_ACTIVATE_IND" }, + { MPH_DEACTIVATE_IND, "MPH_DEACTIVATE_IND" }, +}; + +const char *get_prim_name(unsigned int prim) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(prim_names); i++) { + if (prim_names[i].prim == prim) + return prim_names[i].name; + } + + return "UNKNOWN"; +} + +static int handle_ts1_read(struct bsc_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct e1inp_sign_link *link; + struct msgb *msg = msgb_alloc(TS1_ALLOC_SIZE, "mISDN TS1"); + struct sockaddr_mISDN l2addr; + struct mISDNhead *hh; + socklen_t alen; + int ret; + + if (!msg) + return -ENOMEM; + + hh = (struct mISDNhead *) msg->data; + + alen = sizeof(l2addr); + ret = recvfrom(bfd->fd, msg->data, 300, 0, + (struct sockaddr *) &l2addr, &alen); + if (ret < 0) { + fprintf(stderr, "recvfrom error %s\n", strerror(errno)); + return ret; + } + + if (alen != sizeof(l2addr)) { + fprintf(stderr, "%s error len\n", __func__); + return -EINVAL; + } + + msgb_put(msg, ret); + + DEBUGP(DMI, "alen =%d, dev(%d) channel(%d) sapi(%d) tei(%d)\n", + alen, l2addr.dev, l2addr.channel, l2addr.sapi, l2addr.tei); + + DEBUGP(DMI, "<= len = %d, prim(0x%x) id(0x%x): %s\n", + ret, hh->prim, hh->id, get_prim_name(hh->prim)); + + switch (hh->prim) { + case DL_INFORMATION_IND: + /* mISDN tells us which channel number is allocated for this + * tuple of (SAPI, TEI). */ + DEBUGP(DMI, "DL_INFORMATION_IND: use channel(%d) sapi(%d) tei(%d) for now\n", + l2addr.channel, l2addr.sapi, l2addr.tei); + link = e1inp_lookup_sign_link(e1i_ts, l2addr.tei, l2addr.sapi); + if (!link) { + DEBUGPC(DMI, "mISDN message for unknown sign_link\n"); + msgb_free(msg); + return -EINVAL; + } + /* save the channel number in the driver private struct */ + link->driver.misdn.channel = l2addr.channel; + break; + case DL_ESTABLISH_IND: + DEBUGP(DMI, "DL_ESTABLISH_IND: channel(%d) sapi(%d) tei(%d)\n", + l2addr.channel, l2addr.sapi, l2addr.tei); + /* For some strange reason, sometimes the DL_INFORMATION_IND tells + * us the wrong channel, and we only get the real channel number + * during the DL_ESTABLISH_IND */ + link = e1inp_lookup_sign_link(e1i_ts, l2addr.tei, l2addr.sapi); + if (!link) { + DEBUGPC(DMI, "mISDN message for unknown sign_link\n"); + msgb_free(msg); + return -EINVAL; + } + /* save the channel number in the driver private struct */ + link->driver.misdn.channel = l2addr.channel; + ret = e1inp_event(e1i_ts, S_INP_TEI_UP, l2addr.tei, l2addr.sapi); + break; + case DL_RELEASE_IND: + DEBUGP(DMI, "DL_RELEASE_IND: channel(%d) sapi(%d) tei(%d)\n", + l2addr.channel, l2addr.sapi, l2addr.tei); + ret = e1inp_event(e1i_ts, S_INP_TEI_DN, l2addr.tei, l2addr.sapi); + break; + case DL_DATA_IND: + case DL_UNITDATA_IND: + msg->l2h = msg->data + MISDN_HEADER_LEN; + DEBUGP(DMI, "RX: %s\n", hexdump(msgb_l2(msg), ret - MISDN_HEADER_LEN)); + ret = e1inp_rx_ts(e1i_ts, msg, l2addr.tei, l2addr.sapi); + break; + case PH_ACTIVATE_IND: + DEBUGP(DMI, "PH_ACTIVATE_IND: channel(%d) sapi(%d) tei(%d)\n", + l2addr.channel, l2addr.sapi, l2addr.tei); + break; + case PH_DEACTIVATE_IND: + DEBUGP(DMI, "PH_DEACTIVATE_IND: channel(%d) sapi(%d) tei(%d)\n", + l2addr.channel, l2addr.sapi, l2addr.tei); + break; + default: + break; + } + return ret; +} + +static int ts_want_write(struct e1inp_ts *e1i_ts) +{ + /* We never include the mISDN B-Channel FD into the + * writeset, since it doesn't support poll() based + * write flow control */ + if (e1i_ts->type == E1INP_TS_TYPE_TRAU) + return 0; + + e1i_ts->driver.misdn.fd.when |= BSC_FD_WRITE; + + return 0; +} + +static void timeout_ts1_write(void *data) +{ + struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data; + + /* trigger write of ts1, due to tx delay timer */ + ts_want_write(e1i_ts); +} + +static int handle_ts1_write(struct bsc_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct e1inp_sign_link *sign_link; + struct sockaddr_mISDN sa; + struct msgb *msg; + struct mISDNhead *hh; + u_int8_t *l2_data; + int ret; + + bfd->when &= ~BSC_FD_WRITE; + + /* get the next msg for this timeslot */ + msg = e1inp_tx_ts(e1i_ts, &sign_link); + if (!msg) { + /* no message after tx delay timer */ + return 0; + } + + l2_data = msg->data; + + /* prepend the mISDNhead */ + hh = (struct mISDNhead *) msgb_push(msg, sizeof(*hh)); + hh->prim = DL_DATA_REQ; + + DEBUGP(DMI, "TX channel(%d) TEI(%d) SAPI(%d): %s\n", + sign_link->driver.misdn.channel, sign_link->tei, + sign_link->sapi, hexdump(l2_data, msg->len - MISDN_HEADER_LEN)); + + /* construct the sockaddr */ + sa.family = AF_ISDN; + sa.sapi = sign_link->sapi; + sa.dev = sign_link->tei; + sa.channel = sign_link->driver.misdn.channel; + + ret = sendto(bfd->fd, msg->data, msg->len, 0, + (struct sockaddr *)&sa, sizeof(sa)); + if (ret < 0) + fprintf(stderr, "%s sendto failed %d\n", __func__, ret); + msgb_free(msg); + + /* set tx delay timer for next event */ + e1i_ts->sign.tx_timer.cb = timeout_ts1_write; + e1i_ts->sign.tx_timer.data = e1i_ts; + bsc_schedule_timer(&e1i_ts->sign.tx_timer, 0, e1i_ts->sign.delay); + + return ret; +} + +#define BCHAN_TX_GRAN 160 +/* write to a B channel TS */ +static int handle_tsX_write(struct bsc_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct mISDNhead *hh; + u_int8_t tx_buf[BCHAN_TX_GRAN + sizeof(*hh)]; + struct subch_mux *mx = &e1i_ts->trau.mux; + int ret; + + hh = (struct mISDNhead *) tx_buf; + hh->prim = PH_DATA_REQ; + + subchan_mux_out(mx, tx_buf+sizeof(*hh), BCHAN_TX_GRAN); + + DEBUGP(DMIB, "BCHAN TX: %s\n", + hexdump(tx_buf+sizeof(*hh), BCHAN_TX_GRAN)); + + ret = send(bfd->fd, tx_buf, sizeof(*hh) + BCHAN_TX_GRAN, 0); + if (ret < sizeof(*hh) + BCHAN_TX_GRAN) + DEBUGP(DMIB, "send returns %d instead of %zu\n", ret, + sizeof(*hh) + BCHAN_TX_GRAN); + + return ret; +} + +#define TSX_ALLOC_SIZE 4096 +/* FIXME: read from a B channel TS */ +static int handle_tsX_read(struct bsc_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1]; + struct msgb *msg = msgb_alloc(TSX_ALLOC_SIZE, "mISDN TSx"); + struct mISDNhead *hh; + int ret; + + if (!msg) + return -ENOMEM; + + hh = (struct mISDNhead *) msg->data; + + ret = recv(bfd->fd, msg->data, TSX_ALLOC_SIZE, 0); + if (ret < 0) { + fprintf(stderr, "recvfrom error %s\n", strerror(errno)); + return ret; + } + + msgb_put(msg, ret); + + if (hh->prim != PH_CONTROL_IND) + DEBUGP(DMIB, "<= BCHAN len = %d, prim(0x%x) id(0x%x): %s\n", + ret, hh->prim, hh->id, get_prim_name(hh->prim)); + + switch (hh->prim) { + case PH_DATA_IND: + msg->l2h = msg->data + MISDN_HEADER_LEN; + DEBUGP(DMIB, "BCHAN RX: %s\n", + hexdump(msgb_l2(msg), ret - MISDN_HEADER_LEN)); + ret = e1inp_rx_ts(e1i_ts, msg, 0, 0); + break; + case PH_ACTIVATE_IND: + case PH_DATA_CNF: + /* physical layer indicates that data has been sent, + * we thus can send some more data */ + ret = handle_tsX_write(bfd); + default: + break; + } + /* FIXME: why do we free signalling msgs in the caller, and trau not? */ + msgb_free(msg); + + return ret; +} + +/* callback from select.c in case one of the fd's can be read/written */ +static int misdn_fd_cb(struct bsc_fd *bfd, unsigned int what) +{ + struct e1inp_line *line = bfd->data; + unsigned int ts_nr = bfd->priv_nr; + unsigned int idx = ts_nr-1; + struct e1inp_ts *e1i_ts = &line->ts[idx]; + int rc = 0; + + switch (e1i_ts->type) { + case E1INP_TS_TYPE_SIGN: + if (what & BSC_FD_READ) + rc = handle_ts1_read(bfd); + if (what & BSC_FD_WRITE) + rc = handle_ts1_write(bfd); + break; + case E1INP_TS_TYPE_TRAU: + if (what & BSC_FD_READ) + rc = handle_tsX_read(bfd); + /* We never include the mISDN B-Channel FD into the + * writeset, since it doesn't support poll() based + * write flow control */ + break; + default: + fprintf(stderr, "unknown E1 TS type %u\n", e1i_ts->type); + break; + } + + return rc; +} + +static int activate_bchan(struct e1inp_line *line, int ts, int act) +{ + struct mISDNhead hh; + int ret; + unsigned int idx = ts-1; + struct e1inp_ts *e1i_ts = &line->ts[idx]; + struct bsc_fd *bfd = &e1i_ts->driver.misdn.fd; + + fprintf(stdout, "activate bchan\n"); + if (act) + hh.prim = PH_ACTIVATE_REQ; + else + hh.prim = PH_DEACTIVATE_REQ; + + hh.id = MISDN_ID_ANY; + ret = sendto(bfd->fd, &hh, sizeof(hh), 0, NULL, 0); + if (ret < 0) { + fprintf(stdout, "could not send ACTIVATE_RQ %s\n", + strerror(errno)); + } + + return ret; +} + +static int mi_e1_line_update(struct e1inp_line *line); + +struct e1inp_driver misdn_driver = { + .name = "misdn", + .want_write = ts_want_write, + .default_delay = 50000, + .line_update = &mi_e1_line_update, +}; + +static int mi_e1_setup(struct e1inp_line *line, int release_l2) +{ + int ts, ret; + + /* TS0 is CRC4, don't need any fd for it */ + for (ts = 1; ts < NUM_E1_TS; ts++) { + unsigned int idx = ts-1; + struct e1inp_ts *e1i_ts = &line->ts[idx]; + struct bsc_fd *bfd = &e1i_ts->driver.misdn.fd; + struct sockaddr_mISDN addr; + + bfd->data = line; + bfd->priv_nr = ts; + bfd->cb = misdn_fd_cb; + + switch (e1i_ts->type) { + case E1INP_TS_TYPE_NONE: + continue; + break; + case E1INP_TS_TYPE_SIGN: + bfd->fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_LAPD_NT); + bfd->when = BSC_FD_READ; + break; + case E1INP_TS_TYPE_TRAU: + bfd->fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_B_RAW); + /* We never include the mISDN B-Channel FD into the + * writeset, since it doesn't support poll() based + * write flow control */ + bfd->when = BSC_FD_READ; + break; + } + + if (bfd->fd < 0) { + fprintf(stderr, "%s could not open socket %s\n", + __func__, strerror(errno)); + return bfd->fd; + } + + memset(&addr, 0, sizeof(addr)); + addr.family = AF_ISDN; + addr.dev = line->num; + switch (e1i_ts->type) { + case E1INP_TS_TYPE_SIGN: + addr.channel = 0; + /* SAPI not supported yet in kernel */ + //addr.sapi = e1inp_ts->sign.sapi; + addr.sapi = 0; + addr.tei = GROUP_TEI; + break; + case E1INP_TS_TYPE_TRAU: + addr.channel = ts; + break; + default: + DEBUGP(DMI, "unsupported E1 TS type: %u\n", + e1i_ts->type); + break; + } + + ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr)); + if (ret < 0) { + fprintf(stderr, "could not bind l2 socket %s\n", + strerror(errno)); + return -EIO; + } + + if (e1i_ts->type == E1INP_TS_TYPE_SIGN) { + ret = ioctl(bfd->fd, IMCLEAR_L2, &release_l2); + if (ret < 0) { + fprintf(stderr, "could not send IOCTL IMCLEAN_L2 %s\n", strerror(errno)); + return -EIO; + } + } + + /* FIXME: only activate B-Channels once we start to + * use them to conserve CPU power */ + if (e1i_ts->type == E1INP_TS_TYPE_TRAU) + activate_bchan(line, ts, 1); + + ret = bsc_register_fd(bfd); + if (ret < 0) { + fprintf(stderr, "could not register FD: %s\n", + strerror(ret)); + return ret; + } + } + + return 0; +} + +static int mi_e1_line_update(struct e1inp_line *line) +{ + struct mISDN_devinfo devinfo; + int sk, ret, cnt; + + if (line->driver != &misdn_driver) + return -EINVAL; + + /* open the ISDN card device */ + sk = socket(PF_ISDN, SOCK_RAW, ISDN_P_BASE); + if (sk < 0) { + fprintf(stderr, "%s could not open socket %s\n", + __func__, strerror(errno)); + return sk; + } + + ret = ioctl(sk, IMGETCOUNT, &cnt); + if (ret) { + fprintf(stderr, "%s error getting interf count: %s\n", + __func__, strerror(errno)); + close(sk); + return -ENODEV; + } + //DEBUGP(DMI,"%d device%s found\n", cnt, (cnt==1)?"":"s"); + printf("%d device%s found\n", cnt, (cnt==1)?"":"s"); +#if 1 + devinfo.id = line->num; + ret = ioctl(sk, IMGETDEVINFO, &devinfo); + if (ret < 0) { + fprintf(stdout, "error getting info for device %d: %s\n", + line->num, strerror(errno)); + return -ENODEV; + } + fprintf(stdout, " id: %d\n", devinfo.id); + fprintf(stdout, " Dprotocols: %08x\n", devinfo.Dprotocols); + fprintf(stdout, " Bprotocols: %08x\n", devinfo.Bprotocols); + fprintf(stdout, " protocol: %d\n", devinfo.protocol); + fprintf(stdout, " nrbchan: %d\n", devinfo.nrbchan); + fprintf(stdout, " name: %s\n", devinfo.name); +#endif + + if (!(devinfo.Dprotocols & (1 << ISDN_P_NT_E1))) { + fprintf(stderr, "error: card is not of type E1 (NT-mode)\n"); + return -EINVAL; + } + + ret = mi_e1_setup(line, 1); + if (ret) + return ret; + + return 0; +} + +void e1inp_misdn_init(void) +{ + /* register the driver with the core */ + e1inp_driver_register(&misdn_driver); +} diff --git a/src/libbsc/Makefile.am b/src/libbsc/Makefile.am new file mode 100644 index 000000000..de7692950 --- /dev/null +++ b/src/libbsc/Makefile.am @@ -0,0 +1,25 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) + +noinst_LIBRARIES = libbsc.a + +libbsc_a_SOURCES = abis_nm.c abis_nm_vty.c \ + abis_om2000.c abis_om2000_vty.c \ + abis_rsl.c bsc_rll.c \ + paging.c \ + bts_ericsson_rbs2000.c \ + bts_ipaccess_nanobts.c \ + bts_siemens_bs11.c \ + bts_hsl_femtocell.c \ + bts_unknown.c \ + chan_alloc.c \ + gsm_subscriber_base.c \ + handover_decision.c handover_logic.c meas_rep.c \ + rest_octets.c system_information.c \ + e1_config.c \ + transaction.c \ + bsc_api.c bsc_msc.c bsc_vty.c \ + gsm_04_08_utils.c \ + bsc_init.c + diff --git a/src/libbsc/Makefile.in b/src/libbsc/Makefile.in new file mode 100644 index 000000000..e5d3d2018 --- /dev/null +++ b/src/libbsc/Makefile.in @@ -0,0 +1,504 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = src/libbsc +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LIBRARIES = $(noinst_LIBRARIES) +AR = ar +ARFLAGS = cru +AM_V_AR = $(am__v_AR_$(V)) +am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY)) +am__v_AR_0 = @echo " AR " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +libbsc_a_AR = $(AR) $(ARFLAGS) +libbsc_a_LIBADD = +am_libbsc_a_OBJECTS = abis_nm.$(OBJEXT) abis_nm_vty.$(OBJEXT) \ + abis_om2000.$(OBJEXT) abis_om2000_vty.$(OBJEXT) \ + abis_rsl.$(OBJEXT) bsc_rll.$(OBJEXT) paging.$(OBJEXT) \ + bts_ericsson_rbs2000.$(OBJEXT) bts_ipaccess_nanobts.$(OBJEXT) \ + bts_siemens_bs11.$(OBJEXT) bts_hsl_femtocell.$(OBJEXT) \ + bts_unknown.$(OBJEXT) chan_alloc.$(OBJEXT) \ + gsm_subscriber_base.$(OBJEXT) handover_decision.$(OBJEXT) \ + handover_logic.$(OBJEXT) meas_rep.$(OBJEXT) \ + rest_octets.$(OBJEXT) system_information.$(OBJEXT) \ + e1_config.$(OBJEXT) transaction.$(OBJEXT) bsc_api.$(OBJEXT) \ + bsc_msc.$(OBJEXT) bsc_vty.$(OBJEXT) gsm_04_08_utils.$(OBJEXT) \ + bsc_init.$(OBJEXT) +libbsc_a_OBJECTS = $(am_libbsc_a_OBJECTS) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(libbsc_a_SOURCES) +DIST_SOURCES = $(libbsc_a_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) +noinst_LIBRARIES = libbsc.a +libbsc_a_SOURCES = abis_nm.c abis_nm_vty.c \ + abis_om2000.c abis_om2000_vty.c \ + abis_rsl.c bsc_rll.c \ + paging.c \ + bts_ericsson_rbs2000.c \ + bts_ipaccess_nanobts.c \ + bts_siemens_bs11.c \ + bts_hsl_femtocell.c \ + bts_unknown.c \ + chan_alloc.c \ + gsm_subscriber_base.c \ + handover_decision.c handover_logic.c meas_rep.c \ + rest_octets.c system_information.c \ + e1_config.c \ + transaction.c \ + bsc_api.c bsc_msc.c bsc_vty.c \ + gsm_04_08_utils.c \ + bsc_init.c + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libbsc/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/libbsc/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) +libbsc.a: $(libbsc_a_OBJECTS) $(libbsc_a_DEPENDENCIES) + $(AM_V_at)-rm -f libbsc.a + $(AM_V_AR)$(libbsc_a_AR) libbsc.a $(libbsc_a_OBJECTS) $(libbsc_a_LIBADD) + $(AM_V_at)$(RANLIB) libbsc.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_nm.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_nm_vty.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_om2000.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_om2000_vty.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_rsl.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_api.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_init.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_msc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_rll.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_vty.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_ericsson_rbs2000.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_hsl_femtocell.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_ipaccess_nanobts.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_siemens_bs11.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_unknown.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chan_alloc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/e1_config.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_04_08_utils.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_subscriber_base.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/handover_decision.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/handover_logic.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/meas_rep.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/paging.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rest_octets.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/system_information.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/transaction.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LIBRARIES) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-noinstLIBRARIES ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/libbsc/abis_nm.c b/src/libbsc/abis_nm.c new file mode 100644 index 000000000..0e7fc8d8c --- /dev/null +++ b/src/libbsc/abis_nm.c @@ -0,0 +1,3132 @@ +/* GSM Network Management (OML) messages on the A-bis interface + * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */ + +/* (C) 2008-2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define OM_ALLOC_SIZE 1024 +#define OM_HEADROOM_SIZE 128 +#define IPACC_SEGMENT_SIZE 245 + +/* unidirectional messages from BTS to BSC */ +static const enum abis_nm_msgtype reports[] = { + NM_MT_SW_ACTIVATED_REP, + NM_MT_TEST_REP, + NM_MT_STATECHG_EVENT_REP, + NM_MT_FAILURE_EVENT_REP, +}; + +/* messages without ACK/NACK */ +static const enum abis_nm_msgtype no_ack_nack[] = { + NM_MT_MEAS_RES_REQ, + NM_MT_STOP_MEAS, + NM_MT_START_MEAS, +}; + +/* Messages related to software load */ +static const enum abis_nm_msgtype sw_load_msgs[] = { + NM_MT_LOAD_INIT_ACK, + NM_MT_LOAD_INIT_NACK, + NM_MT_LOAD_SEG_ACK, + NM_MT_LOAD_ABORT, + NM_MT_LOAD_END_ACK, + NM_MT_LOAD_END_NACK, + //NM_MT_SW_ACT_REQ, + NM_MT_ACTIVATE_SW_ACK, + NM_MT_ACTIVATE_SW_NACK, + NM_MT_SW_ACTIVATED_REP, +}; + +static const enum abis_nm_msgtype nacks[] = { + NM_MT_LOAD_INIT_NACK, + NM_MT_LOAD_END_NACK, + NM_MT_SW_ACT_REQ_NACK, + NM_MT_ACTIVATE_SW_NACK, + NM_MT_ESTABLISH_TEI_NACK, + NM_MT_CONN_TERR_SIGN_NACK, + NM_MT_DISC_TERR_SIGN_NACK, + NM_MT_CONN_TERR_TRAF_NACK, + NM_MT_DISC_TERR_TRAF_NACK, + NM_MT_CONN_MDROP_LINK_NACK, + NM_MT_DISC_MDROP_LINK_NACK, + NM_MT_SET_BTS_ATTR_NACK, + NM_MT_SET_RADIO_ATTR_NACK, + NM_MT_SET_CHAN_ATTR_NACK, + NM_MT_PERF_TEST_NACK, + NM_MT_SEND_TEST_REP_NACK, + NM_MT_STOP_TEST_NACK, + NM_MT_STOP_EVENT_REP_NACK, + NM_MT_REST_EVENT_REP_NACK, + NM_MT_CHG_ADM_STATE_NACK, + NM_MT_CHG_ADM_STATE_REQ_NACK, + NM_MT_REP_OUTST_ALARMS_NACK, + NM_MT_CHANGEOVER_NACK, + NM_MT_OPSTART_NACK, + NM_MT_REINIT_NACK, + NM_MT_SET_SITE_OUT_NACK, + NM_MT_CHG_HW_CONF_NACK, + NM_MT_GET_ATTR_NACK, + NM_MT_SET_ALARM_THRES_NACK, + NM_MT_BS11_BEGIN_DB_TX_NACK, + NM_MT_BS11_END_DB_TX_NACK, + NM_MT_BS11_CREATE_OBJ_NACK, + NM_MT_BS11_DELETE_OBJ_NACK, +}; + +static const struct value_string nack_names[] = { + { NM_MT_LOAD_INIT_NACK, "SOFTWARE LOAD INIT" }, + { NM_MT_LOAD_END_NACK, "SOFTWARE LOAD END" }, + { NM_MT_SW_ACT_REQ_NACK, "SOFTWARE ACTIVATE REQUEST" }, + { NM_MT_ACTIVATE_SW_NACK, "ACTIVATE SOFTWARE" }, + { NM_MT_ESTABLISH_TEI_NACK, "ESTABLISH TEI" }, + { NM_MT_CONN_TERR_SIGN_NACK, "CONNECT TERRESTRIAL SIGNALLING" }, + { NM_MT_DISC_TERR_SIGN_NACK, "DISCONNECT TERRESTRIAL SIGNALLING" }, + { NM_MT_CONN_TERR_TRAF_NACK, "CONNECT TERRESTRIAL TRAFFIC" }, + { NM_MT_DISC_TERR_TRAF_NACK, "DISCONNECT TERRESTRIAL TRAFFIC" }, + { NM_MT_CONN_MDROP_LINK_NACK, "CONNECT MULTI-DROP LINK" }, + { NM_MT_DISC_MDROP_LINK_NACK, "DISCONNECT MULTI-DROP LINK" }, + { NM_MT_SET_BTS_ATTR_NACK, "SET BTS ATTRIBUTE" }, + { NM_MT_SET_RADIO_ATTR_NACK, "SET RADIO ATTRIBUTE" }, + { NM_MT_SET_CHAN_ATTR_NACK, "SET CHANNEL ATTRIBUTE" }, + { NM_MT_PERF_TEST_NACK, "PERFORM TEST" }, + { NM_MT_SEND_TEST_REP_NACK, "SEND TEST REPORT" }, + { NM_MT_STOP_TEST_NACK, "STOP TEST" }, + { NM_MT_STOP_EVENT_REP_NACK, "STOP EVENT REPORT" }, + { NM_MT_REST_EVENT_REP_NACK, "RESET EVENT REPORT" }, + { NM_MT_CHG_ADM_STATE_NACK, "CHANGE ADMINISTRATIVE STATE" }, + { NM_MT_CHG_ADM_STATE_REQ_NACK, + "CHANGE ADMINISTRATIVE STATE REQUEST" }, + { NM_MT_REP_OUTST_ALARMS_NACK, "REPORT OUTSTANDING ALARMS" }, + { NM_MT_CHANGEOVER_NACK, "CHANGEOVER" }, + { NM_MT_OPSTART_NACK, "OPSTART" }, + { NM_MT_REINIT_NACK, "REINIT" }, + { NM_MT_SET_SITE_OUT_NACK, "SET SITE OUTPUT" }, + { NM_MT_CHG_HW_CONF_NACK, "CHANGE HARDWARE CONFIGURATION" }, + { NM_MT_GET_ATTR_NACK, "GET ATTRIBUTE" }, + { NM_MT_SET_ALARM_THRES_NACK, "SET ALARM THRESHOLD" }, + { NM_MT_BS11_BEGIN_DB_TX_NACK, "BS11 BEGIN DATABASE TRANSMISSION" }, + { NM_MT_BS11_END_DB_TX_NACK, "BS11 END DATABASE TRANSMISSION" }, + { NM_MT_BS11_CREATE_OBJ_NACK, "BS11 CREATE OBJECT" }, + { NM_MT_BS11_DELETE_OBJ_NACK, "BS11 DELETE OBJECT" }, + { 0, NULL } +}; + +/* Chapter 9.4.36 */ +static const struct value_string nack_cause_names[] = { + /* General Nack Causes */ + { NM_NACK_INCORR_STRUCT, "Incorrect message structure" }, + { NM_NACK_MSGTYPE_INVAL, "Invalid message type value" }, + { NM_NACK_OBJCLASS_INVAL, "Invalid Object class value" }, + { NM_NACK_OBJCLASS_NOTSUPP, "Object class not supported" }, + { NM_NACK_BTSNR_UNKN, "BTS no. unknown" }, + { NM_NACK_TRXNR_UNKN, "Baseband Transceiver no. unknown" }, + { NM_NACK_OBJINST_UNKN, "Object Instance unknown" }, + { NM_NACK_ATTRID_INVAL, "Invalid attribute identifier value" }, + { NM_NACK_ATTRID_NOTSUPP, "Attribute identifier not supported" }, + { NM_NACK_PARAM_RANGE, "Parameter value outside permitted range" }, + { NM_NACK_ATTRLIST_INCONSISTENT,"Inconsistency in attribute list" }, + { NM_NACK_SPEC_IMPL_NOTSUPP, "Specified implementation not supported" }, + { NM_NACK_CANT_PERFORM, "Message cannot be performed" }, + /* Specific Nack Causes */ + { NM_NACK_RES_NOTIMPL, "Resource not implemented" }, + { NM_NACK_RES_NOTAVAIL, "Resource not available" }, + { NM_NACK_FREQ_NOTAVAIL, "Frequency not available" }, + { NM_NACK_TEST_NOTSUPP, "Test not supported" }, + { NM_NACK_CAPACITY_RESTR, "Capacity restrictions" }, + { NM_NACK_PHYSCFG_NOTPERFORM, "Physical configuration cannot be performed" }, + { NM_NACK_TEST_NOTINIT, "Test not initiated" }, + { NM_NACK_PHYSCFG_NOTRESTORE, "Physical configuration cannot be restored" }, + { NM_NACK_TEST_NOSUCH, "No such test" }, + { NM_NACK_TEST_NOSTOP, "Test cannot be stopped" }, + { NM_NACK_MSGINCONSIST_PHYSCFG, "Message inconsistent with physical configuration" }, + { NM_NACK_FILE_INCOMPLETE, "Complete file notreceived" }, + { NM_NACK_FILE_NOTAVAIL, "File not available at destination" }, + { NM_NACK_FILE_NOTACTIVATE, "File cannot be activate" }, + { NM_NACK_REQ_NOT_GRANT, "Request not granted" }, + { NM_NACK_WAIT, "Wait" }, + { NM_NACK_NOTH_REPORT_EXIST, "Nothing reportable existing" }, + { NM_NACK_MEAS_NOTSUPP, "Measurement not supported" }, + { NM_NACK_MEAS_NOTSTART, "Measurement not started" }, + { 0, NULL } +}; + +static const char *nack_cause_name(u_int8_t cause) +{ + return get_value_string(nack_cause_names, cause); +} + +/* Chapter 9.4.16: Event Type */ +static const struct value_string event_type_names[] = { + { NM_EVT_COMM_FAIL, "communication failure" }, + { NM_EVT_QOS_FAIL, "quality of service failure" }, + { NM_EVT_PROC_FAIL, "processing failure" }, + { NM_EVT_EQUIP_FAIL, "equipment failure" }, + { NM_EVT_ENV_FAIL, "environment failure" }, + { 0, NULL } +}; + +static const char *event_type_name(u_int8_t cause) +{ + return get_value_string(event_type_names, cause); +} + +/* Chapter 9.4.63: Perceived Severity */ +static const struct value_string severity_names[] = { + { NM_SEVER_CEASED, "failure ceased" }, + { NM_SEVER_CRITICAL, "critical failure" }, + { NM_SEVER_MAJOR, "major failure" }, + { NM_SEVER_MINOR, "minor failure" }, + { NM_SEVER_WARNING, "warning level failure" }, + { NM_SEVER_INDETERMINATE, "indeterminate failure" }, + { 0, NULL } +}; + +static const char *severity_name(u_int8_t cause) +{ + return get_value_string(severity_names, cause); +} + +/* Attributes that the BSC can set, not only get, according to Section 9.4 */ +static const enum abis_nm_attr nm_att_settable[] = { + NM_ATT_ADD_INFO, + NM_ATT_ADD_TEXT, + NM_ATT_DEST, + NM_ATT_EVENT_TYPE, + NM_ATT_FILE_DATA, + NM_ATT_GET_ARI, + NM_ATT_HW_CONF_CHG, + NM_ATT_LIST_REQ_ATTR, + NM_ATT_MDROP_LINK, + NM_ATT_MDROP_NEXT, + NM_ATT_NACK_CAUSES, + NM_ATT_OUTST_ALARM, + NM_ATT_PHYS_CONF, + NM_ATT_PROB_CAUSE, + NM_ATT_RAD_SUBC, + NM_ATT_SOURCE, + NM_ATT_SPEC_PROB, + NM_ATT_START_TIME, + NM_ATT_TEST_DUR, + NM_ATT_TEST_NO, + NM_ATT_TEST_REPORT, + NM_ATT_WINDOW_SIZE, + NM_ATT_SEVERITY, + NM_ATT_MEAS_RES, + NM_ATT_MEAS_TYPE, +}; + +const struct tlv_definition nm_att_tlvdef = { + .def = { + [NM_ATT_ABIS_CHANNEL] = { TLV_TYPE_FIXED, 3 }, + [NM_ATT_ADD_INFO] = { TLV_TYPE_TL16V }, + [NM_ATT_ADD_TEXT] = { TLV_TYPE_TL16V }, + [NM_ATT_ADM_STATE] = { TLV_TYPE_TV }, + [NM_ATT_ARFCN_LIST]= { TLV_TYPE_TL16V }, + [NM_ATT_AUTON_REPORT] = { TLV_TYPE_TV }, + [NM_ATT_AVAIL_STATUS] = { TLV_TYPE_TL16V }, + [NM_ATT_BCCH_ARFCN] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_BSIC] = { TLV_TYPE_TV }, + [NM_ATT_BTS_AIR_TIMER] = { TLV_TYPE_TV }, + [NM_ATT_CCCH_L_I_P] = { TLV_TYPE_TV }, + [NM_ATT_CCCH_L_T] = { TLV_TYPE_TV }, + [NM_ATT_CHAN_COMB] = { TLV_TYPE_TV }, + [NM_ATT_CONN_FAIL_CRIT] = { TLV_TYPE_TL16V }, + [NM_ATT_DEST] = { TLV_TYPE_TL16V }, + [NM_ATT_EVENT_TYPE] = { TLV_TYPE_TV }, + [NM_ATT_FILE_DATA] = { TLV_TYPE_TL16V }, + [NM_ATT_FILE_ID] = { TLV_TYPE_TL16V }, + [NM_ATT_FILE_VERSION] = { TLV_TYPE_TL16V }, + [NM_ATT_GSM_TIME] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_HSN] = { TLV_TYPE_TV }, + [NM_ATT_HW_CONFIG] = { TLV_TYPE_TL16V }, + [NM_ATT_HW_DESC] = { TLV_TYPE_TL16V }, + [NM_ATT_INTAVE_PARAM] = { TLV_TYPE_TV }, + [NM_ATT_INTERF_BOUND] = { TLV_TYPE_FIXED, 6 }, + [NM_ATT_LIST_REQ_ATTR] = { TLV_TYPE_TL16V }, + [NM_ATT_MAIO] = { TLV_TYPE_TV }, + [NM_ATT_MANUF_STATE] = { TLV_TYPE_TV }, + [NM_ATT_MANUF_THRESH] = { TLV_TYPE_TL16V }, + [NM_ATT_MANUF_ID] = { TLV_TYPE_TL16V }, + [NM_ATT_MAX_TA] = { TLV_TYPE_TV }, + [NM_ATT_MDROP_LINK] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_MDROP_NEXT] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_NACK_CAUSES] = { TLV_TYPE_TV }, + [NM_ATT_NY1] = { TLV_TYPE_TV }, + [NM_ATT_OPER_STATE] = { TLV_TYPE_TV }, + [NM_ATT_OVERL_PERIOD] = { TLV_TYPE_TL16V }, + [NM_ATT_PHYS_CONF] = { TLV_TYPE_TL16V }, + [NM_ATT_POWER_CLASS] = { TLV_TYPE_TV }, + [NM_ATT_POWER_THRESH] = { TLV_TYPE_FIXED, 3 }, + [NM_ATT_PROB_CAUSE] = { TLV_TYPE_FIXED, 3 }, + [NM_ATT_RACH_B_THRESH] = { TLV_TYPE_TV }, + [NM_ATT_LDAVG_SLOTS] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_RAD_SUBC] = { TLV_TYPE_TV }, + [NM_ATT_RF_MAXPOWR_R] = { TLV_TYPE_TV }, + [NM_ATT_SITE_INPUTS] = { TLV_TYPE_TL16V }, + [NM_ATT_SITE_OUTPUTS] = { TLV_TYPE_TL16V }, + [NM_ATT_SOURCE] = { TLV_TYPE_TL16V }, + [NM_ATT_SPEC_PROB] = { TLV_TYPE_TV }, + [NM_ATT_START_TIME] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_T200] = { TLV_TYPE_FIXED, 7 }, + [NM_ATT_TEI] = { TLV_TYPE_TV }, + [NM_ATT_TEST_DUR] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_TEST_NO] = { TLV_TYPE_TV }, + [NM_ATT_TEST_REPORT] = { TLV_TYPE_TL16V }, + [NM_ATT_VSWR_THRESH] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_WINDOW_SIZE] = { TLV_TYPE_TV }, + [NM_ATT_TSC] = { TLV_TYPE_TV }, + [NM_ATT_SW_CONFIG] = { TLV_TYPE_TL16V }, + [NM_ATT_SEVERITY] = { TLV_TYPE_TV }, + [NM_ATT_GET_ARI] = { TLV_TYPE_TL16V }, + [NM_ATT_HW_CONF_CHG] = { TLV_TYPE_TL16V }, + [NM_ATT_OUTST_ALARM] = { TLV_TYPE_TV }, + [NM_ATT_MEAS_RES] = { TLV_TYPE_TL16V }, + }, +}; + +static const enum abis_nm_chan_comb chcomb4pchan[] = { + [GSM_PCHAN_CCCH] = NM_CHANC_mainBCCH, + [GSM_PCHAN_CCCH_SDCCH4] = NM_CHANC_BCCHComb, + [GSM_PCHAN_TCH_F] = NM_CHANC_TCHFull, + [GSM_PCHAN_TCH_H] = NM_CHANC_TCHHalf, + [GSM_PCHAN_SDCCH8_SACCH8C] = NM_CHANC_SDCCH, + [GSM_PCHAN_PDCH] = NM_CHANC_IPAC_PDCH, + [GSM_PCHAN_TCH_F_PDCH] = NM_CHANC_IPAC_TCHFull_PDCH, + /* FIXME: bounds check */ +}; + +int abis_nm_chcomb4pchan(enum gsm_phys_chan_config pchan) +{ + if (pchan < ARRAY_SIZE(chcomb4pchan)) + return chcomb4pchan[pchan]; + + return -EINVAL; +} + +int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const u_int8_t *buf, int len) +{ + if (!bts->model) + return -EIO; + return tlv_parse(tp, &bts->model->nm_att_tlvdef, buf, len, 0, 0); +} + +static int is_in_arr(enum abis_nm_msgtype mt, const enum abis_nm_msgtype *arr, int size) +{ + int i; + + for (i = 0; i < size; i++) { + if (arr[i] == mt) + return 1; + } + + return 0; +} + +#if 0 +/* is this msgtype the usual ACK/NACK type ? */ +static int is_ack_nack(enum abis_nm_msgtype mt) +{ + return !is_in_arr(mt, no_ack_nack, ARRAY_SIZE(no_ack_nack)); +} +#endif + +/* is this msgtype a report ? */ +static int is_report(enum abis_nm_msgtype mt) +{ + return is_in_arr(mt, reports, ARRAY_SIZE(reports)); +} + +#define MT_ACK(x) (x+1) +#define MT_NACK(x) (x+2) + +static void fill_om_hdr(struct abis_om_hdr *oh, u_int8_t len) +{ + oh->mdisc = ABIS_OM_MDISC_FOM; + oh->placement = ABIS_OM_PLACEMENT_ONLY; + oh->sequence = 0; + oh->length = len; +} + +static void fill_om_fom_hdr(struct abis_om_hdr *oh, u_int8_t len, + u_int8_t msg_type, u_int8_t obj_class, + u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr) +{ + struct abis_om_fom_hdr *foh = + (struct abis_om_fom_hdr *) oh->data; + + fill_om_hdr(oh, len+sizeof(*foh)); + foh->msg_type = msg_type; + foh->obj_class = obj_class; + foh->obj_inst.bts_nr = bts_nr; + foh->obj_inst.trx_nr = trx_nr; + foh->obj_inst.ts_nr = ts_nr; +} + +static struct msgb *nm_msgb_alloc(void) +{ + return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE, + "OML"); +} + +/* Send a OML NM Message from BSC to BTS */ +static int abis_nm_queue_msg(struct gsm_bts *bts, struct msgb *msg) +{ + msg->trx = bts->c0; + + /* queue OML messages */ + if (llist_empty(&bts->abis_queue) && !bts->abis_nm_pend) { + bts->abis_nm_pend = OBSC_NM_W_ACK_CB(msg); + return _abis_nm_sendmsg(msg, 0); + } else { + msgb_enqueue(&bts->abis_queue, msg); + return 0; + } + +} + +int abis_nm_sendmsg(struct gsm_bts *bts, struct msgb *msg) +{ + OBSC_NM_W_ACK_CB(msg) = 1; + return abis_nm_queue_msg(bts, msg); +} + +static int abis_nm_sendmsg_direct(struct gsm_bts *bts, struct msgb *msg) +{ + OBSC_NM_W_ACK_CB(msg) = 0; + return abis_nm_queue_msg(bts, msg); +} + +static int abis_nm_rcvmsg_sw(struct msgb *mb); + +const struct value_string abis_nm_obj_class_names[] = { + { NM_OC_SITE_MANAGER, "SITE-MANAGER" }, + { NM_OC_BTS, "BTS" }, + { NM_OC_RADIO_CARRIER, "RADIO-CARRIER" }, + { NM_OC_BASEB_TRANSC, "BASEBAND-TRANSCEIVER" }, + { NM_OC_CHANNEL, "CHANNEL" }, + { NM_OC_BS11_ADJC, "ADJC" }, + { NM_OC_BS11_HANDOVER, "HANDOVER" }, + { NM_OC_BS11_PWR_CTRL, "POWER-CONTROL" }, + { NM_OC_BS11_BTSE, "BTSE" }, + { NM_OC_BS11_RACK, "RACK" }, + { NM_OC_BS11_TEST, "TEST" }, + { NM_OC_BS11_ENVABTSE, "ENVABTSE" }, + { NM_OC_BS11_BPORT, "BPORT" }, + { NM_OC_GPRS_NSE, "GPRS-NSE" }, + { NM_OC_GPRS_CELL, "GPRS-CELL" }, + { NM_OC_GPRS_NSVC, "GPRS-NSVC" }, + { NM_OC_BS11, "SIEMENSHW" }, + { 0, NULL } +}; + +static const char *obj_class_name(u_int8_t oc) +{ + return get_value_string(abis_nm_obj_class_names, oc); +} + +const char *nm_opstate_name(u_int8_t os) +{ + switch (os) { + case NM_OPSTATE_DISABLED: + return "Disabled"; + case NM_OPSTATE_ENABLED: + return "Enabled"; + case NM_OPSTATE_NULL: + return "NULL"; + default: + return "RFU"; + } +} + +/* Chapter 9.4.7 */ +static const struct value_string avail_names[] = { + { 0, "In test" }, + { 1, "Failed" }, + { 2, "Power off" }, + { 3, "Off line" }, + /* Not used */ + { 5, "Dependency" }, + { 6, "Degraded" }, + { 7, "Not installed" }, + { 0xff, "OK" }, + { 0, NULL } +}; + +const char *nm_avail_name(u_int8_t avail) +{ + return get_value_string(avail_names, avail); +} + +static struct value_string test_names[] = { + /* FIXME: standard test names */ + { NM_IPACC_TESTNO_CHAN_USAGE, "Channel Usage" }, + { NM_IPACC_TESTNO_BCCH_CHAN_USAGE, "BCCH Channel Usage" }, + { NM_IPACC_TESTNO_FREQ_SYNC, "Frequency Synchronization" }, + { NM_IPACC_TESTNO_BCCH_INFO, "BCCH Info" }, + { NM_IPACC_TESTNO_TX_BEACON, "Transmit Beacon" }, + { NM_IPACC_TESTNO_SYSINFO_MONITOR, "System Info Monitor" }, + { NM_IPACC_TESTNO_BCCCH_MONITOR, "BCCH Monitor" }, + { 0, NULL } +}; + +const struct value_string abis_nm_adm_state_names[] = { + { NM_STATE_LOCKED, "Locked" }, + { NM_STATE_UNLOCKED, "Unlocked" }, + { NM_STATE_SHUTDOWN, "Shutdown" }, + { NM_STATE_NULL, "NULL" }, + { 0, NULL } +}; + +const char *nm_adm_name(u_int8_t adm) +{ + return get_value_string(abis_nm_adm_state_names, adm); +} + +int nm_is_running(struct gsm_nm_state *s) { + return (s->operational == NM_OPSTATE_ENABLED) && ( + (s->availability == NM_AVSTATE_OK) || + (s->availability == 0xff) + ); +} + +static void debugp_foh(struct abis_om_fom_hdr *foh) +{ + DEBUGP(DNM, "OC=%s(%02x) INST=(%02x,%02x,%02x) ", + obj_class_name(foh->obj_class), foh->obj_class, + foh->obj_inst.bts_nr, foh->obj_inst.trx_nr, + foh->obj_inst.ts_nr); +} + +/* obtain the gsm_nm_state data structure for a given object instance */ +static struct gsm_nm_state * +objclass2nmstate(struct gsm_bts *bts, u_int8_t obj_class, + struct abis_om_obj_inst *obj_inst) +{ + struct gsm_bts_trx *trx; + struct gsm_nm_state *nm_state = NULL; + + switch (obj_class) { + case NM_OC_BTS: + nm_state = &bts->nm_state; + break; + case NM_OC_RADIO_CARRIER: + if (obj_inst->trx_nr >= bts->num_trx) { + DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr); + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + nm_state = &trx->nm_state; + break; + case NM_OC_BASEB_TRANSC: + if (obj_inst->trx_nr >= bts->num_trx) { + DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr); + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + nm_state = &trx->bb_transc.nm_state; + break; + case NM_OC_CHANNEL: + if (obj_inst->trx_nr >= bts->num_trx) { + DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr); + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + if (obj_inst->ts_nr >= TRX_NR_TS) + return NULL; + nm_state = &trx->ts[obj_inst->ts_nr].nm_state; + break; + case NM_OC_SITE_MANAGER: + nm_state = &bts->site_mgr.nm_state; + break; + case NM_OC_BS11: + switch (obj_inst->bts_nr) { + case BS11_OBJ_CCLK: + nm_state = &bts->bs11.cclk.nm_state; + break; + case BS11_OBJ_BBSIG: + if (obj_inst->ts_nr > bts->num_trx) + return NULL; + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + nm_state = &trx->bs11.bbsig.nm_state; + break; + case BS11_OBJ_PA: + if (obj_inst->ts_nr > bts->num_trx) + return NULL; + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + nm_state = &trx->bs11.pa.nm_state; + break; + default: + return NULL; + } + case NM_OC_BS11_RACK: + nm_state = &bts->bs11.rack.nm_state; + break; + case NM_OC_BS11_ENVABTSE: + if (obj_inst->trx_nr >= ARRAY_SIZE(bts->bs11.envabtse)) + return NULL; + nm_state = &bts->bs11.envabtse[obj_inst->trx_nr].nm_state; + break; + case NM_OC_GPRS_NSE: + nm_state = &bts->gprs.nse.nm_state; + break; + case NM_OC_GPRS_CELL: + nm_state = &bts->gprs.cell.nm_state; + break; + case NM_OC_GPRS_NSVC: + if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc)) + return NULL; + nm_state = &bts->gprs.nsvc[obj_inst->trx_nr].nm_state; + break; + } + return nm_state; +} + +/* obtain the in-memory data structure of a given object instance */ +static void * +objclass2obj(struct gsm_bts *bts, u_int8_t obj_class, + struct abis_om_obj_inst *obj_inst) +{ + struct gsm_bts_trx *trx; + void *obj = NULL; + + switch (obj_class) { + case NM_OC_BTS: + obj = bts; + break; + case NM_OC_RADIO_CARRIER: + if (obj_inst->trx_nr >= bts->num_trx) { + DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr); + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + obj = trx; + break; + case NM_OC_BASEB_TRANSC: + if (obj_inst->trx_nr >= bts->num_trx) { + DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr); + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + obj = &trx->bb_transc; + break; + case NM_OC_CHANNEL: + if (obj_inst->trx_nr >= bts->num_trx) { + DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr); + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + if (obj_inst->ts_nr >= TRX_NR_TS) + return NULL; + obj = &trx->ts[obj_inst->ts_nr]; + break; + case NM_OC_SITE_MANAGER: + obj = &bts->site_mgr; + break; + case NM_OC_GPRS_NSE: + obj = &bts->gprs.nse; + break; + case NM_OC_GPRS_CELL: + obj = &bts->gprs.cell; + break; + case NM_OC_GPRS_NSVC: + if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc)) + return NULL; + obj = &bts->gprs.nsvc[obj_inst->trx_nr]; + break; + } + return obj; +} + +/* Update the administrative state of a given object in our in-memory data + * structures and send an event to the higher layer */ +static int update_admstate(struct gsm_bts *bts, u_int8_t obj_class, + struct abis_om_obj_inst *obj_inst, u_int8_t adm_state) +{ + struct gsm_nm_state *nm_state, new_state; + struct nm_statechg_signal_data nsd; + + nsd.obj = objclass2obj(bts, obj_class, obj_inst); + if (!nsd.obj) + return -EINVAL; + nm_state = objclass2nmstate(bts, obj_class, obj_inst); + if (!nm_state) + return -1; + + new_state = *nm_state; + new_state.administrative = adm_state; + + nsd.obj_class = obj_class; + nsd.old_state = nm_state; + nsd.new_state = &new_state; + nsd.obj_inst = obj_inst; + dispatch_signal(SS_NM, S_NM_STATECHG_ADM, &nsd); + + nm_state->administrative = adm_state; + + return 0; +} + +static int abis_nm_rx_statechg_rep(struct msgb *mb) +{ + struct abis_om_hdr *oh = msgb_l2(mb); + struct abis_om_fom_hdr *foh = msgb_l3(mb); + struct gsm_bts *bts = mb->trx->bts; + struct tlv_parsed tp; + struct gsm_nm_state *nm_state, new_state; + + DEBUGPC(DNM, "STATE CHG: "); + + memset(&new_state, 0, sizeof(new_state)); + + nm_state = objclass2nmstate(bts, foh->obj_class, &foh->obj_inst); + if (!nm_state) { + DEBUGPC(DNM, "unknown object class\n"); + return -EINVAL; + } + + new_state = *nm_state; + + abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh)); + if (TLVP_PRESENT(&tp, NM_ATT_OPER_STATE)) { + new_state.operational = *TLVP_VAL(&tp, NM_ATT_OPER_STATE); + DEBUGPC(DNM, "OP_STATE=%s ", nm_opstate_name(new_state.operational)); + } + if (TLVP_PRESENT(&tp, NM_ATT_AVAIL_STATUS)) { + if (TLVP_LEN(&tp, NM_ATT_AVAIL_STATUS) == 0) + new_state.availability = 0xff; + else + new_state.availability = *TLVP_VAL(&tp, NM_ATT_AVAIL_STATUS); + DEBUGPC(DNM, "AVAIL=%s(%02x) ", nm_avail_name(new_state.availability), + new_state.availability); + } else + new_state.availability = 0xff; + if (TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) { + new_state.administrative = *TLVP_VAL(&tp, NM_ATT_ADM_STATE); + DEBUGPC(DNM, "ADM=%2s ", nm_adm_name(new_state.administrative)); + } + DEBUGPC(DNM, "\n"); + + if ((new_state.administrative != 0 && nm_state->administrative == 0) || + new_state.operational != nm_state->operational || + new_state.availability != nm_state->availability) { + /* Update the operational state of a given object in our in-memory data + * structures and send an event to the higher layer */ + struct nm_statechg_signal_data nsd; + nsd.obj = objclass2obj(bts, foh->obj_class, &foh->obj_inst); + nsd.obj_class = foh->obj_class; + nsd.old_state = nm_state; + nsd.new_state = &new_state; + nsd.obj_inst = &foh->obj_inst; + dispatch_signal(SS_NM, S_NM_STATECHG_OPER, &nsd); + nm_state->operational = new_state.operational; + nm_state->availability = new_state.availability; + if (nm_state->administrative == 0) + nm_state->administrative = new_state.administrative; + } +#if 0 + if (op_state == 1) { + /* try to enable objects that are disabled */ + abis_nm_opstart(bts, foh->obj_class, + foh->obj_inst.bts_nr, + foh->obj_inst.trx_nr, + foh->obj_inst.ts_nr); + } +#endif + return 0; +} + +static int rx_fail_evt_rep(struct msgb *mb) +{ + struct abis_om_hdr *oh = msgb_l2(mb); + struct abis_om_fom_hdr *foh = msgb_l3(mb); + struct tlv_parsed tp; + const uint8_t *p_val; + char *p_text; + + DEBUGPC(DNM, "Failure Event Report "); + + abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh)); + + if (TLVP_PRESENT(&tp, NM_ATT_EVENT_TYPE)) + DEBUGPC(DNM, "Type=%s ", event_type_name(*TLVP_VAL(&tp, NM_ATT_EVENT_TYPE))); + if (TLVP_PRESENT(&tp, NM_ATT_SEVERITY)) + DEBUGPC(DNM, "Severity=%s ", severity_name(*TLVP_VAL(&tp, NM_ATT_SEVERITY))); + if (TLVP_PRESENT(&tp, NM_ATT_PROB_CAUSE)) { + p_val = TLVP_VAL(&tp, NM_ATT_PROB_CAUSE); + DEBUGPC(DNM, "Probable cause= %02X %02X %02X ", p_val[0], p_val[1], p_val[2]); + } + if (TLVP_PRESENT(&tp, NM_ATT_ADD_TEXT)) { + p_val = TLVP_VAL(&tp, NM_ATT_ADD_TEXT); + p_text = talloc_strndup(tall_bsc_ctx, (const char *) p_val, TLVP_LEN(&tp, NM_ATT_ADD_TEXT)); + if (p_text) { + DEBUGPC(DNM, "Additional Text=%s ", p_text); + talloc_free(p_text); + } + } + + DEBUGPC(DNM, "\n"); + + return 0; +} + +static int abis_nm_rcvmsg_report(struct msgb *mb) +{ + struct abis_om_fom_hdr *foh = msgb_l3(mb); + u_int8_t mt = foh->msg_type; + + debugp_foh(foh); + + //nmh->cfg->report_cb(mb, foh); + + switch (mt) { + case NM_MT_STATECHG_EVENT_REP: + return abis_nm_rx_statechg_rep(mb); + break; + case NM_MT_SW_ACTIVATED_REP: + DEBUGPC(DNM, "Software Activated Report\n"); + dispatch_signal(SS_NM, S_NM_SW_ACTIV_REP, mb); + break; + case NM_MT_FAILURE_EVENT_REP: + rx_fail_evt_rep(mb); + dispatch_signal(SS_NM, S_NM_FAIL_REP, mb); + break; + case NM_MT_TEST_REP: + DEBUGPC(DNM, "Test Report\n"); + dispatch_signal(SS_NM, S_NM_TEST_REP, mb); + break; + default: + DEBUGPC(DNM, "reporting NM MT 0x%02x\n", mt); + break; + + }; + + return 0; +} + +/* Activate the specified software into the BTS */ +static int ipacc_sw_activate(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i0, u_int8_t i1, + u_int8_t i2, const u_int8_t *sw_desc, u_int8_t swdesc_len) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + u_int8_t len = swdesc_len; + u_int8_t *trailer; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, obj_class, i0, i1, i2); + + trailer = msgb_put(msg, swdesc_len); + memcpy(trailer, sw_desc, swdesc_len); + + return abis_nm_sendmsg(bts, msg); +} + +static int abis_nm_parse_sw_descr(const u_int8_t *sw_descr, int sw_descr_len) +{ + static const struct tlv_definition sw_descr_def = { + .def = { + [NM_ATT_FILE_ID] = { TLV_TYPE_TL16V, }, + [NM_ATT_FILE_VERSION] = { TLV_TYPE_TL16V, }, + }, + }; + + u_int8_t tag; + u_int16_t tag_len; + const u_int8_t *val; + int ofs = 0, len; + + /* Classic TLV parsing doesn't work well with SW_DESCR because of it's + * nested nature and the fact you have to assume it contains only two sub + * tags NM_ATT_FILE_VERSION & NM_ATT_FILE_ID to parse it */ + + if (sw_descr[0] != NM_ATT_SW_DESCR) { + DEBUGP(DNM, "SW_DESCR attribute identifier not found!\n"); + return -1; + } + ofs += 1; + + len = tlv_parse_one(&tag, &tag_len, &val, + &sw_descr_def, &sw_descr[ofs], sw_descr_len-ofs); + if (len < 0 || (tag != NM_ATT_FILE_ID)) { + DEBUGP(DNM, "FILE_ID attribute identifier not found!\n"); + return -2; + } + ofs += len; + + len = tlv_parse_one(&tag, &tag_len, &val, + &sw_descr_def, &sw_descr[ofs], sw_descr_len-ofs); + if (len < 0 || (tag != NM_ATT_FILE_VERSION)) { + DEBUGP(DNM, "FILE_VERSION attribute identifier not found!\n"); + return -3; + } + ofs += len; + + return ofs; +} + +static int abis_nm_rx_sw_act_req(struct msgb *mb) +{ + struct abis_om_hdr *oh = msgb_l2(mb); + struct abis_om_fom_hdr *foh = msgb_l3(mb); + struct tlv_parsed tp; + const u_int8_t *sw_config; + int ret, sw_config_len, sw_descr_len; + + debugp_foh(foh); + + DEBUGPC(DNM, "SW Activate Request: "); + + DEBUGP(DNM, "Software Activate Request, ACKing and Activating\n"); + + ret = abis_nm_sw_act_req_ack(mb->trx->bts, foh->obj_class, + foh->obj_inst.bts_nr, + foh->obj_inst.trx_nr, + foh->obj_inst.ts_nr, 0, + foh->data, oh->length-sizeof(*foh)); + + abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh)); + sw_config = TLVP_VAL(&tp, NM_ATT_SW_CONFIG); + sw_config_len = TLVP_LEN(&tp, NM_ATT_SW_CONFIG); + if (!TLVP_PRESENT(&tp, NM_ATT_SW_CONFIG)) { + DEBUGP(DNM, "SW config not found! Can't continue.\n"); + return -EINVAL; + } else { + DEBUGP(DNM, "Found SW config: %s\n", hexdump(sw_config, sw_config_len)); + } + + /* Use the first SW_DESCR present in SW config */ + sw_descr_len = abis_nm_parse_sw_descr(sw_config, sw_config_len); + if (sw_descr_len < 0) + return -EINVAL; + + return ipacc_sw_activate(mb->trx->bts, foh->obj_class, + foh->obj_inst.bts_nr, + foh->obj_inst.trx_nr, + foh->obj_inst.ts_nr, + sw_config, sw_descr_len); +} + +/* Receive a CHANGE_ADM_STATE_ACK, parse the TLV and update local state */ +static int abis_nm_rx_chg_adm_state_ack(struct msgb *mb) +{ + struct abis_om_hdr *oh = msgb_l2(mb); + struct abis_om_fom_hdr *foh = msgb_l3(mb); + struct tlv_parsed tp; + u_int8_t adm_state; + + abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh)); + if (!TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) + return -EINVAL; + + adm_state = *TLVP_VAL(&tp, NM_ATT_ADM_STATE); + + return update_admstate(mb->trx->bts, foh->obj_class, &foh->obj_inst, adm_state); +} + +static int abis_nm_rx_lmt_event(struct msgb *mb) +{ + struct abis_om_hdr *oh = msgb_l2(mb); + struct abis_om_fom_hdr *foh = msgb_l3(mb); + struct tlv_parsed tp; + + DEBUGP(DNM, "LMT Event "); + abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh)); + if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_LOGON_SESSION) && + TLVP_LEN(&tp, NM_ATT_BS11_LMT_LOGON_SESSION) >= 1) { + u_int8_t onoff = *TLVP_VAL(&tp, NM_ATT_BS11_LMT_LOGON_SESSION); + DEBUGPC(DNM, "LOG%s ", onoff ? "ON" : "OFF"); + } + if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) && + TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) >= 1) { + u_int8_t level = *TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV); + DEBUGPC(DNM, "Level=%u ", level); + } + if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_NAME) && + TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_NAME) >= 1) { + char *name = (char *) TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_NAME); + DEBUGPC(DNM, "Username=%s ", name); + } + DEBUGPC(DNM, "\n"); + /* FIXME: parse LMT LOGON TIME */ + return 0; +} + +static void abis_nm_queue_send_next(struct gsm_bts *bts) +{ + int wait = 0; + struct msgb *msg; + /* the queue is empty */ + while (!llist_empty(&bts->abis_queue)) { + msg = msgb_dequeue(&bts->abis_queue); + wait = OBSC_NM_W_ACK_CB(msg); + _abis_nm_sendmsg(msg, 0); + + if (wait) + break; + } + + bts->abis_nm_pend = wait; +} + +/* Receive a OML NM Message from BTS */ +static int abis_nm_rcvmsg_fom(struct msgb *mb) +{ + struct abis_om_hdr *oh = msgb_l2(mb); + struct abis_om_fom_hdr *foh = msgb_l3(mb); + u_int8_t mt = foh->msg_type; + int ret = 0; + + /* check for unsolicited message */ + if (is_report(mt)) + return abis_nm_rcvmsg_report(mb); + + if (is_in_arr(mt, sw_load_msgs, ARRAY_SIZE(sw_load_msgs))) + return abis_nm_rcvmsg_sw(mb); + + if (is_in_arr(mt, nacks, ARRAY_SIZE(nacks))) { + struct nm_nack_signal_data nack_data; + struct tlv_parsed tp; + + debugp_foh(foh); + + DEBUGPC(DNM, "%s NACK ", get_value_string(nack_names, mt)); + + abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh)); + if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) + DEBUGPC(DNM, "CAUSE=%s\n", + nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); + else + DEBUGPC(DNM, "\n"); + + nack_data.msg = mb; + nack_data.mt = mt; + dispatch_signal(SS_NM, S_NM_NACK, &nack_data); + abis_nm_queue_send_next(mb->trx->bts); + return 0; + } +#if 0 + /* check if last message is to be acked */ + if (is_ack_nack(nmh->last_msgtype)) { + if (mt == MT_ACK(nmh->last_msgtype)) { + DEBUGP(DNM, "received ACK (0x%x)\n", foh->msg_type); + /* we got our ACK, continue sending the next msg */ + } else if (mt == MT_NACK(nmh->last_msgtype)) { + /* we got a NACK, signal this to the caller */ + DEBUGP(DNM, "received NACK (0x%x)\n", foh->msg_type); + /* FIXME: somehow signal this to the caller */ + } else { + /* really strange things happen */ + return -EINVAL; + } + } +#endif + + switch (mt) { + case NM_MT_CHG_ADM_STATE_ACK: + ret = abis_nm_rx_chg_adm_state_ack(mb); + break; + case NM_MT_SW_ACT_REQ: + ret = abis_nm_rx_sw_act_req(mb); + break; + case NM_MT_BS11_LMT_SESSION: + ret = abis_nm_rx_lmt_event(mb); + break; + case NM_MT_CONN_MDROP_LINK_ACK: + DEBUGP(DNM, "CONN MDROP LINK ACK\n"); + break; + case NM_MT_IPACC_RESTART_ACK: + dispatch_signal(SS_NM, S_NM_IPACC_RESTART_ACK, NULL); + break; + case NM_MT_IPACC_RESTART_NACK: + dispatch_signal(SS_NM, S_NM_IPACC_RESTART_NACK, NULL); + break; + case NM_MT_SET_BTS_ATTR_ACK: + /* The HSL wants an OPSTART _after_ the SI has been set */ + if (mb->trx->bts->type == GSM_BTS_TYPE_HSL_FEMTO) { + abis_nm_opstart(mb->trx->bts, NM_OC_BTS, 255, 255, 255); + } + break; + } + + abis_nm_queue_send_next(mb->trx->bts); + return ret; +} + +static int abis_nm_rx_ipacc(struct msgb *mb); + +static int abis_nm_rcvmsg_manuf(struct msgb *mb) +{ + int rc; + int bts_type = mb->trx->bts->type; + + switch (bts_type) { + case GSM_BTS_TYPE_NANOBTS: + rc = abis_nm_rx_ipacc(mb); + abis_nm_queue_send_next(mb->trx->bts); + break; + default: + LOGP(DNM, LOGL_ERROR, "don't know how to parse OML for this " + "BTS type (%u)\n", bts_type); + rc = 0; + break; + } + + return rc; +} + +/* High-Level API */ +/* Entry-point where L2 OML from BTS enters the NM code */ +int abis_nm_rcvmsg(struct msgb *msg) +{ + struct abis_om_hdr *oh = msgb_l2(msg); + int rc = 0; + + /* Various consistency checks */ + if (oh->placement != ABIS_OM_PLACEMENT_ONLY) { + LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n", + oh->placement); + if (oh->placement != ABIS_OM_PLACEMENT_FIRST) + return -EINVAL; + } + if (oh->sequence != 0) { + LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n", + oh->sequence); + return -EINVAL; + } +#if 0 + unsigned int l2_len = msg->tail - (u_int8_t *)msgb_l2(msg); + unsigned int hlen = sizeof(*oh) + sizeof(struct abis_om_fom_hdr); + if (oh->length + hlen > l2_len) { + LOGP(DNM, LOGL_ERROR, "ABIS OML truncated message (%u > %u)\n", + oh->length + sizeof(*oh), l2_len); + return -EINVAL; + } + if (oh->length + hlen < l2_len) + LOGP(DNM, LOGL_ERROR, "ABIS OML message with extra trailer?!? (oh->len=%d, sizeof_oh=%d l2_len=%d\n", oh->length, sizeof(*oh), l2_len); +#endif + msg->l3h = (unsigned char *)oh + sizeof(*oh); + + switch (oh->mdisc) { + case ABIS_OM_MDISC_FOM: + rc = abis_nm_rcvmsg_fom(msg); + break; + case ABIS_OM_MDISC_MANUF: + rc = abis_nm_rcvmsg_manuf(msg); + break; + case ABIS_OM_MDISC_MMI: + case ABIS_OM_MDISC_TRAU: + LOGP(DNM, LOGL_ERROR, "unimplemented ABIS OML message discriminator 0x%x\n", + oh->mdisc); + break; + default: + LOGP(DNM, LOGL_ERROR, "unknown ABIS OML message discriminator 0x%x\n", + oh->mdisc); + return -EINVAL; + } + + msgb_free(msg); + return rc; +} + +#if 0 +/* initialized all resources */ +struct abis_nm_h *abis_nm_init(struct abis_nm_cfg *cfg) +{ + struct abis_nm_h *nmh; + + nmh = malloc(sizeof(*nmh)); + if (!nmh) + return NULL; + + nmh->cfg = cfg; + + return nmh; +} + +/* free all resources */ +void abis_nm_fini(struct abis_nm_h *nmh) +{ + free(nmh); +} +#endif + +/* Here we are trying to define a high-level API that can be used by + * the actual BSC implementation. However, the architecture is currently + * still under design. Ideally the calls to this API would be synchronous, + * while the underlying stack behind the APi runs in a traditional select + * based state machine. + */ + +/* 6.2 Software Load: */ +enum sw_state { + SW_STATE_NONE, + SW_STATE_WAIT_INITACK, + SW_STATE_WAIT_SEGACK, + SW_STATE_WAIT_ENDACK, + SW_STATE_WAIT_ACTACK, + SW_STATE_ERROR, +}; + +struct abis_nm_sw { + struct gsm_bts *bts; + int trx_nr; + gsm_cbfn *cbfn; + void *cb_data; + int forced; + + /* this will become part of the SW LOAD INITIATE */ + u_int8_t obj_class; + u_int8_t obj_instance[3]; + + u_int8_t file_id[255]; + u_int8_t file_id_len; + + u_int8_t file_version[255]; + u_int8_t file_version_len; + + u_int8_t window_size; + u_int8_t seg_in_window; + + int fd; + FILE *stream; + enum sw_state state; + int last_seg; +}; + +static struct abis_nm_sw g_sw; + +static void sw_add_file_id_and_ver(struct abis_nm_sw *sw, struct msgb *msg) +{ + if (sw->bts->type == GSM_BTS_TYPE_NANOBTS) { + msgb_v_put(msg, NM_ATT_SW_DESCR); + msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id); + msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len, + sw->file_version); + } else if (sw->bts->type == GSM_BTS_TYPE_BS11) { + msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id); + msgb_tlv_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len, + sw->file_version); + } else { + LOGP(DNM, LOGL_ERROR, "Please implement this for the BTS.\n"); + } +} + +/* 6.2.1 / 8.3.1: Load Data Initiate */ +static int sw_load_init(struct abis_nm_sw *sw) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + u_int8_t len = 3*2 + sw->file_id_len + sw->file_version_len; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_LOAD_INIT, sw->obj_class, + sw->obj_instance[0], sw->obj_instance[1], + sw->obj_instance[2]); + + sw_add_file_id_and_ver(sw, msg); + msgb_tv_put(msg, NM_ATT_WINDOW_SIZE, sw->window_size); + + return abis_nm_sendmsg(sw->bts, msg); +} + +static int is_last_line(FILE *stream) +{ + char next_seg_buf[256]; + long pos; + + /* check if we're sending the last line */ + pos = ftell(stream); + if (!fgets(next_seg_buf, sizeof(next_seg_buf)-2, stream)) { + fseek(stream, pos, SEEK_SET); + return 1; + } + + fseek(stream, pos, SEEK_SET); + return 0; +} + +/* 6.2.2 / 8.3.2 Load Data Segment */ +static int sw_load_segment(struct abis_nm_sw *sw) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + char seg_buf[256]; + char *line_buf = seg_buf+2; + unsigned char *tlv; + u_int8_t len; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + + switch (sw->bts->type) { + case GSM_BTS_TYPE_BS11: + if (fgets(line_buf, sizeof(seg_buf)-2, sw->stream) == NULL) { + perror("fgets reading segment"); + return -EINVAL; + } + seg_buf[0] = 0x00; + + /* check if we're sending the last line */ + sw->last_seg = is_last_line(sw->stream); + if (sw->last_seg) + seg_buf[1] = 0; + else + seg_buf[1] = 1 + sw->seg_in_window++; + + len = strlen(line_buf) + 2; + tlv = msgb_put(msg, TLV_GROSS_LEN(len)); + tlv_put(tlv, NM_ATT_BS11_FILE_DATA, len, (u_int8_t *)seg_buf); + /* BS11 wants CR + LF in excess of the TLV length !?! */ + tlv[1] -= 2; + + /* we only now know the exact length for the OM hdr */ + len = strlen(line_buf)+2; + break; + case GSM_BTS_TYPE_NANOBTS: { + static_assert(sizeof(seg_buf) >= IPACC_SEGMENT_SIZE, buffer_big_enough); + len = read(sw->fd, &seg_buf, IPACC_SEGMENT_SIZE); + if (len < 0) { + perror("read failed"); + return -EINVAL; + } + + if (len != IPACC_SEGMENT_SIZE) + sw->last_seg = 1; + + ++sw->seg_in_window; + msgb_tl16v_put(msg, NM_ATT_IPACC_FILE_DATA, len, (const u_int8_t *) seg_buf); + len += 3; + break; + } + default: + LOGP(DNM, LOGL_ERROR, "sw_load_segment needs implementation for the BTS.\n"); + /* FIXME: Other BTS types */ + return -1; + } + + fill_om_fom_hdr(oh, len, NM_MT_LOAD_SEG, sw->obj_class, + sw->obj_instance[0], sw->obj_instance[1], + sw->obj_instance[2]); + + return abis_nm_sendmsg_direct(sw->bts, msg); +} + +/* 6.2.4 / 8.3.4 Load Data End */ +static int sw_load_end(struct abis_nm_sw *sw) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + u_int8_t len = 2*2 + sw->file_id_len + sw->file_version_len; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_LOAD_END, sw->obj_class, + sw->obj_instance[0], sw->obj_instance[1], + sw->obj_instance[2]); + + sw_add_file_id_and_ver(sw, msg); + return abis_nm_sendmsg(sw->bts, msg); +} + +/* Activate the specified software into the BTS */ +static int sw_activate(struct abis_nm_sw *sw) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + u_int8_t len = 2*2 + sw->file_id_len + sw->file_version_len; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, sw->obj_class, + sw->obj_instance[0], sw->obj_instance[1], + sw->obj_instance[2]); + + /* FIXME: this is BS11 specific format */ + msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id); + msgb_tlv_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len, + sw->file_version); + + return abis_nm_sendmsg(sw->bts, msg); +} + +struct sdp_firmware { + char magic[4]; + char more_magic[4]; + unsigned int header_length; + unsigned int file_length; +} __attribute__ ((packed)); + +static int parse_sdp_header(struct abis_nm_sw *sw) +{ + struct sdp_firmware firmware_header; + int rc; + struct stat stat; + + rc = read(sw->fd, &firmware_header, sizeof(firmware_header)); + if (rc != sizeof(firmware_header)) { + LOGP(DNM, LOGL_ERROR, "Could not read SDP file header.\n"); + return -1; + } + + if (strncmp(firmware_header.magic, " SDP", 4) != 0) { + LOGP(DNM, LOGL_ERROR, "The magic number1 is wrong.\n"); + return -1; + } + + if (firmware_header.more_magic[0] != 0x10 || + firmware_header.more_magic[1] != 0x02 || + firmware_header.more_magic[2] != 0x00 || + firmware_header.more_magic[3] != 0x00) { + LOGP(DNM, LOGL_ERROR, "The more magic number is wrong.\n"); + return -1; + } + + + if (fstat(sw->fd, &stat) == -1) { + LOGP(DNM, LOGL_ERROR, "Could not stat the file.\n"); + return -1; + } + + if (ntohl(firmware_header.file_length) != stat.st_size) { + LOGP(DNM, LOGL_ERROR, "The filesizes do not match.\n"); + return -1; + } + + /* go back to the start as we checked the whole filesize.. */ + lseek(sw->fd, 0l, SEEK_SET); + LOGP(DNM, LOGL_NOTICE, "The ipaccess SDP header is not fully understood.\n" + "There might be checksums in the file that are not\n" + "verified and incomplete firmware might be flashed.\n" + "There is absolutely no WARRANTY that flashing will\n" + "work.\n"); + return 0; +} + +static int sw_open_file(struct abis_nm_sw *sw, const char *fname) +{ + char file_id[12+1]; + char file_version[80+1]; + int rc; + + sw->fd = open(fname, O_RDONLY); + if (sw->fd < 0) + return sw->fd; + + switch (sw->bts->type) { + case GSM_BTS_TYPE_BS11: + sw->stream = fdopen(sw->fd, "r"); + if (!sw->stream) { + perror("fdopen"); + return -1; + } + /* read first line and parse file ID and VERSION */ + rc = fscanf(sw->stream, "@(#)%12s:%80s\r\n", + file_id, file_version); + if (rc != 2) { + perror("parsing header line of software file"); + return -1; + } + strcpy((char *)sw->file_id, file_id); + sw->file_id_len = strlen(file_id); + strcpy((char *)sw->file_version, file_version); + sw->file_version_len = strlen(file_version); + /* rewind to start of file */ + rewind(sw->stream); + break; + case GSM_BTS_TYPE_NANOBTS: + /* TODO: extract that from the filename or content */ + rc = parse_sdp_header(sw); + if (rc < 0) { + fprintf(stderr, "Could not parse the ipaccess SDP header\n"); + return -1; + } + + strcpy((char *)sw->file_id, "id"); + sw->file_id_len = 3; + strcpy((char *)sw->file_version, "version"); + sw->file_version_len = 8; + break; + default: + /* We don't know how to treat them yet */ + close(sw->fd); + return -EINVAL; + } + + return 0; +} + +static void sw_close_file(struct abis_nm_sw *sw) +{ + switch (sw->bts->type) { + case GSM_BTS_TYPE_BS11: + fclose(sw->stream); + break; + default: + close(sw->fd); + break; + } +} + +/* Fill the window */ +static int sw_fill_window(struct abis_nm_sw *sw) +{ + int rc; + + while (sw->seg_in_window < sw->window_size) { + rc = sw_load_segment(sw); + if (rc < 0) + return rc; + if (sw->last_seg) + break; + } + return 0; +} + +/* callback function from abis_nm_rcvmsg() handler */ +static int abis_nm_rcvmsg_sw(struct msgb *mb) +{ + struct abis_om_fom_hdr *foh = msgb_l3(mb); + int rc = -1; + struct abis_nm_sw *sw = &g_sw; + enum sw_state old_state = sw->state; + + //DEBUGP(DNM, "state %u, NM MT 0x%02x\n", sw->state, foh->msg_type); + + switch (sw->state) { + case SW_STATE_WAIT_INITACK: + switch (foh->msg_type) { + case NM_MT_LOAD_INIT_ACK: + /* fill window with segments */ + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_INIT_ACK, mb, + sw->cb_data, NULL); + rc = sw_fill_window(sw); + sw->state = SW_STATE_WAIT_SEGACK; + abis_nm_queue_send_next(mb->trx->bts); + break; + case NM_MT_LOAD_INIT_NACK: + if (sw->forced) { + DEBUGP(DNM, "FORCED: Ignoring Software Load " + "Init NACK\n"); + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_INIT_ACK, mb, + sw->cb_data, NULL); + rc = sw_fill_window(sw); + sw->state = SW_STATE_WAIT_SEGACK; + } else { + DEBUGP(DNM, "Software Load Init NACK\n"); + /* FIXME: cause */ + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_INIT_NACK, mb, + sw->cb_data, NULL); + sw->state = SW_STATE_ERROR; + } + abis_nm_queue_send_next(mb->trx->bts); + break; + } + break; + case SW_STATE_WAIT_SEGACK: + switch (foh->msg_type) { + case NM_MT_LOAD_SEG_ACK: + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_SEG_ACK, mb, + sw->cb_data, NULL); + sw->seg_in_window = 0; + if (!sw->last_seg) { + /* fill window with more segments */ + rc = sw_fill_window(sw); + sw->state = SW_STATE_WAIT_SEGACK; + } else { + /* end the transfer */ + sw->state = SW_STATE_WAIT_ENDACK; + rc = sw_load_end(sw); + } + abis_nm_queue_send_next(mb->trx->bts); + break; + case NM_MT_LOAD_ABORT: + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_ABORT, mb, + sw->cb_data, NULL); + break; + } + break; + case SW_STATE_WAIT_ENDACK: + switch (foh->msg_type) { + case NM_MT_LOAD_END_ACK: + sw_close_file(sw); + DEBUGP(DNM, "Software Load End (BTS %u)\n", + sw->bts->nr); + sw->state = SW_STATE_NONE; + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_END_ACK, mb, + sw->cb_data, NULL); + rc = 0; + abis_nm_queue_send_next(mb->trx->bts); + break; + case NM_MT_LOAD_END_NACK: + if (sw->forced) { + DEBUGP(DNM, "FORCED: Ignoring Software Load" + "End NACK\n"); + sw->state = SW_STATE_NONE; + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_END_ACK, mb, + sw->cb_data, NULL); + } else { + DEBUGP(DNM, "Software Load End NACK\n"); + /* FIXME: cause */ + sw->state = SW_STATE_ERROR; + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_END_NACK, mb, + sw->cb_data, NULL); + } + abis_nm_queue_send_next(mb->trx->bts); + break; + } + case SW_STATE_WAIT_ACTACK: + switch (foh->msg_type) { + case NM_MT_ACTIVATE_SW_ACK: + /* we're done */ + DEBUGP(DNM, "Activate Software DONE!\n"); + sw->state = SW_STATE_NONE; + rc = 0; + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_ACTIVATE_SW_ACK, mb, + sw->cb_data, NULL); + abis_nm_queue_send_next(mb->trx->bts); + break; + case NM_MT_ACTIVATE_SW_NACK: + DEBUGP(DNM, "Activate Software NACK\n"); + /* FIXME: cause */ + sw->state = SW_STATE_ERROR; + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_ACTIVATE_SW_NACK, mb, + sw->cb_data, NULL); + abis_nm_queue_send_next(mb->trx->bts); + break; + } + case SW_STATE_NONE: + switch (foh->msg_type) { + case NM_MT_ACTIVATE_SW_ACK: + rc = 0; + break; + } + break; + case SW_STATE_ERROR: + break; + } + + if (rc) + DEBUGP(DNM, "unexpected NM MT 0x%02x in state %u -> %u\n", + foh->msg_type, old_state, sw->state); + + return rc; +} + +/* Load the specified software into the BTS */ +int abis_nm_software_load(struct gsm_bts *bts, int trx_nr, const char *fname, + u_int8_t win_size, int forced, + gsm_cbfn *cbfn, void *cb_data) +{ + struct abis_nm_sw *sw = &g_sw; + int rc; + + DEBUGP(DNM, "Software Load (BTS %u, File \"%s\")\n", + bts->nr, fname); + + if (sw->state != SW_STATE_NONE) + return -EBUSY; + + sw->bts = bts; + sw->trx_nr = trx_nr; + + switch (bts->type) { + case GSM_BTS_TYPE_BS11: + sw->obj_class = NM_OC_SITE_MANAGER; + sw->obj_instance[0] = 0xff; + sw->obj_instance[1] = 0xff; + sw->obj_instance[2] = 0xff; + break; + case GSM_BTS_TYPE_NANOBTS: + sw->obj_class = NM_OC_BASEB_TRANSC; + sw->obj_instance[0] = sw->bts->nr; + sw->obj_instance[1] = sw->trx_nr; + sw->obj_instance[2] = 0xff; + break; + case GSM_BTS_TYPE_UNKNOWN: + default: + LOGPC(DNM, LOGL_ERROR, "Software Load not properly implemented.\n"); + return -1; + break; + } + sw->window_size = win_size; + sw->state = SW_STATE_WAIT_INITACK; + sw->cbfn = cbfn; + sw->cb_data = cb_data; + sw->forced = forced; + + rc = sw_open_file(sw, fname); + if (rc < 0) { + sw->state = SW_STATE_NONE; + return rc; + } + + return sw_load_init(sw); +} + +int abis_nm_software_load_status(struct gsm_bts *bts) +{ + struct abis_nm_sw *sw = &g_sw; + struct stat st; + int rc, percent; + + rc = fstat(sw->fd, &st); + if (rc < 0) { + perror("ERROR during stat"); + return rc; + } + + if (sw->stream) + percent = (ftell(sw->stream) * 100) / st.st_size; + else + percent = (lseek(sw->fd, 0, SEEK_CUR) * 100) / st.st_size; + return percent; +} + +/* Activate the specified software into the BTS */ +int abis_nm_software_activate(struct gsm_bts *bts, const char *fname, + gsm_cbfn *cbfn, void *cb_data) +{ + struct abis_nm_sw *sw = &g_sw; + int rc; + + DEBUGP(DNM, "Activating Software (BTS %u, File \"%s\")\n", + bts->nr, fname); + + if (sw->state != SW_STATE_NONE) + return -EBUSY; + + sw->bts = bts; + sw->obj_class = NM_OC_SITE_MANAGER; + sw->obj_instance[0] = 0xff; + sw->obj_instance[1] = 0xff; + sw->obj_instance[2] = 0xff; + sw->state = SW_STATE_WAIT_ACTACK; + sw->cbfn = cbfn; + sw->cb_data = cb_data; + + /* Open the file in order to fill some sw struct members */ + rc = sw_open_file(sw, fname); + if (rc < 0) { + sw->state = SW_STATE_NONE; + return rc; + } + sw_close_file(sw); + + return sw_activate(sw); +} + +static void fill_nm_channel(struct abis_nm_channel *ch, u_int8_t bts_port, + u_int8_t ts_nr, u_int8_t subslot_nr) +{ + ch->attrib = NM_ATT_ABIS_CHANNEL; + ch->bts_port = bts_port; + ch->timeslot = ts_nr; + ch->subslot = subslot_nr; +} + +int abis_nm_establish_tei(struct gsm_bts *bts, u_int8_t trx_nr, + u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_t e1_subslot, + u_int8_t tei) +{ + struct abis_om_hdr *oh; + struct abis_nm_channel *ch; + u_int8_t len = sizeof(*ch) + 2; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_ESTABLISH_TEI, NM_OC_RADIO_CARRIER, + bts->bts_nr, trx_nr, 0xff); + + msgb_tv_put(msg, NM_ATT_TEI, tei); + + ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch)); + fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot); + + return abis_nm_sendmsg(bts, msg); +} + +/* connect signalling of one (BTS,TRX) to a particular timeslot on the E1 */ +int abis_nm_conn_terr_sign(struct gsm_bts_trx *trx, + u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_t e1_subslot) +{ + struct gsm_bts *bts = trx->bts; + struct abis_om_hdr *oh; + struct abis_nm_channel *ch; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_SIGN, + NM_OC_RADIO_CARRIER, bts->bts_nr, trx->nr, 0xff); + + ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch)); + fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot); + + return abis_nm_sendmsg(bts, msg); +} + +#if 0 +int abis_nm_disc_terr_sign(struct abis_nm_h *h, struct abis_om_obj_inst *inst, + struct abis_nm_abis_channel *chan) +{ +} +#endif + +int abis_nm_conn_terr_traf(struct gsm_bts_trx_ts *ts, + u_int8_t e1_port, u_int8_t e1_timeslot, + u_int8_t e1_subslot) +{ + struct gsm_bts *bts = ts->trx->bts; + struct abis_om_hdr *oh; + struct abis_nm_channel *ch; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_TRAF, + NM_OC_CHANNEL, bts->bts_nr, ts->trx->nr, ts->nr); + + ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch)); + fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot); + + DEBUGP(DNM, "CONNECT TERR TRAF Um=%s E1=(%u,%u,%u)\n", + gsm_ts_name(ts), + e1_port, e1_timeslot, e1_subslot); + + return abis_nm_sendmsg(bts, msg); +} + +#if 0 +int abis_nm_disc_terr_traf(struct abis_nm_h *h, struct abis_om_obj_inst *inst, + struct abis_nm_abis_channel *chan, + u_int8_t subchan) +{ +} +#endif + +/* Chapter 8.6.1 */ +int abis_nm_set_bts_attr(struct gsm_bts *bts, u_int8_t *attr, int attr_len) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + u_int8_t *cur; + + DEBUGP(DNM, "Set BTS Attr (bts=%d)\n", bts->nr); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, attr_len, NM_MT_SET_BTS_ATTR, NM_OC_BTS, bts->bts_nr, 0xff, 0xff); + cur = msgb_put(msg, attr_len); + memcpy(cur, attr, attr_len); + + return abis_nm_sendmsg(bts, msg); +} + +/* Chapter 8.6.2 */ +int abis_nm_set_radio_attr(struct gsm_bts_trx *trx, u_int8_t *attr, int attr_len) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + u_int8_t *cur; + + DEBUGP(DNM, "Set TRX Attr (bts=%d,trx=%d)\n", trx->bts->nr, trx->nr); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, attr_len, NM_MT_SET_RADIO_ATTR, NM_OC_RADIO_CARRIER, + trx->bts->bts_nr, trx->nr, 0xff); + cur = msgb_put(msg, attr_len); + memcpy(cur, attr, attr_len); + + return abis_nm_sendmsg(trx->bts, msg); +} + +static int verify_chan_comb(struct gsm_bts_trx_ts *ts, u_int8_t chan_comb) +{ + int i; + + /* As it turns out, the BS-11 has some very peculiar restrictions + * on the channel combinations it allows */ + switch (ts->trx->bts->type) { + case GSM_BTS_TYPE_BS11: + switch (chan_comb) { + case NM_CHANC_TCHHalf: + case NM_CHANC_TCHHalf2: + /* not supported */ + return -EINVAL; + case NM_CHANC_SDCCH: + /* only one SDCCH/8 per TRX */ + for (i = 0; i < TRX_NR_TS; i++) { + if (i == ts->nr) + continue; + if (ts->trx->ts[i].nm_chan_comb == + NM_CHANC_SDCCH) + return -EINVAL; + } + /* not allowed for TS0 of BCCH-TRX */ + if (ts->trx == ts->trx->bts->c0 && + ts->nr == 0) + return -EINVAL; + /* not on the same TRX that has a BCCH+SDCCH4 + * combination */ + if (ts->trx == ts->trx->bts->c0 && + (ts->trx->ts[0].nm_chan_comb == 5 || + ts->trx->ts[0].nm_chan_comb == 8)) + return -EINVAL; + break; + case NM_CHANC_mainBCCH: + case NM_CHANC_BCCHComb: + /* allowed only for TS0 of C0 */ + if (ts->trx != ts->trx->bts->c0 || + ts->nr != 0) + return -EINVAL; + break; + case NM_CHANC_BCCH: + /* allowed only for TS 2/4/6 of C0 */ + if (ts->trx != ts->trx->bts->c0) + return -EINVAL; + if (ts->nr != 2 && ts->nr != 4 && + ts->nr != 6) + return -EINVAL; + break; + case 8: /* this is not like 08.58, but in fact + * FCCH+SCH+BCCH+CCCH+SDCCH/4+SACCH/C4+CBCH */ + /* FIXME: only one CBCH allowed per cell */ + break; + } + break; + case GSM_BTS_TYPE_NANOBTS: + switch (ts->nr) { + case 0: + if (ts->trx->nr == 0) { + /* only on TRX0 */ + switch (chan_comb) { + case NM_CHANC_BCCH: + case NM_CHANC_mainBCCH: + case NM_CHANC_BCCHComb: + return 0; + break; + default: + return -EINVAL; + } + } else { + switch (chan_comb) { + case NM_CHANC_TCHFull: + case NM_CHANC_TCHHalf: + case NM_CHANC_IPAC_TCHFull_TCHHalf: + return 0; + default: + return -EINVAL; + } + } + break; + case 1: + if (ts->trx->nr == 0) { + switch (chan_comb) { + case NM_CHANC_SDCCH_CBCH: + if (ts->trx->ts[0].nm_chan_comb == + NM_CHANC_mainBCCH) + return 0; + return -EINVAL; + case NM_CHANC_SDCCH: + case NM_CHANC_TCHFull: + case NM_CHANC_TCHHalf: + case NM_CHANC_IPAC_TCHFull_TCHHalf: + case NM_CHANC_IPAC_TCHFull_PDCH: + return 0; + } + } else { + switch (chan_comb) { + case NM_CHANC_SDCCH: + case NM_CHANC_TCHFull: + case NM_CHANC_TCHHalf: + case NM_CHANC_IPAC_TCHFull_TCHHalf: + return 0; + default: + return -EINVAL; + } + } + break; + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + switch (chan_comb) { + case NM_CHANC_TCHFull: + case NM_CHANC_TCHHalf: + case NM_CHANC_IPAC_TCHFull_TCHHalf: + return 0; + case NM_CHANC_IPAC_PDCH: + case NM_CHANC_IPAC_TCHFull_PDCH: + if (ts->trx->nr == 0) + return 0; + else + return -EINVAL; + } + break; + } + return -EINVAL; + default: + /* unknown BTS type */ + return 0; + } + return 0; +} + +/* Chapter 8.6.3 */ +int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, u_int8_t chan_comb) +{ + struct gsm_bts *bts = ts->trx->bts; + struct abis_om_hdr *oh; + u_int8_t zero = 0x00; + struct msgb *msg = nm_msgb_alloc(); + u_int8_t len = 2 + 2; + + if (bts->type == GSM_BTS_TYPE_BS11) + len += 4 + 2 + 2 + 3; + + DEBUGP(DNM, "Set Chan Attr %s\n", gsm_ts_name(ts)); + if (verify_chan_comb(ts, chan_comb) < 0) { + msgb_free(msg); + DEBUGP(DNM, "Invalid Channel Combination!!!\n"); + return -EINVAL; + } + ts->nm_chan_comb = chan_comb; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_SET_CHAN_ATTR, + NM_OC_CHANNEL, bts->bts_nr, + ts->trx->nr, ts->nr); + msgb_tv_put(msg, NM_ATT_CHAN_COMB, chan_comb); + if (ts->hopping.enabled) { + unsigned int i; + uint8_t *len; + + msgb_tv_put(msg, NM_ATT_HSN, ts->hopping.hsn); + msgb_tv_put(msg, NM_ATT_MAIO, ts->hopping.maio); + + /* build the ARFCN list */ + msgb_put_u8(msg, NM_ATT_ARFCN_LIST); + len = msgb_put(msg, 1); + *len = 0; + for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) { + if (bitvec_get_bit_pos(&ts->hopping.arfcns, i)) { + msgb_put_u16(msg, i); + /* At least BS-11 wants a TLV16 here */ + if (bts->type == GSM_BTS_TYPE_BS11) + *len += 1; + else + *len += sizeof(uint16_t); + } + } + } + msgb_tv_put(msg, NM_ATT_TSC, bts->tsc); /* training sequence */ + if (bts->type == GSM_BTS_TYPE_BS11) + msgb_tlv_put(msg, 0x59, 1, &zero); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_sw_act_req_ack(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i1, + u_int8_t i2, u_int8_t i3, int nack, u_int8_t *attr, int att_len) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + u_int8_t msgtype = NM_MT_SW_ACT_REQ_ACK; + u_int8_t len = att_len; + + if (nack) { + len += 2; + msgtype = NM_MT_SW_ACT_REQ_NACK; + } + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, att_len, msgtype, obj_class, i1, i2, i3); + + if (attr) { + u_int8_t *ptr = msgb_put(msg, att_len); + memcpy(ptr, attr, att_len); + } + if (nack) + msgb_tv_put(msg, NM_ATT_NACK_CAUSES, NM_NACK_OBJCLASS_NOTSUPP); + + return abis_nm_sendmsg_direct(bts, msg); +} + +int abis_nm_raw_msg(struct gsm_bts *bts, int len, u_int8_t *rawmsg) +{ + struct msgb *msg = nm_msgb_alloc(); + struct abis_om_hdr *oh; + u_int8_t *data; + + oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh)); + fill_om_hdr(oh, len); + data = msgb_put(msg, len); + memcpy(data, rawmsg, len); + + return abis_nm_sendmsg(bts, msg); +} + +/* Siemens specific commands */ +static int __simple_cmd(struct gsm_bts *bts, u_int8_t msg_type) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 0, msg_type, NM_OC_SITE_MANAGER, + 0xff, 0xff, 0xff); + + return abis_nm_sendmsg(bts, msg); +} + +/* Chapter 8.9.2 */ +int abis_nm_opstart(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i0, u_int8_t i1, u_int8_t i2) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 0, NM_MT_OPSTART, obj_class, i0, i1, i2); + + debugp_foh((struct abis_om_fom_hdr *) oh->data); + DEBUGPC(DNM, "Sending OPSTART\n"); + + return abis_nm_sendmsg(bts, msg); +} + +/* Chapter 8.8.5 */ +int abis_nm_chg_adm_state(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i0, + u_int8_t i1, u_int8_t i2, enum abis_nm_adm_state adm_state) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2, NM_MT_CHG_ADM_STATE, obj_class, i0, i1, i2); + msgb_tv_put(msg, NM_ATT_ADM_STATE, adm_state); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_conn_mdrop_link(struct gsm_bts *bts, u_int8_t e1_port0, u_int8_t ts0, + u_int8_t e1_port1, u_int8_t ts1) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + u_int8_t *attr; + + DEBUGP(DNM, "CONNECT MDROP LINK E1=(%u,%u) -> E1=(%u, %u)\n", + e1_port0, ts0, e1_port1, ts1); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 6, NM_MT_CONN_MDROP_LINK, + NM_OC_SITE_MANAGER, 0x00, 0x00, 0x00); + + attr = msgb_put(msg, 3); + attr[0] = NM_ATT_MDROP_LINK; + attr[1] = e1_port0; + attr[2] = ts0; + + attr = msgb_put(msg, 3); + attr[0] = NM_ATT_MDROP_NEXT; + attr[1] = e1_port1; + attr[2] = ts1; + + return abis_nm_sendmsg(bts, msg); +} + +/* Chapter 8.7.1 */ +int abis_nm_perform_test(struct gsm_bts *bts, u_int8_t obj_class, + u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr, + u_int8_t test_nr, u_int8_t auton_report, struct msgb *msg) +{ + struct abis_om_hdr *oh; + + DEBUGP(DNM, "PEFORM TEST %s\n", get_value_string(test_names, test_nr)); + + if (!msg) + msg = nm_msgb_alloc(); + + msgb_tv_push(msg, NM_ATT_AUTON_REPORT, auton_report); + msgb_tv_push(msg, NM_ATT_TEST_NO, test_nr); + oh = (struct abis_om_hdr *) msgb_push(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, msgb_l3len(msg), NM_MT_PERF_TEST, + obj_class, bts_nr, trx_nr, ts_nr); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_event_reports(struct gsm_bts *bts, int on) +{ + if (on == 0) + return __simple_cmd(bts, NM_MT_STOP_EVENT_REP); + else + return __simple_cmd(bts, NM_MT_REST_EVENT_REP); +} + +/* Siemens (or BS-11) specific commands */ + +int abis_nm_bs11_bsc_disconnect(struct gsm_bts *bts, int reconnect) +{ + if (reconnect == 0) + return __simple_cmd(bts, NM_MT_BS11_DISCONNECT); + else + return __simple_cmd(bts, NM_MT_BS11_RECONNECT); +} + +int abis_nm_bs11_restart(struct gsm_bts *bts) +{ + return __simple_cmd(bts, NM_MT_BS11_RESTART); +} + + +struct bs11_date_time { + u_int16_t year; + u_int8_t month; + u_int8_t day; + u_int8_t hour; + u_int8_t min; + u_int8_t sec; +} __attribute__((packed)); + + +void get_bs11_date_time(struct bs11_date_time *aet) +{ + time_t t; + struct tm *tm; + + t = time(NULL); + tm = localtime(&t); + aet->sec = tm->tm_sec; + aet->min = tm->tm_min; + aet->hour = tm->tm_hour; + aet->day = tm->tm_mday; + aet->month = tm->tm_mon; + aet->year = htons(1900 + tm->tm_year); +} + +int abis_nm_bs11_reset_resource(struct gsm_bts *bts) +{ + return __simple_cmd(bts, NM_MT_BS11_RESET_RESOURCE); +} + +int abis_nm_bs11_db_transmission(struct gsm_bts *bts, int begin) +{ + if (begin) + return __simple_cmd(bts, NM_MT_BS11_BEGIN_DB_TX); + else + return __simple_cmd(bts, NM_MT_BS11_END_DB_TX); +} + +int abis_nm_bs11_create_object(struct gsm_bts *bts, + enum abis_bs11_objtype type, u_int8_t idx, + u_int8_t attr_len, const u_int8_t *attr) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + u_int8_t *cur; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, attr_len, NM_MT_BS11_CREATE_OBJ, + NM_OC_BS11, type, 0, idx); + cur = msgb_put(msg, attr_len); + memcpy(cur, attr, attr_len); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_delete_object(struct gsm_bts *bts, + enum abis_bs11_objtype type, u_int8_t idx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ, + NM_OC_BS11, type, 0, idx); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_create_envaBTSE(struct gsm_bts *bts, u_int8_t idx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + u_int8_t zero = 0x00; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 3, NM_MT_BS11_CREATE_OBJ, + NM_OC_BS11_ENVABTSE, 0, idx, 0xff); + msgb_tlv_put(msg, 0x99, 1, &zero); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_create_bport(struct gsm_bts *bts, u_int8_t idx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 0, NM_MT_BS11_CREATE_OBJ, NM_OC_BS11_BPORT, + idx, 0xff, 0xff); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_delete_bport(struct gsm_bts *bts, u_int8_t idx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ, NM_OC_BS11_BPORT, + idx, 0xff, 0xff); + + return abis_nm_sendmsg(bts, msg); +} + +static const u_int8_t sm_attr[] = { NM_ATT_TEI, NM_ATT_ABIS_CHANNEL }; +int abis_nm_bs11_get_oml_tei_ts(struct gsm_bts *bts) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+sizeof(sm_attr), NM_MT_GET_ATTR, NM_OC_SITE_MANAGER, + 0xff, 0xff, 0xff); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(sm_attr), sm_attr); + + return abis_nm_sendmsg(bts, msg); +} + +/* like abis_nm_conn_terr_traf + set_tei */ +int abis_nm_bs11_conn_oml_tei(struct gsm_bts *bts, u_int8_t e1_port, + u_int8_t e1_timeslot, u_int8_t e1_subslot, + u_int8_t tei) +{ + struct abis_om_hdr *oh; + struct abis_nm_channel *ch; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, sizeof(*ch)+2, NM_MT_BS11_SET_ATTR, + NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff); + + ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch)); + fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot); + msgb_tv_put(msg, NM_ATT_TEI, tei); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_set_trx_power(struct gsm_bts_trx *trx, u_int8_t level) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, + NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr); + msgb_tlv_put(msg, NM_ATT_BS11_TXPWR, 1, &level); + + return abis_nm_sendmsg(trx->bts, msg); +} + +int abis_nm_bs11_get_trx_power(struct gsm_bts_trx *trx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + u_int8_t attr = NM_ATT_BS11_TXPWR; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, + NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), &attr); + + return abis_nm_sendmsg(trx->bts, msg); +} + +int abis_nm_bs11_get_pll_mode(struct gsm_bts *bts) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + u_int8_t attr[] = { NM_ATT_BS11_PLL_MODE }; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, + NM_OC_BS11, BS11_OBJ_LI, 0x00, 0x00); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_get_cclk(struct gsm_bts *bts) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + u_int8_t attr[] = { NM_ATT_BS11_CCLK_ACCURACY, + NM_ATT_BS11_CCLK_TYPE }; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, + NM_OC_BS11, BS11_OBJ_CCLK, 0x00, 0x00); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr); + + return abis_nm_sendmsg(bts, msg); + +} + +//static const u_int8_t bs11_logon_c7[] = { 0x07, 0xd9, 0x01, 0x11, 0x0d, 0x10, 0x20 }; + +int abis_nm_bs11_factory_logon(struct gsm_bts *bts, int on) +{ + return abis_nm_bs11_logon(bts, 0x02, "FACTORY", on); +} + +int abis_nm_bs11_infield_logon(struct gsm_bts *bts, int on) +{ + return abis_nm_bs11_logon(bts, 0x03, "FIELD ", on); +} + +int abis_nm_bs11_logon(struct gsm_bts *bts, u_int8_t level, const char *name, int on) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + struct bs11_date_time bdt; + + get_bs11_date_time(&bdt); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + if (on) { + u_int8_t len = 3*2 + sizeof(bdt) + + 1 + strlen(name); + fill_om_fom_hdr(oh, len, NM_MT_BS11_LMT_LOGON, + NM_OC_BS11_BTSE, 0xff, 0xff, 0xff); + msgb_tlv_put(msg, NM_ATT_BS11_LMT_LOGIN_TIME, + sizeof(bdt), (u_int8_t *) &bdt); + msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_ACC_LEV, + 1, &level); + msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_NAME, + strlen(name), (u_int8_t *)name); + } else { + fill_om_fom_hdr(oh, 0, NM_MT_BS11_LMT_LOGOFF, + NM_OC_BS11_BTSE, 0xff, 0xff, 0xff); + } + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_set_trx1_pw(struct gsm_bts *bts, const char *password) +{ + struct abis_om_hdr *oh; + struct msgb *msg; + + if (strlen(password) != 10) + return -EINVAL; + + msg = nm_msgb_alloc(); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+strlen(password), NM_MT_BS11_SET_ATTR, + NM_OC_BS11, BS11_OBJ_TRX1, 0x00, 0x00); + msgb_tlv_put(msg, NM_ATT_BS11_PASSWORD, 10, (const u_int8_t *)password); + + return abis_nm_sendmsg(bts, msg); +} + +/* change the BS-11 PLL Mode to either locked (E1 derived) or standalone */ +int abis_nm_bs11_set_pll_locked(struct gsm_bts *bts, int locked) +{ + struct abis_om_hdr *oh; + struct msgb *msg; + u_int8_t tlv_value; + + msg = nm_msgb_alloc(); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11, + BS11_OBJ_LI, 0x00, 0x00); + + if (locked) + tlv_value = BS11_LI_PLL_LOCKED; + else + tlv_value = BS11_LI_PLL_STANDALONE; + + msgb_tlv_put(msg, NM_ATT_BS11_PLL_MODE, 1, &tlv_value); + + return abis_nm_sendmsg(bts, msg); +} + +/* Set the calibration value of the PLL (work value/set value) + * It depends on the login which one is changed */ +int abis_nm_bs11_set_pll(struct gsm_bts *bts, int value) +{ + struct abis_om_hdr *oh; + struct msgb *msg; + u_int8_t tlv_value[2]; + + msg = nm_msgb_alloc(); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11, + BS11_OBJ_TRX1, 0x00, 0x00); + + tlv_value[0] = value>>8; + tlv_value[1] = value&0xff; + + msgb_tlv_put(msg, NM_ATT_BS11_PLL, 2, tlv_value); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_get_state(struct gsm_bts *bts) +{ + return __simple_cmd(bts, NM_MT_BS11_GET_STATE); +} + +/* BS11 SWL */ + +void *tall_fle_ctx; + +struct abis_nm_bs11_sw { + struct gsm_bts *bts; + char swl_fname[PATH_MAX]; + u_int8_t win_size; + int forced; + struct llist_head file_list; + gsm_cbfn *user_cb; /* specified by the user */ +}; +static struct abis_nm_bs11_sw _g_bs11_sw, *g_bs11_sw = &_g_bs11_sw; + +struct file_list_entry { + struct llist_head list; + char fname[PATH_MAX]; +}; + +struct file_list_entry *fl_dequeue(struct llist_head *queue) +{ + struct llist_head *lh; + + if (llist_empty(queue)) + return NULL; + + lh = queue->next; + llist_del(lh); + + return llist_entry(lh, struct file_list_entry, list); +} + +static int bs11_read_swl_file(struct abis_nm_bs11_sw *bs11_sw) +{ + char linebuf[255]; + struct llist_head *lh, *lh2; + FILE *swl; + int rc = 0; + + swl = fopen(bs11_sw->swl_fname, "r"); + if (!swl) + return -ENODEV; + + /* zero the stale file list, if any */ + llist_for_each_safe(lh, lh2, &bs11_sw->file_list) { + llist_del(lh); + talloc_free(lh); + } + + while (fgets(linebuf, sizeof(linebuf), swl)) { + char file_id[12+1]; + char file_version[80+1]; + struct file_list_entry *fle; + static char dir[PATH_MAX]; + + if (strlen(linebuf) < 4) + continue; + + rc = sscanf(linebuf+4, "%12s:%80s\r\n", file_id, file_version); + if (rc < 0) { + perror("ERR parsing SWL file"); + rc = -EINVAL; + goto out; + } + if (rc < 2) + continue; + + fle = talloc_zero(tall_fle_ctx, struct file_list_entry); + if (!fle) { + rc = -ENOMEM; + goto out; + } + + /* construct new filename */ + strncpy(dir, bs11_sw->swl_fname, sizeof(dir)); + strncat(fle->fname, dirname(dir), sizeof(fle->fname) - 1); + strcat(fle->fname, "/"); + strncat(fle->fname, file_id, sizeof(fle->fname) - 1 -strlen(fle->fname)); + + llist_add_tail(&fle->list, &bs11_sw->file_list); + } + +out: + fclose(swl); + return rc; +} + +/* bs11 swload specific callback, passed to abis_nm core swload */ +static int bs11_swload_cbfn(unsigned int hook, unsigned int event, + struct msgb *msg, void *data, void *param) +{ + struct abis_nm_bs11_sw *bs11_sw = data; + struct file_list_entry *fle; + int rc = 0; + + switch (event) { + case NM_MT_LOAD_END_ACK: + fle = fl_dequeue(&bs11_sw->file_list); + if (fle) { + /* start download the next file of our file list */ + rc = abis_nm_software_load(bs11_sw->bts, 0xff, fle->fname, + bs11_sw->win_size, + bs11_sw->forced, + &bs11_swload_cbfn, bs11_sw); + talloc_free(fle); + } else { + /* activate the SWL */ + rc = abis_nm_software_activate(bs11_sw->bts, + bs11_sw->swl_fname, + bs11_swload_cbfn, + bs11_sw); + } + break; + case NM_MT_LOAD_SEG_ACK: + case NM_MT_LOAD_END_NACK: + case NM_MT_LOAD_INIT_ACK: + case NM_MT_LOAD_INIT_NACK: + case NM_MT_ACTIVATE_SW_NACK: + case NM_MT_ACTIVATE_SW_ACK: + default: + /* fallthrough to the user callback */ + if (bs11_sw->user_cb) + rc = bs11_sw->user_cb(hook, event, msg, NULL, NULL); + break; + } + + return rc; +} + +/* Siemens provides a SWL file that is a mere listing of all the other + * files that are part of a software release. We need to upload first + * the list file, and then each file that is listed in the list file */ +int abis_nm_bs11_load_swl(struct gsm_bts *bts, const char *fname, + u_int8_t win_size, int forced, gsm_cbfn *cbfn) +{ + struct abis_nm_bs11_sw *bs11_sw = g_bs11_sw; + struct file_list_entry *fle; + int rc = 0; + + INIT_LLIST_HEAD(&bs11_sw->file_list); + bs11_sw->bts = bts; + bs11_sw->win_size = win_size; + bs11_sw->user_cb = cbfn; + bs11_sw->forced = forced; + + strncpy(bs11_sw->swl_fname, fname, sizeof(bs11_sw->swl_fname)); + rc = bs11_read_swl_file(bs11_sw); + if (rc < 0) + return rc; + + /* dequeue next item in file list */ + fle = fl_dequeue(&bs11_sw->file_list); + if (!fle) + return -EINVAL; + + /* start download the next file of our file list */ + rc = abis_nm_software_load(bts, 0xff, fle->fname, win_size, forced, + bs11_swload_cbfn, bs11_sw); + talloc_free(fle); + return rc; +} + +#if 0 +static u_int8_t req_attr_btse[] = { + NM_ATT_ADM_STATE, NM_ATT_BS11_LMT_LOGON_SESSION, + NM_ATT_BS11_LMT_LOGIN_TIME, NM_ATT_BS11_LMT_USER_ACC_LEV, + NM_ATT_BS11_LMT_USER_NAME, + + 0xaf, NM_ATT_BS11_RX_OFFSET, NM_ATT_BS11_VENDOR_NAME, + + NM_ATT_BS11_SW_LOAD_INTENDED, NM_ATT_BS11_SW_LOAD_SAFETY, + + NM_ATT_BS11_SW_LOAD_STORED }; + +static u_int8_t req_attr_btsm[] = { + NM_ATT_ABIS_CHANNEL, NM_ATT_TEI, NM_ATT_BS11_ABIS_EXT_TIME, + NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xce, NM_ATT_FILE_ID, + NM_ATT_FILE_VERSION, NM_ATT_OPER_STATE, 0xe8, NM_ATT_BS11_ALL_TEST_CATG, + NM_ATT_SW_DESCR, NM_ATT_GET_ARI }; +#endif + +static u_int8_t req_attr[] = { + NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xa8, NM_ATT_OPER_STATE, + 0xd5, 0xa1, NM_ATT_BS11_ESN_FW_CODE_NO, NM_ATT_BS11_ESN_HW_CODE_NO, + 0x42, NM_ATT_BS11_ESN_PCB_SERIAL, NM_ATT_BS11_PLL }; + +int abis_nm_bs11_get_serno(struct gsm_bts *bts) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + /* SiemensHW CCTRL object */ + fill_om_fom_hdr(oh, 2+sizeof(req_attr), NM_MT_GET_ATTR, NM_OC_BS11, + 0x03, 0x00, 0x00); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(req_attr), req_attr); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_set_ext_time(struct gsm_bts *bts) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + struct bs11_date_time aet; + + get_bs11_date_time(&aet); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + /* SiemensHW CCTRL object */ + fill_om_fom_hdr(oh, 2+sizeof(aet), NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER, + 0xff, 0xff, 0xff); + msgb_tlv_put(msg, NM_ATT_BS11_ABIS_EXT_TIME, sizeof(aet), (u_int8_t *) &aet); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_get_bport_line_cfg(struct gsm_bts *bts, u_int8_t bport) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + u_int8_t attr = NM_ATT_BS11_LINE_CFG; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, + NM_OC_BS11_BPORT, bport, 0xff, 0x02); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), &attr); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_set_bport_line_cfg(struct gsm_bts *bts, u_int8_t bport, enum abis_bs11_line_cfg line_cfg) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + struct bs11_date_time aet; + + get_bs11_date_time(&aet); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2, NM_MT_BS11_SET_ATTR, NM_OC_BS11_BPORT, + bport, 0xff, 0x02); + msgb_tv_put(msg, NM_ATT_BS11_LINE_CFG, line_cfg); + + return abis_nm_sendmsg(bts, msg); +} + +/* ip.access nanoBTS specific commands */ +static const char ipaccess_magic[] = "com.ipaccess"; + + +static int abis_nm_rx_ipacc(struct msgb *msg) +{ + struct in_addr addr; + struct abis_om_hdr *oh = msgb_l2(msg); + struct abis_om_fom_hdr *foh; + u_int8_t idstrlen = oh->data[0]; + struct tlv_parsed tp; + struct ipacc_ack_signal_data signal; + + if (strncmp((char *)&oh->data[1], ipaccess_magic, idstrlen)) { + LOGP(DNM, LOGL_ERROR, "id string is not com.ipaccess !?!\n"); + return -EINVAL; + } + + foh = (struct abis_om_fom_hdr *) (oh->data + 1 + idstrlen); + abis_nm_tlv_parse(&tp, msg->trx->bts, foh->data, oh->length-sizeof(*foh)); + + debugp_foh(foh); + + DEBUGPC(DNM, "IPACCESS(0x%02x): ", foh->msg_type); + + switch (foh->msg_type) { + case NM_MT_IPACC_RSL_CONNECT_ACK: + DEBUGPC(DNM, "RSL CONNECT ACK "); + if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP)) { + memcpy(&addr, + TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP), sizeof(addr)); + + DEBUGPC(DNM, "IP=%s ", inet_ntoa(addr)); + } + if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP_PORT)) + DEBUGPC(DNM, "PORT=%u ", + ntohs(*((u_int16_t *) + TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP_PORT)))); + if (TLVP_PRESENT(&tp, NM_ATT_IPACC_STREAM_ID)) + DEBUGPC(DNM, "STREAM=0x%02x ", + *TLVP_VAL(&tp, NM_ATT_IPACC_STREAM_ID)); + DEBUGPC(DNM, "\n"); + break; + case NM_MT_IPACC_RSL_CONNECT_NACK: + LOGP(DNM, LOGL_ERROR, "RSL CONNECT NACK "); + if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) + DEBUGPC(DNM, " CAUSE=%s\n", + nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); + else + DEBUGPC(DNM, "\n"); + break; + case NM_MT_IPACC_SET_NVATTR_ACK: + DEBUGPC(DNM, "SET NVATTR ACK\n"); + /* FIXME: decode and show the actual attributes */ + break; + case NM_MT_IPACC_SET_NVATTR_NACK: + LOGP(DNM, LOGL_ERROR, "SET NVATTR NACK "); + if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) + LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n", + nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); + else + LOGPC(DNM, LOGL_ERROR, "\n"); + break; + case NM_MT_IPACC_GET_NVATTR_ACK: + DEBUGPC(DNM, "GET NVATTR ACK\n"); + /* FIXME: decode and show the actual attributes */ + break; + case NM_MT_IPACC_GET_NVATTR_NACK: + LOGPC(DNM, LOGL_ERROR, "GET NVATTR NACK "); + if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) + LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n", + nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); + else + LOGPC(DNM, LOGL_ERROR, "\n"); + break; + case NM_MT_IPACC_SET_ATTR_ACK: + DEBUGPC(DNM, "SET ATTR ACK\n"); + break; + case NM_MT_IPACC_SET_ATTR_NACK: + LOGPC(DNM, LOGL_ERROR, "SET ATTR NACK "); + if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) + LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n", + nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); + else + LOGPC(DNM, LOGL_ERROR, "\n"); + break; + default: + DEBUGPC(DNM, "unknown\n"); + break; + } + + /* signal handling */ + switch (foh->msg_type) { + case NM_MT_IPACC_RSL_CONNECT_NACK: + case NM_MT_IPACC_SET_NVATTR_NACK: + case NM_MT_IPACC_GET_NVATTR_NACK: + signal.trx = gsm_bts_trx_by_nr(msg->trx->bts, foh->obj_inst.trx_nr); + signal.msg_type = foh->msg_type; + dispatch_signal(SS_NM, S_NM_IPACC_NACK, &signal); + break; + case NM_MT_IPACC_SET_NVATTR_ACK: + signal.trx = gsm_bts_trx_by_nr(msg->trx->bts, foh->obj_inst.trx_nr); + signal.msg_type = foh->msg_type; + dispatch_signal(SS_NM, S_NM_IPACC_ACK, &signal); + break; + default: + break; + } + + return 0; +} + +/* send an ip-access manufacturer specific message */ +int abis_nm_ipaccess_msg(struct gsm_bts *bts, u_int8_t msg_type, + u_int8_t obj_class, u_int8_t bts_nr, + u_int8_t trx_nr, u_int8_t ts_nr, + u_int8_t *attr, int attr_len) +{ + struct msgb *msg = nm_msgb_alloc(); + struct abis_om_hdr *oh; + struct abis_om_fom_hdr *foh; + u_int8_t *data; + + /* construct the 12.21 OM header, observe the erroneous length */ + oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh)); + fill_om_hdr(oh, sizeof(*foh) + attr_len); + oh->mdisc = ABIS_OM_MDISC_MANUF; + + /* add the ip.access magic */ + data = msgb_put(msg, sizeof(ipaccess_magic)+1); + *data++ = sizeof(ipaccess_magic); + memcpy(data, ipaccess_magic, sizeof(ipaccess_magic)); + + /* fill the 12.21 FOM header */ + foh = (struct abis_om_fom_hdr *) msgb_put(msg, sizeof(*foh)); + foh->msg_type = msg_type; + foh->obj_class = obj_class; + foh->obj_inst.bts_nr = bts_nr; + foh->obj_inst.trx_nr = trx_nr; + foh->obj_inst.ts_nr = ts_nr; + + if (attr && attr_len) { + data = msgb_put(msg, attr_len); + memcpy(data, attr, attr_len); + } + + return abis_nm_sendmsg(bts, msg); +} + +/* set some attributes in NVRAM */ +int abis_nm_ipaccess_set_nvattr(struct gsm_bts_trx *trx, u_int8_t *attr, + int attr_len) +{ + return abis_nm_ipaccess_msg(trx->bts, NM_MT_IPACC_SET_NVATTR, + NM_OC_BASEB_TRANSC, 0, trx->nr, 0xff, attr, + attr_len); +} + +int abis_nm_ipaccess_rsl_connect(struct gsm_bts_trx *trx, + u_int32_t ip, u_int16_t port, u_int8_t stream) +{ + struct in_addr ia; + u_int8_t attr[] = { NM_ATT_IPACC_STREAM_ID, 0, + NM_ATT_IPACC_DST_IP_PORT, 0, 0, + NM_ATT_IPACC_DST_IP, 0, 0, 0, 0 }; + + int attr_len = sizeof(attr); + + ia.s_addr = htonl(ip); + attr[1] = stream; + attr[3] = port >> 8; + attr[4] = port & 0xff; + *(u_int32_t *)(attr+6) = ia.s_addr; + + /* if ip == 0, we use the default IP */ + if (ip == 0) + attr_len -= 5; + + DEBUGP(DNM, "ip.access RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n", + inet_ntoa(ia), port, stream); + + return abis_nm_ipaccess_msg(trx->bts, NM_MT_IPACC_RSL_CONNECT, + NM_OC_BASEB_TRANSC, trx->bts->bts_nr, + trx->nr, 0xff, attr, attr_len); +} + +/* restart / reboot an ip.access nanoBTS */ +int abis_nm_ipaccess_restart(struct gsm_bts_trx *trx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 0, NM_MT_IPACC_RESTART, NM_OC_BASEB_TRANSC, + trx->bts->nr, trx->nr, 0xff); + + return abis_nm_sendmsg(trx->bts, msg); +} + +int abis_nm_ipaccess_set_attr(struct gsm_bts *bts, u_int8_t obj_class, + u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr, + u_int8_t *attr, u_int8_t attr_len) +{ + return abis_nm_ipaccess_msg(bts, NM_MT_IPACC_SET_ATTR, + obj_class, bts_nr, trx_nr, ts_nr, + attr, attr_len); +} + +void abis_nm_ipaccess_cgi(u_int8_t *buf, struct gsm_bts *bts) +{ + /* we simply reuse the GSM48 function and overwrite the RAC + * with the Cell ID */ + gsm48_ra_id_by_bts(buf, bts); + *((u_int16_t *)(buf + 5)) = htons(bts->cell_identity); +} + +void gsm_trx_lock_rf(struct gsm_bts_trx *trx, int locked) +{ + int new_state = locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED; + + trx->nm_state.administrative = new_state; + if (!trx->bts || !trx->bts->oml_link) + return; + + abis_nm_chg_adm_state(trx->bts, NM_OC_RADIO_CARRIER, + trx->bts->bts_nr, trx->nr, 0xff, + new_state); +} + +static const struct value_string ipacc_testres_names[] = { + { NM_IPACC_TESTRES_SUCCESS, "SUCCESS" }, + { NM_IPACC_TESTRES_TIMEOUT, "TIMEOUT" }, + { NM_IPACC_TESTRES_NO_CHANS, "NO CHANNELS" }, + { NM_IPACC_TESTRES_PARTIAL, "PARTIAL" }, + { NM_IPACC_TESTRES_STOPPED, "STOPPED" }, + { 0, NULL } +}; + +const char *ipacc_testres_name(u_int8_t res) +{ + return get_value_string(ipacc_testres_names, res); +} + +void ipac_parse_cgi(struct cell_global_id *cid, const u_int8_t *buf) +{ + cid->mcc = (buf[0] & 0xf) * 100; + cid->mcc += (buf[0] >> 4) * 10; + cid->mcc += (buf[1] & 0xf) * 1; + + if (buf[1] >> 4 == 0xf) { + cid->mnc = (buf[2] & 0xf) * 10; + cid->mnc += (buf[2] >> 4) * 1; + } else { + cid->mnc = (buf[2] & 0xf) * 100; + cid->mnc += (buf[2] >> 4) * 10; + cid->mnc += (buf[1] >> 4) * 1; + } + + cid->lac = ntohs(*((u_int16_t *)&buf[3])); + cid->ci = ntohs(*((u_int16_t *)&buf[5])); +} + +/* parse BCCH information IEI from wire format to struct ipac_bcch_info */ +int ipac_parse_bcch_info(struct ipac_bcch_info *binf, u_int8_t *buf) +{ + u_int8_t *cur = buf; + u_int16_t len; + + memset(binf, 0, sizeof(*binf)); + + if (cur[0] != NM_IPAC_EIE_BCCH_INFO) + return -EINVAL; + cur++; + + len = ntohs(*(u_int16_t *)cur); + cur += 2; + + binf->info_type = ntohs(*(u_int16_t *)cur); + cur += 2; + + if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL) + binf->freq_qual = *cur >> 2; + + binf->arfcn = (*cur++ & 3) << 8; + binf->arfcn |= *cur++; + + if (binf->info_type & IPAC_BINF_RXLEV) + binf->rx_lev = *cur & 0x3f; + cur++; + + if (binf->info_type & IPAC_BINF_RXQUAL) + binf->rx_qual = *cur & 0x7; + cur++; + + if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL) + binf->freq_err = ntohs(*(u_int16_t *)cur); + cur += 2; + + if (binf->info_type & IPAC_BINF_FRAME_OFFSET) + binf->frame_offset = ntohs(*(u_int16_t *)cur); + cur += 2; + + if (binf->info_type & IPAC_BINF_FRAME_NR_OFFSET) + binf->frame_nr_offset = ntohl(*(u_int32_t *)cur); + cur += 4; + +#if 0 + /* Somehow this is not set correctly */ + if (binf->info_type & IPAC_BINF_BSIC) +#endif + binf->bsic = *cur & 0x3f; + cur++; + + ipac_parse_cgi(&binf->cgi, cur); + cur += 7; + + if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2) { + memcpy(binf->ba_list_si2, cur, sizeof(binf->ba_list_si2)); + cur += sizeof(binf->ba_list_si2); + } + + if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2bis) { + memcpy(binf->ba_list_si2bis, cur, + sizeof(binf->ba_list_si2bis)); + cur += sizeof(binf->ba_list_si2bis); + } + + if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2ter) { + memcpy(binf->ba_list_si2ter, cur, + sizeof(binf->ba_list_si2ter)); + cur += sizeof(binf->ba_list_si2ter); + } + + return 0; +} + +void abis_nm_clear_queue(struct gsm_bts *bts) +{ + struct msgb *msg; + + while (!llist_empty(&bts->abis_queue)) { + msg = msgb_dequeue(&bts->abis_queue); + msgb_free(msg); + } + + bts->abis_nm_pend = 0; +} diff --git a/src/libbsc/abis_nm_vty.c b/src/libbsc/abis_nm_vty.c new file mode 100644 index 000000000..996a85749 --- /dev/null +++ b/src/libbsc/abis_nm_vty.c @@ -0,0 +1,197 @@ +/* VTY interface for A-bis OML (Netowrk Management) */ + +/* (C) 2009-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +extern struct gsm_network *bsc_gsmnet; + +static struct cmd_node oml_node = { + OML_NODE, + "%s(oml)# ", + 1, +}; + +struct oml_node_state { + struct gsm_bts *bts; + uint8_t obj_class; + uint8_t obj_inst[3]; +}; + +static int dummy_config_write(struct vty *v) +{ + return CMD_SUCCESS; +} + +/* FIXME: auto-generate those strings from the value_string lists */ +#define NM_OBJCLASS_VTY "(site-manager|bts|radio-carrier|baseband-transceiver|channel|adjc|handover|power-contorl|btse|rack|test|envabtse|bport|gprs-nse|gprs-cell|gprs-nsvc|siemenshw)" +#define NM_OBJCLASS_VTY_HELP "FIXME" + +DEFUN(oml_class_inst, oml_class_inst_cmd, + "bts <0-255> oml class " NM_OBJCLASS_VTY + " instance <0-255> <0-255> <0-255>", + "BTS related commands\n" "BTS Number\n" + "Manipulate the OML managed objects\n" + "Object Class\n" NM_OBJCLASS_VTY_HELP + "Object Instance\n" "BTS Number\n" "TRX Number\n" "TS Number\n") +{ + struct gsm_bts *bts; + struct oml_node_state *oms; + int bts_nr = atoi(argv[0]); + + bts = gsm_bts_num(bsc_gsmnet, bts_nr); + if (!bts) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); + if (!oms) + return CMD_WARNING; + + oms->bts = bts; + oms->obj_class = get_string_value(abis_nm_obj_class_names, argv[1]); + oms->obj_inst[0] = atoi(argv[2]); + oms->obj_inst[1] = atoi(argv[3]); + oms->obj_inst[2] = atoi(argv[4]); + + vty->index = oms; + vty->node = OML_NODE; + + return CMD_SUCCESS; + +} + +DEFUN(oml_classnum_inst, oml_classnum_inst_cmd, + "bts <0-255> oml class <0-255> instance <0-255> <0-255> <0-255>", + "BTS related commands\n" "BTS Number\n" + "Manipulate the OML managed objects\n" + "Object Class\n" "Object Class\n" + "Object Instance\n" "BTS Number\n" "TRX Number\n" "TS Number\n") +{ + struct gsm_bts *bts; + struct oml_node_state *oms; + int bts_nr = atoi(argv[0]); + + bts = gsm_bts_num(bsc_gsmnet, bts_nr); + if (!bts) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); + if (!oms) + return CMD_WARNING; + + oms->bts = bts; + oms->obj_class = atoi(argv[1]); + oms->obj_inst[0] = atoi(argv[2]); + oms->obj_inst[1] = atoi(argv[3]); + oms->obj_inst[2] = atoi(argv[4]); + + vty->index = oms; + vty->node = OML_NODE; + + return CMD_SUCCESS; +} + +DEFUN(oml_attrib_get, oml_attrib_get_cmd, + "attribute get <0-255>", + "OML Attribute Actions\n" "Get a single OML Attribute\n" + "OML Attribute Number\n") +{ + struct oml_node_state *oms = vty->index; + + /* FIXME */ + return CMD_SUCCESS; +} + +DEFUN(oml_attrib_set, oml_attrib_set_cmd, + "attribute set <0-255> .HEX", + "OML Attribute Actions\n" "Set a single OML Attribute\n" + "OML Attribute Number\n") +{ + struct oml_node_state *oms = vty->index; + + /* FIXME */ + return CMD_SUCCESS; +} + +DEFUN(oml_chg_adm_state, oml_chg_adm_state_cmd, + "change-adm-state (locked|unlocked|shutdown|null)", + "Change the Administrative State\n" + "Locked\n" "Unlocked\n" "Shutdown\n" "NULL\n") +{ + struct oml_node_state *oms = vty->index; + enum abis_nm_adm_state state; + + state = get_string_value(abis_nm_adm_state_names, argv[0]); + + abis_nm_chg_adm_state(oms->bts, oms->obj_class, oms->obj_inst[0], + oms->obj_inst[1], oms->obj_inst[2], state); + + return CMD_SUCCESS; +} + +DEFUN(oml_opstart, oml_opstart_cmd, + "opstart", "Send an OPSTART message to the object") +{ + struct oml_node_state *oms = vty->index; + + abis_nm_opstart(oms->bts, oms->obj_class, oms->obj_inst[0], + oms->obj_inst[1], oms->obj_inst[2]); + + return CMD_SUCCESS; +} + +int abis_nm_vty_init(void) +{ + install_element(ENABLE_NODE, &oml_class_inst_cmd); + install_element(ENABLE_NODE, &oml_classnum_inst_cmd); + install_node(&oml_node, dummy_config_write); + + install_default(OML_NODE); + install_element(OML_NODE, &ournode_exit_cmd); + install_element(OML_NODE, &oml_attrib_get_cmd); + install_element(OML_NODE, &oml_attrib_set_cmd); + install_element(OML_NODE, &oml_chg_adm_state_cmd); + install_element(OML_NODE, &oml_opstart_cmd); + + return 0; +} diff --git a/src/libbsc/abis_om2000.c b/src/libbsc/abis_om2000.c new file mode 100644 index 000000000..805b844c3 --- /dev/null +++ b/src/libbsc/abis_om2000.c @@ -0,0 +1,1078 @@ +/* Ericsson RBS 2xxx GSM O&M (OM2000) messages on the A-bis interface + * implemented based on protocol trace analysis, no formal documentation */ + +/* (C) 2010-2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define OM_ALLOC_SIZE 1024 +#define OM_HEADROOM_SIZE 128 + +/* use following functions from abis_nm.c: + * om2k_msgb_alloc() + * abis_om2k_sendmsg() + */ + +struct abis_om2k_hdr { + struct abis_om_hdr om; + uint16_t msg_type; + struct abis_om2k_mo mo; + uint8_t data[0]; +} __attribute__ ((packed)); + +enum abis_om2k_msgtype { + OM2K_MSGT_ABORT_SP_CMD = 0x0000, + OM2K_MSGT_ABORT_SP_COMPL = 0x0002, + OM2K_MSGT_ALARM_REP_ACK = 0x0004, + OM2K_MSGT_ALARM_REP_NACK = 0x0005, + OM2K_MSGT_ALARM_REP = 0x0006, + OM2K_MSGT_ALARM_STATUS_REQ = 0x0008, + OM2K_MSGT_ALARM_STATUS_REQ_ACK = 0x000a, + OM2K_MSGT_ALARM_STATUS_REQ_REJ = 0x000b, + OM2K_MSGT_ALARM_STATUS_RES_ACK = 0x000c, + OM2K_MSGT_ALARM_STATUS_RES_NACK = 0x000d, + OM2K_MSGT_ALARM_STATUS_RES = 0x000e, + OM2K_MSGT_CAL_TIME_RESP = 0x0010, + OM2K_MSGT_CAL_TIME_REJ = 0x0011, + OM2K_MSGT_CAL_TIME_REQ = 0x0012, + + OM2K_MSGT_CON_CONF_REQ = 0x0014, + OM2K_MSGT_CON_CONF_REQ_ACK = 0x0016, + OM2K_MSGT_CON_CONF_REQ_REJ = 0x0017, + OM2K_MSGT_CON_CONF_RES_ACK = 0x0018, + OM2K_MSGT_CON_CONF_RES_NACK = 0x0019, + OM2K_MSGT_CON_CONF_RES = 0x001a, + + OM2K_MSGT_CONNECT_CMD = 0x001c, + OM2K_MSGT_CONNECT_COMPL = 0x001e, + OM2K_MSGT_CONNECT_REJ = 0x001f, + + OM2K_MSGT_DISABLE_REQ = 0x0028, + OM2K_MSGT_DISABLE_REQ_ACK = 0x002a, + OM2K_MSGT_DISABLE_REQ_REJ = 0x002b, + OM2K_MSGT_DISABLE_RES_ACK = 0x002c, + OM2K_MSGT_DISABLE_RES_NACK = 0x002d, + OM2K_MSGT_DISABLE_RES = 0x002e, + OM2K_MSGT_DISCONNECT_CMD = 0x0030, + OM2K_MSGT_DISCONNECT_COMPL = 0x0032, + OM2K_MSGT_DISCONNECT_REJ = 0x0033, + OM2K_MSGT_ENABLE_REQ = 0x0034, + OM2K_MSGT_ENABLE_REQ_ACK = 0x0036, + OM2K_MSGT_ENABLE_REQ_REJ = 0x0037, + OM2K_MSGT_ENABLE_RES_ACK = 0x0038, + OM2K_MSGT_ENABLE_RES_NACK = 0x0039, + OM2K_MSGT_ENABLE_RES = 0x003a, + + OM2K_MSGT_FAULT_REP_ACK = 0x0040, + OM2K_MSGT_FAULT_REP_NACK = 0x0041, + OM2K_MSGT_FAULT_REP = 0x0042, + + OM2K_MSGT_IS_CONF_REQ = 0x0060, + OM2K_MSGT_IS_CONF_REQ_ACK = 0x0062, + OM2K_MSGT_IS_CONF_REQ_REJ = 0x0063, + OM2K_MSGT_IS_CONF_RES_ACK = 0x0064, + OM2K_MSGT_IS_CONF_RES_NACK = 0x0065, + OM2K_MSGT_IS_CONF_RES = 0x0066, + + OM2K_MSGT_OP_INFO = 0x0074, + OM2K_MSGT_OP_INFO_ACK = 0x0076, + OM2K_MSGT_OP_INFO_REJ = 0x0077, + OM2K_MSGT_RESET_CMD = 0x0078, + OM2K_MSGT_RESET_COMPL = 0x007a, + OM2K_MSGT_RESET_REJ = 0x007b, + OM2K_MSGT_RX_CONF_REQ = 0x007c, + OM2K_MSGT_RX_CONF_REQ_ACK = 0x007e, + OM2K_MSGT_RX_CONF_REQ_REJ = 0x007f, + OM2K_MSGT_RX_CONF_RES_ACK = 0x0080, + OM2K_MSGT_RX_CONF_RES_NACK = 0x0081, + OM2K_MSGT_RX_CONF_RES = 0x0082, + OM2K_MSGT_START_REQ = 0x0084, + OM2K_MSGT_START_REQ_ACK = 0x0086, + OM2K_MSGT_START_REQ_REJ = 0x0087, + OM2K_MSGT_START_RES_ACK = 0x0088, + OM2K_MSGT_START_RES_NACK = 0x0089, + OM2K_MSGT_START_RES = 0x008a, + OM2K_MSGT_STATUS_REQ = 0x008c, + OM2K_MSGT_STATUS_RESP = 0x008e, + OM2K_MSGT_STATUS_REJ = 0x008f, + + OM2K_MSGT_TEST_REQ = 0x0094, + OM2K_MSGT_TEST_REQ_ACK = 0x0096, + OM2K_MSGT_TEST_REQ_REJ = 0x0097, + OM2K_MSGT_TEST_RES_ACK = 0x0098, + OM2K_MSGT_TEST_RES_NACK = 0x0099, + OM2K_MSGT_TEST_RES = 0x009a, + + OM2K_MSGT_TF_CONF_REQ = 0x00a0, + OM2K_MSGT_TF_CONF_REQ_ACK = 0x00a2, + OM2K_MSGT_TF_CONF_REQ_REJ = 0x00a3, + OM2K_MSGT_TF_CONF_RES_ACK = 0x00a4, + OM2K_MSGT_TF_CONF_RES_NACK = 0x00a5, + OM2K_MSGT_TF_CONF_RES = 0x00a6, + OM2K_MSGT_TS_CONF_REQ = 0x00a8, + OM2K_MSGT_TS_CONF_REQ_ACK = 0x00aa, + OM2K_MSGT_TS_CONF_REQ_REJ = 0x00ab, + OM2K_MSGT_TS_CONF_RES_ACK = 0x00ac, + OM2K_MSGT_TS_CONF_RES_NACK = 0x00ad, + OM2K_MSGT_TS_CONF_RES = 0x00ae, + OM2K_MSGT_TX_CONF_REQ = 0x00b0, + OM2K_MSGT_TX_CONF_REQ_ACK = 0x00b2, + OM2K_MSGT_TX_CONF_REQ_REJ = 0x00b3, + OM2K_MSGT_TX_CONF_RES_ACK = 0x00b4, + OM2K_MSGT_TX_CONF_RES_NACK = 0x00b5, + OM2K_MSGT_TX_CONF_RES = 0x00b6, + + OM2K_MSGT_NEGOT_REQ_ACK = 0x0104, + OM2K_MSGT_NEGOT_REQ_NACK = 0x0105, + OM2K_MSGT_NEGOT_REQ = 0x0106, +}; + +enum abis_om2k_dei { + OM2K_DEI_BCC = 0x06, + OM2K_DEI_BSIC = 0x09, + OM2K_DEI_CAL_TIME = 0x0d, + OM2K_DEI_COMBINATION = 0x0f, + OM2K_DEI_CON_CONN_LIST = 0x10, + OM2K_DEI_END_LIST_NR = 0x13, + OM2K_DEI_FILLING_MARKER = 0x1c, + OM2K_DEI_FN_OFFSET = 0x1d, + OM2K_DEI_FREQ_LIST = 0x1e, + OM2K_DEI_FREQ_SPEC_RX = 0x1f, + OM2K_DEI_FREQ_SPEC_TX = 0x20, + OM2K_DEI_HSN = 0x21, + OM2K_DEI_IS_CONN_LIST = 0x27, + OM2K_DEI_LIST_NR = 0x28, + OM2K_DEI_MAIO = 0x2b, + OM2K_DEI_OP_INFO = 0x2e, + OM2K_DEI_POWER = 0x2f, + OM2K_DEI_RX_DIVERSITY = 0x33, + OM2K_DEI_TF_MODE = 0x3a, + OM2K_DEI_TS_NR = 0x3c, + OM2K_DEI_EXT_RANGE = 0x47, + OM2K_DEI_NEGOT_REC1 = 0x90, + OM2K_DEI_NEGOT_REC2 = 0x91, + OM2K_DEI_FS_OFFSET = 0x98, +}; + +static const struct value_string om2k_msgcode_vals[] = { + { 0x0000, "Abort SP Command" }, + { 0x0002, "Abort SP Complete" }, + { 0x0004, "Alarm Report ACK" }, + { 0x0005, "Alarm Report NACK" }, + { 0x0006, "Alarm Report" }, + { 0x0008, "Alarm Status Request" }, + { 0x000a, "Alarm Status Request Accept" }, + { 0x000b, "Alarm Status Request Reject" }, + { 0x000c, "Alarm Status Result ACK" }, + { 0x000d, "Alarm Status Result NACK" }, + { 0x000e, "Alarm Status Result" }, + { 0x0010, "Calendar Time Response" }, + { 0x0011, "Calendar Time Reject" }, + { 0x0012, "Calendar Time Request" }, + { 0x0014, "CON Configuration Request" }, + { 0x0016, "CON Configuration Request Accept" }, + { 0x0017, "CON Configuration Request Reject" }, + { 0x0018, "CON Configuration Result ACK" }, + { 0x0019, "CON Configuration Result NACK" }, + { 0x001a, "CON Configuration Result" }, + { 0x001c, "Connect Command" }, + { 0x001e, "Connect Complete" }, + { 0x001f, "Connect Rejecte" }, + { 0x0028, "Disable Request" }, + { 0x002a, "Disable Request Accept" }, + { 0x002b, "Disable Request Reject" }, + { 0x002c, "Disable Result ACK" }, + { 0x002d, "Disable Result NACK" }, + { 0x002e, "Disable Result" }, + { 0x0030, "Disconnect Command" }, + { 0x0032, "Disconnect Complete" }, + { 0x0033, "Disconnect Reject" }, + { 0x0034, "Enable Request" }, + { 0x0036, "Enable Request Accept" }, + { 0x0037, "Enable Request Reject" }, + { 0x0038, "Enable Result ACK" }, + { 0x0039, "Enable Result NACK" }, + { 0x003a, "Enable Result" }, + { 0x003c, "Escape Downlink Normal" }, + { 0x003d, "Escape Downlink NACK" }, + { 0x003e, "Escape Uplink Normal" }, + { 0x003f, "Escape Uplink NACK" }, + { 0x0040, "Fault Report ACK" }, + { 0x0041, "Fault Report NACK" }, + { 0x0042, "Fault Report" }, + { 0x0044, "File Package End Command" }, + { 0x0046, "File Package End Result" }, + { 0x0047, "File Package End Reject" }, + { 0x0048, "File Relation Request" }, + { 0x004a, "File Relation Response" }, + { 0x004b, "File Relation Request Reject" }, + { 0x004c, "File Segment Transfer" }, + { 0x004e, "File Segment Transfer Complete" }, + { 0x004f, "File Segment Transfer Reject" }, + { 0x0050, "HW Information Request" }, + { 0x0052, "HW Information Request Accept" }, + { 0x0053, "HW Information Request Reject" }, + { 0x0054, "HW Information Result ACK" }, + { 0x0055, "HW Information Result NACK" }, + { 0x0056, "HW Information Result" }, + { 0x0060, "IS Configuration Request" }, + { 0x0062, "IS Configuration Request Accept" }, + { 0x0063, "IS Configuration Request Reject" }, + { 0x0064, "IS Configuration Result ACK" }, + { 0x0065, "IS Configuration Result NACK" }, + { 0x0066, "IS Configuration Result" }, + { 0x0068, "Load Data End" }, + { 0x006a, "Load Data End Result" }, + { 0x006b, "Load Data End Reject" }, + { 0x006c, "Load Data Init" }, + { 0x006e, "Load Data Init Accept" }, + { 0x006f, "Load Data Init Reject" }, + { 0x0070, "Loop Control Command" }, + { 0x0072, "Loop Control Complete" }, + { 0x0073, "Loop Control Reject" }, + { 0x0074, "Operational Information" }, + { 0x0076, "Operational Information Accept" }, + { 0x0077, "Operational Information Reject" }, + { 0x0078, "Reset Command" }, + { 0x007a, "Reset Complete" }, + { 0x007b, "Reset Reject" }, + { 0x007c, "RX Configuration Request" }, + { 0x007e, "RX Configuration Request Accept" }, + { 0x007f, "RX Configuration Request Reject" }, + { 0x0080, "RX Configuration Result ACK" }, + { 0x0081, "RX Configuration Result NACK" }, + { 0x0082, "RX Configuration Result" }, + { 0x0084, "Start Request" }, + { 0x0086, "Start Request Accept" }, + { 0x0087, "Start Request Reject" }, + { 0x0088, "Start Result ACK" }, + { 0x0089, "Start Result NACK" }, + { 0x008a, "Start Result" }, + { 0x008c, "Status Request" }, + { 0x008e, "Status Response" }, + { 0x008f, "Status Reject" }, + { 0x0094, "Test Request" }, + { 0x0096, "Test Request Accept" }, + { 0x0097, "Test Request Reject" }, + { 0x0098, "Test Result ACK" }, + { 0x0099, "Test Result NACK" }, + { 0x009a, "Test Result" }, + { 0x00a0, "TF Configuration Request" }, + { 0x00a2, "TF Configuration Request Accept" }, + { 0x00a3, "TF Configuration Request Reject" }, + { 0x00a4, "TF Configuration Result ACK" }, + { 0x00a5, "TF Configuration Result NACK" }, + { 0x00a6, "TF Configuration Result" }, + { 0x00a8, "TS Configuration Request" }, + { 0x00aa, "TS Configuration Request Accept" }, + { 0x00ab, "TS Configuration Request Reject" }, + { 0x00ac, "TS Configuration Result ACK" }, + { 0x00ad, "TS Configuration Result NACK" }, + { 0x00ae, "TS Configuration Result" }, + { 0x00b0, "TX Configuration Request" }, + { 0x00b2, "TX Configuration Request Accept" }, + { 0x00b3, "TX Configuration Request Reject" }, + { 0x00b4, "TX Configuration Result ACK" }, + { 0x00b5, "TX Configuration Result NACK" }, + { 0x00b6, "TX Configuration Result" }, + { 0x00bc, "DIP Alarm Report ACK" }, + { 0x00bd, "DIP Alarm Report NACK" }, + { 0x00be, "DIP Alarm Report" }, + { 0x00c0, "DIP Alarm Status Request" }, + { 0x00c2, "DIP Alarm Status Response" }, + { 0x00c3, "DIP Alarm Status Reject" }, + { 0x00c4, "DIP Quality Report I ACK" }, + { 0x00c5, "DIP Quality Report I NACK" }, + { 0x00c6, "DIP Quality Report I" }, + { 0x00c8, "DIP Quality Report II ACK" }, + { 0x00c9, "DIP Quality Report II NACK" }, + { 0x00ca, "DIP Quality Report II" }, + { 0x00dc, "DP Configuration Request" }, + { 0x00de, "DP Configuration Request Accept" }, + { 0x00df, "DP Configuration Request Reject" }, + { 0x00e0, "DP Configuration Result ACK" }, + { 0x00e1, "DP Configuration Result NACK" }, + { 0x00e2, "DP Configuration Result" }, + { 0x00e4, "Capabilities HW Info Report ACK" }, + { 0x00e5, "Capabilities HW Info Report NACK" }, + { 0x00e6, "Capabilities HW Info Report" }, + { 0x00e8, "Capabilities Request" }, + { 0x00ea, "Capabilities Request Accept" }, + { 0x00eb, "Capabilities Request Reject" }, + { 0x00ec, "Capabilities Result ACK" }, + { 0x00ed, "Capabilities Result NACK" }, + { 0x00ee, "Capabilities Result" }, + { 0x00f0, "FM Configuration Request" }, + { 0x00f2, "FM Configuration Request Accept" }, + { 0x00f3, "FM Configuration Request Reject" }, + { 0x00f4, "FM Configuration Result ACK" }, + { 0x00f5, "FM Configuration Result NACK" }, + { 0x00f6, "FM Configuration Result" }, + { 0x00f8, "FM Report Request" }, + { 0x00fa, "FM Report Response" }, + { 0x00fb, "FM Report Reject" }, + { 0x00fc, "FM Start Command" }, + { 0x00fe, "FM Start Complete" }, + { 0x00ff, "FM Start Reject" }, + { 0x0100, "FM Stop Command" }, + { 0x0102, "FM Stop Complete" }, + { 0x0103, "FM Stop Reject" }, + { 0x0104, "Negotiation Request ACK" }, + { 0x0105, "Negotiation Request NACK" }, + { 0x0106, "Negotiation Request" }, + { 0x0108, "BTS Initiated Request ACK" }, + { 0x0109, "BTS Initiated Request NACK" }, + { 0x010a, "BTS Initiated Request" }, + { 0x010c, "Radio Channels Release Command" }, + { 0x010e, "Radio Channels Release Complete" }, + { 0x010f, "Radio Channels Release Reject" }, + { 0x0118, "Feature Control Command" }, + { 0x011a, "Feature Control Complete" }, + { 0x011b, "Feature Control Reject" }, + + { 0, NULL } +}; + +/* TS 12.21 Section 9.4: Attributes */ +static const struct value_string om2k_attr_vals[] = { + { 0x00, "Accordance indication" }, + { 0x01, "Alarm Id" }, + { 0x02, "Alarm Data" }, + { 0x03, "Alarm Severity" }, + { 0x04, "Alarm Status" }, + { 0x05, "Alarm Status Type" }, + { 0x06, "BCC" }, + { 0x07, "BS_AG_BKS_RES" }, + { 0x09, "BSIC" }, + { 0x0a, "BA_PA_MFRMS" }, + { 0x0b, "CBCH Indicator" }, + { 0x0c, "CCCH Options" }, + { 0x0d, "Calendar Time" }, + { 0x0f, "Channel Combination" }, + { 0x10, "CON Connection List" }, + { 0x11, "Data End Indication" }, + { 0x12, "DRX_DEV_MAX" }, + { 0x13, "End List Number" }, + { 0x14, "External Condition Map Class 1" }, + { 0x15, "External Condition Map Class 2" }, + { 0x16, "File Relation Indication" }, + { 0x17, "File Revision" }, + { 0x18, "File Segment Data" }, + { 0x19, "File Segment Length" }, + { 0x1a, "File Segment Sequence Number" }, + { 0x1b, "File Size" }, + { 0x1c, "Filling Marker" }, + { 0x1d, "FN Offset" }, + { 0x1e, "Frequency List" }, + { 0x1f, "Frequency Specifier RX" }, + { 0x20, "Frequency Specifier TX" }, + { 0x21, "HSN" }, + { 0x22, "ICM Indicator" }, + { 0x23, "Internal Fault Map Class 1A" }, + { 0x24, "Internal Fault Map Class 1B" }, + { 0x25, "Internal Fault Map Class 2A" }, + { 0x26, "Internal Fault Map Class 2A Extension" }, + { 0x27, "IS Connection List" }, + { 0x28, "List Number" }, + { 0x29, "File Package State Indication" }, + { 0x2a, "Local Access State" }, + { 0x2b, "MAIO" }, + { 0x2c, "MO State" }, + { 0x2d, "Ny1" }, + { 0x2e, "Operational Information" }, + { 0x2f, "Power" }, + { 0x30, "RU Position Data" }, + { 0x31, "Protocol Error" }, + { 0x32, "Reason Code" }, + { 0x33, "Receiver Diversity" }, + { 0x34, "Replacement Unit Map" }, + { 0x35, "Result Code" }, + { 0x36, "RU Revision Data" }, + { 0x38, "T3105" }, + { 0x39, "Test Loop Setting" }, + { 0x3a, "TF Mode" }, + { 0x3b, "TF Compensation Value" }, + { 0x3c, "Time Slot Number" }, + { 0x3d, "TSC" }, + { 0x3e, "RU Logical Id" }, + { 0x3f, "RU Serial Number Data" }, + { 0x40, "BTS Version" }, + { 0x41, "OML IWD Version" }, + { 0x42, "RWL IWD Version" }, + { 0x43, "OML Function Map 1" }, + { 0x44, "OML Function Map 2" }, + { 0x45, "RSL Function Map 1" }, + { 0x46, "RSL Function Map 2" }, + { 0x47, "Extended Range Indicator" }, + { 0x48, "Request Indicators" }, + { 0x49, "DIP Alarm Condition Map" }, + { 0x4a, "ES Incoming" }, + { 0x4b, "ES Outgoing" }, + { 0x4e, "SES Incoming" }, + { 0x4f, "SES Outgoing" }, + { 0x50, "Replacement Unit Map Extension" }, + { 0x52, "UAS Incoming" }, + { 0x53, "UAS Outgoing" }, + { 0x58, "DF Incoming" }, + { 0x5a, "DF Outgoing" }, + { 0x5c, "SF" }, + { 0x60, "S Bits Setting" }, + { 0x61, "CRC-4 Use Option" }, + { 0x62, "T Parameter" }, + { 0x63, "N Parameter" }, + { 0x64, "N1 Parameter" }, + { 0x65, "N3 Parameter" }, + { 0x66, "N4 Parameter" }, + { 0x67, "P Parameter" }, + { 0x68, "Q Parameter" }, + { 0x69, "BI_Q1" }, + { 0x6a, "BI_Q2" }, + { 0x74, "ICM Boundary Parameters" }, + { 0x77, "AFT" }, + { 0x78, "AFT RAI" }, + { 0x79, "Link Supervision Control" }, + { 0x7a, "Link Supervision Filtering Time" }, + { 0x7b, "Call Supervision Time" }, + { 0x7c, "Interval Length UAS Incoming" }, + { 0x7d, "Interval Length UAS Outgoing" }, + { 0x7e, "ICM Channel Rate" }, + { 0x7f, "Attribute Identifier" }, + { 0x80, "FM Frequency List" }, + { 0x81, "FM Frequency Report" }, + { 0x82, "FM Percentile" }, + { 0x83, "FM Clear Indication" }, + { 0x84, "HW Info Signature" }, + { 0x85, "MO Record" }, + { 0x86, "TF Synchronisation Source" }, + { 0x87, "TTA" }, + { 0x88, "End Segment Number" }, + { 0x89, "Segment Number" }, + { 0x8a, "Capabilities Signature" }, + { 0x8c, "File Relation List" }, + { 0x90, "Negotiation Record I" }, + { 0x91, "Negotiation Record II" }, + { 0x92, "Encryption Algorithm" }, + { 0x94, "Interference Rejection Combining" }, + { 0x95, "Dedication Information" }, + { 0x97, "Feature Code" }, + { 0x98, "FS Offset" }, + { 0x99, "ESB Timeslot" }, + { 0x9a, "Master TG Instance" }, + { 0x9b, "Master TX Chain Delay" }, + { 0x9c, "External Condition Class 2 Extension" }, + { 0x9d, "TSs MO State" }, + { 0, NULL } +}; + +const struct value_string om2k_mo_class_short_vals[] = { + { 0x01, "TRXC" }, + { 0x03, "TS" }, + { 0x04, "TF" }, + { 0x05, "IS" }, + { 0x06, "CON" }, + { 0x07, "DP" }, + { 0x0a, "CF" }, + { 0x0b, "TX" }, + { 0x0c, "RX" }, + { 0, NULL } +}; + +static struct msgb *om2k_msgb_alloc(void) +{ + return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE, + "OM2000"); +} + +static char *om2k_mo_name(const struct abis_om2k_mo *mo) +{ + static char mo_buf[64]; + + memset(mo_buf, 0, sizeof(mo_buf)); + snprintf(mo_buf, sizeof(mo_buf), "%s/%02x/%02x/%02x", + get_value_string(om2k_mo_class_short_vals, mo->class), + mo->bts, mo->assoc_so, mo->inst); + return mo_buf; +} + +static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg) +{ + struct abis_om2k_hdr *o2h; + int to_trx_oml; + + msg->l2h = msg->data; + o2h = (struct abis_om2k_hdr *) msg->l2h; + + switch (o2h->mo.class) { + case OM2K_MO_CLS_TRXC: + case OM2K_MO_CLS_TX: + case OM2K_MO_CLS_RX: + case OM2K_MO_CLS_TS: + /* Route through per-TRX OML Link to the appropriate TRX */ + to_trx_oml = 1; + msg->trx = gsm_bts_trx_by_nr(bts, o2h->mo.inst); + if (!msg->trx) { + LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to " + "non-existing TRX\n", om2k_mo_name(&o2h->mo)); + return -ENODEV; + } + break; + default: + /* Route through the IXU/DXU OML Link */ + msg->trx = bts->c0; + to_trx_oml = 0; + break; + } + + return _abis_nm_sendmsg(msg, to_trx_oml); +} + +static void fill_om2k_hdr(struct abis_om2k_hdr *o2h, const struct abis_om2k_mo *mo, + uint16_t msg_type, uint8_t attr_len) +{ + o2h->om.mdisc = ABIS_OM_MDISC_FOM; + o2h->om.placement = ABIS_OM_PLACEMENT_ONLY; + o2h->om.sequence = 0; + o2h->om.length = 6 + attr_len; + o2h->msg_type = htons(msg_type); + memcpy(&o2h->mo, mo, sizeof(o2h->mo)); +} + +const struct abis_om2k_mo om2k_mo_cf = { OM2K_MO_CLS_CF, 0, 0xFF, 0 }; +const struct abis_om2k_mo om2k_mo_is = { OM2K_MO_CLS_IS, 0, 0xFF, 0 }; +const struct abis_om2k_mo om2k_mo_con = { OM2K_MO_CLS_CON, 0, 0xFF, 0 }; +const struct abis_om2k_mo om2k_mo_tf = { OM2K_MO_CLS_TF, 0, 0xFF, 0 }; + +static int abis_om2k_cal_time_resp(struct gsm_bts *bts) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + time_t tm_t; + struct tm *tm; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &om2k_mo_cf, OM2K_MSGT_CAL_TIME_RESP, 7); + + tm_t = time(NULL); + tm = localtime(&tm_t); + + msgb_put_u8(msg, OM2K_DEI_CAL_TIME); + msgb_put_u8(msg, tm->tm_year % 100); + msgb_put_u8(msg, tm->tm_mon + 1); + msgb_put_u8(msg, tm->tm_mday); + msgb_put_u8(msg, tm->tm_hour); + msgb_put_u8(msg, tm->tm_min); + msgb_put_u8(msg, tm->tm_sec); + + return abis_om2k_sendmsg(bts, msg); +} + +static int abis_om2k_tx_simple(struct gsm_bts *bts, const struct abis_om2k_mo *mo, + uint8_t msg_type) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, mo, msg_type, 0); + + DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo), + get_value_string(om2k_msgcode_vals, msg_type)); + + return abis_om2k_sendmsg(bts, msg); +} + +int abis_om2k_tx_reset_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_RESET_CMD); +} + +int abis_om2k_tx_start_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_START_REQ); +} + +int abis_om2k_tx_status_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_STATUS_REQ); +} + +int abis_om2k_tx_connect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_CONNECT_CMD); +} + +int abis_om2k_tx_disconnect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_DISCONNECT_CMD); +} + +int abis_om2k_tx_test_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_TEST_REQ); +} + +int abis_om2k_tx_enable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_ENABLE_REQ); +} + +int abis_om2k_tx_disable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_DISABLE_REQ); +} + +int abis_om2k_tx_op_info(struct gsm_bts *bts, const struct abis_om2k_mo *mo, + uint8_t operational) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, mo, OM2K_MSGT_OP_INFO, 2); + + msgb_tv_put(msg, OM2K_DEI_OP_INFO, operational); + + DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo), + get_value_string(om2k_msgcode_vals, OM2K_MSGT_OP_INFO)); + + return abis_om2k_sendmsg(bts, msg); +} + +int abis_om2k_tx_is_conf_req(struct gsm_bts *bts, struct om2k_is_conn_grp *cg, + unsigned int num_cg ) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &om2k_mo_is, OM2K_MSGT_IS_CONF_REQ, + 2 + 2 + TLV_GROSS_LEN(num_cg * sizeof(*cg))); + + msgb_tv_put(msg, OM2K_DEI_LIST_NR, 1); + msgb_tv_put(msg, OM2K_DEI_END_LIST_NR, 1); + + msgb_tlv_put(msg, OM2K_DEI_IS_CONN_LIST, + num_cg * sizeof(*cg), (uint8_t *)cg); + + return abis_om2k_sendmsg(bts, msg); +} + +int abis_om2k_tx_con_conf_req(struct gsm_bts *bts, uint8_t *data, + unsigned int len) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &om2k_mo_con, OM2K_MSGT_CON_CONF_REQ, + 2 + 2 + TLV_GROSS_LEN(len)); + + msgb_tv_put(msg, OM2K_DEI_LIST_NR, 1); + msgb_tv_put(msg, OM2K_DEI_END_LIST_NR, 1); + + msgb_tlv_put(msg, OM2K_DEI_CON_CONN_LIST, len, data); + + return abis_om2k_sendmsg(bts, msg); +} + +static void om2k_trx_to_mo(struct abis_om2k_mo *mo, + const struct gsm_bts_trx *trx, + enum abis_om2k_mo_cls cls) +{ + mo->class = cls; + mo->bts = 0; + mo->inst = trx->nr; + mo->assoc_so = 0; +} + +static void om2k_ts_to_mo(struct abis_om2k_mo *mo, + const struct gsm_bts_trx_ts *ts) +{ + mo->class = OM2K_MO_CLS_TS; + mo->bts = 0; + mo->inst = ts->nr; + mo->assoc_so = ts->trx->nr; +} + +/* Configure a Receiver MO */ +int abis_om2k_tx_rx_conf_req(struct gsm_bts_trx *trx) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + struct abis_om2k_mo mo; + + om2k_trx_to_mo(&mo, trx, OM2K_MO_CLS_RX); + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &mo, OM2K_MSGT_RX_CONF_REQ, 3+2); + + msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_RX, trx->arfcn); + msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x03); /* A+B */ + + return abis_om2k_sendmsg(trx->bts, msg); +} + +/* Configure a Transmitter MO */ +int abis_om2k_tx_tx_conf_req(struct gsm_bts_trx *trx) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + struct abis_om2k_mo mo; + + om2k_trx_to_mo(&mo, trx, OM2K_MO_CLS_TX); + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &mo, OM2K_MSGT_TX_CONF_REQ, 3+2+2+2); + + msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_TX, trx->arfcn); + msgb_tv_put(msg, OM2K_DEI_POWER, trx->nominal_power-trx->max_power_red); + msgb_tv_put(msg, OM2K_DEI_FILLING_MARKER, 0); /* Filling enabled */ + msgb_tv_put(msg, OM2K_DEI_BCC, trx->bts->bsic & 0x7); + /* Dedication Information is optional */ + + return abis_om2k_sendmsg(trx->bts, msg); +} + +enum abis_om2k_tf_mode { + OM2K_TF_MODE_MASTER = 0x00, + OM2K_TF_MODE_STANDALONE = 0x01, + OM2K_TF_MODE_SLAVE = 0x02, + OM2K_TF_MODE_UNDEFINED = 0xff, +}; + +static const uint8_t fs_offset_undef[5] = { 0xff, 0xff, 0xff, 0xff, 0xff }; + +int abis_om2k_tx_tf_conf_req(struct gsm_bts *bts) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &om2k_mo_tf, OM2K_MSGT_TF_CONF_REQ, + 2+1+sizeof(fs_offset_undef)); + + msgb_tv_put(msg, OM2K_DEI_TF_MODE, OM2K_TF_MODE_STANDALONE); + msgb_tv_fixed_put(msg, OM2K_DEI_FS_OFFSET, + sizeof(fs_offset_undef), fs_offset_undef); + + return abis_om2k_sendmsg(bts, msg); +} + +static uint8_t pchan2comb(enum gsm_phys_chan_config pchan) +{ + switch (pchan) { + case GSM_PCHAN_CCCH: + return 4; + case GSM_PCHAN_CCCH_SDCCH4: + return 5; + case GSM_PCHAN_SDCCH8_SACCH8C: + return 3; + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_PDCH: + case GSM_PCHAN_TCH_F_PDCH: + return 8; + default: + return 0; + } +} + +/* Compute a frequency list in OM2000 fomrmat */ +static int om2k_gen_freq_list(uint8_t *list, struct gsm_bts_trx_ts *ts) +{ + uint8_t *cur = list; + + if (ts->hopping.enabled) { + unsigned int i; + for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) { + if (bitvec_get_bit_pos(&ts->hopping.arfcns, i)) { + *cur++ = 0x00; + *cur++ = i >> 8; + *cur++ = i & 0xff; + } + } + } else { + *cur++ = 0x00; /* TX/RX address */ + *cur++ = ts->trx->arfcn >> 8; + *cur++ = ts->trx->arfcn && 0xff; + } + return (cur - list); +} + +int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + struct abis_om2k_mo mo; + uint8_t freq_list[64*3]; /* BA max size: 64 ARFCN */ + int freq_list_len; + + om2k_ts_to_mo(&mo, ts); + + freq_list_len = om2k_gen_freq_list(freq_list, ts); + if (freq_list_len < 0) + return freq_list_len; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &mo, OM2K_MSGT_TS_CONF_REQ, + 2+2+TLV_GROSS_LEN(freq_list_len)+2+2+2+2+3+2); + + msgb_tv_put(msg, OM2K_DEI_COMBINATION, pchan2comb(ts->pchan)); + msgb_tv_put(msg, OM2K_DEI_TS_NR, ts->nr); + msgb_tlv_put(msg, OM2K_DEI_FREQ_LIST, freq_list_len, freq_list); + msgb_tv_put(msg, OM2K_DEI_HSN, ts->hopping.hsn); + msgb_tv_put(msg, OM2K_DEI_MAIO, ts->hopping.maio); + msgb_tv_put(msg, OM2K_DEI_BSIC, ts->trx->bts->bsic); + msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x03); /* A+B */ + msgb_tv16_put(msg, OM2K_DEI_FN_OFFSET, 0); + msgb_tv_put(msg, OM2K_DEI_EXT_RANGE, 0); /* Off */ + /* Optional: Interference Rejection Combining */ + + return abis_om2k_sendmsg(ts->trx->bts, msg); +} + +static int abis_om2k_tx_negot_req_ack(struct gsm_bts *bts, const struct abis_om2k_mo *mo, + uint8_t *data, unsigned int len) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, mo, OM2K_MSGT_NEGOT_REQ_ACK, 2+len); + + msgb_tlv_put(msg, OM2K_DEI_NEGOT_REC2, len, data); + + DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo), + get_value_string(om2k_msgcode_vals, OM2K_MSGT_NEGOT_REQ_ACK)); + + return abis_om2k_sendmsg(bts, msg); +} + +struct iwd_version { + uint8_t gen_char[3+1]; + uint8_t rev_char[3+1]; +}; + +struct iwd_type { + uint8_t num_vers; + struct iwd_version v[8]; +}; + +static int om2k_rx_negot_req(struct msgb *msg) +{ + struct abis_om2k_hdr *o2h = msgb_l2(msg); + struct iwd_type iwd_types[16]; + uint8_t num_iwd_types = o2h->data[2]; + uint8_t *cur = o2h->data+3; + unsigned int i, v; + + uint8_t out_buf[1024]; + uint8_t *out_cur = out_buf+1; + uint8_t out_num_types = 0; + + memset(iwd_types, 0, sizeof(iwd_types)); + + /* Parse the RBS-supported IWD versions into iwd_types array */ + for (i = 0; i < num_iwd_types; i++) { + uint8_t num_versions = *cur++; + uint8_t iwd_type = *cur++; + + iwd_types[iwd_type].num_vers = num_versions; + + for (v = 0; v < num_versions; v++) { + struct iwd_version *iwd_v = &iwd_types[iwd_type].v[v]; + + memcpy(iwd_v->gen_char, cur, 3); + cur += 3; + memcpy(iwd_v->rev_char, cur, 3); + cur += 3; + + DEBUGP(DNM, "\tIWD Type %u Gen %s Rev %s\n", iwd_type, + iwd_v->gen_char, iwd_v->rev_char); + } + } + + /* Select the last version for each IWD type */ + for (i = 0; i < ARRAY_SIZE(iwd_types); i++) { + struct iwd_type *type = &iwd_types[i]; + struct iwd_version *last_v; + + if (type->num_vers == 0) + continue; + + out_num_types++; + + last_v = &type->v[type->num_vers-1]; + + *out_cur++ = i; + memcpy(out_cur, last_v->gen_char, 3); + out_cur += 3; + memcpy(out_cur, last_v->rev_char, 3); + out_cur += 3; + } + + out_buf[0] = out_num_types; + + return abis_om2k_tx_negot_req_ack(msg->trx->bts, &o2h->mo, out_buf, out_cur - out_buf); +} + +static int om2k_rx_start_res(struct msgb *msg) +{ + struct abis_om2k_hdr *o2h = msgb_l2(msg); + int rc; + + rc = abis_om2k_tx_simple(msg->trx->bts, &o2h->mo, OM2K_MSGT_START_RES_ACK); + rc = abis_om2k_tx_op_info(msg->trx->bts, &o2h->mo, 1); + + return rc; +} + +static int om2k_rx_op_info_ack(struct msgb *msg) +{ + struct abis_om2k_hdr *o2h = msgb_l2(msg); + + /* FIXME: update Operational state in our structures */ + + return 0; +} + +int abis_om2k_rcvmsg(struct msgb *msg) +{ + struct gsm_bts *bts = msg->trx->bts; + struct abis_om2k_hdr *o2h = msgb_l2(msg); + struct abis_om_hdr *oh = &o2h->om; + uint16_t msg_type = ntohs(o2h->msg_type); + int rc = 0; + + /* Various consistency checks */ + if (oh->placement != ABIS_OM_PLACEMENT_ONLY) { + LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n", + oh->placement); + if (oh->placement != ABIS_OM_PLACEMENT_FIRST) + return -EINVAL; + } + if (oh->sequence != 0) { + LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n", + oh->sequence); + return -EINVAL; + } + + msg->l3h = (unsigned char *)o2h + sizeof(*o2h); + + if (oh->mdisc != ABIS_OM_MDISC_FOM) { + LOGP(DNM, LOGL_ERROR, "unknown ABIS OM2000 message discriminator 0x%x\n", + oh->mdisc); + return -EINVAL; + } + + DEBUGP(DNM, "Rx MO=%s %s (%s)\n", om2k_mo_name(&o2h->mo), + get_value_string(om2k_msgcode_vals, msg_type), + hexdump(msg->l2h, msgb_l2len(msg))); + + switch (msg_type) { + case OM2K_MSGT_CAL_TIME_REQ: + rc = abis_om2k_cal_time_resp(bts); + break; + case OM2K_MSGT_FAULT_REP: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_FAULT_REP_ACK); + break; + case OM2K_MSGT_NEGOT_REQ: + rc = om2k_rx_negot_req(msg); + break; + case OM2K_MSGT_START_RES: + rc = om2k_rx_start_res(msg); + break; + case OM2K_MSGT_OP_INFO_ACK: + rc = om2k_rx_op_info_ack(msg); + break; + case OM2K_MSGT_IS_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_IS_CONF_RES_ACK); + break; + case OM2K_MSGT_CON_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_CON_CONF_RES_ACK); + break; + case OM2K_MSGT_TX_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TX_CONF_RES_ACK); + break; + case OM2K_MSGT_RX_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_RX_CONF_RES_ACK); + break; + case OM2K_MSGT_TS_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TS_CONF_RES_ACK); + break; + case OM2K_MSGT_TF_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TF_CONF_RES_ACK); + break; + case OM2K_MSGT_CONNECT_COMPL: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_RESET_CMD); + break; + case OM2K_MSGT_RESET_COMPL: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_START_REQ); + break; + case OM2K_MSGT_ENABLE_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_ENABLE_RES_ACK); + break; + case OM2K_MSGT_DISABLE_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_DISABLE_RES_ACK); + break; + case OM2K_MSGT_TEST_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TEST_RES_ACK); + break; + case OM2K_MSGT_STATUS_RESP: + break; + case OM2K_MSGT_START_REQ_ACK: + case OM2K_MSGT_CON_CONF_REQ_ACK: + case OM2K_MSGT_IS_CONF_REQ_ACK: + case OM2K_MSGT_TX_CONF_REQ_ACK: + case OM2K_MSGT_RX_CONF_REQ_ACK: + case OM2K_MSGT_TS_CONF_REQ_ACK: + case OM2K_MSGT_TF_CONF_REQ_ACK: + case OM2K_MSGT_ENABLE_REQ_ACK: + case OM2K_MSGT_ALARM_STATUS_REQ_ACK: + case OM2K_MSGT_DISABLE_REQ_ACK: + break; + default: + LOGP(DNM, LOGL_NOTICE, "Rx unhandled OM2000 msg %s\n", + get_value_string(om2k_msgcode_vals, msg_type)); + } + + msgb_free(msg); + return rc; +} diff --git a/src/libbsc/abis_om2000_vty.c b/src/libbsc/abis_om2000_vty.c new file mode 100644 index 000000000..5ebb2a39d --- /dev/null +++ b/src/libbsc/abis_om2000_vty.c @@ -0,0 +1,513 @@ +/* VTY interface for A-bis OM2000 */ + +/* (C) 2010-2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +extern struct gsm_network *bsc_gsmnet; + +static struct cmd_node om2k_node = { + OM2K_NODE, + "%s(om2k)# ", + 1, +}; + +struct oml_node_state { + struct gsm_bts *bts; + struct abis_om2k_mo mo; +}; + +static int dummy_config_write(struct vty *v) +{ + return CMD_SUCCESS; +} + +/* FIXME: auto-generate those strings from the value_string lists */ +#define OM2K_OBJCLASS_VTY "(trxc|ts|tf|is|con|dp|cf|tx|rx)" +#define OM2K_OBJCLASS_VTY_HELP "TRX Controller\n" \ + "Timeslot\n" \ + "Timing Function\n" \ + "Interface Switch\n" \ + "Abis Concentrator\n" \ + "Digital Path\n" \ + "Central Function\n" \ + "Transmitter\n" \ + "Receiver\n" + +DEFUN(om2k_class_inst, om2k_class_inst_cmd, + "bts <0-255> om2000 class " OM2K_OBJCLASS_VTY + " <0-255> <0-255> <0-255>", + "BTS related commands\n" "BTS Number\n" + "Manipulate the OM2000 managed objects\n" + "Object Class\n" OM2K_OBJCLASS_VTY_HELP + "BTS Number\n" "Associated SO Instance\n" "Instance Number\n") +{ + struct gsm_bts *bts; + struct oml_node_state *oms; + int bts_nr = atoi(argv[0]); + + bts = gsm_bts_num(bsc_gsmnet, bts_nr); + if (!bts) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (bts->type != GSM_BTS_TYPE_RBS2000) { + vty_out(vty, "%% BTS %d not an Ericsson RBS%s", + bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); + if (!oms) + return CMD_WARNING; + + oms->bts = bts; + oms->mo.class = get_string_value(om2k_mo_class_short_vals, argv[1]); + oms->mo.bts = atoi(argv[2]); + oms->mo.assoc_so = atoi(argv[3]); + oms->mo.inst = atoi(argv[4]); + + vty->index = oms; + vty->node = OM2K_NODE; + + return CMD_SUCCESS; + +} + +DEFUN(om2k_classnum_inst, om2k_classnum_inst_cmd, + "bts <0-255> om2000 class <0-255> <0-255> <0-255> <0-255>", + "BTS related commands\n" "BTS Number\n" + "Manipulate the OML managed objects\n" + "Object Class\n" "Object Class\n" + "BTS Number\n" "Associated SO Instance\n" "Instance Number\n") +{ + struct gsm_bts *bts; + struct oml_node_state *oms; + int bts_nr = atoi(argv[0]); + + bts = gsm_bts_num(bsc_gsmnet, bts_nr); + if (!bts) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); + if (!oms) + return CMD_WARNING; + + oms->bts = bts; + oms->mo.class = atoi(argv[1]); + oms->mo.bts = atoi(argv[2]); + oms->mo.assoc_so = atoi(argv[3]); + oms->mo.inst = atoi(argv[4]); + + vty->index = oms; + vty->node = OM2K_NODE; + + return CMD_SUCCESS; +} + +DEFUN(om2k_reset, om2k_reset_cmd, + "reset-command", + "Reset the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_reset_cmd(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_start, om2k_start_cmd, + "start-request", + "Start the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_start_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_status, om2k_status_cmd, + "status-request", + "Get the MO Status\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_status_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_connect, om2k_connect_cmd, + "connect-command", + "Connect the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_connect_cmd(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_disconnect, om2k_disconnect_cmd, + "disconnect-command", + "Disconnect the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_disconnect_cmd(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_enable, om2k_enable_cmd, + "enable-request", + "Enable the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_enable_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_disable, om2k_disable_cmd, + "disable-request", + "Disable the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_disable_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_op_info, om2k_op_info_cmd, + "operational-info <0-1>", + "Set operational information\n") +{ + struct oml_node_state *oms = vty->index; + int oper = atoi(argv[0]); + + abis_om2k_tx_op_info(oms->bts, &oms->mo, oper); + return CMD_SUCCESS; +} + +DEFUN(om2k_test, om2k_test_cmd, + "test-request", + "Test the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_test_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +struct con_conn_group { + struct llist_head list; + + uint8_t cg; + uint16_t ccp; + uint8_t tag; + uint8_t tei; +}; + +static void add_con_list(struct gsm_bts *bts, uint8_t cg, uint16_t ccp, + uint8_t tag, uint8_t tei) +{ + struct con_conn_group *ent = talloc_zero(bts, struct con_conn_group); + + ent->cg = cg; + ent->ccp = ccp; + ent->tag = tag; + ent->tei = tei; + + llist_add_tail(&ent->list, &bts->rbs2000.con.conn_groups); +} + +static int del_con_list(struct gsm_bts *bts, uint8_t cg, uint16_t ccp, + uint8_t tag, uint8_t tei) +{ + struct con_conn_group *grp, *grp2; + + llist_for_each_entry_safe(grp, grp2, &bts->rbs2000.con.conn_groups, list) { + if (grp->cg == cg && grp->ccp == ccp && grp->tag == tag + && grp->tei == tei) { + llist_del(&grp->list); + talloc_free(grp); + return 0; + } + } + return -ENOENT; +} + +#define CON_LIST_HELP "CON connetiton list\n" \ + "Add entry to CON list\n" \ + "Delete entry from CON list\n" \ + "Connection Group Number\n" \ + "CON Connection Point\n" \ + +DEFUN(om2k_con_list_dec, om2k_con_list_dec_cmd, + "con-connection-list (add|del) <1-255> <0-1023> deconcentrated", + CON_LIST_HELP "De-concentrated in/outlet\n") +{ + struct oml_node_state *oms = vty->index; + struct gsm_bts *bts = oms->bts; + uint8_t cg = atoi(argv[1]); + uint16_t ccp = atoi(argv[2]); + + if (!strcmp(argv[0], "add")) + add_con_list(bts, cg, ccp, 0, 0xff); + else { + if (del_con_list(bts, cg, ccp, 0, 0xff) < 0) { + vty_out(vty, "%% No matching CON list entry%s", + VTY_NEWLINE); + return CMD_WARNING; + } + } + + return CMD_SUCCESS; +} + +DEFUN(om2k_con_list_tei, om2k_con_list_tei_cmd, + "con-connection-list (add|del) <1-255> <0-1023> tei <0-63>", + CON_LIST_HELP "Concentrated in/outlet with TEI\n" "TEI Number\n") +{ + struct oml_node_state *oms = vty->index; + struct gsm_bts *bts = oms->bts; + uint8_t cg = atoi(argv[1]); + uint16_t ccp = atoi(argv[2]); + uint8_t tei = atoi(argv[3]); + + if (!strcmp(argv[0], "add")) + add_con_list(bts, cg, ccp, cg, tei); + else { + if (del_con_list(bts, cg, ccp, cg, tei) < 0) { + vty_out(vty, "%% No matching CON list entry%s", + VTY_NEWLINE); + return CMD_WARNING; + } + } + + return CMD_SUCCESS; +} + +static void om2k_fill_is_conn_grp(struct om2k_is_conn_grp *grp, uint16_t icp1, + uint16_t icp2, uint8_t cont_idx) +{ + grp->icp1 = htons(icp1); + grp->icp2 = htons(icp2); + grp->cont_idx = cont_idx; +} + +struct is_conn_group { + struct llist_head list; + uint16_t icp1; + uint16_t icp2; + uint8_t ci; +}; + +DEFUN(cfg_bts_is_conn_list, cfg_bts_is_conn_list_cmd, + "is-connection-list (add|del) <0-2047> <0-2047> <0-255>", + "Interface Switch Connnection List\n" + "Add to IS list\n" "Delete from IS list\n" + "ICP1\n" "ICP2\n" "Contiguity Index\n") +{ + struct gsm_bts *bts = vty->index; + uint16_t icp1 = atoi(argv[1]); + uint16_t icp2 = atoi(argv[2]); + uint8_t ci = atoi(argv[3]); + struct is_conn_group *grp, *grp2; + + if (!strcmp(argv[0], "add")) { + grp = talloc_zero(bts, struct is_conn_group); + grp->icp1 = icp1; + grp->icp2 = icp2; + grp->ci = ci; + llist_add_tail(&grp->list, &bts->rbs2000.is.conn_groups); + } else { + llist_for_each_entry_safe(grp, grp2, &bts->rbs2000.is.conn_groups, list) { + if (grp->icp1 == icp1 && grp->icp2 == icp2 + && grp->ci == ci) { + llist_del(&grp->list); + talloc_free(grp); + return CMD_SUCCESS; + } + } + vty_out(vty, "%% No matching IS Conn Group found!%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + + +DEFUN(om2k_is_conf_req, om2k_is_conf_req_cmd, + "is-conf-req", + "Send IS Configuration Request\n") +{ + struct oml_node_state *oms = vty->index; + struct gsm_bts *bts = oms->bts; + struct is_conn_group *grp; + unsigned int num_grps = 0, i = 0; + struct om2k_is_conn_grp *o2grps; + + /* count number of groups in linked list */ + llist_for_each_entry(grp, &bts->rbs2000.is.conn_groups, list) + num_grps++; + + if (!num_grps) { + vty_out(vty, "%% No IS connection groups configured!%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + /* allocate buffer for oml group array */ + o2grps = talloc_zero_array(bts, struct om2k_is_conn_grp, num_grps); + + /* fill array with data from linked list */ + llist_for_each_entry(grp, &bts->rbs2000.is.conn_groups, list) + om2k_fill_is_conn_grp(&o2grps[i++], grp->icp1, grp->icp2, grp->ci); + + /* send the actual OML request */ + abis_om2k_tx_is_conf_req(oms->bts, o2grps, num_grps); + + talloc_free(o2grps); + + return CMD_SUCCESS; +} + +DEFUN(om2k_conf_req, om2k_conf_req_cmd, + "configuration-request", + "Send the configuration request for current MO\n") +{ + struct oml_node_state *oms = vty->index; + struct gsm_bts *bts = oms->bts; + struct gsm_bts_trx *trx = NULL; + struct gsm_bts_trx_ts *ts = NULL; + + switch (oms->mo.class) { + case OM2K_MO_CLS_TS: + trx = gsm_bts_trx_by_nr(bts, oms->mo.assoc_so); + if (!trx) { + vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr, + oms->mo.assoc_so, VTY_NEWLINE); + return CMD_WARNING; + } + if (oms->mo.inst >= ARRAY_SIZE(trx->ts)) { + vty_out(vty, "%% Timeslot %u out of range%s", + oms->mo.inst, VTY_NEWLINE); + return CMD_WARNING; + } + ts = &trx->ts[oms->mo.inst]; + abis_om2k_tx_ts_conf_req(ts); + break; + case OM2K_MO_CLS_RX: + case OM2K_MO_CLS_TX: + case OM2K_MO_CLS_TRXC: + trx = gsm_bts_trx_by_nr(bts, oms->mo.inst); + if (!trx) { + vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr, + oms->mo.inst, VTY_NEWLINE); + return CMD_WARNING; + } + switch (oms->mo.class) { + case OM2K_MO_CLS_RX: + abis_om2k_tx_rx_conf_req(trx); + break; + case OM2K_MO_CLS_TX: + abis_om2k_tx_rx_conf_req(trx); + break; + default: + break; + } + break; + case OM2K_MO_CLS_TF: + abis_om2k_tx_tf_conf_req(bts); + break; + default: + vty_out(vty, "%% Don't know how to configure MO%s", + VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts) +{ + struct is_conn_group *igrp; + struct con_conn_group *cgrp; + + llist_for_each_entry(igrp, &bts->rbs2000.is.conn_groups, list) + vty_out(vty, " is-connection-list add %u %u %u%s", + igrp->icp1, igrp->icp2, igrp->ci, VTY_NEWLINE); + + llist_for_each_entry(cgrp, &bts->rbs2000.con.conn_groups, list) { + vty_out(vty, " con-connection-list add %u %u ", + cgrp->cg, cgrp->ccp); + if (cgrp->tei == 0xff) + vty_out(vty, "deconcentrated%s", VTY_NEWLINE); + else + vty_out(vty, "tei %u%s", cgrp->tei, VTY_NEWLINE); + } +} + +int abis_om2k_vty_init(void) +{ + install_element(ENABLE_NODE, &om2k_class_inst_cmd); + install_element(ENABLE_NODE, &om2k_classnum_inst_cmd); + install_node(&om2k_node, dummy_config_write); + + install_default(OM2K_NODE); + install_element(OM2K_NODE, &ournode_exit_cmd); + install_element(OM2K_NODE, &om2k_reset_cmd); + install_element(OM2K_NODE, &om2k_start_cmd); + install_element(OM2K_NODE, &om2k_status_cmd); + install_element(OM2K_NODE, &om2k_connect_cmd); + install_element(OM2K_NODE, &om2k_disconnect_cmd); + install_element(OM2K_NODE, &om2k_enable_cmd); + install_element(OM2K_NODE, &om2k_disable_cmd); + install_element(OM2K_NODE, &om2k_op_info_cmd); + install_element(OM2K_NODE, &om2k_test_cmd); + install_element(OM2K_NODE, &om2k_conf_req_cmd); + install_element(OM2K_NODE, &om2k_is_conf_req_cmd); + install_element(OM2K_NODE, &om2k_con_list_dec_cmd); + install_element(OM2K_NODE, &om2k_con_list_tei_cmd); + + install_element(BTS_NODE, &cfg_bts_is_conn_list_cmd); + + return 0; +} diff --git a/src/libbsc/abis_rsl.c b/src/libbsc/abis_rsl.c new file mode 100644 index 000000000..9a4dc5b4f --- /dev/null +++ b/src/libbsc/abis_rsl.c @@ -0,0 +1,1971 @@ +/* GSM Radio Signalling Link messages on the A-bis interface + * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */ + +/* (C) 2008-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define RSL_ALLOC_SIZE 1024 +#define RSL_ALLOC_HEADROOM 128 + +#define MAX(a, b) (a) >= (b) ? (a) : (b) + +static int rsl_send_imm_assignment(struct gsm_lchan *lchan); + +static void send_lchan_signal(int sig_no, struct gsm_lchan *lchan, + struct gsm_meas_rep *resp) +{ + struct lchan_signal_data sig; + sig.lchan = lchan; + sig.mr = resp; + dispatch_signal(SS_LCHAN, sig_no, &sig); +} + +static u_int8_t mdisc_by_msgtype(u_int8_t msg_type) +{ + /* mask off the transparent bit ? */ + msg_type &= 0xfe; + + if ((msg_type & 0xf0) == 0x00) + return ABIS_RSL_MDISC_RLL; + if ((msg_type & 0xf0) == 0x10) { + if (msg_type >= 0x19 && msg_type <= 0x22) + return ABIS_RSL_MDISC_TRX; + else + return ABIS_RSL_MDISC_COM_CHAN; + } + if ((msg_type & 0xe0) == 0x20) + return ABIS_RSL_MDISC_DED_CHAN; + + return ABIS_RSL_MDISC_LOC; +} + +static inline void init_dchan_hdr(struct abis_rsl_dchan_hdr *dh, + u_int8_t msg_type) +{ + dh->c.msg_discr = mdisc_by_msgtype(msg_type); + dh->c.msg_type = msg_type; + dh->ie_chan = RSL_IE_CHAN_NR; +} + +/* determine logical channel based on TRX and channel number IE */ +struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, u_int8_t chan_nr) +{ + struct gsm_lchan *lchan; + u_int8_t ts_nr = chan_nr & 0x07; + u_int8_t cbits = chan_nr >> 3; + u_int8_t lch_idx; + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + + if (cbits == 0x01) { + lch_idx = 0; /* TCH/F */ + if (ts->pchan != GSM_PCHAN_TCH_F && + ts->pchan != GSM_PCHAN_PDCH && + ts->pchan != GSM_PCHAN_TCH_F_PDCH) + LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n", + chan_nr, ts->pchan); + } else if ((cbits & 0x1e) == 0x02) { + lch_idx = cbits & 0x1; /* TCH/H */ + if (ts->pchan != GSM_PCHAN_TCH_H) + LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n", + chan_nr, ts->pchan); + } else if ((cbits & 0x1c) == 0x04) { + lch_idx = cbits & 0x3; /* SDCCH/4 */ + if (ts->pchan != GSM_PCHAN_CCCH_SDCCH4) + LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n", + chan_nr, ts->pchan); + } else if ((cbits & 0x18) == 0x08) { + lch_idx = cbits & 0x7; /* SDCCH/8 */ + if (ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C) + LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n", + chan_nr, ts->pchan); + } else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) { + lch_idx = 0; + if (ts->pchan != GSM_PCHAN_CCCH && + ts->pchan != GSM_PCHAN_CCCH_SDCCH4) + LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n", + chan_nr, ts->pchan); + /* FIXME: we should not return first sdcch4 !!! */ + } else { + LOGP(DRSL, LOGL_ERROR, "unknown chan_nr=0x%02x\n", chan_nr); + return NULL; + } + + lchan = &ts->lchan[lch_idx]; + log_set_context(BSC_CTX_LCHAN, lchan); + if (lchan->conn) + log_set_context(BSC_CTX_SUBSCR, lchan->conn->subscr); + + return lchan; +} + +/* See Table 10.5.25 of GSM04.08 */ +static u_int8_t ts2chan_nr(const struct gsm_bts_trx_ts *ts, uint8_t lchan_nr) +{ + u_int8_t cbits, chan_nr; + + switch (ts->pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_PDCH: + case GSM_PCHAN_TCH_F_PDCH: + cbits = 0x01; + break; + case GSM_PCHAN_TCH_H: + cbits = 0x02; + cbits += lchan_nr; + break; + case GSM_PCHAN_CCCH_SDCCH4: + cbits = 0x04; + cbits += lchan_nr; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + cbits = 0x08; + cbits += lchan_nr; + break; + default: + case GSM_PCHAN_CCCH: + cbits = 0x10; + break; + } + + chan_nr = (cbits << 3) | (ts->nr & 0x7); + + return chan_nr; +} + +u_int8_t lchan2chan_nr(const struct gsm_lchan *lchan) +{ + return ts2chan_nr(lchan->ts, lchan->nr); +} + +/* As per TS 03.03 Section 2.2, the IMSI has 'not more than 15 digits' */ +u_int64_t str_to_imsi(const char *imsi_str) +{ + u_int64_t ret; + + ret = strtoull(imsi_str, NULL, 10); + + return ret; +} + +/* Table 5 Clause 7 TS 05.02 */ +unsigned int n_pag_blocks(int bs_ccch_sdcch_comb, unsigned int bs_ag_blks_res) +{ + if (!bs_ccch_sdcch_comb) + return 9 - bs_ag_blks_res; + else + return 3 - bs_ag_blks_res; +} + +/* Chapter 6.5.2 of TS 05.02 */ +unsigned int get_ccch_group(u_int64_t imsi, unsigned int bs_cc_chans, + unsigned int n_pag_blocks) +{ + return (imsi % 1000) % (bs_cc_chans * n_pag_blocks) / n_pag_blocks; +} + +/* Chapter 6.5.2 of TS 05.02 */ +unsigned int get_paging_group(u_int64_t imsi, unsigned int bs_cc_chans, + int n_pag_blocks) +{ + return (imsi % 1000) % (bs_cc_chans * n_pag_blocks) % n_pag_blocks; +} + +static struct msgb *rsl_msgb_alloc(void) +{ + return msgb_alloc_headroom(RSL_ALLOC_SIZE, RSL_ALLOC_HEADROOM, + "RSL"); +} + +#define MACBLOCK_SIZE 23 +static void pad_macblock(u_int8_t *out, const u_int8_t *in, int len) +{ + memcpy(out, in, len); + + if (len < MACBLOCK_SIZE) + memset(out+len, 0x2b, MACBLOCK_SIZE-len); +} + +/* Chapter 9.3.7: Encryption Information */ +static int build_encr_info(u_int8_t *out, struct gsm_lchan *lchan) +{ + *out++ = lchan->encr.alg_id & 0xff; + if (lchan->encr.key_len) + memcpy(out, lchan->encr.key, lchan->encr.key_len); + return lchan->encr.key_len + 1; +} + +static void print_rsl_cause(int lvl, const u_int8_t *cause_v, u_int8_t cause_len) +{ + int i; + + LOGPC(DRSL, lvl, "CAUSE=0x%02x(%s) ", + cause_v[0], rsl_err_name(cause_v[0])); + for (i = 1; i < cause_len-1; i++) + LOGPC(DRSL, lvl, "%02x ", cause_v[i]); +} + +/* Send a BCCH_INFO message as per Chapter 8.5.1 */ +int rsl_bcch_info(struct gsm_bts_trx *trx, u_int8_t type, + const u_int8_t *data, int len) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg = rsl_msgb_alloc(); + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof*dh); + init_dchan_hdr(dh, RSL_MT_BCCH_INFO); + dh->chan_nr = RSL_CHAN_BCCH; + + msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type); + msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data); + + msg->trx = trx; + + return abis_rsl_sendmsg(msg); +} + +int rsl_sacch_filling(struct gsm_bts_trx *trx, u_int8_t type, + const u_int8_t *data, int len) +{ + struct abis_rsl_common_hdr *ch; + struct msgb *msg = rsl_msgb_alloc(); + + ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch)); + ch->msg_discr = ABIS_RSL_MDISC_TRX; + ch->msg_type = RSL_MT_SACCH_FILL; + + msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type); + msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data); + + msg->trx = trx; + + return abis_rsl_sendmsg(msg); +} + +int rsl_sacch_info_modify(struct gsm_lchan *lchan, u_int8_t type, + const u_int8_t *data, int len) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg = rsl_msgb_alloc(); + u_int8_t chan_nr = lchan2chan_nr(lchan); + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_SACCH_INFO_MODIFY); + dh->chan_nr = chan_nr; + + msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type); + msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data); + + msg->trx = lchan->ts->trx; + + return abis_rsl_sendmsg(msg); +} + +int rsl_chan_bs_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int db) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg; + u_int8_t chan_nr = lchan2chan_nr(lchan); + + db = abs(db); + if (db > 30) + return -EINVAL; + + msg = rsl_msgb_alloc(); + + lchan->bs_power = db/2; + if (fpc) + lchan->bs_power |= 0x10; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_BS_POWER_CONTROL); + dh->chan_nr = chan_nr; + + msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power); + + msg->trx = lchan->ts->trx; + + return abis_rsl_sendmsg(msg); +} + +int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int dbm) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg; + u_int8_t chan_nr = lchan2chan_nr(lchan); + int ctl_lvl; + + ctl_lvl = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, dbm); + if (ctl_lvl < 0) + return ctl_lvl; + + msg = rsl_msgb_alloc(); + + lchan->ms_power = ctl_lvl; + + if (fpc) + lchan->ms_power |= 0x20; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_MS_POWER_CONTROL); + dh->chan_nr = chan_nr; + + msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power); + + msg->trx = lchan->ts->trx; + + return abis_rsl_sendmsg(msg); +} + +static int channel_mode_from_lchan(struct rsl_ie_chan_mode *cm, + struct gsm_lchan *lchan) +{ + memset(cm, 0, sizeof(cm)); + + /* FIXME: what to do with data calls ? */ + if (lchan->ts->trx->bts->network->dtx_enabled) + cm->dtx_dtu = 0x03; + else + cm->dtx_dtu = 0x00; + + /* set TCH Speech/Data */ + cm->spd_ind = lchan->rsl_cmode; + + if (lchan->rsl_cmode == RSL_CMOD_SPD_SIGN && + lchan->tch_mode != GSM48_CMODE_SIGN) + LOGP(DRSL, LOGL_ERROR, "unsupported: rsl_mode == signalling, " + "but tch_mode != signalling\n"); + + switch (lchan->type) { + case GSM_LCHAN_SDCCH: + cm->chan_rt = RSL_CMOD_CRT_SDCCH; + break; + case GSM_LCHAN_TCH_F: + cm->chan_rt = RSL_CMOD_CRT_TCH_Bm; + break; + case GSM_LCHAN_TCH_H: + cm->chan_rt = RSL_CMOD_CRT_TCH_Lm; + break; + case GSM_LCHAN_NONE: + case GSM_LCHAN_UNKNOWN: + default: + return -EINVAL; + } + + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + cm->chan_rate = 0; + break; + case GSM48_CMODE_SPEECH_V1: + cm->chan_rate = RSL_CMOD_SP_GSM1; + break; + case GSM48_CMODE_SPEECH_EFR: + cm->chan_rate = RSL_CMOD_SP_GSM2; + break; + case GSM48_CMODE_SPEECH_AMR: + cm->chan_rate = RSL_CMOD_SP_GSM3; + break; + case GSM48_CMODE_DATA_14k5: + cm->chan_rate = RSL_CMOD_SP_NT_14k5; + break; + case GSM48_CMODE_DATA_12k0: + cm->chan_rate = RSL_CMOD_SP_NT_12k0; + break; + case GSM48_CMODE_DATA_6k0: + cm->chan_rate = RSL_CMOD_SP_NT_6k0; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* Chapter 8.4.1 */ +#if 0 +int rsl_chan_activate(struct gsm_bts_trx *trx, u_int8_t chan_nr, + u_int8_t act_type, + struct rsl_ie_chan_mode *chan_mode, + struct rsl_ie_chan_ident *chan_ident, + u_int8_t bs_power, u_int8_t ms_power, + u_int8_t ta) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg = rsl_msgb_alloc(); + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_CHAN_ACTIV); + dh->chan_nr = chan_nr; + + msgb_tv_put(msg, RSL_IE_ACT_TYPE, act_type); + /* For compatibility with Phase 1 */ + msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(*chan_mode), + (u_int8_t *) chan_mode); + msgb_tlv_put(msg, RSL_IE_CHAN_IDENT, 4, + (u_int8_t *) chan_ident); +#if 0 + msgb_tlv_put(msg, RSL_IE_ENCR_INFO, 1, + (u_int8_t *) &encr_info); +#endif + msgb_tv_put(msg, RSL_IE_BS_POWER, bs_power); + msgb_tv_put(msg, RSL_IE_MS_POWER, ms_power); + msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, ta); + + msg->trx = trx; + + return abis_rsl_sendmsg(msg); +} +#endif + +int rsl_chan_activate_lchan(struct gsm_lchan *lchan, u_int8_t act_type, + u_int8_t ta, u_int8_t ho_ref) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg; + int rc; + uint8_t *len; + + u_int8_t chan_nr = lchan2chan_nr(lchan); + struct rsl_ie_chan_mode cm; + struct gsm48_chan_desc cd; + + rc = channel_mode_from_lchan(&cm, lchan); + if (rc < 0) + return rc; + + memset(&cd, 0, sizeof(cd)); + gsm48_lchan2chan_desc(&cd, lchan); + + msg = rsl_msgb_alloc(); + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_CHAN_ACTIV); + dh->chan_nr = chan_nr; + + msgb_tv_put(msg, RSL_IE_ACT_TYPE, act_type); + msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm), + (u_int8_t *) &cm); + + /* + * The Channel Identification is needed for Phase1 phones + * and it contains the GSM48 Channel Description and the + * Mobile Allocation. The GSM 08.58 asks for the Mobile + * Allocation to have a length of zero. We are using the + * msgb_l3len to calculate the length of both messages. + */ + msgb_v_put(msg, RSL_IE_CHAN_IDENT); + len = msgb_put(msg, 1); + msgb_tlv_put(msg, GSM48_IE_CHANDESC_2, sizeof(cd), (const uint8_t *) &cd); + + if (lchan->ts->hopping.enabled) + msgb_tlv_put(msg, GSM48_IE_MA_AFTER, lchan->ts->hopping.ma_len, + lchan->ts->hopping.ma_data); + else + msgb_tlv_put(msg, GSM48_IE_MA_AFTER, 0, NULL); + + /* update the calculated size */ + msg->l3h = len + 1; + *len = msgb_l3len(msg); + + if (lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) { + u_int8_t encr_info[MAX_A5_KEY_LEN+2]; + rc = build_encr_info(encr_info, lchan); + if (rc > 0) + msgb_tlv_put(msg, RSL_IE_ENCR_INFO, rc, encr_info); + } + + switch (act_type) { + case RSL_ACT_INTER_ASYNC: + case RSL_ACT_INTER_SYNC: + msgb_tv_put(msg, RSL_IE_HANDO_REF, ho_ref); + break; + default: + break; + } + + msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power); + msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power); + msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, ta); + + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) + msgb_tlv_put(msg, RSL_IE_MR_CONFIG, sizeof(lchan->mr_conf), + (u_int8_t *) &lchan->mr_conf); + + msg->trx = lchan->ts->trx; + + return abis_rsl_sendmsg(msg); +} + +/* Chapter 8.4.9: Modify channel mode on BTS side */ +int rsl_chan_mode_modify_req(struct gsm_lchan *lchan) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg; + int rc; + + u_int8_t chan_nr = lchan2chan_nr(lchan); + struct rsl_ie_chan_mode cm; + + rc = channel_mode_from_lchan(&cm, lchan); + if (rc < 0) + return rc; + + msg = rsl_msgb_alloc(); + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_MODE_MODIFY_REQ); + dh->chan_nr = chan_nr; + + msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm), + (u_int8_t *) &cm); + + if (lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) { + u_int8_t encr_info[MAX_A5_KEY_LEN+2]; + rc = build_encr_info(encr_info, lchan); + if (rc > 0) + msgb_tlv_put(msg, RSL_IE_ENCR_INFO, rc, encr_info); + } + + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) { + msgb_tlv_put(msg, RSL_IE_MR_CONFIG, sizeof(lchan->mr_conf), + (u_int8_t *) &lchan->mr_conf); + } + + msg->trx = lchan->ts->trx; + + return abis_rsl_sendmsg(msg); +} + +/* Chapter 8.4.6: Send the encryption command with given L3 info */ +int rsl_encryption_cmd(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh; + struct gsm_lchan *lchan = msg->lchan; + u_int8_t chan_nr = lchan2chan_nr(lchan); + u_int8_t encr_info[MAX_A5_KEY_LEN+2]; + u_int8_t l3_len = msg->len; + int rc; + + /* First push the L3 IE tag and length */ + msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len); + + /* then the link identifier (SAPI0, main sign link) */ + msgb_tv_push(msg, RSL_IE_LINK_IDENT, 0); + + /* then encryption information */ + rc = build_encr_info(encr_info, lchan); + if (rc <= 0) + return rc; + msgb_tlv_push(msg, RSL_IE_ENCR_INFO, rc, encr_info); + + /* and finally the DCHAN header */ + dh = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_ENCR_CMD); + dh->chan_nr = chan_nr; + + msg->trx = lchan->ts->trx; + + return abis_rsl_sendmsg(msg); +} + +/* Chapter 8.4.5 / 4.6: Deactivate the SACCH after 04.08 RR CHAN RELEASE */ +int rsl_deact_sacch(struct gsm_lchan *lchan) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg = rsl_msgb_alloc(); + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_DEACTIVATE_SACCH); + dh->chan_nr = lchan2chan_nr(lchan); + + msg->lchan = lchan; + msg->trx = lchan->ts->trx; + + DEBUGP(DRSL, "%s DEACTivate SACCH CMD\n", gsm_lchan_name(lchan)); + + return abis_rsl_sendmsg(msg); +} + +static void error_timeout_cb(void *data) +{ + struct gsm_lchan *lchan = data; + if (lchan->state != LCHAN_S_REL_ERR) { + LOGP(DRSL, LOGL_ERROR, "%s error timeout but not in error state: %d\n", + gsm_lchan_name(lchan), lchan->state); + return; + } + + /* go back to the none state */ + LOGP(DRSL, LOGL_NOTICE, "%s is back in operation.\n", gsm_lchan_name(lchan)); + rsl_lchan_set_state(lchan, LCHAN_S_NONE); +} + +static int rsl_rx_rf_chan_rel_ack(struct gsm_lchan *lchan); + +/* Chapter 8.4.14 / 4.7: Tell BTS to release the radio channel */ +static int rsl_rf_chan_release(struct gsm_lchan *lchan, int error) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg; + int rc; + + if (lchan->state == LCHAN_S_REL_ERR) { + LOGP(DRSL, LOGL_NOTICE, "%s is in error state not sending release.\n", + gsm_lchan_name(lchan)); + return -1; + } + + msg = rsl_msgb_alloc(); + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_RF_CHAN_REL); + dh->chan_nr = lchan2chan_nr(lchan); + + msg->lchan = lchan; + msg->trx = lchan->ts->trx; + + DEBUGP(DRSL, "%s RF Channel Release CMD due error %d\n", gsm_lchan_name(lchan), error); + + if (error) { + /* + * the nanoBTS sends RLL release indications after the channel release. This can + * be a problem when we have reassigned the channel to someone else and then can + * not figure out who used this channel. + */ + rsl_lchan_set_state(lchan, LCHAN_S_REL_ERR); + lchan->error_timer.data = lchan; + lchan->error_timer.cb = error_timeout_cb; + bsc_schedule_timer(&lchan->error_timer, + msg->trx->bts->network->T3111 + 2, 0); + } + + rc = abis_rsl_sendmsg(msg); + + /* BTS will respond by RF CHAN REL ACK */ +#ifdef HSL_SR_1_0 + /* The HSL Femto seems to 'forget' sending a REL ACK for TS1...TS7 */ + if (lchan->ts->trx->bts->type == GSM_BTS_TYPE_HSL_FEMTO && lchan->ts->nr != 0) + rc = rsl_rx_rf_chan_rel_ack(lchan); +#endif + + return rc; +} + +static int rsl_rx_rf_chan_rel_ack(struct gsm_lchan *lchan) +{ + + DEBUGP(DRSL, "%s RF CHANNEL RELEASE ACK\n", gsm_lchan_name(lchan)); + + if (lchan->state != LCHAN_S_REL_REQ && lchan->state != LCHAN_S_REL_ERR) + LOGP(DRSL, LOGL_NOTICE, "%s CHAN REL ACK but state %s\n", + gsm_lchan_name(lchan), + gsm_lchans_name(lchan->state)); + bsc_del_timer(&lchan->T3111); + /* we have an error timer pending to release that */ + if (lchan->state != LCHAN_S_REL_ERR) + rsl_lchan_set_state(lchan, LCHAN_S_NONE); + lchan_free(lchan); + + return 0; +} + +int rsl_paging_cmd(struct gsm_bts *bts, u_int8_t paging_group, u_int8_t len, + u_int8_t *ms_ident, u_int8_t chan_needed) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg = rsl_msgb_alloc(); + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_PAGING_CMD); + dh->chan_nr = RSL_CHAN_PCH_AGCH; + + msgb_tv_put(msg, RSL_IE_PAGING_GROUP, paging_group); + msgb_tlv_put(msg, RSL_IE_MS_IDENTITY, len-2, ms_ident+2); + msgb_tv_put(msg, RSL_IE_CHAN_NEEDED, chan_needed); + + msg->trx = bts->c0; + + return abis_rsl_sendmsg(msg); +} + +int imsi_str2bcd(u_int8_t *bcd_out, const char *str_in) +{ + int i, len = strlen(str_in); + + for (i = 0; i < len; i++) { + int num = str_in[i] - 0x30; + if (num < 0 || num > 9) + return -1; + if (i % 2 == 0) + bcd_out[i/2] = num; + else + bcd_out[i/2] |= (num << 4); + } + + return 0; +} + +/* Chapter 8.5.6 */ +int rsl_imm_assign_cmd(struct gsm_bts *bts, u_int8_t len, u_int8_t *val) +{ + struct msgb *msg = rsl_msgb_alloc(); + struct abis_rsl_dchan_hdr *dh; + u_int8_t buf[MACBLOCK_SIZE]; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_IMMEDIATE_ASSIGN_CMD); + dh->chan_nr = RSL_CHAN_PCH_AGCH; + + switch (bts->type) { + case GSM_BTS_TYPE_BS11: + msgb_tlv_put(msg, RSL_IE_IMM_ASS_INFO, len, val); + break; + default: + /* If phase 2, construct a FULL_IMM_ASS_INFO */ + pad_macblock(buf, val, len); + msgb_tlv_put(msg, RSL_IE_FULL_IMM_ASS_INFO, MACBLOCK_SIZE, buf); + break; + } + + msg->trx = bts->c0; + + return abis_rsl_sendmsg(msg); +} + +/* Send Siemens specific MS RF Power Capability Indication */ +int rsl_siemens_mrpci(struct gsm_lchan *lchan, struct rsl_mrpci *mrpci) +{ + struct msgb *msg = rsl_msgb_alloc(); + struct abis_rsl_dchan_hdr *dh; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_SIEMENS_MRPCI); + dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN; + dh->chan_nr = lchan2chan_nr(lchan); + msgb_tv_put(msg, RSL_IE_SIEMENS_MRPCI, *(u_int8_t *)mrpci); + + DEBUGP(DRSL, "%s TX Siemens MRPCI 0x%02x\n", + gsm_lchan_name(lchan), *(u_int8_t *)mrpci); + + msg->trx = lchan->ts->trx; + + return abis_rsl_sendmsg(msg); +} + + +/* Send "DATA REQUEST" message with given L3 Info payload */ +/* Chapter 8.3.1 */ +int rsl_data_request(struct msgb *msg, u_int8_t link_id) +{ + if (msg->lchan == NULL) { + LOGP(DRSL, LOGL_ERROR, "cannot send DATA REQUEST to unknown lchan\n"); + return -EINVAL; + } + + rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, lchan2chan_nr(msg->lchan), + link_id, 1); + + msg->trx = msg->lchan->ts->trx; + + return abis_rsl_sendmsg(msg); +} + +/* Send "ESTABLISH REQUEST" message with given L3 Info payload */ +/* Chapter 8.3.1 */ +int rsl_establish_request(struct gsm_lchan *lchan, u_int8_t link_id) +{ + struct msgb *msg; + + msg = rsl_rll_simple(RSL_MT_EST_REQ, lchan2chan_nr(lchan), + link_id, 0); + msg->trx = lchan->ts->trx; + + return abis_rsl_sendmsg(msg); +} + +/* Chapter 8.3.7 Request the release of multiframe mode of RLL connection. + This is what higher layers should call. The BTS then responds with + RELEASE CONFIRM, which we in turn use to trigger RSL CHANNEL RELEASE, + which in turn is acknowledged by RSL CHANNEL RELEASE ACK, which calls + lchan_free() */ +int rsl_release_request(struct gsm_lchan *lchan, u_int8_t link_id, u_int8_t reason) +{ + + struct msgb *msg; + + msg = rsl_rll_simple(RSL_MT_REL_REQ, lchan2chan_nr(lchan), + link_id, 0); + /* 0 is normal release, 1 is local end */ + msgb_tv_put(msg, RSL_IE_RELEASE_MODE, reason); + + /* FIXME: start some timer in case we don't receive a REL ACK ? */ + + msg->trx = lchan->ts->trx; + + return abis_rsl_sendmsg(msg); +} + +int rsl_lchan_set_state(struct gsm_lchan *lchan, int state) +{ + lchan->state = state; + return 0; +} + +/* Chapter 8.4.2: Channel Activate Acknowledge */ +static int rsl_rx_chan_act_ack(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg); + + /* BTS has confirmed channel activation, we now need + * to assign the activated channel to the MS */ + if (rslh->ie_chan != RSL_IE_CHAN_NR) + return -EINVAL; + + if (msg->lchan->state != LCHAN_S_ACT_REQ) + LOGP(DRSL, LOGL_NOTICE, "%s CHAN ACT ACK, but state %s\n", + gsm_lchan_name(msg->lchan), + gsm_lchans_name(msg->lchan->state)); + rsl_lchan_set_state(msg->lchan, LCHAN_S_ACTIVE); + + if (msg->lchan->rqd_ref) { + rsl_send_imm_assignment(msg->lchan); + talloc_free(msg->lchan->rqd_ref); + msg->lchan->rqd_ref = NULL; + msg->lchan->rqd_ta = 0; + } + + send_lchan_signal(S_LCHAN_ACTIVATE_ACK, msg->lchan, NULL); + + return 0; +} + +/* Chapter 8.4.3: Channel Activate NACK */ +static int rsl_rx_chan_act_nack(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tp; + + LOGP(DRSL, LOGL_ERROR, "%s CHANNEL ACTIVATE NACK", + gsm_lchan_name(msg->lchan)); + + /* BTS has rejected channel activation ?!? */ + if (dh->ie_chan != RSL_IE_CHAN_NR) + return -EINVAL; + + rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) { + const u_int8_t *cause = TLVP_VAL(&tp, RSL_IE_CAUSE); + print_rsl_cause(LOGL_ERROR, cause, + TLVP_LEN(&tp, RSL_IE_CAUSE)); + if (*cause != RSL_ERR_RCH_ALR_ACTV_ALLOC) + rsl_lchan_set_state(msg->lchan, LCHAN_S_NONE); + } else + rsl_lchan_set_state(msg->lchan, LCHAN_S_NONE); + + LOGPC(DRSL, LOGL_ERROR, "\n"); + + send_lchan_signal(S_LCHAN_ACTIVATE_NACK, msg->lchan, NULL); + + lchan_free(msg->lchan); + return 0; +} + +/* Chapter 8.4.4: Connection Failure Indication */ +static int rsl_rx_conn_fail(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tp; + + /* FIXME: print which channel */ + LOGP(DRSL, LOGL_NOTICE, "%s CONNECTION FAIL: RELEASING ", + gsm_lchan_name(msg->lchan)); + + rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + + if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) + print_rsl_cause(LOGL_NOTICE, TLVP_VAL(&tp, RSL_IE_CAUSE), + TLVP_LEN(&tp, RSL_IE_CAUSE)); + + LOGPC(DRSL, LOGL_NOTICE, "\n"); + /* FIXME: only free it after channel release ACK */ + counter_inc(msg->lchan->ts->trx->bts->network->stats.chan.rf_fail); + return rsl_rf_chan_release(msg->lchan, 1); +} + +static void print_meas_rep_uni(struct gsm_meas_rep_unidir *mru, + const char *prefix) +{ + DEBUGPC(DMEAS, "RXL-FULL-%s=%3ddBm RXL-SUB-%s=%3ddBm ", + prefix, rxlev2dbm(mru->full.rx_lev), + prefix, rxlev2dbm(mru->sub.rx_lev)); + DEBUGPC(DMEAS, "RXQ-FULL-%s=%d RXQ-SUB-%s=%d ", + prefix, mru->full.rx_qual, prefix, mru->sub.rx_qual); +} + +static void print_meas_rep(struct gsm_meas_rep *mr) +{ + int i; + + DEBUGP(DMEAS, "MEASUREMENT RESULT NR=%d ", mr->nr); + + if (mr->flags & MEAS_REP_F_DL_DTX) + DEBUGPC(DMEAS, "DTXd "); + + print_meas_rep_uni(&mr->ul, "ul"); + DEBUGPC(DMEAS, "BS_POWER=%d ", mr->bs_power); + if (mr->flags & MEAS_REP_F_MS_TO) + DEBUGPC(DMEAS, "MS_TO=%d ", mr->ms_timing_offset); + + if (mr->flags & MEAS_REP_F_MS_L1) { + DEBUGPC(DMEAS, "L1_MS_PWR=%3ddBm ", mr->ms_l1.pwr); + DEBUGPC(DMEAS, "L1_FPC=%u ", + mr->flags & MEAS_REP_F_FPC ? 1 : 0); + DEBUGPC(DMEAS, "L1_TA=%u ", mr->ms_l1.ta); + } + + if (mr->flags & MEAS_REP_F_UL_DTX) + DEBUGPC(DMEAS, "DTXu "); + if (mr->flags & MEAS_REP_F_BA1) + DEBUGPC(DMEAS, "BA1 "); + if (!(mr->flags & MEAS_REP_F_DL_VALID)) + DEBUGPC(DMEAS, "NOT VALID "); + else + print_meas_rep_uni(&mr->dl, "dl"); + + DEBUGPC(DMEAS, "NUM_NEIGH=%u\n", mr->num_cell); + if (mr->num_cell == 7) + return; + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + DEBUGP(DMEAS, "IDX=%u ARFCN=%u BSIC=%u => %d dBm\n", + mrc->neigh_idx, mrc->arfcn, mrc->bsic, rxlev2dbm(mrc->rxlev)); + } +} + +static int rsl_rx_meas_res(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tp; + struct gsm_meas_rep *mr = lchan_next_meas_rep(msg->lchan); + u_int8_t len; + const u_int8_t *val; + int rc; + + /* check if this channel is actually active */ + /* FIXME: maybe this check should be way more generic/centralized */ + if (msg->lchan->state != LCHAN_S_ACTIVE) { + LOGP(DRSL, LOGL_DEBUG, "%s: MEAS RES for inactive channel\n", + gsm_lchan_name(msg->lchan)); + return 0; + } + + memset(mr, 0, sizeof(*mr)); + mr->lchan = msg->lchan; + + rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + + if (!TLVP_PRESENT(&tp, RSL_IE_MEAS_RES_NR) || + !TLVP_PRESENT(&tp, RSL_IE_UPLINK_MEAS) || + !TLVP_PRESENT(&tp, RSL_IE_BS_POWER)) + return -EIO; + + /* Mandatory Parts */ + mr->nr = *TLVP_VAL(&tp, RSL_IE_MEAS_RES_NR); + + len = TLVP_LEN(&tp, RSL_IE_UPLINK_MEAS); + val = TLVP_VAL(&tp, RSL_IE_UPLINK_MEAS); + if (len >= 3) { + if (val[0] & 0x40) + mr->flags |= MEAS_REP_F_DL_DTX; + mr->ul.full.rx_lev = val[0] & 0x3f; + mr->ul.sub.rx_lev = val[1] & 0x3f; + mr->ul.full.rx_qual = val[2]>>3 & 0x7; + mr->ul.sub.rx_qual = val[2] & 0x7; + } + + mr->bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER); + + /* Optional Parts */ + if (TLVP_PRESENT(&tp, RSL_IE_MS_TIMING_OFFSET)) + mr->ms_timing_offset = + *TLVP_VAL(&tp, RSL_IE_MS_TIMING_OFFSET); + + if (TLVP_PRESENT(&tp, RSL_IE_L1_INFO)) { + val = TLVP_VAL(&tp, RSL_IE_L1_INFO); + mr->flags |= MEAS_REP_F_MS_L1; + mr->ms_l1.pwr = ms_pwr_dbm(msg->trx->bts->band, val[0] >> 3); + if (val[0] & 0x04) + mr->flags |= MEAS_REP_F_FPC; + mr->ms_l1.ta = val[1]; + } + if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) { + msg->l3h = (u_int8_t *) TLVP_VAL(&tp, RSL_IE_L3_INFO); + rc = gsm48_parse_meas_rep(mr, msg); + if (rc < 0) + return rc; + } + + print_meas_rep(mr); + + send_lchan_signal(S_LCHAN_MEAS_REP, msg->lchan, mr); + + return 0; +} + +/* Chapter 8.4.7 */ +static int rsl_rx_hando_det(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tp; + + DEBUGP(DRSL, "%s HANDOVER DETECT ", gsm_lchan_name(msg->lchan)); + + rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + + if (TLVP_PRESENT(&tp, RSL_IE_ACCESS_DELAY)) + DEBUGPC(DRSL, "access delay = %u\n", + *TLVP_VAL(&tp, RSL_IE_ACCESS_DELAY)); + else + DEBUGPC(DRSL, "\n"); + + send_lchan_signal(S_LCHAN_HANDOVER_DETECT, msg->lchan, NULL); + + return 0; +} + +static int abis_rsl_rx_dchan(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg); + int rc = 0; + char *ts_name; + + msg->lchan = lchan_lookup(msg->trx, rslh->chan_nr); + ts_name = gsm_lchan_name(msg->lchan); + + switch (rslh->c.msg_type) { + case RSL_MT_CHAN_ACTIV_ACK: + DEBUGP(DRSL, "%s CHANNEL ACTIVATE ACK\n", ts_name); + rc = rsl_rx_chan_act_ack(msg); + break; + case RSL_MT_CHAN_ACTIV_NACK: + rc = rsl_rx_chan_act_nack(msg); + break; + case RSL_MT_CONN_FAIL: + rc = rsl_rx_conn_fail(msg); + break; + case RSL_MT_MEAS_RES: + rc = rsl_rx_meas_res(msg); + break; + case RSL_MT_HANDO_DET: + rc = rsl_rx_hando_det(msg); + break; + case RSL_MT_RF_CHAN_REL_ACK: + rc = rsl_rx_rf_chan_rel_ack(msg->lchan); + break; + case RSL_MT_MODE_MODIFY_ACK: + DEBUGP(DRSL, "%s CHANNEL MODE MODIFY ACK\n", ts_name); + break; + case RSL_MT_MODE_MODIFY_NACK: + LOGP(DRSL, LOGL_ERROR, "%s CHANNEL MODE MODIFY NACK\n", ts_name); + break; + case RSL_MT_IPAC_PDCH_ACT_ACK: + DEBUGPC(DRSL, "%s IPAC PDCH ACT ACK\n", ts_name); + msg->lchan->ts->flags |= TS_F_PDCH_MODE; + break; + case RSL_MT_IPAC_PDCH_ACT_NACK: + LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH ACT NACK\n", ts_name); + break; + case RSL_MT_IPAC_PDCH_DEACT_ACK: + DEBUGP(DRSL, "%s IPAC PDCH DEACT ACK\n", ts_name); + msg->lchan->ts->flags &= ~TS_F_PDCH_MODE; + break; + case RSL_MT_IPAC_PDCH_DEACT_NACK: + LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH DEACT NACK\n", ts_name); + break; + case RSL_MT_PHY_CONTEXT_CONF: + case RSL_MT_PREPROC_MEAS_RES: + case RSL_MT_TALKER_DET: + case RSL_MT_LISTENER_DET: + case RSL_MT_REMOTE_CODEC_CONF_REP: + case RSL_MT_MR_CODEC_MOD_ACK: + case RSL_MT_MR_CODEC_MOD_NACK: + case RSL_MT_MR_CODEC_MOD_PER: + LOGP(DRSL, LOGL_NOTICE, "%s Unimplemented Abis RSL DChan " + "msg 0x%02x\n", ts_name, rslh->c.msg_type); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "%s unknown Abis RSL DChan msg 0x%02x\n", + ts_name, rslh->c.msg_type); + return -EINVAL; + } + + return rc; +} + +static int rsl_rx_error_rep(struct msgb *msg) +{ + struct abis_rsl_common_hdr *rslh = msgb_l2(msg); + struct tlv_parsed tp; + + LOGP(DRSL, LOGL_ERROR, "%s ERROR REPORT ", gsm_trx_name(msg->trx)); + + rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg)-sizeof(*rslh)); + + if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) + print_rsl_cause(LOGL_ERROR, TLVP_VAL(&tp, RSL_IE_CAUSE), + TLVP_LEN(&tp, RSL_IE_CAUSE)); + + LOGPC(DRSL, LOGL_ERROR, "\n"); + + return 0; +} + +static int abis_rsl_rx_trx(struct msgb *msg) +{ + struct abis_rsl_common_hdr *rslh = msgb_l2(msg); + int rc = 0; + + switch (rslh->msg_type) { + case RSL_MT_ERROR_REPORT: + rc = rsl_rx_error_rep(msg); + break; + case RSL_MT_RF_RES_IND: + /* interference on idle channels of TRX */ + //DEBUGP(DRSL, "%s RF Resource Indication\n", gsm_trx_name(msg->trx)); + break; + case RSL_MT_OVERLOAD: + /* indicate CCCH / ACCH / processor overload */ + LOGP(DRSL, LOGL_ERROR, "%s CCCH/ACCH/CPU Overload\n", + gsm_trx_name(msg->trx)); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "%s Unknown Abis RSL TRX message " + "type 0x%02x\n", gsm_trx_name(msg->trx), rslh->msg_type); + return -EINVAL; + } + return rc; +} + +/* If T3101 expires, we never received a response to IMMEDIATE ASSIGN */ +static void t3101_expired(void *data) +{ + struct gsm_lchan *lchan = data; + + rsl_rf_chan_release(lchan, 1); +} + +/* If T3111 expires, we will send the RF Channel Request */ +static void t3111_expired(void *data) +{ + struct gsm_lchan *lchan = data; + + rsl_rf_chan_release(lchan, 0); +} + +#define GSM48_LEN2PLEN(a) (((a) << 2) | 1) + +/* Format an IMM ASS REJ according to 04.08 Chapter 9.1.20 */ +static int rsl_send_imm_ass_rej(struct gsm_bts *bts, + unsigned int num_req_refs, + struct gsm48_req_ref *rqd_refs, + uint8_t wait_ind) +{ + uint8_t buf[GSM_MACBLOCK_LEN]; + struct gsm48_imm_ass_rej *iar = (struct gsm48_imm_ass_rej *)buf; + + /* create IMMEDIATE ASSIGN REJECT 04.08 message */ + memset(iar, 0, sizeof(*iar)); + iar->proto_discr = GSM48_PDISC_RR; + iar->msg_type = GSM48_MT_RR_IMM_ASS; + iar->page_mode = GSM48_PM_SAME; + + memcpy(&iar->req_ref1, &rqd_refs[0], sizeof(iar->req_ref1)); + iar->wait_ind1 = wait_ind; + + if (num_req_refs >= 2) + memcpy(&iar->req_ref2, &rqd_refs[1], sizeof(iar->req_ref2)); + else + memcpy(&iar->req_ref2, &rqd_refs[0], sizeof(iar->req_ref2)); + iar->wait_ind2 = wait_ind; + + if (num_req_refs >= 3) + memcpy(&iar->req_ref3, &rqd_refs[2], sizeof(iar->req_ref3)); + else + memcpy(&iar->req_ref3, &rqd_refs[0], sizeof(iar->req_ref3)); + iar->wait_ind3 = wait_ind; + + if (num_req_refs >= 4) + memcpy(&iar->req_ref4, &rqd_refs[3], sizeof(iar->req_ref4)); + else + memcpy(&iar->req_ref4, &rqd_refs[0], sizeof(iar->req_ref4)); + iar->wait_ind4 = wait_ind; + + return rsl_imm_assign_cmd(bts, sizeof(iar), (uint8_t *) iar); +} + +/* MS has requested a channel on the RACH */ +static int rsl_rx_chan_rqd(struct msgb *msg) +{ + struct gsm_bts *bts = msg->trx->bts; + struct abis_rsl_dchan_hdr *rqd_hdr = msgb_l2(msg); + struct gsm48_req_ref *rqd_ref; + enum gsm_chan_t lctype; + enum gsm_chreq_reason_t chreq_reason; + struct gsm_lchan *lchan; + u_int8_t rqd_ta; + int is_lu; + + u_int16_t arfcn; + u_int8_t ts_number, subch; + + /* parse request reference to be used in immediate assign */ + if (rqd_hdr->data[0] != RSL_IE_REQ_REFERENCE) + return -EINVAL; + + rqd_ref = (struct gsm48_req_ref *) &rqd_hdr->data[1]; + + /* parse access delay and use as TA */ + if (rqd_hdr->data[sizeof(struct gsm48_req_ref)+1] != RSL_IE_ACCESS_DELAY) + return -EINVAL; + rqd_ta = rqd_hdr->data[sizeof(struct gsm48_req_ref)+2]; + + /* determine channel type (SDCCH/TCH_F/TCH_H) based on + * request reference RA */ + lctype = get_ctype_by_chreq(bts->network, rqd_ref->ra); + chreq_reason = get_reason_by_chreq(rqd_ref->ra, bts->network->neci); + + counter_inc(bts->network->stats.chreq.total); + + /* + * We want LOCATION UPDATES to succeed and will assign a TCH + * if we have no SDCCH available. + */ + is_lu = !!(chreq_reason == GSM_CHREQ_REASON_LOCATION_UPD); + + /* check availability / allocate channel */ + lchan = lchan_alloc(bts, lctype, is_lu); + if (!lchan) { + LOGP(DRSL, LOGL_NOTICE, "BTS %d CHAN RQD: no resources for %s 0x%x\n", + msg->lchan->ts->trx->bts->nr, gsm_lchant_name(lctype), rqd_ref->ra); + counter_inc(bts->network->stats.chreq.no_channel); + /* FIXME gather multiple CHAN RQD and reject up to 4 at the same time */ + if (bts->network->T3122) + rsl_send_imm_ass_rej(bts, 1, rqd_ref, bts->network->T3122 & 0xff); + return -ENOMEM; + } + + if (lchan->state != LCHAN_S_NONE) + LOGP(DRSL, LOGL_NOTICE, "%s lchan_alloc() returned channel " + "in state %s\n", gsm_lchan_name(lchan), + gsm_lchans_name(lchan->state)); + rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ); + + /* save the RACH data as we need it after the CHAN ACT ACK */ + lchan->rqd_ref = talloc_zero(bts, struct gsm48_req_ref); + if (!lchan->rqd_ref) { + LOGP(DRSL, LOGL_ERROR, "Failed to allocate gsm48_req_ref.\n"); + lchan_free(lchan); + return -ENOMEM; + } + + memcpy(lchan->rqd_ref, rqd_ref, sizeof(*rqd_ref)); + lchan->rqd_ta = rqd_ta; + + ts_number = lchan->ts->nr; + arfcn = lchan->ts->trx->arfcn; + subch = lchan->nr; + + lchan->encr.alg_id = RSL_ENC_ALG_A5(0); /* no encryption */ + lchan->ms_power = ms_pwr_ctl_lvl(bts->band, bts->ms_max_power); + lchan->bs_power = 0; /* 0dB reduction, output power = Pn */ + lchan->rsl_cmode = RSL_CMOD_SPD_SIGN; + lchan->tch_mode = GSM48_CMODE_SIGN; + + /* FIXME: Start another timer or assume the BTS sends a ACK/NACK? */ + rsl_chan_activate_lchan(lchan, 0x00, rqd_ta, 0); + + DEBUGP(DRSL, "%s Activating ARFCN(%u) SS(%u) lctype %s " + "r=%s ra=0x%02x\n", gsm_lchan_name(lchan), arfcn, subch, + gsm_lchant_name(lchan->type), gsm_chreq_name(chreq_reason), + rqd_ref->ra); + return 0; +} + +static int rsl_send_imm_assignment(struct gsm_lchan *lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + u_int8_t buf[GSM_MACBLOCK_LEN]; + struct gsm48_imm_ass *ia = (struct gsm48_imm_ass *) buf; + + /* create IMMEDIATE ASSIGN 04.08 messge */ + memset(ia, 0, sizeof(*ia)); + /* we set ia->l2_plen once we know the length of the MA below */ + ia->proto_discr = GSM48_PDISC_RR; + ia->msg_type = GSM48_MT_RR_IMM_ASS; + ia->page_mode = GSM48_PM_SAME; + gsm48_lchan2chan_desc(&ia->chan_desc, lchan); + + /* use request reference extracted from CHAN_RQD */ + memcpy(&ia->req_ref, lchan->rqd_ref, sizeof(ia->req_ref)); + ia->timing_advance = lchan->rqd_ta; + if (!lchan->ts->hopping.enabled) { + ia->mob_alloc_len = 0; + } else { + ia->mob_alloc_len = lchan->ts->hopping.ma_len; + memcpy(ia->mob_alloc, lchan->ts->hopping.ma_data, ia->mob_alloc_len); + } + /* we need to subtract 1 byte from sizeof(*ia) since ia includes the l2_plen field */ + ia->l2_plen = GSM48_LEN2PLEN((sizeof(*ia)-1) + ia->mob_alloc_len); + + /* Start timer T3101 to wait for GSM48_MT_RR_PAG_RESP */ + lchan->T3101.cb = t3101_expired; + lchan->T3101.data = lchan; + bsc_schedule_timer(&lchan->T3101, bts->network->T3101, 0); + + /* send IMMEDIATE ASSIGN CMD on RSL to BTS (to send on CCCH to MS) */ + return rsl_imm_assign_cmd(bts, sizeof(*ia)+ia->mob_alloc_len, (u_int8_t *) ia); +} + +/* MS has requested a channel on the RACH */ +static int rsl_rx_ccch_load(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg); + u_int16_t pg_buf_space; + u_int16_t rach_slot_count = -1; + u_int16_t rach_busy_count = -1; + u_int16_t rach_access_count = -1; + + switch (rslh->data[0]) { + case RSL_IE_PAGING_LOAD: + pg_buf_space = rslh->data[1] << 8 | rslh->data[2]; + if (is_ipaccess_bts(msg->trx->bts) && pg_buf_space == 0xffff) { + /* paging load below configured threshold, use 50 as default */ + pg_buf_space = 50; + } + paging_update_buffer_space(msg->trx->bts, pg_buf_space); + break; + case RSL_IE_RACH_LOAD: + if (msg->data_len >= 7) { + rach_slot_count = rslh->data[2] << 8 | rslh->data[3]; + rach_busy_count = rslh->data[4] << 8 | rslh->data[5]; + rach_access_count = rslh->data[6] << 8 | rslh->data[7]; + } + break; + default: + break; + } + + return 0; +} + +static int abis_rsl_rx_cchan(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg); + int rc = 0; + + msg->lchan = lchan_lookup(msg->trx, rslh->chan_nr); + + switch (rslh->c.msg_type) { + case RSL_MT_CHAN_RQD: + /* MS has requested a channel on the RACH */ + rc = rsl_rx_chan_rqd(msg); + break; + case RSL_MT_CCCH_LOAD_IND: + /* current load on the CCCH */ + rc = rsl_rx_ccch_load(msg); + break; + case RSL_MT_DELETE_IND: + /* CCCH overloaded, IMM_ASSIGN was dropped */ + case RSL_MT_CBCH_LOAD_IND: + /* current load on the CBCH */ + LOGP(DRSL, LOGL_NOTICE, "Unimplemented Abis RSL TRX message " + "type 0x%02x\n", rslh->c.msg_type); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "Unknown Abis RSL TRX message type " + "0x%02x\n", rslh->c.msg_type); + return -EINVAL; + } + + return rc; +} + +static int rsl_rx_rll_err_ind(struct msgb *msg) +{ + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + u_int8_t *rlm_cause = rllh->data; + + LOGP(DRLL, LOGL_ERROR, "%s ERROR INDICATION cause=%s\n", + gsm_lchan_name(msg->lchan), + rsl_rlm_cause_name(rlm_cause[1])); + + rll_indication(msg->lchan, rllh->link_id, BSC_RLLR_IND_ERR_IND); + + if (rlm_cause[1] == RLL_CAUSE_T200_EXPIRED) { + counter_inc(msg->lchan->ts->trx->bts->network->stats.chan.rll_err); + return rsl_rf_chan_release(msg->lchan, 1); + } + + return 0; +} + +static void rsl_handle_release(struct gsm_lchan *lchan) +{ + int sapi; + struct gsm_bts *bts; + + /* maybe we have only brought down one RLL */ + if (lchan->state != LCHAN_S_REL_REQ) + return; + + for (sapi = 0; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) { + if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) + continue; + LOGP(DRSL, LOGL_DEBUG, "%s waiting for SAPI=%d to be released.\n", + gsm_lchan_name(lchan), sapi); + return; + } + + + + /* wait a bit to send the RF Channel Release */ + lchan->T3111.cb = t3111_expired; + lchan->T3111.data = lchan; + bts = lchan->ts->trx->bts; + bsc_schedule_timer(&lchan->T3111, bts->network->T3111, 0); +} + +/* ESTABLISH INDICATION, LOCATION AREA UPDATE REQUEST + 0x02, 0x06, + 0x01, 0x20, + 0x02, 0x00, + 0x0b, 0x00, 0x0f, 0x05, 0x08, ... */ + +static int abis_rsl_rx_rll(struct msgb *msg) +{ + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + int rc = 0; + char *ts_name; + u_int8_t sapi = rllh->link_id & 7; + + msg->lchan = lchan_lookup(msg->trx, rllh->chan_nr); + ts_name = gsm_lchan_name(msg->lchan); + DEBUGP(DRLL, "%s SAPI=%u ", ts_name, sapi); + + switch (rllh->c.msg_type) { + case RSL_MT_DATA_IND: + DEBUGPC(DRLL, "DATA INDICATION\n"); + if (msgb_l2len(msg) > + sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) && + rllh->data[0] == RSL_IE_L3_INFO) { + msg->l3h = &rllh->data[3]; + return gsm0408_rcvmsg(msg, rllh->link_id); + } + break; + case RSL_MT_EST_IND: + DEBUGPC(DRLL, "ESTABLISH INDICATION\n"); + /* lchan is established, stop T3101 */ + msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_MS; + bsc_del_timer(&msg->lchan->T3101); + if (msgb_l2len(msg) > + sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) && + rllh->data[0] == RSL_IE_L3_INFO) { + msg->l3h = &rllh->data[3]; + return gsm0408_rcvmsg(msg, rllh->link_id); + } + break; + case RSL_MT_EST_CONF: + DEBUGPC(DRLL, "ESTABLISH CONFIRM\n"); + msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_NET; + rll_indication(msg->lchan, rllh->link_id, + BSC_RLLR_IND_EST_CONF); + break; + case RSL_MT_REL_IND: + /* BTS informs us of having received DISC from MS */ + DEBUGPC(DRLL, "RELEASE INDICATION\n"); + msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED; + rll_indication(msg->lchan, rllh->link_id, + BSC_RLLR_IND_REL_IND); + rsl_handle_release(msg->lchan); + rsl_lchan_rll_release(msg->lchan, rllh->link_id); + break; + case RSL_MT_REL_CONF: + /* BTS informs us of having received UA from MS, + * in response to DISC that we've sent earlier */ + DEBUGPC(DRLL, "RELEASE CONFIRMATION\n"); + msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED; + rsl_handle_release(msg->lchan); + rsl_lchan_rll_release(msg->lchan, rllh->link_id); + break; + case RSL_MT_ERROR_IND: + rc = rsl_rx_rll_err_ind(msg); + break; + case RSL_MT_UNIT_DATA_IND: + LOGP(DRLL, LOGL_NOTICE, "unimplemented Abis RLL message " + "type 0x%02x\n", rllh->c.msg_type); + break; + default: + LOGP(DRLL, LOGL_NOTICE, "unknown Abis RLL message " + "type 0x%02x\n", rllh->c.msg_type); + } + return rc; +} + +static u_int8_t ipa_smod_s_for_lchan(struct gsm_lchan *lchan) +{ + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return 0x00; + case GSM_LCHAN_TCH_H: + return 0x03; + default: + break; + } + case GSM48_CMODE_SPEECH_EFR: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return 0x01; + /* there's no half-rate EFR */ + default: + break; + } + case GSM48_CMODE_SPEECH_AMR: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return 0x02; + case GSM_LCHAN_TCH_H: + return 0x05; + default: + break; + } + default: + break; + } + LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access speech mode for " + "tch_mode == 0x%02x\n", lchan->tch_mode); + return 0; +} + +static u_int8_t ipa_rtp_pt_for_lchan(struct gsm_lchan *lchan) +{ + struct gsm_network *net = lchan->ts->trx->bts->network; + + /* allow to hardcode the rtp payload */ + if (net->hardcoded_rtp_payload != 0) + return net->hardcoded_rtp_payload; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return RTP_PT_GSM_FULL; + case GSM_LCHAN_TCH_H: + return RTP_PT_GSM_HALF; + default: + break; + } + case GSM48_CMODE_SPEECH_EFR: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return RTP_PT_GSM_EFR; + /* there's no half-rate EFR */ + default: + break; + } + case GSM48_CMODE_SPEECH_AMR: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return RTP_PT_AMR_FULL; + case GSM_LCHAN_TCH_H: + return RTP_PT_AMR_HALF; + default: + break; + } + default: + break; + } + LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access rtp payload type for " + "tch_mode == 0x%02x\n & lchan_type == %d", + lchan->tch_mode, lchan->type); + return 0; +} + +/* ip.access specific RSL extensions */ +static void ipac_parse_rtp(struct gsm_lchan *lchan, struct tlv_parsed *tv) +{ + struct in_addr ip; + u_int16_t port, conn_id; + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_IP)) { + ip.s_addr = *((u_int32_t *) TLVP_VAL(tv, RSL_IE_IPAC_LOCAL_IP)); + DEBUGPC(DRSL, "LOCAL_IP=%s ", inet_ntoa(ip)); + lchan->abis_ip.bound_ip = ntohl(ip.s_addr); + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_PORT)) { + port = *((u_int16_t *) TLVP_VAL(tv, RSL_IE_IPAC_LOCAL_PORT)); + port = ntohs(port); + DEBUGPC(DRSL, "LOCAL_PORT=%u ", port); + lchan->abis_ip.bound_port = port; + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_CONN_ID)) { + conn_id = *((u_int16_t *) TLVP_VAL(tv, RSL_IE_IPAC_CONN_ID)); + conn_id = ntohs(conn_id); + DEBUGPC(DRSL, "CON_ID=%u ", conn_id); + lchan->abis_ip.conn_id = conn_id; + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_RTP_PAYLOAD2)) { + lchan->abis_ip.rtp_payload2 = + *TLVP_VAL(tv, RSL_IE_IPAC_RTP_PAYLOAD2); + DEBUGPC(DRSL, "RTP_PAYLOAD2=0x%02x ", + lchan->abis_ip.rtp_payload2); + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_SPEECH_MODE)) { + lchan->abis_ip.speech_mode = + *TLVP_VAL(tv, RSL_IE_IPAC_SPEECH_MODE); + DEBUGPC(DRSL, "speech_mode=0x%02x ", + lchan->abis_ip.speech_mode); + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_IP)) { + ip.s_addr = *((u_int32_t *) TLVP_VAL(tv, RSL_IE_IPAC_REMOTE_IP)); + DEBUGPC(DRSL, "REMOTE_IP=%s ", inet_ntoa(ip)); + lchan->abis_ip.connect_ip = ntohl(ip.s_addr); + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_PORT)) { + port = *((u_int16_t *) TLVP_VAL(tv, RSL_IE_IPAC_REMOTE_PORT)); + port = ntohs(port); + DEBUGPC(DRSL, "REMOTE_PORT=%u ", port); + lchan->abis_ip.connect_port = port; + } +} + +int rsl_ipacc_crcx(struct gsm_lchan *lchan) +{ + struct msgb *msg = rsl_msgb_alloc(); + struct abis_rsl_dchan_hdr *dh; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_IPAC_CRCX); + dh->c.msg_discr = ABIS_RSL_MDISC_IPACCESS; + dh->chan_nr = lchan2chan_nr(lchan); + + /* 0x1- == receive-only, 0x-1 == EFR codec */ + lchan->abis_ip.speech_mode = 0x10 | ipa_smod_s_for_lchan(lchan); + lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan); + msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode); + msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload); + + DEBUGP(DRSL, "%s IPAC_BIND speech_mode=0x%02x RTP_PAYLOAD=%d\n", + gsm_lchan_name(lchan), lchan->abis_ip.speech_mode, + lchan->abis_ip.rtp_payload); + + msg->trx = lchan->ts->trx; + + return abis_rsl_sendmsg(msg); +} + +int rsl_ipacc_mdcx(struct gsm_lchan *lchan, u_int32_t ip, u_int16_t port, + u_int8_t rtp_payload2) +{ + struct msgb *msg = rsl_msgb_alloc(); + struct abis_rsl_dchan_hdr *dh; + u_int32_t *att_ip; + struct in_addr ia; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_IPAC_MDCX); + dh->c.msg_discr = ABIS_RSL_MDISC_IPACCESS; + dh->chan_nr = lchan2chan_nr(lchan); + + /* we need to store these now as MDCX_ACK does not return them :( */ + lchan->abis_ip.rtp_payload2 = rtp_payload2; + lchan->abis_ip.connect_port = port; + lchan->abis_ip.connect_ip = ip; + + /* 0x0- == both directions, 0x-1 == EFR codec */ + lchan->abis_ip.speech_mode = 0x00 | ipa_smod_s_for_lchan(lchan); + lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan); + + ia.s_addr = htonl(ip); + DEBUGP(DRSL, "%s IPAC_MDCX IP=%s PORT=%d RTP_PAYLOAD=%d RTP_PAYLOAD2=%d " + "CONN_ID=%d speech_mode=0x%02x\n", gsm_lchan_name(lchan), + inet_ntoa(ia), port, lchan->abis_ip.rtp_payload, rtp_payload2, + lchan->abis_ip.conn_id, lchan->abis_ip.speech_mode); + + msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id); + msgb_v_put(msg, RSL_IE_IPAC_REMOTE_IP); + att_ip = (u_int32_t *) msgb_put(msg, sizeof(ip)); + *att_ip = ia.s_addr; + msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT, port); + msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode); + msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload); + if (rtp_payload2) + msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2, rtp_payload2); + + msg->trx = lchan->ts->trx; + + return abis_rsl_sendmsg(msg); +} + +/* tell BTS to connect RTP stream to our local RTP socket */ +int rsl_ipacc_mdcx_to_rtpsock(struct gsm_lchan *lchan) +{ + struct rtp_socket *rs = lchan->abis_ip.rtp_socket; + int rc; + + rc = rsl_ipacc_mdcx(lchan, ntohl(rs->rtp.sin_local.sin_addr.s_addr), + ntohs(rs->rtp.sin_local.sin_port), + /* FIXME: use RTP payload of bound socket, not BTS*/ + lchan->abis_ip.rtp_payload2); + + return rc; +} + +int rsl_ipacc_pdch_activate(struct gsm_bts_trx_ts *ts, int act) +{ + struct msgb *msg = rsl_msgb_alloc(); + struct abis_rsl_dchan_hdr *dh; + u_int8_t msg_type; + + if (act) + msg_type = RSL_MT_IPAC_PDCH_ACT; + else + msg_type = RSL_MT_IPAC_PDCH_DEACT; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, msg_type); + dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN; + dh->chan_nr = ts2chan_nr(ts, 0); + + DEBUGP(DRSL, "%s IPAC_PDCH_%sACT\n", gsm_ts_name(ts), + act ? "" : "DE"); + + msg->trx = ts->trx; + + return abis_rsl_sendmsg(msg); +} + +static int abis_rsl_rx_ipacc_crcx_ack(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tv; + struct gsm_lchan *lchan = msg->lchan; + + /* the BTS has acknowledged a local bind, it now tells us the IP + * address and port number to which it has bound the given logical + * channel */ + + rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh)); + if (!TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_PORT) || + !TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_IP) || + !TLVP_PRESENT(&tv, RSL_IE_IPAC_CONN_ID)) { + LOGP(DRSL, LOGL_NOTICE, "mandatory IE missing"); + return -EINVAL; + } + + ipac_parse_rtp(lchan, &tv); + + dispatch_signal(SS_ABISIP, S_ABISIP_CRCX_ACK, msg->lchan); + + return 0; +} + +static int abis_rsl_rx_ipacc_mdcx_ack(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tv; + struct gsm_lchan *lchan = msg->lchan; + + /* the BTS has acknowledged a remote connect request and + * it now tells us the IP address and port number to which it has + * connected the given logical channel */ + + rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh)); + ipac_parse_rtp(lchan, &tv); + dispatch_signal(SS_ABISIP, S_ABISIP_MDCX_ACK, msg->lchan); + + return 0; +} + +static int abis_rsl_rx_ipacc_dlcx_ind(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tv; + + rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh)); + + if (TLVP_PRESENT(&tv, RSL_IE_CAUSE)) + print_rsl_cause(LOGL_DEBUG, TLVP_VAL(&tv, RSL_IE_CAUSE), + TLVP_LEN(&tv, RSL_IE_CAUSE)); + + dispatch_signal(SS_ABISIP, S_ABISIP_DLCX_IND, msg->lchan); + + return 0; +} + +static int abis_rsl_rx_ipacc(struct msgb *msg) +{ + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + char *ts_name; + int rc = 0; + + msg->lchan = lchan_lookup(msg->trx, rllh->chan_nr); + ts_name = gsm_lchan_name(msg->lchan); + + switch (rllh->c.msg_type) { + case RSL_MT_IPAC_CRCX_ACK: + DEBUGP(DRSL, "%s IPAC_CRCX_ACK ", ts_name); + rc = abis_rsl_rx_ipacc_crcx_ack(msg); + break; + case RSL_MT_IPAC_CRCX_NACK: + /* somehow the BTS was unable to bind the lchan to its local + * port?!? */ + LOGP(DRSL, LOGL_ERROR, "%s IPAC_CRCX_NACK\n", ts_name); + break; + case RSL_MT_IPAC_MDCX_ACK: + /* the BTS tells us that a connect operation was successful */ + DEBUGP(DRSL, "%s IPAC_MDCX_ACK ", ts_name); + rc = abis_rsl_rx_ipacc_mdcx_ack(msg); + break; + case RSL_MT_IPAC_MDCX_NACK: + /* somehow the BTS was unable to connect the lchan to a remote + * port */ + LOGP(DRSL, LOGL_ERROR, "%s IPAC_MDCX_NACK\n", ts_name); + break; + case RSL_MT_IPAC_DLCX_IND: + DEBUGP(DRSL, "%s IPAC_DLCX_IND ", ts_name); + rc = abis_rsl_rx_ipacc_dlcx_ind(msg); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "Unknown ip.access msg_type 0x%02x\n", + rllh->c.msg_type); + break; + } + DEBUGPC(DRSL, "\n"); + + return rc; +} + + +/* Entry-point where L2 RSL from BTS enters */ +int abis_rsl_rcvmsg(struct msgb *msg) +{ + struct abis_rsl_common_hdr *rslh; + int rc = 0; + + if (!msg) { + DEBUGP(DRSL, "Empty RSL msg?..\n"); + return -1; + } + + if (msgb_l2len(msg) < sizeof(*rslh)) { + DEBUGP(DRSL, "Truncated RSL message with l2len: %u\n", msgb_l2len(msg)); + return -1; + } + + rslh = msgb_l2(msg); + + switch (rslh->msg_discr & 0xfe) { + case ABIS_RSL_MDISC_RLL: + rc = abis_rsl_rx_rll(msg); + break; + case ABIS_RSL_MDISC_DED_CHAN: + rc = abis_rsl_rx_dchan(msg); + break; + case ABIS_RSL_MDISC_COM_CHAN: + rc = abis_rsl_rx_cchan(msg); + break; + case ABIS_RSL_MDISC_TRX: + rc = abis_rsl_rx_trx(msg); + break; + case ABIS_RSL_MDISC_LOC: + LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL msg disc 0x%02x\n", + rslh->msg_discr); + break; + case ABIS_RSL_MDISC_IPACCESS: + rc = abis_rsl_rx_ipacc(msg); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "unknown RSL message discriminator " + "0x%02x\n", rslh->msg_discr); + return -EINVAL; + } + msgb_free(msg); + return rc; +} + +/* From Table 10.5.33 of GSM 04.08 */ +int rsl_number_of_paging_subchannels(struct gsm_bts *bts) +{ + if (bts->si_common.chan_desc.ccch_conf == RSL_BCCH_CCCH_CONF_1_C) { + return MAX(1, (3 - bts->si_common.chan_desc.bs_ag_blks_res)) + * (bts->si_common.chan_desc.bs_pa_mfrms + 2); + } else { + return (9 - bts->si_common.chan_desc.bs_ag_blks_res) + * (bts->si_common.chan_desc.bs_pa_mfrms + 2); + } +} + +int rsl_sms_cb_command(struct gsm_bts *bts, uint8_t chan_number, + uint8_t cb_command, const uint8_t *data, int len) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *cb_cmd; + + cb_cmd = rsl_msgb_alloc(); + if (!cb_cmd) + return -1; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(cb_cmd, sizeof*dh); + init_dchan_hdr(dh, RSL_MT_SMS_BC_CMD); + dh->chan_nr = RSL_CHAN_SDCCH4_ACCH; /* TODO: check the chan config */ + + msgb_tv_put(cb_cmd, RSL_IE_CB_CMD_TYPE, cb_command); + msgb_tlv_put(cb_cmd, RSL_IE_SMSCB_MSG, len, data); + + cb_cmd->trx = bts->c0; + + return abis_rsl_sendmsg(cb_cmd); +} diff --git a/src/libbsc/bsc_api.c b/src/libbsc/bsc_api.c new file mode 100644 index 000000000..0f09aecd3 --- /dev/null +++ b/src/libbsc/bsc_api.c @@ -0,0 +1,675 @@ +/* GSM 08.08 like API for OpenBSC. The bridge from MSC to BSC */ + +/* (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * (C) 2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define GSM0808_T10_VALUE 6, 0 + +static LLIST_HEAD(sub_connections); + +static void rll_ind_cb(struct gsm_lchan *, uint8_t, void *, enum bsc_rllr_ind); +static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id); +static void handle_release(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan); +static void handle_chan_ack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan); +static void handle_chan_nack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan); + +/* GSM 08.08 3.2.2.33 */ +static u_int8_t lchan_to_chosen_channel(struct gsm_lchan *lchan) +{ + u_int8_t channel_mode = 0, channel = 0; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + channel_mode = 0x9; + break; + case GSM48_CMODE_SIGN: + channel_mode = 0x8; + break; + case GSM48_CMODE_DATA_14k5: + channel_mode = 0xe; + break; + case GSM48_CMODE_DATA_12k0: + channel_mode = 0xb; + break; + case GSM48_CMODE_DATA_6k0: + channel_mode = 0xc; + break; + case GSM48_CMODE_DATA_3k6: + channel_mode = 0xd; + break; + } + + switch (lchan->type) { + case GSM_LCHAN_NONE: + channel = 0x0; + break; + case GSM_LCHAN_SDCCH: + channel = 0x1; + break; + case GSM_LCHAN_TCH_F: + channel = 0x8; + break; + case GSM_LCHAN_TCH_H: + channel = 0x9; + break; + case GSM_LCHAN_UNKNOWN: + LOGP(DMSC, LOGL_ERROR, "Unknown lchan type: %p\n", lchan); + break; + } + + return channel_mode << 4 | channel; +} + +static u_int8_t chan_mode_to_speech(struct gsm_lchan *lchan) +{ + int mode = 0; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + mode = 1; + break; + case GSM48_CMODE_SPEECH_EFR: + mode = 0x11; + break; + case GSM48_CMODE_SPEECH_AMR: + mode = 0x21; + break; + case GSM48_CMODE_SIGN: + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + default: + LOGP(DMSC, LOGL_ERROR, "Using non speech mode: %d\n", mode); + return 0; + break; + } + + /* assume to always do AMR HR on any TCH type */ + if (lchan->type == GSM_LCHAN_TCH_H || + lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) + mode |= 0x4; + + return mode; +} + +static void assignment_t10_timeout(void *_conn) +{ + struct bsc_api *api; + struct gsm_subscriber_connection *conn = + (struct gsm_subscriber_connection *) _conn; + + LOGP(DMSC, LOGL_ERROR, "Assigment T10 timeout on %p\n", conn); + + /* normal release on the secondary channel */ + lchan_release(conn->secondary_lchan, 0, 1); + conn->secondary_lchan = NULL; + + /* inform them about the failure */ + api = conn->bts->network->bsc_api; + api->assign_fail(conn, GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL); +} + +/* + * Start a new assignment and make sure that it is completed within T10 either + * positively, negatively or by the timeout. + * + * 1.) allocate a new lchan + * 2.) copy the encryption key and other data from the + * old to the new channel. + * 3.) RSL Channel Activate this channel and wait + * + * -> Signal handler for the LCHAN + * 4.) Send GSM 04.08 assignment command to the MS + * + * -> Assignment Complete/Assignment Failure + * 5.) Release the SDCCH, continue signalling on the new link + */ +static int handle_new_assignment(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate) +{ + struct gsm_lchan *new_lchan; + int chan_type; + + chan_type = full_rate ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H; + + new_lchan = lchan_alloc(conn->bts, chan_type, 0); + + if (!new_lchan) { + LOGP(DMSC, LOGL_NOTICE, "No free channel.\n"); + return -1; + } + + /* copy old data to the new channel */ + memcpy(&new_lchan->encr, &conn->lchan->encr, sizeof(new_lchan->encr)); + new_lchan->ms_power = conn->lchan->ms_power; + new_lchan->bs_power = conn->lchan->bs_power; + + /* copy new data to it */ + new_lchan->tch_mode = chan_mode; + new_lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH; + + /* handle AMR correctly */ + if (chan_mode == GSM48_CMODE_SPEECH_AMR) { + new_lchan->mr_conf.ver = 1; + new_lchan->mr_conf.icmi = 1; + new_lchan->mr_conf.m5_90 = 1; + } + + if (rsl_chan_activate_lchan(new_lchan, 0x1, 0, 0) < 0) { + LOGP(DHO, LOGL_ERROR, "could not activate channel\n"); + lchan_free(new_lchan); + return -1; + } + + /* remember that we have the channel */ + conn->secondary_lchan = new_lchan; + new_lchan->conn = conn; + + rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ); + return 0; +} + +struct gsm_subscriber_connection *subscr_con_allocate(struct gsm_lchan *lchan) +{ + struct gsm_subscriber_connection *conn; + + conn = talloc_zero(lchan->ts->trx->bts->network, struct gsm_subscriber_connection); + if (!conn) + return NULL; + + /* Configure the time and start it so it will be closed */ + conn->lchan = lchan; + conn->bts = lchan->ts->trx->bts; + lchan->conn = conn; + llist_add_tail(&conn->entry, &sub_connections); + return conn; +} + +/* TODO: move subscriber put here... */ +void subscr_con_free(struct gsm_subscriber_connection *conn) +{ + if (!conn) + return; + + + if (conn->subscr) { + subscr_put(conn->subscr); + conn->subscr = NULL; + } + + + if (conn->ho_lchan) { + LOGP(DNM, LOGL_ERROR, "The ho_lchan should have been cleared.\n"); + conn->ho_lchan->conn = NULL; + } + + if (conn->lchan) { + LOGP(DNM, LOGL_ERROR, "The lchan should have been cleared.\n"); + conn->lchan->conn = NULL; + } + + if (conn->secondary_lchan) { + LOGP(DNM, LOGL_ERROR, "The secondary_lchan should have been cleared.\n"); + conn->secondary_lchan->conn = NULL; + } + + llist_del(&conn->entry); + talloc_free(conn); +} + +int bsc_api_init(struct gsm_network *network, struct bsc_api *api) +{ + network->bsc_api = api; + return 0; +} + +int gsm0808_submit_dtap(struct gsm_subscriber_connection *conn, + struct msgb *msg, int link_id, int allow_sach) +{ + uint8_t sapi; + + + if (!conn->lchan) { + LOGP(DMSC, LOGL_ERROR, + "Called submit dtap without an lchan.\n"); + msgb_free(msg); + return -1; + } + + sapi = link_id & 0x7; + msg->lchan = conn->lchan; + msg->trx = msg->lchan->ts->trx; + + /* If we are on a TCH and need to submit a SMS (on SAPI=3) we need to use the SACH */ + if (allow_sach && sapi != 0) { + if (conn->lchan->type == GSM_LCHAN_TCH_F || conn->lchan->type == GSM_LCHAN_TCH_H) + link_id |= 0x40; + } + + msg->l3h = msg->data; + if (conn->lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) { + OBSC_LINKID_CB(msg) = link_id; + if (rll_establish(msg->lchan, sapi, rll_ind_cb, msg) != 0) { + msgb_free(msg); + send_sapi_reject(conn, link_id); + return -1; + } + return 0; + } else { + return rsl_data_request(msg, link_id); + } +} + +/** + * Send a GSM08.08 Assignment Request. Right now this does not contain the + * audio codec type or the allowed rates for the config. It is assumed that + * this is for audio handling and that when we have a TCH it is capable of + * handling the audio codec. On top of that it is assumed that we are using + * AMR 5.9 when assigning a TCH/H. + */ +int gsm0808_assign_req(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate) +{ + struct bsc_api *api; + api = conn->bts->network->bsc_api; + + if (conn->lchan->type == GSM_LCHAN_SDCCH) { + if (handle_new_assignment(conn, chan_mode, full_rate) != 0) + goto error; + } else { + LOGP(DMSC, LOGL_NOTICE, + "Sending ChanModify for speech %d %d\n", chan_mode, full_rate); + if (chan_mode == GSM48_CMODE_SPEECH_AMR) { + conn->lchan->mr_conf.ver = 1; + conn->lchan->mr_conf.icmi = 1; + conn->lchan->mr_conf.m5_90 = 1; + } + + gsm48_lchan_modify(conn->lchan, chan_mode); + } + + /* we will now start the timer to complete the assignment */ + conn->T10.cb = assignment_t10_timeout; + conn->T10.data = conn; + bsc_schedule_timer(&conn->T10, GSM0808_T10_VALUE); + return 0; + +error: + api->assign_fail(conn, 0, NULL); + return -1; +} + +int gsm0808_page(struct gsm_bts *bts, unsigned int page_group, unsigned int mi_len, + uint8_t *mi, int chan_type) +{ + return rsl_paging_cmd(bts, page_group, mi_len, mi, chan_type); +} + +static void handle_ass_compl(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm48_hdr *gh; + struct bsc_api *api = conn->bts->network->bsc_api; + + if (conn->secondary_lchan != msg->lchan) { + LOGP(DMSC, LOGL_ERROR, "Assignment Compl should occur on second lchan.\n"); + return; + } + + gh = msgb_l3(msg); + if (msgb_l3len(msg) - sizeof(*gh) != 1) { + LOGP(DMSC, LOGL_ERROR, "Assignment Compl invalid: %lu\n", + msgb_l3len(msg) - sizeof(*gh)); + return; + } + + /* swap channels */ + bsc_del_timer(&conn->T10); + + lchan_release(conn->lchan, 0, 1); + conn->lchan = conn->secondary_lchan; + conn->secondary_lchan = NULL; + + if (is_ipaccess_bts(conn->bts) && conn->lchan->tch_mode != GSM48_CMODE_SIGN) + rsl_ipacc_crcx(conn->lchan); + + api->assign_compl(conn, gh->data[0], + lchan_to_chosen_channel(conn->lchan), + conn->lchan->encr.alg_id, + chan_mode_to_speech(conn->lchan)); +} + +static void handle_ass_fail(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct bsc_api *api = conn->bts->network->bsc_api; + uint8_t *rr_failure; + struct gsm48_hdr *gh; + + + if (conn->lchan != msg->lchan) { + LOGP(DMSC, LOGL_ERROR, "Assignment failure should occur on primary lchan.\n"); + return; + } + + /* stop the timer and release it */ + bsc_del_timer(&conn->T10); + lchan_release(conn->secondary_lchan, 0, 1); + conn->secondary_lchan = NULL; + + gh = msgb_l3(msg); + if (msgb_l3len(msg) - sizeof(*gh) != 1) { + LOGP(DMSC, LOGL_ERROR, "assignemnt failure unhandled: %lu\n", + msgb_l3len(msg) - sizeof(*gh)); + rr_failure = NULL; + } else { + rr_failure = &gh->data[0]; + } + + api->assign_fail(conn, + GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE, + rr_failure); +} + +static void dispatch_dtap(struct gsm_subscriber_connection *conn, + uint8_t link_id, struct msgb *msg) +{ + struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api; + struct gsm48_hdr *gh; + uint8_t pdisc; + int rc; + + if (msgb_l3len(msg) < sizeof(*gh)) { + LOGP(DMSC, LOGL_ERROR, "Message too short for a GSM48 header.\n"); + return; + } + + gh = msgb_l3(msg); + pdisc = gh->proto_discr & 0x0f; + switch (pdisc) { + case GSM48_PDISC_RR: + switch (gh->msg_type) { + case GSM48_MT_RR_CIPH_M_COMPL: + if (api->cipher_mode_compl) + return api->cipher_mode_compl(conn, msg, + conn->lchan->encr.alg_id); + break; + case GSM48_MT_RR_ASS_COMPL: + handle_ass_compl(conn, msg); + break; + case GSM48_MT_RR_ASS_FAIL: + handle_ass_fail(conn, msg); + break; + case GSM48_MT_RR_CHAN_MODE_MODIF_ACK: + bsc_del_timer(&conn->T10); + rc = gsm48_rx_rr_modif_ack(msg); + if (rc < 0 && api->assign_fail) { + api->assign_fail(conn, + GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, + NULL); + } else if (rc >= 0 && api->assign_compl) + api->assign_compl(conn, 0, + lchan_to_chosen_channel(conn->lchan), + conn->lchan->encr.alg_id, + chan_mode_to_speech(conn->lchan)); + return; + break; + } + break; + case GSM48_PDISC_MM: + break; + } + + /* default case */ + if (api->dtap) + api->dtap(conn, link_id, msg); +} + +int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id) +{ + int rc; + struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api; + struct gsm_lchan *lchan; + + lchan = msg->lchan; + if (lchan->state != LCHAN_S_ACTIVE) { + LOGP(DRSL, LOGL_INFO, "Got data in non active state(%s), " + "discarding.\n", gsm_lchans_name(lchan->state)); + return -1; + } + + + if (lchan->conn) { + dispatch_dtap(lchan->conn, link_id, msg); + } else { + rc = BSC_API_CONN_POL_REJECT; + lchan->conn = subscr_con_allocate(msg->lchan); + if (!lchan->conn) { + lchan_release(lchan, 0, 0); + return -1; + } + + rc = api->compl_l3(lchan->conn, msg, 0); + + if (rc != BSC_API_CONN_POL_ACCEPT) { + lchan->conn->lchan = NULL; + subscr_con_free(lchan->conn); + lchan_release(lchan, 0, 0); + } + } + + return 0; +} + +int gsm0808_cipher_mode(struct gsm_subscriber_connection *conn, int cipher, + const uint8_t *key, int len, int include_imeisv) +{ + if (cipher > 0 && key == NULL) { + LOGP(DRSL, LOGL_ERROR, "Need to have an encrytpion key.\n"); + return -1; + } + + if (len > MAX_A5_KEY_LEN) { + LOGP(DRSL, LOGL_ERROR, "The key is too long: %d\n", len); + return -1; + } + + conn->lchan->encr.alg_id = RSL_ENC_ALG_A5(cipher); + if (key) { + conn->lchan->encr.key_len = len; + memcpy(conn->lchan->encr.key, key, len); + } + + return gsm48_send_rr_ciph_mode(conn->lchan, include_imeisv); +} + +/* + * Release all occupied RF Channels but stay around for more. + */ +int gsm0808_clear(struct gsm_subscriber_connection *conn) +{ + if (conn->ho_lchan) + bsc_clear_handover(conn, 1); + + if (conn->secondary_lchan) + lchan_release(conn->secondary_lchan, 0, 1); + + if (conn->lchan) + lchan_release(conn->lchan, 1, 0); + + conn->lchan = NULL; + conn->secondary_lchan = NULL; + conn->ho_lchan = NULL; + conn->bts = NULL; + + bsc_del_timer(&conn->T10); + + return 0; +} + +static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id) +{ + struct bsc_api *api; + + if (!conn) + return; + + api = conn->bts->network->bsc_api; + if (!api || !api->sapi_n_reject) + return; + + api->sapi_n_reject(conn, link_id); +} + +static void rll_ind_cb(struct gsm_lchan *lchan, uint8_t link_id, void *_data, enum bsc_rllr_ind rllr_ind) +{ + struct msgb *msg = _data; + + /* + * There seems to be a small window that the RLL timer can + * fire after a lchan_release call and before the S_CHALLOC_FREED + * is called. Check if a conn is set before proceeding. + */ + if (!lchan->conn) + return; + + switch (rllr_ind) { + case BSC_RLLR_IND_EST_CONF: + rsl_data_request(msg, OBSC_LINKID_CB(msg)); + break; + case BSC_RLLR_IND_REL_IND: + case BSC_RLLR_IND_ERR_IND: + case BSC_RLLR_IND_TIMEOUT: + send_sapi_reject(lchan->conn, OBSC_LINKID_CB(msg)); + msgb_free(msg); + break; + } +} + +static int bsc_handle_lchan_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct bsc_api *bsc; + struct gsm_lchan *lchan; + struct lchan_signal_data *lchan_data; + + if (subsys != SS_LCHAN) + return 0; + + + lchan_data = signal_data; + if (!lchan_data->lchan || !lchan_data->lchan->conn) + return 0; + + lchan = lchan_data->lchan; + bsc = lchan->ts->trx->bts->network->bsc_api; + if (!bsc) + return 0; + + switch (signal) { + case S_LCHAN_UNEXPECTED_RELEASE: + handle_release(lchan->conn, bsc, lchan); + break; + case S_LCHAN_ACTIVATE_ACK: + handle_chan_ack(lchan->conn, bsc, lchan); + break; + case S_LCHAN_ACTIVATE_NACK: + handle_chan_nack(lchan->conn, bsc, lchan); + break; + } + + return 0; +} + +static void handle_release(struct gsm_subscriber_connection *conn, + struct bsc_api *bsc, struct gsm_lchan *lchan) +{ + int destruct = 1; + + if (conn->secondary_lchan == lchan) { + bsc_del_timer(&conn->T10); + conn->secondary_lchan = NULL; + + bsc->assign_fail(conn, + GSM0808_CAUSE_RADIO_INTERFACE_FAILURE, + NULL); + } + + /* clear the connection now */ + if (bsc->clear_request) + destruct = bsc->clear_request(conn, 0); + + /* now give up all channels */ + if (conn->lchan == lchan) + conn->lchan = NULL; + if (conn->ho_lchan == lchan) { + bsc_clear_handover(conn, 0); + conn->ho_lchan = NULL; + } + lchan->conn = NULL; + + gsm0808_clear(conn); + + if (destruct) + subscr_con_free(conn); +} + +static void handle_chan_ack(struct gsm_subscriber_connection *conn, + struct bsc_api *api, struct gsm_lchan *lchan) +{ + if (conn->secondary_lchan != lchan) + return; + + LOGP(DMSC, LOGL_NOTICE, "Sending assignment on chan: %p\n", lchan); + gsm48_send_rr_ass_cmd(conn->lchan, lchan, 0x3); +} + +static void handle_chan_nack(struct gsm_subscriber_connection *conn, + struct bsc_api *api, struct gsm_lchan *lchan) +{ + if (conn->secondary_lchan != lchan) + return; + + LOGP(DMSC, LOGL_ERROR, "Channel activation failed. Waiting for timeout now\n"); + conn->secondary_lchan->conn = NULL; + conn->secondary_lchan = NULL; +} + +static __attribute__((constructor)) void on_dso_load_bsc(void) +{ + register_signal_handler(SS_LCHAN, bsc_handle_lchan_signal, NULL); +} diff --git a/src/libbsc/bsc_init.c b/src/libbsc/bsc_init.c new file mode 100644 index 000000000..0072bb6f8 --- /dev/null +++ b/src/libbsc/bsc_init.c @@ -0,0 +1,466 @@ +/* A hackish minimal BSC (+MSC +HLR) implementation */ + +/* (C) 2008-2010 by Harald Welte + * (C) 2009 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* global pointer to the gsm network data structure */ +extern struct gsm_network *bsc_gsmnet; + +static void patch_nm_tables(struct gsm_bts *bts); + +/* Callback function for NACK on the OML NM */ +static int oml_msg_nack(struct nm_nack_signal_data *nack) +{ + int i; + + if (nack->mt == NM_MT_SET_BTS_ATTR_NACK) { + + LOGP(DNM, LOGL_FATAL, "Failed to set BTS attributes. That is fatal. " + "Was the bts type and frequency properly specified?\n"); + exit(-1); + } else { + LOGP(DNM, LOGL_ERROR, "Got a NACK going to drop the OML links.\n"); + for (i = 0; i < bsc_gsmnet->num_bts; ++i) { + struct gsm_bts *bts = gsm_bts_num(bsc_gsmnet, i); + if (is_ipaccess_bts(bts)) + ipaccess_drop_oml(bts); + } + } + + return 0; +} + +/* Callback function to be called every time we receive a signal from NM */ +static int nm_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct nm_nack_signal_data *nack; + + switch (signal) { + case S_NM_NACK: + nack = signal_data; + return oml_msg_nack(nack); + default: + break; + } + return 0; +} + +int bsc_shutdown_net(struct gsm_network *net) +{ + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) { + LOGP(DNM, LOGL_NOTICE, "shutting down OML for BTS %u\n", bts->nr); + dispatch_signal(SS_GLOBAL, S_GLOBAL_BTS_CLOSE_OM, bts); + } + + return 0; +} + +static int generate_and_rsl_si(struct gsm_bts_trx *trx, enum osmo_sysinfo_type i) +{ + struct gsm_bts *bts = trx->bts; + int si_len, rc, j; + + /* Only generate SI if this SI is not in "static" (user-defined) mode */ + if (!(bts->si_mode_static & (1 << i))) { + rc = gsm_generate_si(bts, i); + if (rc < 0) + return rc; + si_len = rc; + } + + DEBUGP(DRR, "SI%s: %s\n", gsm_sitype_name(i), + hexdump(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN)); + + switch (i) { + case SYSINFO_TYPE_5: + case SYSINFO_TYPE_5bis: + case SYSINFO_TYPE_5ter: + case SYSINFO_TYPE_6: + if (trx->bts->type == GSM_BTS_TYPE_HSL_FEMTO) { + /* HSL has mistaken SACCH INFO MODIFY for SACCH FILLING, + * so we need a special workaround here */ + /* This assumes a combined BCCH and TCH on TS1...7 */ + for (j = 0; j < 4; j++) + rsl_sacch_info_modify(&trx->ts[0].lchan[j], + gsm_sitype2rsl(i), + GSM_BTS_SI(bts, i), si_len); + for (j = 1; j < 8; j++) { + rsl_sacch_info_modify(&trx->ts[j].lchan[0], + gsm_sitype2rsl(i), + GSM_BTS_SI(bts, i), si_len); + rsl_sacch_info_modify(&trx->ts[j].lchan[1], + gsm_sitype2rsl(i), + GSM_BTS_SI(bts, i), si_len); + } + } else + rc = rsl_sacch_filling(trx, gsm_sitype2rsl(i), + GSM_BTS_SI(bts, i), rc); + break; + default: + rc = rsl_bcch_info(trx, gsm_sitype2rsl(i), + GSM_BTS_SI(bts, i), rc); + break; + } + + return rc; +} + +/* set all system information types */ +static int set_system_infos(struct gsm_bts_trx *trx) +{ + int i, rc; + struct gsm_bts *bts = trx->bts; + + bts->si_common.cell_sel_par.ms_txpwr_max_ccch = + ms_pwr_ctl_lvl(bts->band, bts->ms_max_power); + bts->si_common.cell_sel_par.neci = bts->network->neci; + + /* First, we determine which of the SI messages we actually need */ + + if (trx == bts->c0) { + /* 1...4 are always present on a C0 TRX */ + for (i = SYSINFO_TYPE_1; i <= SYSINFO_TYPE_4; i++) + bts->si_valid |= (1 << i); + + /* 13 is always present on a C0 TRX of a GPRS BTS */ + if (bts->gprs.mode != BTS_GPRS_NONE) + bts->si_valid |= (1 << SYSINFO_TYPE_13); + } + + /* 5 and 6 are always present on every TRX */ + bts->si_valid |= (1 << SYSINFO_TYPE_5); + bts->si_valid |= (1 << SYSINFO_TYPE_6); + + /* Second, we generate and send the selected SI via RSL */ + for (i = SYSINFO_TYPE_1; i < _MAX_SYSINFO_TYPE; i++) { + if (!(bts->si_valid & (1 << i))) + continue; + + rc = generate_and_rsl_si(trx, i); + if (rc < 0) + goto err_out; + } + + return 0; +err_out: + LOGP(DRR, LOGL_ERROR, "Cannot generate SI %u for BTS %u, most likely " + "a problem with neighbor cell list generation\n", + i, bts->nr); + return rc; +} + +/* Produce a MA as specified in 10.5.2.21 */ +static int generate_ma_for_ts(struct gsm_bts_trx_ts *ts) +{ + /* we have three bitvecs: the per-timeslot ARFCNs, the cell chan ARFCNs + * and the MA */ + struct bitvec *cell_chan = &ts->trx->bts->si_common.cell_alloc; + struct bitvec *ts_arfcn = &ts->hopping.arfcns; + struct bitvec *ma = &ts->hopping.ma; + unsigned int num_cell_arfcns, bitnum, n_chan; + int i; + + /* re-set the MA to all-zero */ + ma->cur_bit = 0; + ts->hopping.ma_len = 0; + memset(ma->data, 0, ma->data_len); + + if (!ts->hopping.enabled) + return 0; + + /* count the number of ARFCNs in the cell channel allocation */ + num_cell_arfcns = 0; + for (i = 1; i < 1024; i++) { + if (bitvec_get_bit_pos(cell_chan, i)) + num_cell_arfcns++; + } + + /* pad it to octet-aligned number of bits */ + ts->hopping.ma_len = num_cell_arfcns / 8; + if (num_cell_arfcns % 8) + ts->hopping.ma_len++; + + n_chan = 0; + for (i = 1; i < 1024; i++) { + if (!bitvec_get_bit_pos(cell_chan, i)) + continue; + /* set the corresponding bit in the MA */ + bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan; + if (bitvec_get_bit_pos(ts_arfcn, i)) + bitvec_set_bit_pos(ma, bitnum, 1); + else + bitvec_set_bit_pos(ma, bitnum, 0); + n_chan++; + } + + /* ARFCN 0 is special: It is coded last in the bitmask */ + if (bitvec_get_bit_pos(cell_chan, 0)) { + n_chan++; + /* set the corresponding bit in the MA */ + bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan; + if (bitvec_get_bit_pos(ts_arfcn, 0)) + bitvec_set_bit_pos(ma, bitnum, 1); + else + bitvec_set_bit_pos(ma, bitnum, 0); + } + + return 0; +} + +static void bootstrap_rsl(struct gsm_bts_trx *trx) +{ + unsigned int i; + + LOGP(DRSL, LOGL_NOTICE, "bootstrapping RSL for BTS/TRX (%u/%u) " + "on ARFCN %u using MCC=%u MNC=%u LAC=%u CID=%u BSIC=%u TSC=%u\n", + trx->bts->nr, trx->nr, trx->arfcn, bsc_gsmnet->country_code, + bsc_gsmnet->network_code, trx->bts->location_area_code, + trx->bts->cell_identity, trx->bts->bsic, trx->bts->tsc); + set_system_infos(trx); + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) + generate_ma_for_ts(&trx->ts[i]); +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int inp_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct input_signal_data *isd = signal_data; + struct gsm_bts_trx *trx = isd->trx; + int ts_no, lchan_no; + + if (subsys != SS_INPUT) + return -EINVAL; + + switch (signal) { + case S_INP_TEI_UP: + if (isd->link_type == E1INP_SIGN_RSL) + bootstrap_rsl(trx); + break; + case S_INP_TEI_DN: + LOGP(DMI, LOGL_ERROR, "Lost some E1 TEI link: %d %p\n", isd->link_type, trx); + + if (isd->link_type == E1INP_SIGN_OML) + counter_inc(trx->bts->network->stats.bts.oml_fail); + else if (isd->link_type == E1INP_SIGN_RSL) + counter_inc(trx->bts->network->stats.bts.rsl_fail); + + /* + * free all allocated channels. change the nm_state so the + * trx and trx_ts becomes unusable and chan_alloc.c can not + * allocate from it. + */ + for (ts_no = 0; ts_no < ARRAY_SIZE(trx->ts); ++ts_no) { + struct gsm_bts_trx_ts *ts = &trx->ts[ts_no]; + + for (lchan_no = 0; lchan_no < ARRAY_SIZE(ts->lchan); ++lchan_no) { + if (ts->lchan[lchan_no].state != LCHAN_S_NONE) + lchan_free(&ts->lchan[lchan_no]); + lchan_reset(&ts->lchan[lchan_no]); + } + + ts->nm_state.operational = 0; + ts->nm_state.availability = 0; + } + + trx->nm_state.operational = 0; + trx->nm_state.availability = 0; + trx->bb_transc.nm_state.operational = 0; + trx->bb_transc.nm_state.availability = 0; + + abis_nm_clear_queue(trx->bts); + break; + default: + break; + } + + return 0; +} + +static int bootstrap_bts(struct gsm_bts *bts) +{ + int i, n; + + /* FIXME: What about secondary TRX of a BTS? What about a BTS that has TRX + * in different bands? Why is 'band' a parameter of the BTS and not of the TRX? */ + switch (bts->band) { + case GSM_BAND_1800: + if (bts->c0->arfcn < 512 || bts->c0->arfcn > 885) { + LOGP(DNM, LOGL_ERROR, "GSM1800 channel must be between 512-885.\n"); + return -EINVAL; + } + break; + case GSM_BAND_1900: + if (bts->c0->arfcn < 512 || bts->c0->arfcn > 810) { + LOGP(DNM, LOGL_ERROR, "GSM1900 channel must be between 512-810.\n"); + return -EINVAL; + } + break; + case GSM_BAND_900: + if (bts->c0->arfcn < 1 || + (bts->c0->arfcn > 124 && bts->c0->arfcn < 955) || + bts->c0->arfcn > 1023) { + LOGP(DNM, LOGL_ERROR, "GSM900 channel must be between 1-124, 955-1023.\n"); + return -EINVAL; + } + break; + case GSM_BAND_850: + if (bts->c0->arfcn < 128 || bts->c0->arfcn > 251) { + LOGP(DNM, LOGL_ERROR, "GSM850 channel must be between 128-251.\n"); + return -EINVAL; + } + break; + default: + LOGP(DNM, LOGL_ERROR, "Unsupported frequency band.\n"); + return -EINVAL; + } + + if (bts->network->auth_policy == GSM_AUTH_POLICY_ACCEPT_ALL && + !bts->si_common.rach_control.cell_bar) + LOGP(DNM, LOGL_ERROR, "\nWARNING: You are running an 'accept-all' " + "network on a BTS that is not barred. This " + "configuration is likely to interfere with production " + "GSM networks and should only be used in a RF " + "shielded environment such as a faraday cage!\n\n"); + + /* Control Channel Description */ + bts->si_common.chan_desc.att = 1; + bts->si_common.chan_desc.bs_pa_mfrms = RSL_BS_PA_MFRMS_5; + bts->si_common.chan_desc.bs_ag_blks_res = 1; + + /* T3212 is set from vty/config */ + + /* Set ccch config by looking at ts config */ + for (n=0, i=0; i<8; i++) + n += bts->c0->ts[i].pchan == GSM_PCHAN_CCCH ? 1 : 0; + + switch (n) { + case 0: + bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_C; + break; + case 1: + bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_NC; + break; + case 2: + bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_2_NC; + break; + case 3: + bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_3_NC; + break; + case 4: + bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_4_NC; + break; + default: + LOGP(DNM, LOGL_ERROR, "Unsupported CCCH timeslot configuration\n"); + return -EINVAL; + } + + /* some defaults for our system information */ + bts->si_common.cell_options.radio_link_timeout = 7; /* 12 */ + + /* allow/disallow DTXu */ + if (bts->network->dtx_enabled) + bts->si_common.cell_options.dtx = 0; + else + bts->si_common.cell_options.dtx = 2; + + bts->si_common.cell_options.pwrc = 0; /* PWRC not set */ + + bts->si_common.cell_sel_par.acs = 0; + + bts->si_common.ncc_permitted = 0xff; + + paging_init(bts); + + return 0; +} + +int bsc_bootstrap_network(int (*mncc_recv)(struct gsm_network *, struct msgb *), + const char *config_file) +{ + struct telnet_connection dummy_conn; + struct gsm_bts *bts; + int rc; + + /* initialize our data structures */ + bsc_gsmnet = gsm_network_init(1, 1, mncc_recv); + if (!bsc_gsmnet) + return -ENOMEM; + + bsc_gsmnet->name_long = talloc_strdup(bsc_gsmnet, "OpenBSC"); + bsc_gsmnet->name_short = talloc_strdup(bsc_gsmnet, "OpenBSC"); + + /* our vty command code expects vty->priv to point to a telnet_connection */ + dummy_conn.priv = bsc_gsmnet; + rc = vty_read_config_file(config_file, &dummy_conn); + if (rc < 0) { + LOGP(DNM, LOGL_FATAL, "Failed to parse the config file: '%s'\n", config_file); + return rc; + } + + rc = telnet_init(tall_bsc_ctx, bsc_gsmnet, 4242); + if (rc < 0) + return rc; + + register_signal_handler(SS_NM, nm_sig_cb, NULL); + register_signal_handler(SS_INPUT, inp_sig_cb, NULL); + + llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) { + rc = bootstrap_bts(bts); + + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + case GSM_BTS_TYPE_HSL_FEMTO: + break; + default: + rc = e1_reconfig_bts(bts); + break; + } + + if (rc < 0) { + fprintf(stderr, "Error in E1 input driver setup\n"); + exit (1); + } + } + + /* initialize nanoBTS support omce */ + rc = ipaccess_setup(bsc_gsmnet); + rc = hsl_setup(bsc_gsmnet); + + return 0; +} diff --git a/src/libbsc/bsc_msc.c b/src/libbsc/bsc_msc.c new file mode 100644 index 000000000..508697ab1 --- /dev/null +++ b/src/libbsc/bsc_msc.c @@ -0,0 +1,259 @@ +/* Routines to talk to the MSC using the IPA Protocol */ +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static void connection_loss(struct bsc_msc_connection *con) +{ + struct bsc_fd *fd; + + fd = &con->write_queue.bfd; + + close(fd->fd); + fd->fd = -1; + fd->cb = write_queue_bfd_cb; + fd->when = 0; + + con->is_connected = 0; + con->first_contact = 0; + con->connection_loss(con); +} + +static void msc_con_timeout(void *_con) +{ + struct bsc_msc_connection *con = _con; + + LOGP(DMSC, LOGL_ERROR, "MSC Connection timeout.\n"); + bsc_msc_lost(con); +} + +/* called in the case of a non blocking connect */ +static int msc_connection_connect(struct bsc_fd *fd, unsigned int what) +{ + int rc; + int val; + struct bsc_msc_connection *con; + struct write_queue *queue; + + socklen_t len = sizeof(val); + + if ((what & BSC_FD_WRITE) == 0) { + LOGP(DMSC, LOGL_ERROR, "Callback but not writable.\n"); + return -1; + } + + queue = container_of(fd, struct write_queue, bfd); + con = container_of(queue, struct bsc_msc_connection, write_queue); + + /* From here on we will either be connected or reconnect */ + bsc_del_timer(&con->timeout_timer); + + /* check the socket state */ + rc = getsockopt(fd->fd, SOL_SOCKET, SO_ERROR, &val, &len); + if (rc != 0) { + LOGP(DMSC, LOGL_ERROR, "getsockopt for the MSC socket failed.\n"); + goto error; + } + if (val != 0) { + LOGP(DMSC, LOGL_ERROR, "Not connected to the MSC: %d\n", val); + goto error; + } + + + /* go to full operation */ + fd->cb = write_queue_bfd_cb; + fd->when = BSC_FD_READ | BSC_FD_EXCEPT; + + con->is_connected = 1; + LOGP(DMSC, LOGL_NOTICE, "(Re)Connected to the MSC.\n"); + if (con->connected) + con->connected(con); + return 0; + +error: + bsc_unregister_fd(fd); + connection_loss(con); + return -1; +} +static void setnonblocking(struct bsc_fd *fd) +{ + int flags; + + flags = fcntl(fd->fd, F_GETFL); + if (flags < 0) { + perror("fcntl get failed"); + close(fd->fd); + fd->fd = -1; + return; + } + + flags |= O_NONBLOCK; + flags = fcntl(fd->fd, F_SETFL, flags); + if (flags < 0) { + perror("fcntl get failed"); + close(fd->fd); + fd->fd = -1; + return; + } +} + +int bsc_msc_connect(struct bsc_msc_connection *con) +{ + struct bsc_fd *fd; + struct sockaddr_in sin; + int on = 1, ret; + + LOGP(DMSC, LOGL_NOTICE, "Attempting to connect MSC at %s:%d\n", con->ip, con->port); + + con->is_connected = 0; + + fd = &con->write_queue.bfd; + fd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + fd->priv_nr = 1; + + if (fd->fd < 0) { + perror("Creating TCP socket failed"); + return fd->fd; + } + + /* make it non blocking */ + setnonblocking(fd); + + /* set the socket priority */ + ret = setsockopt(fd->fd, IPPROTO_IP, IP_TOS, + &con->prio, sizeof(con->prio)); + if (ret != 0) + LOGP(DMSC, LOGL_ERROR, "Failed to set prio to %d. %s\n", + con->prio, strerror(errno)); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(con->port); + inet_aton(con->ip, &sin.sin_addr); + + setsockopt(fd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + ret = connect(fd->fd, (struct sockaddr *) &sin, sizeof(sin)); + + if (ret == -1 && errno == EINPROGRESS) { + LOGP(DMSC, LOGL_ERROR, "MSC Connection in progress\n"); + fd->when = BSC_FD_WRITE; + fd->cb = msc_connection_connect; + con->timeout_timer.cb = msc_con_timeout; + con->timeout_timer.data = con; + bsc_schedule_timer(&con->timeout_timer, 20, 0); + } else if (ret < 0) { + perror("Connection failed"); + connection_loss(con); + return ret; + } else { + fd->when = BSC_FD_READ | BSC_FD_EXCEPT; + fd->cb = write_queue_bfd_cb; + con->is_connected = 1; + if (con->connected) + con->connected(con); + } + + ret = bsc_register_fd(fd); + if (ret < 0) { + perror("Registering the fd failed"); + close(fd->fd); + return ret; + } + + return ret; +} + +struct bsc_msc_connection *bsc_msc_create(const char *ip, int port, int prio) +{ + struct bsc_msc_connection *con; + + con = talloc_zero(NULL, struct bsc_msc_connection); + if (!con) { + LOGP(DMSC, LOGL_FATAL, "Failed to create the MSC connection.\n"); + return NULL; + } + + con->ip = ip; + con->port = port; + con->prio = prio; + write_queue_init(&con->write_queue, 100); + return con; +} + +void bsc_msc_lost(struct bsc_msc_connection *con) +{ + write_queue_clear(&con->write_queue); + bsc_del_timer(&con->timeout_timer); + + if (con->write_queue.bfd.fd >= 0) + bsc_unregister_fd(&con->write_queue.bfd); + connection_loss(con); +} + +static void reconnect_msc(void *_msc) +{ + struct bsc_msc_connection *con = _msc; + + LOGP(DMSC, LOGL_NOTICE, "Attempting to reconnect to the MSC.\n"); + bsc_msc_connect(con); +} + +void bsc_msc_schedule_connect(struct bsc_msc_connection *con) +{ + LOGP(DMSC, LOGL_NOTICE, "Attempting to reconnect to the MSC.\n"); + con->reconnect_timer.cb = reconnect_msc; + con->reconnect_timer.data = con; + bsc_schedule_timer(&con->reconnect_timer, 5, 0); +} + +struct msgb *bsc_msc_id_get_resp(const char *token) +{ + struct msgb *msg; + + if (!token) { + LOGP(DMSC, LOGL_ERROR, "No token specified.\n"); + return NULL; + } + + msg = msgb_alloc_headroom(4096, 128, "id resp"); + if (!msg) { + LOGP(DMSC, LOGL_ERROR, "Failed to create the message.\n"); + return NULL; + } + + msg->l2h = msgb_v_put(msg, IPAC_MSGT_ID_RESP); + msgb_l16tv_put(msg, strlen(token) + 1, + IPAC_IDTAG_UNITNAME, (u_int8_t *) token); + return msg; +} diff --git a/src/libbsc/bsc_rll.c b/src/libbsc/bsc_rll.c new file mode 100644 index 000000000..722f3fafc --- /dev/null +++ b/src/libbsc/bsc_rll.c @@ -0,0 +1,141 @@ +/* GSM BSC Radio Link Layer API + * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */ + +/* (C) 2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct bsc_rll_req { + struct llist_head list; + struct timer_list timer; + + struct gsm_lchan *lchan; + u_int8_t link_id; + + void (*cb)(struct gsm_lchan *lchan, u_int8_t link_id, + void *data, enum bsc_rllr_ind); + void *data; +}; + +/* we only compare C1, C2 and SAPI */ +#define LINKID_MASK 0xC7 + +static LLIST_HEAD(bsc_rll_reqs); + +static void complete_rllr(struct bsc_rll_req *rllr, enum bsc_rllr_ind type) +{ + llist_del(&rllr->list); + rllr->cb(rllr->lchan, rllr->link_id, rllr->data, type); + talloc_free(rllr); +} + +static void timer_cb(void *_rllr) +{ + struct bsc_rll_req *rllr = _rllr; + + complete_rllr(rllr, BSC_RLLR_IND_TIMEOUT); +} + +/* establish a RLL connection with given SAPI / priority */ +int rll_establish(struct gsm_lchan *lchan, u_int8_t sapi, + void (*cb)(struct gsm_lchan *, u_int8_t, void *, + enum bsc_rllr_ind), + void *data) +{ + struct bsc_rll_req *rllr = talloc_zero(tall_bsc_ctx, struct bsc_rll_req); + u_int8_t link_id; + if (!rllr) + return -ENOMEM; + + link_id = sapi; + + /* If we are a TCH and not in signalling mode, we need to + * indicate that the new RLL connection is to be made on the SACCH */ + if ((lchan->type == GSM_LCHAN_TCH_F || + lchan->type == GSM_LCHAN_TCH_H) && sapi != 0) + link_id |= 0x40; + + rllr->lchan = lchan; + rllr->link_id = link_id; + rllr->cb = cb; + rllr->data = data; + + llist_add(&rllr->list, &bsc_rll_reqs); + + rllr->timer.cb = &timer_cb; + rllr->timer.data = rllr; + + bsc_schedule_timer(&rllr->timer, 10, 0); + + /* send the RSL RLL ESTablish REQuest */ + return rsl_establish_request(rllr->lchan, rllr->link_id); +} + +/* Called from RSL code in case we have received an indication regarding + * any RLL link */ +void rll_indication(struct gsm_lchan *lchan, u_int8_t link_id, u_int8_t type) +{ + struct bsc_rll_req *rllr, *rllr2; + + llist_for_each_entry_safe(rllr, rllr2, &bsc_rll_reqs, list) { + if (rllr->lchan == lchan && + (rllr->link_id & LINKID_MASK) == (link_id & LINKID_MASK)) { + bsc_del_timer(&rllr->timer); + complete_rllr(rllr, type); + return; + } + } +} + +static int rll_lchan_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct challoc_signal_data *challoc; + struct bsc_rll_req *rllr, *rllr2; + + if (subsys != SS_CHALLOC || signal != S_CHALLOC_FREED) + return 0; + + challoc = (struct challoc_signal_data *) signal_data; + + llist_for_each_entry_safe(rllr, rllr2, &bsc_rll_reqs, list) { + if (rllr->lchan == challoc->lchan) { + bsc_del_timer(&rllr->timer); + complete_rllr(rllr, BSC_RLLR_IND_ERR_IND); + } + } + + return 0; +} + +static __attribute__((constructor)) void on_dso_load_rll(void) +{ + register_signal_handler(SS_CHALLOC, rll_lchan_signal, NULL); +} diff --git a/src/libbsc/bsc_vty.c b/src/libbsc/bsc_vty.c new file mode 100644 index 000000000..c0909db51 --- /dev/null +++ b/src/libbsc/bsc_vty.c @@ -0,0 +1,2799 @@ +/* OpenBSC interface to quagga VTY */ +/* (C) 2009-2010 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../bscconfig.h" + +/* FIXME: this should go to some common file */ +static const struct value_string gprs_ns_timer_strs[] = { + { 0, "tns-block" }, + { 1, "tns-block-retries" }, + { 2, "tns-reset" }, + { 3, "tns-reset-retries" }, + { 4, "tns-test" }, + { 5, "tns-alive" }, + { 6, "tns-alive-retries" }, + { 0, NULL } +}; + +static const struct value_string gprs_bssgp_cfg_strs[] = { + { 0, "blocking-timer" }, + { 1, "blocking-retries" }, + { 2, "unblocking-retries" }, + { 3, "reset-timer" }, + { 4, "reset-retries" }, + { 5, "suspend-timer" }, + { 6, "suspend-retries" }, + { 7, "resume-timer" }, + { 8, "resume-retries" }, + { 9, "capability-update-timer" }, + { 10, "capability-update-retries" }, + { 0, NULL } +}; + +static const struct value_string bts_neigh_mode_strs[] = { + { NL_MODE_AUTOMATIC, "automatic" }, + { NL_MODE_MANUAL, "manual" }, + { NL_MODE_MANUAL_SI5SEP, "manual-si5" }, + { 0, NULL } +}; + +struct cmd_node net_node = { + GSMNET_NODE, + "%s(network)#", + 1, +}; + +struct cmd_node bts_node = { + BTS_NODE, + "%s(bts)#", + 1, +}; + +struct cmd_node trx_node = { + TRX_NODE, + "%s(trx)#", + 1, +}; + +struct cmd_node ts_node = { + TS_NODE, + "%s(ts)#", + 1, +}; + +extern struct gsm_network *bsc_gsmnet; + +struct gsm_network *gsmnet_from_vty(struct vty *v) +{ + /* In case we read from the config file, the vty->priv cannot + * point to a struct telnet_connection, and thus conn->priv + * will not point to the gsm_network structure */ +#if 0 + struct telnet_connection *conn = v->priv; + return (struct gsm_network *) conn->priv; +#else + return bsc_gsmnet; +#endif +} + +static int dummy_config_write(struct vty *v) +{ + return CMD_SUCCESS; +} + +static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms) +{ + vty_out(vty,"Oper '%s', Admin %u, Avail '%s'%s", + nm_opstate_name(nms->operational), nms->administrative, + nm_avail_name(nms->availability), VTY_NEWLINE); +} + +static void dump_pchan_load_vty(struct vty *vty, char *prefix, + const struct pchan_load *pl) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pl->pchan); i++) { + const struct load_counter *lc = &pl->pchan[i]; + unsigned int percent; + + if (lc->total == 0) + continue; + + percent = (lc->used * 100) / lc->total; + + vty_out(vty, "%s%20s: %3u%% (%u/%u)%s", prefix, + gsm_pchan_name(i), percent, lc->used, lc->total, + VTY_NEWLINE); + } +} + +static void net_dump_vty(struct vty *vty, struct gsm_network *net) +{ + struct pchan_load pl; + + vty_out(vty, "BSC is on Country Code %u, Network Code %u " + "and has %u BTS%s", net->country_code, net->network_code, + net->num_bts, VTY_NEWLINE); + vty_out(vty, " Long network name: '%s'%s", + net->name_long, VTY_NEWLINE); + vty_out(vty, " Short network name: '%s'%s", + net->name_short, VTY_NEWLINE); + vty_out(vty, " Authentication policy: %s%s", + gsm_auth_policy_name(net->auth_policy), VTY_NEWLINE); + vty_out(vty, " Location updating reject cause: %u%s", + net->reject_cause, VTY_NEWLINE); + vty_out(vty, " Encryption: A5/%u%s", net->a5_encryption, + VTY_NEWLINE); + vty_out(vty, " NECI (TCH/H): %u%s", net->neci, + VTY_NEWLINE); + vty_out(vty, " Use TCH for Paging any: %d%s", net->pag_any_tch, + VTY_NEWLINE); + vty_out(vty, " RRLP Mode: %s%s", rrlp_mode_name(net->rrlp.mode), + VTY_NEWLINE); + vty_out(vty, " MM Info: %s%s", net->send_mm_info ? "On" : "Off", + VTY_NEWLINE); + vty_out(vty, " Handover: %s%s", net->handover.active ? "On" : "Off", + VTY_NEWLINE); + network_chan_load(&pl, net); + vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE); + dump_pchan_load_vty(vty, " ", &pl); + + /* show rf */ + if (net->msc_data) + vty_out(vty, " Last RF Command: %s%s", + net->msc_data->rf_ctl->last_state_command, + VTY_NEWLINE); +} + +DEFUN(show_net, show_net_cmd, "show network", + SHOW_STR "Display information about a GSM NETWORK\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + net_dump_vty(vty, net); + + return CMD_SUCCESS; +} + +static void e1isl_dump_vty(struct vty *vty, struct e1inp_sign_link *e1l) +{ + struct e1inp_line *line; + + if (!e1l) { + vty_out(vty, " None%s", VTY_NEWLINE); + return; + } + + line = e1l->ts->line; + + vty_out(vty, " E1 Line %u, Type %s: Timeslot %u, Mode %s%s", + line->num, line->driver->name, e1l->ts->num, + e1inp_signtype_name(e1l->type), VTY_NEWLINE); + vty_out(vty, " E1 TEI %u, SAPI %u%s", + e1l->tei, e1l->sapi, VTY_NEWLINE); +} + +static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts) +{ + struct pchan_load pl; + + vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, " + "BSIC %u, TSC %u and %u TRX%s", + bts->nr, btstype2str(bts->type), gsm_band_name(bts->band), + bts->cell_identity, + bts->location_area_code, bts->bsic, bts->tsc, + bts->num_trx, VTY_NEWLINE); + vty_out(vty, "Description: %s%s", + bts->description ? bts->description : "(null)", VTY_NEWLINE); + vty_out(vty, "MS Max power: %u dBm%s", bts->ms_max_power, VTY_NEWLINE); + vty_out(vty, "Minimum Rx Level for Access: %i dBm%s", + rxlev2dbm(bts->si_common.cell_sel_par.rxlev_acc_min), + VTY_NEWLINE); + vty_out(vty, "Cell Reselection Hysteresis: %u dBm%s", + bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE); + vty_out(vty, "RACH TX-Integer: %u%s", bts->si_common.rach_control.tx_integer, + VTY_NEWLINE); + vty_out(vty, "RACH Max transmissions: %u%s", + rach_max_trans_raw2val(bts->si_common.rach_control.max_trans), + VTY_NEWLINE); + if (bts->si_common.rach_control.cell_bar) + vty_out(vty, " CELL IS BARRED%s", VTY_NEWLINE); + vty_out(vty, "System Information present: 0x%08x, static: 0x%08x%s", + bts->si_valid, bts->si_mode_static, VTY_NEWLINE); + if (is_ipaccess_bts(bts)) + vty_out(vty, " Unit ID: %u/%u/0, OML Stream ID 0x%02x%s", + bts->ip_access.site_id, bts->ip_access.bts_id, + bts->oml_tei, VTY_NEWLINE); + else if (bts->type == GSM_BTS_TYPE_HSL_FEMTO) + vty_out(vty, " Serial Number: %lu%s", bts->hsl.serno, VTY_NEWLINE); + vty_out(vty, " NM State: "); + net_dump_nmstate(vty, &bts->nm_state); + vty_out(vty, " Site Mgr NM State: "); + net_dump_nmstate(vty, &bts->site_mgr.nm_state); + vty_out(vty, " Paging: FIXME pending requests, %u free slots%s", + bts->paging.available_slots, VTY_NEWLINE); + if (is_ipaccess_bts(bts)) { + vty_out(vty, " OML Link state: %s.%s", + bts->oml_link ? "connected" : "disconnected", VTY_NEWLINE); + } else { + vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE); + e1isl_dump_vty(vty, bts->oml_link); + } + + /* FIXME: chan_desc */ + memset(&pl, 0, sizeof(pl)); + bts_chan_load(&pl, bts); + vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE); + dump_pchan_load_vty(vty, " ", &pl); +} + +DEFUN(show_bts, show_bts_cmd, "show bts [number]", + SHOW_STR "Display information about a BTS\n" + "BTS number") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + int bts_nr; + + if (argc != 0) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + if (bts_nr >= net->num_bts) { + vty_out(vty, "%% can't find BTS '%s'%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + bts_dump_vty(vty, gsm_bts_num(net, bts_nr)); + return CMD_SUCCESS; + } + /* print all BTS's */ + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) + bts_dump_vty(vty, gsm_bts_num(net, bts_nr)); + + return CMD_SUCCESS; +} + +/* utility functions */ +static void parse_e1_link(struct gsm_e1_subslot *e1_link, const char *line, + const char *ts, const char *ss) +{ + e1_link->e1_nr = atoi(line); + e1_link->e1_ts = atoi(ts); + if (!strcmp(ss, "full")) + e1_link->e1_ts_ss = 255; + else + e1_link->e1_ts_ss = atoi(ss); +} + +static void config_write_e1_link(struct vty *vty, struct gsm_e1_subslot *e1_link, + const char *prefix) +{ + if (!e1_link->e1_ts) + return; + + if (e1_link->e1_ts_ss == 255) + vty_out(vty, "%se1 line %u timeslot %u sub-slot full%s", + prefix, e1_link->e1_nr, e1_link->e1_ts, VTY_NEWLINE); + else + vty_out(vty, "%se1 line %u timeslot %u sub-slot %u%s", + prefix, e1_link->e1_nr, e1_link->e1_ts, + e1_link->e1_ts_ss, VTY_NEWLINE); +} + + +static void config_write_ts_single(struct vty *vty, struct gsm_bts_trx_ts *ts) +{ + vty_out(vty, " timeslot %u%s", ts->nr, VTY_NEWLINE); + if (ts->pchan != GSM_PCHAN_NONE) + vty_out(vty, " phys_chan_config %s%s", + gsm_pchan_name(ts->pchan), VTY_NEWLINE); + vty_out(vty, " hopping enabled %u%s", + ts->hopping.enabled, VTY_NEWLINE); + if (ts->hopping.enabled) { + unsigned int i; + vty_out(vty, " hopping sequence-number %u%s", + ts->hopping.hsn, VTY_NEWLINE); + vty_out(vty, " hopping maio %u%s", + ts->hopping.maio, VTY_NEWLINE); + for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) { + if (!bitvec_get_bit_pos(&ts->hopping.arfcns, i)) + continue; + vty_out(vty, " hopping arfcn add %u%s", + i, VTY_NEWLINE); + } + } + config_write_e1_link(vty, &ts->e1_link, " "); + + if (ts->trx->bts->model->config_write_ts) + ts->trx->bts->model->config_write_ts(vty, ts); +} + +static void config_write_trx_single(struct vty *vty, struct gsm_bts_trx *trx) +{ + int i; + + vty_out(vty, " trx %u%s", trx->nr, VTY_NEWLINE); + if (trx->description) + vty_out(vty, " description %s%s", trx->description, + VTY_NEWLINE); + vty_out(vty, " rf_locked %u%s", + trx->nm_state.administrative == NM_STATE_LOCKED ? 1 : 0, + VTY_NEWLINE); + vty_out(vty, " arfcn %u%s", trx->arfcn, VTY_NEWLINE); + vty_out(vty, " nominal power %u%s", trx->nominal_power, VTY_NEWLINE); + vty_out(vty, " max_power_red %u%s", trx->max_power_red, VTY_NEWLINE); + config_write_e1_link(vty, &trx->rsl_e1_link, " rsl "); + vty_out(vty, " rsl e1 tei %u%s", trx->rsl_tei, VTY_NEWLINE); + + if (trx->bts->model->config_write_trx) + trx->bts->model->config_write_trx(vty, trx); + + for (i = 0; i < TRX_NR_TS; i++) + config_write_ts_single(vty, &trx->ts[i]); +} + +static void config_write_bts_gprs(struct vty *vty, struct gsm_bts *bts) +{ + unsigned int i; + vty_out(vty, " gprs mode %s%s", bts_gprs_mode_name(bts->gprs.mode), + VTY_NEWLINE); + if (bts->gprs.mode == BTS_GPRS_NONE) + return; + + vty_out(vty, " gprs routing area %u%s", bts->gprs.rac, + VTY_NEWLINE); + vty_out(vty, " gprs cell bvci %u%s", bts->gprs.cell.bvci, + VTY_NEWLINE); + for (i = 0; i < ARRAY_SIZE(bts->gprs.cell.timer); i++) + vty_out(vty, " gprs cell timer %s %u%s", + get_value_string(gprs_bssgp_cfg_strs, i), + bts->gprs.cell.timer[i], VTY_NEWLINE); + vty_out(vty, " gprs nsei %u%s", bts->gprs.nse.nsei, + VTY_NEWLINE); + for (i = 0; i < ARRAY_SIZE(bts->gprs.nse.timer); i++) + vty_out(vty, " gprs ns timer %s %u%s", + get_value_string(gprs_ns_timer_strs, i), + bts->gprs.nse.timer[i], VTY_NEWLINE); + for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) { + struct gsm_bts_gprs_nsvc *nsvc = + &bts->gprs.nsvc[i]; + struct in_addr ia; + + ia.s_addr = htonl(nsvc->remote_ip); + vty_out(vty, " gprs nsvc %u nsvci %u%s", i, + nsvc->nsvci, VTY_NEWLINE); + vty_out(vty, " gprs nsvc %u local udp port %u%s", i, + nsvc->local_port, VTY_NEWLINE); + vty_out(vty, " gprs nsvc %u remote udp port %u%s", i, + nsvc->remote_port, VTY_NEWLINE); + vty_out(vty, " gprs nsvc %u remote ip %s%s", i, + inet_ntoa(ia), VTY_NEWLINE); + } +} + +static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + int i; + + vty_out(vty, " bts %u%s", bts->nr, VTY_NEWLINE); + vty_out(vty, " type %s%s", btstype2str(bts->type), VTY_NEWLINE); + if (bts->description) + vty_out(vty, " description %s%s", bts->description, VTY_NEWLINE); + vty_out(vty, " band %s%s", gsm_band_name(bts->band), VTY_NEWLINE); + vty_out(vty, " cell_identity %u%s", bts->cell_identity, VTY_NEWLINE); + vty_out(vty, " location_area_code %u%s", bts->location_area_code, + VTY_NEWLINE); + vty_out(vty, " training_sequence_code %u%s", bts->tsc, VTY_NEWLINE); + vty_out(vty, " base_station_id_code %u%s", bts->bsic, VTY_NEWLINE); + vty_out(vty, " ms max power %u%s", bts->ms_max_power, VTY_NEWLINE); + vty_out(vty, " cell reselection hysteresis %u%s", + bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE); + vty_out(vty, " rxlev access min %u%s", + bts->si_common.cell_sel_par.rxlev_acc_min, VTY_NEWLINE); + + if (bts->si_common.cell_ro_sel_par.present) { + struct gsm48_si_selection_params *sp; + sp = &bts->si_common.cell_ro_sel_par; + + if (sp->cbq) + vty_out(vty, " cell bar qualify %u%s", + sp->cbq, VTY_NEWLINE); + + if (sp->cell_resel_off) + vty_out(vty, " cell reselection offset %u%s", + sp->cell_resel_off*2, VTY_NEWLINE); + + if (sp->temp_offs == 7) + vty_out(vty, " temporary offset infinite%s", + VTY_NEWLINE); + else if (sp->temp_offs) + vty_out(vty, " temporary offset %u%s", + sp->temp_offs*10, VTY_NEWLINE); + + if (sp->penalty_time == 31) + vty_out(vty, " penalty time reserved%s", + VTY_NEWLINE); + else if (sp->penalty_time) + vty_out(vty, " penalty time %u%s", + (sp->penalty_time*20)+20, VTY_NEWLINE); + } + + if (bts->si_common.chan_desc.t3212) + vty_out(vty, " periodic location update %u%s", + bts->si_common.chan_desc.t3212 * 6, VTY_NEWLINE); + vty_out(vty, " channel allocator %s%s", + bts->chan_alloc_reverse ? "descending" : "ascending", + VTY_NEWLINE); + vty_out(vty, " rach tx integer %u%s", + bts->si_common.rach_control.tx_integer, VTY_NEWLINE); + vty_out(vty, " rach max transmission %u%s", + rach_max_trans_raw2val(bts->si_common.rach_control.max_trans), + VTY_NEWLINE); + + if (bts->rach_b_thresh != -1) + vty_out(vty, " rach nm busy threshold %u%s", + bts->rach_b_thresh, VTY_NEWLINE); + if (bts->rach_ldavg_slots != -1) + vty_out(vty, " rach nm load average %u%s", + bts->rach_ldavg_slots, VTY_NEWLINE); + if (bts->si_common.rach_control.cell_bar) + vty_out(vty, " cell barred 1%s", VTY_NEWLINE); + if ((bts->si_common.rach_control.t2 & 0x4) == 0) + vty_out(vty, " rach emergency call allowed 1%s", VTY_NEWLINE); + for (i = SYSINFO_TYPE_1; i < _MAX_SYSINFO_TYPE; i++) { + if (bts->si_mode_static & (1 << i)) { + vty_out(vty, " system-information %s mode static%s", + get_value_string(osmo_sitype_strs, i), VTY_NEWLINE); + vty_out(vty, " system-information %s static %s%s", + get_value_string(osmo_sitype_strs, i), + hexdump_nospc(bts->si_buf[i], sizeof(bts->si_buf[i])), + VTY_NEWLINE); + } + } + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + vty_out(vty, " ip.access unit_id %u %u%s", + bts->ip_access.site_id, bts->ip_access.bts_id, VTY_NEWLINE); + vty_out(vty, " oml ip.access stream_id %u%s", bts->oml_tei, VTY_NEWLINE); + break; + case GSM_BTS_TYPE_HSL_FEMTO: + vty_out(vty, " hsl serial-number %lu%s", bts->hsl.serno, VTY_NEWLINE); + break; + default: + config_write_e1_link(vty, &bts->oml_e1_link, " oml "); + vty_out(vty, " oml e1 tei %u%s", bts->oml_tei, VTY_NEWLINE); + break; + } + + /* if we have a limit, write it */ + if (bts->paging.free_chans_need >= 0) + vty_out(vty, " paging free %d%s", bts->paging.free_chans_need, VTY_NEWLINE); + + vty_out(vty, " neighbor-list mode %s%s", + get_value_string(bts_neigh_mode_strs, bts->neigh_list_manual_mode), VTY_NEWLINE); + if (bts->neigh_list_manual_mode != NL_MODE_AUTOMATIC) { + for (i = 0; i < 1024; i++) { + if (bitvec_get_bit_pos(&bts->si_common.neigh_list, i)) + vty_out(vty, " neighbor-list add arfcn %u%s", + i, VTY_NEWLINE); + } + } + if (bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) { + for (i = 0; i < 1024; i++) { + if (bitvec_get_bit_pos(&bts->si_common.si5_neigh_list, i)) + vty_out(vty, " si5 neighbor-list add arfcn %u%s", + i, VTY_NEWLINE); + } + } + + config_write_bts_gprs(vty, bts); + + if (bts->model->config_write_bts) + bts->model->config_write_bts(vty, bts); + + llist_for_each_entry(trx, &bts->trx_list, list) + config_write_trx_single(vty, trx); +} + +static int config_write_bts(struct vty *v) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(v); + struct gsm_bts *bts; + + llist_for_each_entry(bts, &gsmnet->bts_list, list) + config_write_bts_single(v, bts); + + return CMD_SUCCESS; +} + +static int config_write_net(struct vty *vty) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + vty_out(vty, "network%s", VTY_NEWLINE); + vty_out(vty, " network country code %u%s", gsmnet->country_code, VTY_NEWLINE); + vty_out(vty, " mobile network code %u%s", gsmnet->network_code, VTY_NEWLINE); + vty_out(vty, " short name %s%s", gsmnet->name_short, VTY_NEWLINE); + vty_out(vty, " long name %s%s", gsmnet->name_long, VTY_NEWLINE); + vty_out(vty, " auth policy %s%s", gsm_auth_policy_name(gsmnet->auth_policy), VTY_NEWLINE); + vty_out(vty, " location updating reject cause %u%s", + gsmnet->reject_cause, VTY_NEWLINE); + vty_out(vty, " encryption a5 %u%s", gsmnet->a5_encryption, VTY_NEWLINE); + vty_out(vty, " neci %u%s", gsmnet->neci, VTY_NEWLINE); + vty_out(vty, " paging any use tch %d%s", gsmnet->pag_any_tch, VTY_NEWLINE); + vty_out(vty, " rrlp mode %s%s", rrlp_mode_name(gsmnet->rrlp.mode), + VTY_NEWLINE); + vty_out(vty, " mm info %u%s", gsmnet->send_mm_info, VTY_NEWLINE); + vty_out(vty, " handover %u%s", gsmnet->handover.active, VTY_NEWLINE); + vty_out(vty, " handover window rxlev averaging %u%s", + gsmnet->handover.win_rxlev_avg, VTY_NEWLINE); + vty_out(vty, " handover window rxqual averaging %u%s", + gsmnet->handover.win_rxqual_avg, VTY_NEWLINE); + vty_out(vty, " handover window rxlev neighbor averaging %u%s", + gsmnet->handover.win_rxlev_avg_neigh, VTY_NEWLINE); + vty_out(vty, " handover power budget interval %u%s", + gsmnet->handover.pwr_interval, VTY_NEWLINE); + vty_out(vty, " handover power budget hysteresis %u%s", + gsmnet->handover.pwr_hysteresis, VTY_NEWLINE); + vty_out(vty, " handover maximum distance %u%s", + gsmnet->handover.max_distance, VTY_NEWLINE); + vty_out(vty, " timer t3101 %u%s", gsmnet->T3101, VTY_NEWLINE); + vty_out(vty, " timer t3103 %u%s", gsmnet->T3103, VTY_NEWLINE); + vty_out(vty, " timer t3105 %u%s", gsmnet->T3105, VTY_NEWLINE); + vty_out(vty, " timer t3107 %u%s", gsmnet->T3107, VTY_NEWLINE); + vty_out(vty, " timer t3109 %u%s", gsmnet->T3109, VTY_NEWLINE); + vty_out(vty, " timer t3111 %u%s", gsmnet->T3111, VTY_NEWLINE); + vty_out(vty, " timer t3113 %u%s", gsmnet->T3113, VTY_NEWLINE); + vty_out(vty, " timer t3115 %u%s", gsmnet->T3115, VTY_NEWLINE); + vty_out(vty, " timer t3117 %u%s", gsmnet->T3117, VTY_NEWLINE); + vty_out(vty, " timer t3119 %u%s", gsmnet->T3119, VTY_NEWLINE); + vty_out(vty, " timer t3122 %u%s", gsmnet->T3122, VTY_NEWLINE); + vty_out(vty, " timer t3141 %u%s", gsmnet->T3141, VTY_NEWLINE); + vty_out(vty, " dtx-used %u%s", gsmnet->dtx_enabled, VTY_NEWLINE); + vty_out(vty, " subscriber-keep-in-ram %d%s", + gsmnet->keep_subscr, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx) +{ + vty_out(vty, "TRX %u of BTS %u is on ARFCN %u%s", + trx->nr, trx->bts->nr, trx->arfcn, VTY_NEWLINE); + vty_out(vty, "Description: %s%s", + trx->description ? trx->description : "(null)", VTY_NEWLINE); + vty_out(vty, " RF Nominal Power: %d dBm, reduced by %u dB, " + "resulting BS power: %d dBm%s", + trx->nominal_power, trx->max_power_red, + trx->nominal_power - trx->max_power_red, VTY_NEWLINE); + vty_out(vty, " NM State: "); + net_dump_nmstate(vty, &trx->nm_state); + vty_out(vty, " Baseband Transceiver NM State: "); + net_dump_nmstate(vty, &trx->bb_transc.nm_state); + if (is_ipaccess_bts(trx->bts)) { + vty_out(vty, " ip.access stream ID: 0x%02x%s", + trx->rsl_tei, VTY_NEWLINE); + } else { + vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE); + e1isl_dump_vty(vty, trx->rsl_link); + } +} + +DEFUN(show_trx, + show_trx_cmd, + "show trx [bts_nr] [trx_nr]", + SHOW_STR "Display information about a TRX\n" + "BTS Number\n" + "TRX Number\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts = NULL; + struct gsm_bts_trx *trx; + int bts_nr, trx_nr; + + if (argc >= 1) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + if (bts_nr >= net->num_bts) { + vty_out(vty, "%% can't find BTS '%s'%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + bts = gsm_bts_num(net, bts_nr); + } + if (argc >= 2) { + trx_nr = atoi(argv[1]); + if (trx_nr >= bts->num_trx) { + vty_out(vty, "%% can't find TRX '%s'%s", argv[1], + VTY_NEWLINE); + return CMD_WARNING; + } + trx = gsm_bts_trx_num(bts, trx_nr); + trx_dump_vty(vty, trx); + return CMD_SUCCESS; + } + if (bts) { + /* print all TRX in this BTS */ + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + trx = gsm_bts_trx_num(bts, trx_nr); + trx_dump_vty(vty, trx); + } + return CMD_SUCCESS; + } + + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { + bts = gsm_bts_num(net, bts_nr); + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + trx = gsm_bts_trx_num(bts, trx_nr); + trx_dump_vty(vty, trx); + } + } + + return CMD_SUCCESS; +} + + +static void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts) +{ + vty_out(vty, "BTS %u, TRX %u, Timeslot %u, phys cfg %s", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan)); + if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) + vty_out(vty, " (%s mode)", + ts->flags & TS_F_PDCH_MODE ? "PDCH" : "TCH/F"); + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, " NM State: "); + net_dump_nmstate(vty, &ts->nm_state); + if (!is_ipaccess_bts(ts->trx->bts)) + vty_out(vty, " E1 Line %u, Timeslot %u, Subslot %u%s", + ts->e1_link.e1_nr, ts->e1_link.e1_ts, + ts->e1_link.e1_ts_ss, VTY_NEWLINE); +} + +DEFUN(show_ts, + show_ts_cmd, + "show timeslot [bts_nr] [trx_nr] [ts_nr]", + SHOW_STR "Display information about a TS\n" + "BTS Number\n" "TRX Number\n" "Timeslot Number\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts = NULL; + struct gsm_bts_trx *trx = NULL; + struct gsm_bts_trx_ts *ts = NULL; + int bts_nr, trx_nr, ts_nr; + + if (argc >= 1) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + if (bts_nr >= net->num_bts) { + vty_out(vty, "%% can't find BTS '%s'%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + bts = gsm_bts_num(net, bts_nr); + } + if (argc >= 2) { + trx_nr = atoi(argv[1]); + if (trx_nr >= bts->num_trx) { + vty_out(vty, "%% can't find TRX '%s'%s", argv[1], + VTY_NEWLINE); + return CMD_WARNING; + } + trx = gsm_bts_trx_num(bts, trx_nr); + } + if (argc >= 3) { + ts_nr = atoi(argv[2]); + if (ts_nr >= TRX_NR_TS) { + vty_out(vty, "%% can't find TS '%s'%s", argv[2], + VTY_NEWLINE); + return CMD_WARNING; + } + /* Fully Specified: print and exit */ + ts = &trx->ts[ts_nr]; + ts_dump_vty(vty, ts); + return CMD_SUCCESS; + } + + if (bts && trx) { + /* Iterate over all TS in this TRX */ + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + ts = &trx->ts[ts_nr]; + ts_dump_vty(vty, ts); + } + } else if (bts) { + /* Iterate over all TRX in this BTS, TS in each TRX */ + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + trx = gsm_bts_trx_num(bts, trx_nr); + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + ts = &trx->ts[ts_nr]; + ts_dump_vty(vty, ts); + } + } + } else { + /* Iterate over all BTS, TRX in each BTS, TS in each TRX */ + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { + bts = gsm_bts_num(net, bts_nr); + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + trx = gsm_bts_trx_num(bts, trx_nr); + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + ts = &trx->ts[ts_nr]; + ts_dump_vty(vty, ts); + } + } + } + } + + return CMD_SUCCESS; +} + +static void subscr_dump_vty(struct vty *vty, struct gsm_subscriber *subscr) +{ + vty_out(vty, " ID: %llu, Authorized: %d%s", subscr->id, + subscr->authorized, VTY_NEWLINE); + if (subscr->name) + vty_out(vty, " Name: '%s'%s", subscr->name, VTY_NEWLINE); + if (subscr->extension) + vty_out(vty, " Extension: %s%s", subscr->extension, + VTY_NEWLINE); + if (subscr->imsi) + vty_out(vty, " IMSI: %s%s", subscr->imsi, VTY_NEWLINE); + if (subscr->tmsi != GSM_RESERVED_TMSI) + vty_out(vty, " TMSI: %08X%s", subscr->tmsi, + VTY_NEWLINE); + + vty_out(vty, " Use count: %u%s", subscr->use_count, VTY_NEWLINE); +} + +static void meas_rep_dump_uni_vty(struct vty *vty, + struct gsm_meas_rep_unidir *mru, + const char *prefix, + const char *dir) +{ + vty_out(vty, "%s RXL-FULL-%s: %4d dBm, RXL-SUB-%s: %4d dBm ", + prefix, dir, rxlev2dbm(mru->full.rx_lev), + dir, rxlev2dbm(mru->sub.rx_lev)); + vty_out(vty, "RXQ-FULL-%s: %d, RXQ-SUB-%s: %d%s", + dir, mru->full.rx_qual, dir, mru->sub.rx_qual, + VTY_NEWLINE); +} + +static void meas_rep_dump_vty(struct vty *vty, struct gsm_meas_rep *mr, + const char *prefix) +{ + vty_out(vty, "%sMeasurement Report:%s", prefix, VTY_NEWLINE); + vty_out(vty, "%s Flags: %s%s%s%s%s", prefix, + mr->flags & MEAS_REP_F_UL_DTX ? "DTXu " : "", + mr->flags & MEAS_REP_F_DL_DTX ? "DTXd " : "", + mr->flags & MEAS_REP_F_FPC ? "FPC " : "", + mr->flags & MEAS_REP_F_DL_VALID ? " " : "DLinval ", + VTY_NEWLINE); + if (mr->flags & MEAS_REP_F_MS_TO) + vty_out(vty, "%s MS Timing Offset: %u%s", prefix, + mr->ms_timing_offset, VTY_NEWLINE); + if (mr->flags & MEAS_REP_F_MS_L1) + vty_out(vty, "%s L1 MS Power: %u dBm, Timing Advance: %u%s", + prefix, mr->ms_l1.pwr, mr->ms_l1.ta, VTY_NEWLINE); + if (mr->flags & MEAS_REP_F_DL_VALID) + meas_rep_dump_uni_vty(vty, &mr->dl, prefix, "dl"); + meas_rep_dump_uni_vty(vty, &mr->ul, prefix, "ul"); +} + +static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan) +{ + int idx; + + vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s", + lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, + lchan->nr, gsm_lchant_name(lchan->type), VTY_NEWLINE); + vty_out(vty, " Connection: %u, State: %s%s", + lchan->conn ? 1: 0, + gsm_lchans_name(lchan->state), VTY_NEWLINE); + vty_out(vty, " BS Power: %u dBm, MS Power: %u dBm%s", + lchan->ts->trx->nominal_power - lchan->ts->trx->max_power_red + - lchan->bs_power*2, + ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power), + VTY_NEWLINE); + if (lchan->conn && lchan->conn->subscr) { + vty_out(vty, " Subscriber:%s", VTY_NEWLINE); + subscr_dump_vty(vty, lchan->conn->subscr); + } else + vty_out(vty, " No Subscriber%s", VTY_NEWLINE); + if (is_ipaccess_bts(lchan->ts->trx->bts)) { + struct in_addr ia; + ia.s_addr = htonl(lchan->abis_ip.bound_ip); + vty_out(vty, " Bound IP: %s Port %u RTP_TYPE2=%u CONN_ID=%u%s", + inet_ntoa(ia), lchan->abis_ip.bound_port, + lchan->abis_ip.rtp_payload2, lchan->abis_ip.conn_id, + VTY_NEWLINE); + } + + /* we want to report the last measurement report */ + idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), + lchan->meas_rep_idx, 1); + meas_rep_dump_vty(vty, &lchan->meas_rep[idx], " "); +} + +static void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan) +{ + struct gsm_meas_rep *mr; + int idx; + + /* we want to report the last measurement report */ + idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), + lchan->meas_rep_idx, 1); + mr = &lchan->meas_rep[idx]; + + vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u, Type %s - " + "L1 MS Power: %u dBm RXL-FULL-dl: %4d dBm RXL-FULL-ul: %4d dBm%s", + lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, + lchan->nr, gsm_lchant_name(lchan->type), mr->ms_l1.pwr, + rxlev2dbm(mr->dl.full.rx_lev), + rxlev2dbm(mr->ul.full.rx_lev), + VTY_NEWLINE); +} + +static int lchan_summary(struct vty *vty, int argc, const char **argv, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + struct gsm_lchan *lchan; + int bts_nr, trx_nr, ts_nr, lchan_nr; + + if (argc >= 1) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + if (bts_nr >= net->num_bts) { + vty_out(vty, "%% can't find BTS %s%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + bts = gsm_bts_num(net, bts_nr); + } + if (argc >= 2) { + trx_nr = atoi(argv[1]); + if (trx_nr >= bts->num_trx) { + vty_out(vty, "%% can't find TRX %s%s", argv[1], + VTY_NEWLINE); + return CMD_WARNING; + } + trx = gsm_bts_trx_num(bts, trx_nr); + } + if (argc >= 3) { + ts_nr = atoi(argv[2]); + if (ts_nr >= TRX_NR_TS) { + vty_out(vty, "%% can't find TS %s%s", argv[2], + VTY_NEWLINE); + return CMD_WARNING; + } + ts = &trx->ts[ts_nr]; + } + if (argc >= 4) { + lchan_nr = atoi(argv[3]); + if (lchan_nr >= TS_MAX_LCHAN) { + vty_out(vty, "%% can't find LCHAN %s%s", argv[3], + VTY_NEWLINE); + return CMD_WARNING; + } + lchan = &ts->lchan[lchan_nr]; + dump_cb(vty, lchan); + return CMD_SUCCESS; + } + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { + bts = gsm_bts_num(net, bts_nr); + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + trx = gsm_bts_trx_num(bts, trx_nr); + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + ts = &trx->ts[ts_nr]; + for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; + lchan_nr++) { + lchan = &ts->lchan[lchan_nr]; + if (lchan->type == GSM_LCHAN_NONE) + continue; + dump_cb(vty, lchan); + } + } + } + } + + return CMD_SUCCESS; +} + + +DEFUN(show_lchan, + show_lchan_cmd, + "show lchan [bts_nr] [trx_nr] [ts_nr] [lchan_nr]", + SHOW_STR "Display information about a logical channel\n" + "BTS Number\n" "TRX Number\n" "Timeslot Number\n" + "Logical Channel Number\n") + +{ + return lchan_summary(vty, argc, argv, lchan_dump_full_vty); +} + +DEFUN(show_lchan_summary, + show_lchan_summary_cmd, + "show lchan summary [bts_nr] [trx_nr] [ts_nr] [lchan_nr]", + SHOW_STR "Display information about a logical channel\n" + "BTS Number\n" "TRX Number\n" "Timeslot Number\n" + "Logical Channel Number\n") +{ + return lchan_summary(vty, argc, argv, lchan_dump_short_vty); +} + +static void e1drv_dump_vty(struct vty *vty, struct e1inp_driver *drv) +{ + vty_out(vty, "E1 Input Driver %s%s", drv->name, VTY_NEWLINE); +} + +DEFUN(show_e1drv, + show_e1drv_cmd, + "show e1_driver", + SHOW_STR "Display information about available E1 drivers\n") +{ + struct e1inp_driver *drv; + + llist_for_each_entry(drv, &e1inp_driver_list, list) + e1drv_dump_vty(vty, drv); + + return CMD_SUCCESS; +} + +static void e1line_dump_vty(struct vty *vty, struct e1inp_line *line) +{ + vty_out(vty, "E1 Line Number %u, Name %s, Driver %s%s", + line->num, line->name ? line->name : "", + line->driver->name, VTY_NEWLINE); +} + +DEFUN(show_e1line, + show_e1line_cmd, + "show e1_line [line_nr]", + SHOW_STR "Display information about a E1 line\n" + "E1 Line Number\n") +{ + struct e1inp_line *line; + + if (argc >= 1) { + int num = atoi(argv[0]); + llist_for_each_entry(line, &e1inp_line_list, list) { + if (line->num == num) { + e1line_dump_vty(vty, line); + return CMD_SUCCESS; + } + } + return CMD_WARNING; + } + + llist_for_each_entry(line, &e1inp_line_list, list) + e1line_dump_vty(vty, line); + + return CMD_SUCCESS; +} + +static void e1ts_dump_vty(struct vty *vty, struct e1inp_ts *ts) +{ + if (ts->type == E1INP_TS_TYPE_NONE) + return; + vty_out(vty, "E1 Timeslot %2u of Line %u is Type %s%s", + ts->num, ts->line->num, e1inp_tstype_name(ts->type), + VTY_NEWLINE); +} + +DEFUN(show_e1ts, + show_e1ts_cmd, + "show e1_timeslot [line_nr] [ts_nr]", + SHOW_STR "Display information about a E1 timeslot\n" + "E1 Line Number\n" "E1 Timeslot Number\n") +{ + struct e1inp_line *line = NULL; + struct e1inp_ts *ts; + int ts_nr; + + if (argc == 0) { + llist_for_each_entry(line, &e1inp_line_list, list) { + for (ts_nr = 0; ts_nr < NUM_E1_TS; ts_nr++) { + ts = &line->ts[ts_nr]; + e1ts_dump_vty(vty, ts); + } + } + return CMD_SUCCESS; + } + if (argc >= 1) { + int num = atoi(argv[0]); + llist_for_each_entry(line, &e1inp_line_list, list) { + if (line->num == num) + break; + } + if (!line || line->num != num) { + vty_out(vty, "E1 line %s is invalid%s", + argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + } + if (argc >= 2) { + ts_nr = atoi(argv[1]); + if (ts_nr > NUM_E1_TS) { + vty_out(vty, "E1 timeslot %s is invalid%s", + argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + ts = &line->ts[ts_nr]; + e1ts_dump_vty(vty, ts); + return CMD_SUCCESS; + } else { + for (ts_nr = 0; ts_nr < NUM_E1_TS; ts_nr++) { + ts = &line->ts[ts_nr]; + e1ts_dump_vty(vty, ts); + } + return CMD_SUCCESS; + } + return CMD_SUCCESS; +} + +static void paging_dump_vty(struct vty *vty, struct gsm_paging_request *pag) +{ + vty_out(vty, "Paging on BTS %u%s", pag->bts->nr, VTY_NEWLINE); + subscr_dump_vty(vty, pag->subscr); +} + +static void bts_paging_dump_vty(struct vty *vty, struct gsm_bts *bts) +{ + struct gsm_paging_request *pag; + + llist_for_each_entry(pag, &bts->paging.pending_requests, entry) + paging_dump_vty(vty, pag); +} + +DEFUN(show_paging, + show_paging_cmd, + "show paging [bts_nr]", + SHOW_STR "Display information about paging reuqests of a BTS\n" + "BTS Number\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts; + int bts_nr; + + if (argc >= 1) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + if (bts_nr >= net->num_bts) { + vty_out(vty, "%% can't find BTS %s%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + bts = gsm_bts_num(net, bts_nr); + bts_paging_dump_vty(vty, bts); + + return CMD_SUCCESS; + } + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { + bts = gsm_bts_num(net, bts_nr); + bts_paging_dump_vty(vty, bts); + } + + return CMD_SUCCESS; +} + +#define NETWORK_STR "Configure the GSM network\n" + +DEFUN(cfg_net, + cfg_net_cmd, + "network", NETWORK_STR) +{ + vty->index = gsmnet_from_vty(vty); + vty->node = GSMNET_NODE; + + return CMD_SUCCESS; +} + + +DEFUN(cfg_net_ncc, + cfg_net_ncc_cmd, + "network country code <1-999>", + "Set the GSM network country code") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + gsmnet->country_code = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_mnc, + cfg_net_mnc_cmd, + "mobile network code <1-999>", + "Set the GSM mobile network code") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + gsmnet->network_code = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_name_short, + cfg_net_name_short_cmd, + "short name NAME", + "Set the short GSM network name") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + bsc_replace_string(gsmnet, &gsmnet->name_short, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_name_long, + cfg_net_name_long_cmd, + "long name NAME", + "Set the long GSM network name") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + bsc_replace_string(gsmnet, &gsmnet->name_long, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_auth_policy, + cfg_net_auth_policy_cmd, + "auth policy (closed|accept-all|token)", + "Authentication (not cryptographic)\n" + "Set the GSM network authentication policy\n" + "Require the MS to be activated in HLR\n" + "Accept all MS, whether in HLR or not\n" + "Use SMS-token based authentication\n") +{ + enum gsm_auth_policy policy = gsm_auth_policy_parse(argv[0]); + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + gsmnet->auth_policy = policy; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_reject_cause, + cfg_net_reject_cause_cmd, + "location updating reject cause <2-111>", + "Set the reject cause of location updating reject\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + gsmnet->reject_cause = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_encryption, + cfg_net_encryption_cmd, + "encryption a5 (0|1|2)", + "Encryption options\n" + "A5 encryption\n" "A5/0: No encryption\n" + "A5/1: Encryption\n" "A5/2: Export-grade Encryption\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + gsmnet->a5_encryption= atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_neci, + cfg_net_neci_cmd, + "neci (0|1)", + "New Establish Cause Indication\n" + "Don't set the NECI bit\n" "Set the NECI bit\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + gsmnet->neci = atoi(argv[0]); + gsm_net_update_ctype(gsmnet); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_rrlp_mode, cfg_net_rrlp_mode_cmd, + "rrlp mode (none|ms-based|ms-preferred|ass-preferred)", + "Radio Resource Location Protocol\n" + "Set the Radio Resource Location Protocol Mode\n" + "Don't send RRLP request\n" + "Request MS-based location\n" + "Request any location, prefer MS-based\n" + "Request any location, prefer MS-assisted\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + gsmnet->rrlp.mode = rrlp_mode_parse(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_mm_info, cfg_net_mm_info_cmd, + "mm info (0|1)", + "Whether to send MM INFO after LOC UPD ACCEPT") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + gsmnet->send_mm_info = atoi(argv[0]); + + return CMD_SUCCESS; +} + +#define HANDOVER_STR "Handover Options\n" + +DEFUN(cfg_net_handover, cfg_net_handover_cmd, + "handover (0|1)", + HANDOVER_STR + "Don't perform in-call handover\n" + "Perform in-call handover\n") +{ + int enable = atoi(argv[0]); + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + if (enable && ipacc_rtp_direct) { + vty_out(vty, "%% Cannot enable handover unless RTP Proxy mode " + "is enabled by using the -P command line option%s", + VTY_NEWLINE); + return CMD_WARNING; + } + gsmnet->handover.active = enable; + + return CMD_SUCCESS; +} + +#define HO_WIN_STR HANDOVER_STR "Measurement Window\n" +#define HO_WIN_RXLEV_STR HO_WIN_STR "Received Level Averaging\n" +#define HO_WIN_RXQUAL_STR HO_WIN_STR "Received Quality Averaging\n" +#define HO_PBUDGET_STR HANDOVER_STR "Power Budget\n" + +DEFUN(cfg_net_ho_win_rxlev_avg, cfg_net_ho_win_rxlev_avg_cmd, + "handover window rxlev averaging <1-10>", + HO_WIN_RXLEV_STR + "How many RxLev measurements are used for averaging") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->handover.win_rxlev_avg = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_ho_win_rxqual_avg, cfg_net_ho_win_rxqual_avg_cmd, + "handover window rxqual averaging <1-10>", + HO_WIN_RXQUAL_STR + "How many RxQual measurements are used for averaging") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->handover.win_rxqual_avg = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_ho_win_rxlev_neigh_avg, cfg_net_ho_win_rxlev_avg_neigh_cmd, + "handover window rxlev neighbor averaging <1-10>", + HO_WIN_RXLEV_STR + "How many RxQual measurements are used for averaging") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->handover.win_rxlev_avg_neigh = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_ho_pwr_interval, cfg_net_ho_pwr_interval_cmd, + "handover power budget interval <1-99>", + HO_PBUDGET_STR + "How often to check if we have a better cell (SACCH frames)") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->handover.pwr_interval = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_ho_pwr_hysteresis, cfg_net_ho_pwr_hysteresis_cmd, + "handover power budget hysteresis <0-999>", + HO_PBUDGET_STR + "How many dB does a neighbor to be stronger to become a HO candidate") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->handover.pwr_hysteresis = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_ho_max_distance, cfg_net_ho_max_distance_cmd, + "handover maximum distance <0-9999>", + HANDOVER_STR + "How big is the maximum timing advance before HO is forced") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->handover.max_distance = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_pag_any_tch, + cfg_net_pag_any_tch_cmd, + "paging any use tch (0|1)", + "Assign a TCH when receiving a Paging Any request") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->pag_any_tch = atoi(argv[0]); + gsm_net_update_ctype(gsmnet); + return CMD_SUCCESS; +} + +#define DECLARE_TIMER(number, doc) \ + DEFUN(cfg_net_T##number, \ + cfg_net_T##number##_cmd, \ + "timer t" #number " <0-65535>", \ + "Configure GSM Timers\n" \ + doc) \ +{ \ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); \ + int value = atoi(argv[0]); \ + \ + if (value < 0 || value > 65535) { \ + vty_out(vty, "Timer value %s out of range.%s", \ + argv[0], VTY_NEWLINE); \ + return CMD_WARNING; \ + } \ + \ + gsmnet->T##number = value; \ + return CMD_SUCCESS; \ +} + +DECLARE_TIMER(3101, "Set the timeout value for IMMEDIATE ASSIGNMENT.") +DECLARE_TIMER(3103, "Set the timeout value for HANDOVER.") +DECLARE_TIMER(3105, "Currently not used.") +DECLARE_TIMER(3107, "Currently not used.") +DECLARE_TIMER(3109, "Currently not used.") +DECLARE_TIMER(3111, "Set the RSL timeout to wait before releasing the RF Channel.") +DECLARE_TIMER(3113, "Set the time to try paging a subscriber.") +DECLARE_TIMER(3115, "Currently not used.") +DECLARE_TIMER(3117, "Currently not used.") +DECLARE_TIMER(3119, "Currently not used.") +DECLARE_TIMER(3122, "Waiting time (seconds) after IMM ASS REJECT") +DECLARE_TIMER(3141, "Currently not used.") + +DEFUN(cfg_net_dtx, + cfg_net_dtx_cmd, + "dtx-used (0|1)", + "Enable the usage of DTX.\n" + "DTX is enabled/disabled") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->dtx_enabled = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_subscr_keep, + cfg_net_subscr_keep_cmd, + "subscriber-keep-in-ram (0|1)", + "Keep unused subscribers in RAM.\n" + "Delete unused subscribers\n" "Keep unused subscribers\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->keep_subscr = atoi(argv[0]); + return CMD_SUCCESS; +} + +/* per-BTS configuration */ +DEFUN(cfg_bts, + cfg_bts_cmd, + "bts BTS_NR", + "Select a BTS to configure\n" + "BTS Number\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + int bts_nr = atoi(argv[0]); + struct gsm_bts *bts; + + if (bts_nr > gsmnet->num_bts) { + vty_out(vty, "%% The next unused BTS number is %u%s", + gsmnet->num_bts, VTY_NEWLINE); + return CMD_WARNING; + } else if (bts_nr == gsmnet->num_bts) { + /* allocate a new one */ + bts = gsm_bts_alloc(gsmnet, GSM_BTS_TYPE_UNKNOWN, + HARDCODED_TSC, HARDCODED_BSIC); + } else + bts = gsm_bts_num(gsmnet, bts_nr); + + if (!bts) { + vty_out(vty, "%% Unable to allocate BTS %u%s", + gsmnet->num_bts, VTY_NEWLINE); + return CMD_WARNING; + } + + vty->index = bts; + vty->index_sub = &bts->description; + vty->node = BTS_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_type, + cfg_bts_type_cmd, + "type TYPE", + "Set the BTS type\n") +{ + struct gsm_bts *bts = vty->index; + int rc; + + rc = gsm_set_bts_type(bts, parse_btstype(argv[0])); + if (rc < 0) + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_band, + cfg_bts_band_cmd, + "band BAND", + "Set the frequency band of this BTS\n") +{ + struct gsm_bts *bts = vty->index; + int band = gsm_band_parse(argv[0]); + + if (band < 0) { + vty_out(vty, "%% BAND %d is not a valid GSM band%s", + band, VTY_NEWLINE); + return CMD_WARNING; + } + + bts->band = band; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_ci, + cfg_bts_ci_cmd, + "cell_identity <0-65535>", + "Set the Cell identity of this BTS\n") +{ + struct gsm_bts *bts = vty->index; + int ci = atoi(argv[0]); + + if (ci < 0 || ci > 0xffff) { + vty_out(vty, "%% CI %d is not in the valid range (0-65535)%s", + ci, VTY_NEWLINE); + return CMD_WARNING; + } + bts->cell_identity = ci; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_lac, + cfg_bts_lac_cmd, + "location_area_code <0-65535>", + "Set the Location Area Code (LAC) of this BTS\n") +{ + struct gsm_bts *bts = vty->index; + int lac = atoi(argv[0]); + + if (lac < 0 || lac > 0xffff) { + vty_out(vty, "%% LAC %d is not in the valid range (0-65535)%s", + lac, VTY_NEWLINE); + return CMD_WARNING; + } + + if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) { + vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s", + lac, VTY_NEWLINE); + return CMD_WARNING; + } + + bts->location_area_code = lac; + + return CMD_SUCCESS; +} + + +DEFUN(cfg_bts_tsc, + cfg_bts_tsc_cmd, + "training_sequence_code <0-255>", + "Set the Training Sequence Code (TSC) of this BTS\n") +{ + struct gsm_bts *bts = vty->index; + int tsc = atoi(argv[0]); + + if (tsc < 0 || tsc > 0xff) { + vty_out(vty, "%% TSC %d is not in the valid range (0-255)%s", + tsc, VTY_NEWLINE); + return CMD_WARNING; + } + bts->tsc = tsc; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_bsic, + cfg_bts_bsic_cmd, + "base_station_id_code <0-63>", + "Set the Base Station Identity Code (BSIC) of this BTS\n") +{ + struct gsm_bts *bts = vty->index; + int bsic = atoi(argv[0]); + + if (bsic < 0 || bsic > 0x3f) { + vty_out(vty, "%% BSIC %d is not in the valid range (0-255)%s", + bsic, VTY_NEWLINE); + return CMD_WARNING; + } + bts->bsic = bsic; + + return CMD_SUCCESS; +} + + +DEFUN(cfg_bts_unit_id, + cfg_bts_unit_id_cmd, + "ip.access unit_id <0-65534> <0-255>", + "Set the ip.access BTS Unit ID of this BTS\n") +{ + struct gsm_bts *bts = vty->index; + int site_id = atoi(argv[0]); + int bts_id = atoi(argv[1]); + + if (!is_ipaccess_bts(bts)) { + vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->ip_access.site_id = site_id; + bts->ip_access.bts_id = bts_id; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_serno, + cfg_bts_serno_cmd, + "hsl serial-number STRING", + "Set the HSL Serial Number of this BTS\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->type != GSM_BTS_TYPE_HSL_FEMTO) { + vty_out(vty, "%% BTS is not of HSL type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->hsl.serno = strtoul(argv[0], NULL, 10); + + return CMD_SUCCESS; +} + +#define OML_STR "Organization & Maintenance Link\n" +#define IPA_STR "ip.access Specific Options\n" + +DEFUN(cfg_bts_stream_id, + cfg_bts_stream_id_cmd, + "oml ip.access stream_id <0-255>", + OML_STR IPA_STR + "Set the ip.access Stream ID of the OML link of this BTS\n") +{ + struct gsm_bts *bts = vty->index; + int stream_id = atoi(argv[0]); + + if (!is_ipaccess_bts(bts)) { + vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->oml_tei = stream_id; + + return CMD_SUCCESS; +} + +#define OML_E1_STR OML_STR "E1 Line\n" + +DEFUN(cfg_bts_oml_e1, + cfg_bts_oml_e1_cmd, + "oml e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)", + OML_E1_STR + "E1 interface to be used for OML\n") +{ + struct gsm_bts *bts = vty->index; + + parse_e1_link(&bts->oml_e1_link, argv[0], argv[1], argv[2]); + + return CMD_SUCCESS; +} + + +DEFUN(cfg_bts_oml_e1_tei, + cfg_bts_oml_e1_tei_cmd, + "oml e1 tei <0-63>", + OML_E1_STR + "Set the TEI to be used for OML") +{ + struct gsm_bts *bts = vty->index; + + bts->oml_tei = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_challoc, cfg_bts_challoc_cmd, + "channel allocator (ascending|descending)", + "Channnel Allocator\n" "Channel Allocator\n" + "Allocate Timeslots and Transceivers in ascending order\n" + "Allocate Timeslots and Transceivers in descending order\n") +{ + struct gsm_bts *bts = vty->index; + + if (!strcmp(argv[0], "ascending")) + bts->chan_alloc_reverse = 0; + else + bts->chan_alloc_reverse = 1; + + return CMD_SUCCESS; +} + +#define RACH_STR "Random Access Control Channel\n" + +DEFUN(cfg_bts_rach_tx_integer, + cfg_bts_rach_tx_integer_cmd, + "rach tx integer <0-15>", + RACH_STR + "Set the raw tx integer value in RACH Control parameters IE") +{ + struct gsm_bts *bts = vty->index; + bts->si_common.rach_control.tx_integer = atoi(argv[0]) & 0xf; + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_rach_max_trans, + cfg_bts_rach_max_trans_cmd, + "rach max transmission (1|2|4|7)", + RACH_STR + "Set the maximum number of RACH burst transmissions") +{ + struct gsm_bts *bts = vty->index; + bts->si_common.rach_control.max_trans = rach_max_trans_val2raw(atoi(argv[0])); + return CMD_SUCCESS; +} + +#define NM_STR "Network Management\n" + +DEFUN(cfg_bts_rach_nm_b_thresh, + cfg_bts_rach_nm_b_thresh_cmd, + "rach nm busy threshold <0-255>", + RACH_STR NM_STR + "Set the NM Busy Threshold in dB") +{ + struct gsm_bts *bts = vty->index; + bts->rach_b_thresh = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_rach_nm_ldavg, + cfg_bts_rach_nm_ldavg_cmd, + "rach nm load average <0-65535>", + RACH_STR NM_STR + "Set the NM Loadaverage Slots value") +{ + struct gsm_bts *bts = vty->index; + bts->rach_ldavg_slots = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_cell_barred, cfg_bts_cell_barred_cmd, + "cell barred (0|1)", + "Should this cell be barred from access?") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.rach_control.cell_bar = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_rach_ec_allowed, cfg_bts_rach_ec_allowed_cmd, + "rach emergency call allowed (0|1)", + "Should this cell allow emergency calls?") +{ + struct gsm_bts *bts = vty->index; + + if (atoi(argv[0]) == 0) + bts->si_common.rach_control.t2 |= 0x4; + else + bts->si_common.rach_control.t2 &= ~0x4; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_ms_max_power, cfg_bts_ms_max_power_cmd, + "ms max power <0-40>", + "Maximum transmit power of the MS") +{ + struct gsm_bts *bts = vty->index; + + bts->ms_max_power = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_cell_resel_hyst, cfg_bts_cell_resel_hyst_cmd, + "cell reselection hysteresis <0-14>", + "Cell Re-Selection Hysteresis in dB") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_sel_par.cell_resel_hyst = atoi(argv[0])/2; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_rxlev_acc_min, cfg_bts_rxlev_acc_min_cmd, + "rxlev access min <0-63>", + "Minimum RxLev needed for cell access (better than -110dBm)") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_sel_par.rxlev_acc_min = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_cell_bar_qualify, cfg_bts_cell_bar_qualify_cmd, + "cell bar qualify (0|1)", + "Cell Bar Qualify") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.cbq = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_cell_resel_ofs, cfg_bts_cell_resel_ofs_cmd, + "cell reselection offset <0-126>", + "Cell Re-Selection Offset in dB") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.cell_resel_off = atoi(argv[0])/2; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_temp_ofs, cfg_bts_temp_ofs_cmd, + "temporary offset <0-60>", + "Cell selection temporary negative offset in dB") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.temp_offs = atoi(argv[0])/10; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_temp_ofs_inf, cfg_bts_temp_ofs_inf_cmd, + "temporary offset infinite", + "Sets cell selection temporary negative offset to infinity") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.temp_offs = 7; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_penalty_time, cfg_bts_penalty_time_cmd, + "penalty time <20-620>", + "Cell selection penalty time in seconds (by 20s increments)") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.penalty_time = (atoi(argv[0])-20)/20; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_penalty_time_rsvd, cfg_bts_penalty_time_rsvd_cmd, + "penalty time reserved", + "Set cell selection penalty time to reserved value 31\n" + "(indicate that CELL_RESELECT_OFFSET is subtracted from C2 " + "and TEMPORARY_OFFSET is ignored)") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.penalty_time = 31; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_per_loc_upd, cfg_bts_per_loc_upd_cmd, + "periodic location update <0-1530>", + "Periodic Location Updating Interval in Minutes") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.chan_desc.t3212 = atoi(argv[0]) / 6; + + return CMD_SUCCESS; +} + +#define GPRS_TEXT "GPRS Packet Network\n" + +DEFUN(cfg_bts_prs_bvci, cfg_bts_gprs_bvci_cmd, + "gprs cell bvci <2-65535>", + GPRS_TEXT + "GPRS Cell Settings\n" + "GPRS BSSGP VC Identifier") +{ + struct gsm_bts *bts = vty->index; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.cell.bvci = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_nsei, cfg_bts_gprs_nsei_cmd, + "gprs nsei <0-65535>", + GPRS_TEXT + "GPRS NS Entity Identifier") +{ + struct gsm_bts *bts = vty->index; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.nse.nsei = atoi(argv[0]); + + return CMD_SUCCESS; +} + +#define NSVC_TEXT "Network Service Virtual Connection (NS-VC)\n" \ + "NSVC Logical Number\n" + +DEFUN(cfg_bts_gprs_nsvci, cfg_bts_gprs_nsvci_cmd, + "gprs nsvc <0-1> nsvci <0-65535>", + GPRS_TEXT NSVC_TEXT + "NS Virtual Connection Identifier\n" + "GPRS NS VC Identifier") +{ + struct gsm_bts *bts = vty->index; + int idx = atoi(argv[0]); + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.nsvc[idx].nsvci = atoi(argv[1]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_nsvc_lport, cfg_bts_gprs_nsvc_lport_cmd, + "gprs nsvc <0-1> local udp port <0-65535>", + GPRS_TEXT NSVC_TEXT + "GPRS NS Local UDP Port") +{ + struct gsm_bts *bts = vty->index; + int idx = atoi(argv[0]); + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.nsvc[idx].local_port = atoi(argv[1]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_nsvc_rport, cfg_bts_gprs_nsvc_rport_cmd, + "gprs nsvc <0-1> remote udp port <0-65535>", + GPRS_TEXT NSVC_TEXT + "GPRS NS Remote UDP Port") +{ + struct gsm_bts *bts = vty->index; + int idx = atoi(argv[0]); + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.nsvc[idx].remote_port = atoi(argv[1]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_nsvc_rip, cfg_bts_gprs_nsvc_rip_cmd, + "gprs nsvc <0-1> remote ip A.B.C.D", + GPRS_TEXT NSVC_TEXT + "GPRS NS Remote IP Address") +{ + struct gsm_bts *bts = vty->index; + int idx = atoi(argv[0]); + struct in_addr ia; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + inet_aton(argv[1], &ia); + bts->gprs.nsvc[idx].remote_ip = ntohl(ia.s_addr); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_pag_free, cfg_bts_pag_free_cmd, + "paging free FREE_NR", + "Only page when having a certain amount of free slots. -1 to disable") +{ + struct gsm_bts *bts = vty->index; + + bts->paging.free_chans_need = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_ns_timer, cfg_bts_gprs_ns_timer_cmd, + "gprs ns timer " NS_TIMERS " <0-255>", + GPRS_TEXT "Network Service\n" + "Network Service Timer\n" + NS_TIMERS_HELP "Timer Value\n") +{ + struct gsm_bts *bts = vty->index; + int idx = get_string_value(gprs_ns_timer_strs, argv[0]); + int val = atoi(argv[1]); + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.nse.timer)) + return CMD_WARNING; + + bts->gprs.nse.timer[idx] = val; + + return CMD_SUCCESS; +} + +#define BSSGP_TIMERS "(blocking-timer|blocking-retries|unblocking-retries|reset-timer|reset-retries|suspend-timer|suspend-retries|resume-timer|resume-retries|capability-update-timer|capability-update-retries)" +#define BSSGP_TIMERS_HELP \ + "Tbvc-block timeout\n" \ + "Tbvc-block retries\n" \ + "Tbvc-unblock retries\n" \ + "Tbvcc-reset timeout\n" \ + "Tbvc-reset retries\n" \ + "Tbvc-suspend timeout\n" \ + "Tbvc-suspend retries\n" \ + "Tbvc-resume timeout\n" \ + "Tbvc-resume retries\n" \ + "Tbvc-capa-update timeout\n" \ + "Tbvc-capa-update retries\n" + +DEFUN(cfg_bts_gprs_cell_timer, cfg_bts_gprs_cell_timer_cmd, + "gprs cell timer " BSSGP_TIMERS " <0-255>", + GPRS_TEXT "Cell / BSSGP\n" + "Cell/BSSGP Timer\n" + BSSGP_TIMERS_HELP "Timer Value\n") +{ + struct gsm_bts *bts = vty->index; + int idx = get_string_value(gprs_bssgp_cfg_strs, argv[0]); + int val = atoi(argv[1]); + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.cell.timer)) + return CMD_WARNING; + + bts->gprs.cell.timer[idx] = val; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_rac, cfg_bts_gprs_rac_cmd, + "gprs routing area <0-255>", + GPRS_TEXT + "GPRS Routing Area Code") +{ + struct gsm_bts *bts = vty->index; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.rac = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_mode, cfg_bts_gprs_mode_cmd, + "gprs mode (none|gprs|egprs)", + GPRS_TEXT + "GPRS Mode for this BTS\n" + "GPRS Disabled on this BTS\n" + "GPRS Enabled on this BTS\n" + "EGPRS (EDGE) Enabled on this BTS\n") +{ + struct gsm_bts *bts = vty->index; + enum bts_gprs_mode mode = bts_gprs_mode_parse(argv[0]); + + if (mode != BTS_GPRS_NONE && + !gsm_bts_has_feature(bts, BTS_FEAT_GPRS)) { + vty_out(vty, "This BTS type does not support %s%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + if (mode == BTS_GPRS_EGPRS && + !gsm_bts_has_feature(bts, BTS_FEAT_EGPRS)) { + vty_out(vty, "This BTS type does not support %s%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.mode = mode; + + return CMD_SUCCESS; +} + +#define SI_TEXT "System Information Messages\n" +#define SI_TYPE_TEXT "(1|2|3|4|5|6|7|8|9|10|13|16|17|18|19|20|2bis|2ter|2quater|5bis|5ter)" +#define SI_TYPE_HELP "System Information Type 1\n" \ + "System Information Type 2\n" \ + "System Information Type 3\n" \ + "System Information Type 4\n" \ + "System Information Type 5\n" \ + "System Information Type 6\n" \ + "System Information Type 7\n" \ + "System Information Type 8\n" \ + "System Information Type 9\n" \ + "System Information Type 10\n" \ + "System Information Type 13\n" \ + "System Information Type 16\n" \ + "System Information Type 17\n" \ + "System Information Type 18\n" \ + "System Information Type 19\n" \ + "System Information Type 20\n" \ + "System Information Type 2bis\n" \ + "System Information Type 2ter\n" \ + "System Information Type 2quater\n" \ + "System Information Type 5bis\n" \ + "System Information Type 5ter\n" + +DEFUN(cfg_bts_si_mode, cfg_bts_si_mode_cmd, + "system-information " SI_TYPE_TEXT " mode (static|computed)", + SI_TEXT SI_TYPE_HELP + "System Information Mode\n" + "Static user-specified\n" + "Dynamic, BSC-computed\n") +{ + struct gsm_bts *bts = vty->index; + int type; + + type = get_string_value(osmo_sitype_strs, argv[0]); + if (type < 0) { + vty_out(vty, "Error SI Type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[1], "static")) + bts->si_mode_static |= (1 << type); + else + bts->si_mode_static &= ~(1 << type); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_si_static, cfg_bts_si_static_cmd, + "system-information " SI_TYPE_TEXT " static HEXSTRING", + SI_TEXT SI_TYPE_HELP + "Static System Information filling\n" + "Static user-specified SI content in HEX notation\n") +{ + struct gsm_bts *bts = vty->index; + int rc, type; + + type = get_string_value(osmo_sitype_strs, argv[0]); + if (type < 0) { + vty_out(vty, "Error SI Type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!(bts->si_mode_static & (1 << type))) { + vty_out(vty, "SI Type %s is not configured in static mode%s", + get_value_string(osmo_sitype_strs, type), VTY_NEWLINE); + return CMD_WARNING; + } + + /* Fill buffer with padding pattern */ + memset(bts->si_buf[type], 0x2b, sizeof(bts->si_buf[type])); + + /* Parse the user-specified SI in hex format, [partially] overwriting padding */ + rc = hexparse(argv[1], bts->si_buf[type], sizeof(bts->si_buf[0])); + if (rc < 0 || rc > sizeof(bts->si_buf[0])) { + vty_out(vty, "Error parsing HEXSTRING%s", VTY_NEWLINE); + return CMD_WARNING; + } + + /* Mark this SI as present */ + bts->si_valid |= (1 << type); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_neigh_mode, cfg_bts_neigh_mode_cmd, + "neighbor-list mode (automatic|manual|manual-si5)", + "Neighbor List\n" "Mode of Neighbor List generation\n" + "Automatically from all BTS in this OpenBSC\n" "Manual\n" + "Manual with different lists for SI2 and SI5\n") +{ + struct gsm_bts *bts = vty->index; + int mode = get_string_value(bts_neigh_mode_strs, argv[0]); + + switch (mode) { + case NL_MODE_MANUAL_SI5SEP: + case NL_MODE_MANUAL: + /* make sure we clear the current list when switching to + * manual mode */ + if (bts->neigh_list_manual_mode == 0) + memset(&bts->si_common.data.neigh_list, 0, + sizeof(bts->si_common.data.neigh_list)); + break; + default: + break; + } + + bts->neigh_list_manual_mode = mode; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_neigh, cfg_bts_neigh_cmd, + "neighbor-list (add|del) arfcn <0-1024>", + "Neighbor List\n" "Add to manual neighbor list\n" + "Delete from manual neighbor list\n" "ARFCN of neighbor\n" + "ARFCN of neighbor\n") +{ + struct gsm_bts *bts = vty->index; + struct bitvec *bv = &bts->si_common.neigh_list; + uint16_t arfcn = atoi(argv[1]); + + if (!bts->neigh_list_manual_mode) { + vty_out(vty, "%% Cannot configure neighbor list in " + "automatic mode%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[0], "add")) + bitvec_set_bit_pos(bv, arfcn, 1); + else + bitvec_set_bit_pos(bv, arfcn, 0); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_si5_neigh, cfg_bts_si5_neigh_cmd, + "si5 neighbor-list (add|del) arfcn <0-1024>", + "SI5 Neighbor List\n" "Add to manual SI5 neighbor list\n" + "Delete from SI5 manual neighbor list\n" "ARFCN of neighbor\n" + "ARFCN of neighbor\n") +{ + struct gsm_bts *bts = vty->index; + struct bitvec *bv = &bts->si_common.si5_neigh_list; + uint16_t arfcn = atoi(argv[1]); + + if (!bts->neigh_list_manual_mode) { + vty_out(vty, "%% Cannot configure neighbor list in " + "automatic mode%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[0], "add")) + bitvec_set_bit_pos(bv, arfcn, 1); + else + bitvec_set_bit_pos(bv, arfcn, 0); + + return CMD_SUCCESS; +} + +#define TRX_TEXT "Radio Transceiver\n" + +/* per TRX configuration */ +DEFUN(cfg_trx, + cfg_trx_cmd, + "trx TRX_NR", + TRX_TEXT + "Select a TRX to configure") +{ + int trx_nr = atoi(argv[0]); + struct gsm_bts *bts = vty->index; + struct gsm_bts_trx *trx; + + if (trx_nr > bts->num_trx) { + vty_out(vty, "%% The next unused TRX number in this BTS is %u%s", + bts->num_trx, VTY_NEWLINE); + return CMD_WARNING; + } else if (trx_nr == bts->num_trx) { + /* we need to allocate a new one */ + trx = gsm_bts_trx_alloc(bts); + } else + trx = gsm_bts_trx_num(bts, trx_nr); + + if (!trx) + return CMD_WARNING; + + vty->index = trx; + vty->index_sub = &trx->description; + vty->node = TRX_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_arfcn, + cfg_trx_arfcn_cmd, + "arfcn <0-1024>", + "Set the ARFCN for this TRX\n") +{ + int arfcn = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + + /* FIXME: check if this ARFCN is supported by this TRX */ + + trx->arfcn = arfcn; + + /* FIXME: patch ARFCN into SYSTEM INFORMATION */ + /* FIXME: use OML layer to update the ARFCN */ + /* FIXME: use RSL layer to update SYSTEM INFORMATION */ + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_nominal_power, + cfg_trx_nominal_power_cmd, + "nominal power <0-100>", + "Nominal TRX RF Power in dB\n") +{ + struct gsm_bts_trx *trx = vty->index; + + trx->nominal_power = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_max_power_red, + cfg_trx_max_power_red_cmd, + "max_power_red <0-100>", + "Reduction of maximum BS RF Power in dB\n") +{ + int maxpwr_r = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + int upper_limit = 24; /* default 12.21 max power red. */ + + /* FIXME: check if our BTS type supports more than 12 */ + if (maxpwr_r < 0 || maxpwr_r > upper_limit) { + vty_out(vty, "%% Power %d dB is not in the valid range%s", + maxpwr_r, VTY_NEWLINE); + return CMD_WARNING; + } + if (maxpwr_r & 1) { + vty_out(vty, "%% Power %d dB is not an even value%s", + maxpwr_r, VTY_NEWLINE); + return CMD_WARNING; + } + + trx->max_power_red = maxpwr_r; + + /* FIXME: make sure we update this using OML */ + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_rsl_e1, + cfg_trx_rsl_e1_cmd, + "rsl e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)", + "E1 interface to be used for RSL\n") +{ + struct gsm_bts_trx *trx = vty->index; + + parse_e1_link(&trx->rsl_e1_link, argv[0], argv[1], argv[2]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_rsl_e1_tei, + cfg_trx_rsl_e1_tei_cmd, + "rsl e1 tei <0-63>", + "Set the TEI to be used for RSL") +{ + struct gsm_bts_trx *trx = vty->index; + + trx->rsl_tei = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_rf_locked, + cfg_trx_rf_locked_cmd, + "rf_locked (0|1)", + "Turn off RF of the TRX.\n") +{ + int locked = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + + gsm_trx_lock_rf(trx, locked); + return CMD_SUCCESS; +} + +/* per TS configuration */ +DEFUN(cfg_ts, + cfg_ts_cmd, + "timeslot <0-7>", + "Select a Timeslot to configure") +{ + int ts_nr = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + struct gsm_bts_trx_ts *ts; + + if (ts_nr >= TRX_NR_TS) { + vty_out(vty, "%% A GSM TRX only has %u Timeslots per TRX%s", + TRX_NR_TS, VTY_NEWLINE); + return CMD_WARNING; + } + + ts = &trx->ts[ts_nr]; + + vty->index = ts; + vty->node = TS_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ts_pchan, + cfg_ts_pchan_cmd, + "phys_chan_config PCHAN", + "Physical Channel configuration (TCH/SDCCH/...)") +{ + struct gsm_bts_trx_ts *ts = vty->index; + int pchanc; + + pchanc = gsm_pchan_parse(argv[0]); + if (pchanc < 0) + return CMD_WARNING; + + ts->pchan = pchanc; + + return CMD_SUCCESS; +} + +#define HOPPING_STR "Configure frequency hopping\n" + +DEFUN(cfg_ts_hopping, + cfg_ts_hopping_cmd, + "hopping enabled (0|1)", + HOPPING_STR "Enable or disable frequency hopping\n" + "Disable frequency hopping\n" "Enable frequency hopping\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + int enabled = atoi(argv[0]); + + if (enabled && !gsm_bts_has_feature(ts->trx->bts, BTS_FEAT_HOPPING)) { + vty_out(vty, "BTS model does not support hopping%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + ts->hopping.enabled = enabled; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ts_hsn, + cfg_ts_hsn_cmd, + "hopping sequence-number <0-63>", + HOPPING_STR + "Which hopping sequence to use for this channel") +{ + struct gsm_bts_trx_ts *ts = vty->index; + + ts->hopping.hsn = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ts_maio, + cfg_ts_maio_cmd, + "hopping maio <0-63>", + HOPPING_STR + "Which hopping MAIO to use for this channel") +{ + struct gsm_bts_trx_ts *ts = vty->index; + + ts->hopping.maio = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ts_arfcn_add, + cfg_ts_arfcn_add_cmd, + "hopping arfcn add <0-1023>", + HOPPING_STR "Configure hopping ARFCN list\n" + "Add an entry to the hopping ARFCN list\n" "ARFCN\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + int arfcn = atoi(argv[0]); + + bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 1); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ts_arfcn_del, + cfg_ts_arfcn_del_cmd, + "hopping arfcn del <0-1023>", + HOPPING_STR "Configure hopping ARFCN list\n" + "Delete an entry to the hopping ARFCN list\n" "ARFCN\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + int arfcn = atoi(argv[0]); + + bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 0); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ts_e1_subslot, + cfg_ts_e1_subslot_cmd, + "e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)", + "E1 sub-slot connected to this on-air timeslot") +{ + struct gsm_bts_trx_ts *ts = vty->index; + + parse_e1_link(&ts->e1_link, argv[0], argv[1], argv[2]); + + return CMD_SUCCESS; +} + +void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *net) +{ + vty_out(vty, "Channel Requests : %lu total, %lu no channel%s", + counter_get(net->stats.chreq.total), + counter_get(net->stats.chreq.no_channel), VTY_NEWLINE); + vty_out(vty, "Channel Failures : %lu rf_failures, %lu rll failures%s", + counter_get(net->stats.chan.rf_fail), + counter_get(net->stats.chan.rll_err), VTY_NEWLINE); + vty_out(vty, "Paging : %lu attempted, %lu complete, %lu expired%s", + counter_get(net->stats.paging.attempted), + counter_get(net->stats.paging.completed), + counter_get(net->stats.paging.expired), VTY_NEWLINE); + vty_out(vty, "BTS failures : %lu OML, %lu RSL%s", + counter_get(net->stats.bts.oml_fail), + counter_get(net->stats.bts.rsl_fail), VTY_NEWLINE); +} + +DEFUN(logging_fltr_imsi, + logging_fltr_imsi_cmd, + "logging filter imsi IMSI", + LOGGING_STR FILTER_STR + "Filter log messages by IMSI\n" "IMSI to be used as filter\n") +{ + struct log_target *tgt = osmo_log_vty2tgt(vty); + + if (!tgt) + return CMD_WARNING; + + log_set_imsi_filter(tgt, argv[0]); + return CMD_SUCCESS; +} + + +DEFUN(drop_bts, + drop_bts_cmd, + "drop bts connection <0-65535> (oml|rsl)", + "Debug/Simulation command to drop ipaccess BTS\n" + "BTS NR\n" "Connection Type\n") +{ + struct gsm_network *gsmnet; + struct gsm_bts_trx *trx; + struct gsm_bts *bts; + unsigned int bts_nr; + + gsmnet = gsmnet_from_vty(vty); + + bts_nr = atoi(argv[0]); + if (bts_nr >= gsmnet->num_bts) { + vty_out(vty, "BTS number must be between 0 and %d. It was %d.%s", + gsmnet->num_bts, bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + bts = gsm_bts_num(gsmnet, bts_nr); + if (!bts) { + vty_out(vty, "BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!is_ipaccess_bts(bts)) { + vty_out(vty, "This command only works for ipaccess.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + + /* close all connections */ + if (strcmp(argv[1], "oml") == 0) { + ipaccess_drop_oml(bts); + } else if (strcmp(argv[1], "rsl") == 0) { + /* close all rsl connections */ + llist_for_each_entry(trx, &bts->trx_list, list) { + ipaccess_drop_rsl(trx); + } + } else { + vty_out(vty, "Argument must be 'oml# or 'rsl'.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(pdch_act, pdch_act_cmd, + "bts <0-255> trx <0-255> timeslot <0-7> pdch (activate|deactivate)", + "BTS related commands\n" "BTS Number\n" "Transceiver\n" "Transceiver Number\n" + "TRX Timeslot\n" "Timeslot Number\n" "Packet Data Channel\n" + "Activate Dynamic PDCH/TCH (-> PDCH mode)\n" + "Deactivate Dynamic PDCH/TCH (-> TCH mode)\n") +{ + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + int bts_nr = atoi(argv[0]); + int trx_nr = atoi(argv[1]); + int ts_nr = atoi(argv[2]); + int activate; + + bts = gsm_bts_num(bsc_gsmnet, bts_nr); + if (!bts) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!is_ipaccess_bts(bts)) { + vty_out(vty, "%% This command only works for ipaccess BTS%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + trx = gsm_bts_trx_num(bts, trx_nr); + if (!trx) { + vty_out(vty, "%% No such TRX (%d)%s", trx_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + ts = &trx->ts[ts_nr]; + if (ts->pchan != GSM_PCHAN_TCH_F_PDCH) { + vty_out(vty, "%% Timeslot %u is not in dynamic TCH_F/PDCH " + "mode%s", ts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[3], "activate")) + activate = 1; + else + activate = 0; + + rsl_ipacc_pdch_activate(ts, activate); + + return CMD_SUCCESS; + +} + +extern int bsc_vty_init_extra(void); +extern const char *openbsc_copyright; + +int bsc_vty_init(void) +{ + install_element_ve(&show_net_cmd); + install_element_ve(&show_bts_cmd); + install_element_ve(&show_trx_cmd); + install_element_ve(&show_ts_cmd); + install_element_ve(&show_lchan_cmd); + install_element_ve(&show_lchan_summary_cmd); + install_element_ve(&logging_fltr_imsi_cmd); + + install_element_ve(&show_e1drv_cmd); + install_element_ve(&show_e1line_cmd); + install_element_ve(&show_e1ts_cmd); + + install_element_ve(&show_paging_cmd); + + logging_vty_add_cmds(); + install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd); + + install_element(CONFIG_NODE, &cfg_net_cmd); + install_node(&net_node, config_write_net); + install_default(GSMNET_NODE); + install_element(GSMNET_NODE, &ournode_exit_cmd); + install_element(GSMNET_NODE, &ournode_end_cmd); + install_element(GSMNET_NODE, &cfg_net_ncc_cmd); + install_element(GSMNET_NODE, &cfg_net_mnc_cmd); + install_element(GSMNET_NODE, &cfg_net_name_short_cmd); + install_element(GSMNET_NODE, &cfg_net_name_long_cmd); + install_element(GSMNET_NODE, &cfg_net_auth_policy_cmd); + install_element(GSMNET_NODE, &cfg_net_reject_cause_cmd); + install_element(GSMNET_NODE, &cfg_net_encryption_cmd); + install_element(GSMNET_NODE, &cfg_net_neci_cmd); + install_element(GSMNET_NODE, &cfg_net_rrlp_mode_cmd); + install_element(GSMNET_NODE, &cfg_net_mm_info_cmd); + install_element(GSMNET_NODE, &cfg_net_handover_cmd); + install_element(GSMNET_NODE, &cfg_net_ho_win_rxlev_avg_cmd); + install_element(GSMNET_NODE, &cfg_net_ho_win_rxqual_avg_cmd); + install_element(GSMNET_NODE, &cfg_net_ho_win_rxlev_avg_neigh_cmd); + install_element(GSMNET_NODE, &cfg_net_ho_pwr_interval_cmd); + install_element(GSMNET_NODE, &cfg_net_ho_pwr_hysteresis_cmd); + install_element(GSMNET_NODE, &cfg_net_ho_max_distance_cmd); + install_element(GSMNET_NODE, &cfg_net_T3101_cmd); + install_element(GSMNET_NODE, &cfg_net_T3103_cmd); + install_element(GSMNET_NODE, &cfg_net_T3105_cmd); + install_element(GSMNET_NODE, &cfg_net_T3107_cmd); + install_element(GSMNET_NODE, &cfg_net_T3109_cmd); + install_element(GSMNET_NODE, &cfg_net_T3111_cmd); + install_element(GSMNET_NODE, &cfg_net_T3113_cmd); + install_element(GSMNET_NODE, &cfg_net_T3115_cmd); + install_element(GSMNET_NODE, &cfg_net_T3117_cmd); + install_element(GSMNET_NODE, &cfg_net_T3119_cmd); + install_element(GSMNET_NODE, &cfg_net_T3122_cmd); + install_element(GSMNET_NODE, &cfg_net_T3141_cmd); + install_element(GSMNET_NODE, &cfg_net_dtx_cmd); + install_element(GSMNET_NODE, &cfg_net_subscr_keep_cmd); + install_element(GSMNET_NODE, &cfg_net_pag_any_tch_cmd); + + install_element(GSMNET_NODE, &cfg_bts_cmd); + install_node(&bts_node, config_write_bts); + install_default(BTS_NODE); + install_element(BTS_NODE, &ournode_exit_cmd); + install_element(BTS_NODE, &ournode_end_cmd); + install_element(BTS_NODE, &cfg_bts_type_cmd); + install_element(BTS_NODE, &cfg_description_cmd); + install_element(BTS_NODE, &cfg_no_description_cmd); + install_element(BTS_NODE, &cfg_bts_band_cmd); + install_element(BTS_NODE, &cfg_bts_ci_cmd); + install_element(BTS_NODE, &cfg_bts_lac_cmd); + install_element(BTS_NODE, &cfg_bts_tsc_cmd); + install_element(BTS_NODE, &cfg_bts_bsic_cmd); + install_element(BTS_NODE, &cfg_bts_unit_id_cmd); + install_element(BTS_NODE, &cfg_bts_serno_cmd); + install_element(BTS_NODE, &cfg_bts_stream_id_cmd); + install_element(BTS_NODE, &cfg_bts_oml_e1_cmd); + install_element(BTS_NODE, &cfg_bts_oml_e1_tei_cmd); + install_element(BTS_NODE, &cfg_bts_challoc_cmd); + install_element(BTS_NODE, &cfg_bts_rach_tx_integer_cmd); + install_element(BTS_NODE, &cfg_bts_rach_max_trans_cmd); + install_element(BTS_NODE, &cfg_bts_rach_nm_b_thresh_cmd); + install_element(BTS_NODE, &cfg_bts_rach_nm_ldavg_cmd); + install_element(BTS_NODE, &cfg_bts_cell_barred_cmd); + install_element(BTS_NODE, &cfg_bts_rach_ec_allowed_cmd); + install_element(BTS_NODE, &cfg_bts_ms_max_power_cmd); + install_element(BTS_NODE, &cfg_bts_per_loc_upd_cmd); + install_element(BTS_NODE, &cfg_bts_cell_resel_hyst_cmd); + install_element(BTS_NODE, &cfg_bts_rxlev_acc_min_cmd); + install_element(BTS_NODE, &cfg_bts_cell_bar_qualify_cmd); + install_element(BTS_NODE, &cfg_bts_cell_resel_ofs_cmd); + install_element(BTS_NODE, &cfg_bts_temp_ofs_cmd); + install_element(BTS_NODE, &cfg_bts_temp_ofs_inf_cmd); + install_element(BTS_NODE, &cfg_bts_penalty_time_cmd); + install_element(BTS_NODE, &cfg_bts_penalty_time_rsvd_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_mode_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_ns_timer_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_rac_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_bvci_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_cell_timer_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_nsei_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_nsvci_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_nsvc_lport_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rport_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rip_cmd); + install_element(BTS_NODE, &cfg_bts_pag_free_cmd); + install_element(BTS_NODE, &cfg_bts_si_mode_cmd); + install_element(BTS_NODE, &cfg_bts_si_static_cmd); + install_element(BTS_NODE, &cfg_bts_neigh_mode_cmd); + install_element(BTS_NODE, &cfg_bts_neigh_cmd); + install_element(BTS_NODE, &cfg_bts_si5_neigh_cmd); + + install_element(BTS_NODE, &cfg_trx_cmd); + install_node(&trx_node, dummy_config_write); + install_default(TRX_NODE); + install_element(TRX_NODE, &ournode_exit_cmd); + install_element(TRX_NODE, &ournode_end_cmd); + install_element(TRX_NODE, &cfg_trx_arfcn_cmd); + install_element(TRX_NODE, &cfg_description_cmd); + install_element(TRX_NODE, &cfg_no_description_cmd); + install_element(TRX_NODE, &cfg_trx_nominal_power_cmd); + install_element(TRX_NODE, &cfg_trx_max_power_red_cmd); + install_element(TRX_NODE, &cfg_trx_rsl_e1_cmd); + install_element(TRX_NODE, &cfg_trx_rsl_e1_tei_cmd); + install_element(TRX_NODE, &cfg_trx_rf_locked_cmd); + + install_element(TRX_NODE, &cfg_ts_cmd); + install_node(&ts_node, dummy_config_write); + install_default(TS_NODE); + install_element(TS_NODE, &ournode_exit_cmd); + install_element(TS_NODE, &ournode_end_cmd); + install_element(TS_NODE, &cfg_ts_pchan_cmd); + install_element(TS_NODE, &cfg_ts_hopping_cmd); + install_element(TS_NODE, &cfg_ts_hsn_cmd); + install_element(TS_NODE, &cfg_ts_maio_cmd); + install_element(TS_NODE, &cfg_ts_arfcn_add_cmd); + install_element(TS_NODE, &cfg_ts_arfcn_del_cmd); + install_element(TS_NODE, &cfg_ts_e1_subslot_cmd); + + install_element(ENABLE_NODE, &drop_bts_cmd); + install_element(ENABLE_NODE, &pdch_act_cmd); + + abis_nm_vty_init(); + abis_om2k_vty_init(); + e1inp_vty_init(); + + bsc_vty_init_extra(); + + return 0; +} diff --git a/src/libbsc/bts_ericsson_rbs2000.c b/src/libbsc/bts_ericsson_rbs2000.c new file mode 100644 index 000000000..27d5ce6f7 --- /dev/null +++ b/src/libbsc/bts_ericsson_rbs2000.c @@ -0,0 +1,164 @@ +/* Ericsson RBS-2xxx specific code */ + +/* (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include + +#include +#include +#include +#include +#include + +#include "../libabis/input/lapd.h" + +static void bootstrap_om_bts(struct gsm_bts *bts) +{ + LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr); + abis_om2k_tx_start_req(bts, &om2k_mo_cf); + /* FIXME */ +} + +static void bootstrap_om_trx(struct gsm_bts_trx *trx) +{ + LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for TRX %u/%u\n", + trx->bts->nr, trx->nr); + /* FIXME */ +} + +static int shutdown_om(struct gsm_bts *bts) +{ + /* FIXME */ + return 0; +} + + +/* Tell LAPD to start start the SAP (send SABM requests) for all signalling + * timeslots in this line */ +static void start_sabm_in_line(struct e1inp_line *line, int start) +{ + struct e1inp_sign_link *link; + int i; + + for (i = 0; i < ARRAY_SIZE(line->ts); i++) { + struct e1inp_ts *ts = &line->ts[i]; + + if (ts->type != E1INP_TS_TYPE_SIGN) + continue; + + llist_for_each_entry(link, &ts->sign.sign_links, list) { + if (start) + lapd_sap_start(ts->driver.dahdi.lapd, link->tei, link->sapi); + else + lapd_sap_stop(ts->driver.dahdi.lapd, link->tei, link->sapi); + } + } +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int gbl_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_bts *bts; + + if (subsys != SS_GLOBAL) + return 0; + + switch (signal) { + case S_GLOBAL_BTS_CLOSE_OM: + bts = signal_data; + if (bts->type == GSM_BTS_TYPE_RBS2000) + shutdown_om(signal_data); + break; + } + + return 0; +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int inp_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct input_signal_data *isd = signal_data; + + if (subsys != SS_INPUT) + return 0; + + switch (signal) { + case S_INP_TEI_UP: + switch (isd->link_type) { + case E1INP_SIGN_OML: + if (isd->trx->bts->type != GSM_BTS_TYPE_RBS2000) + break; + if (isd->tei == isd->trx->bts->oml_tei) + bootstrap_om_bts(isd->trx->bts); + else + bootstrap_om_trx(isd->trx); + break; + } + break; + case S_INP_LINE_INIT: + /* Right now Ericsson RBS are only supported on DAHDI */ + if (strcasecmp(isd->line->driver->name, "DAHDI")) + break; + start_sabm_in_line(isd->line, 1); + break; + case S_INP_LINE_ALARM: + if (strcasecmp(isd->line->driver->name, "DAHDI")) + break; + start_sabm_in_line(isd->line, 0); + break; + case S_INP_LINE_NOALARM: + if (strcasecmp(isd->line->driver->name, "DAHDI")) + break; + start_sabm_in_line(isd->line, 1); + break; + } + + return 0; +} + +static void config_write_bts(struct vty *vty, struct gsm_bts *bts) +{ + abis_om2k_config_write_bts(vty, bts); +} + +static struct gsm_bts_model model_rbs2k = { + .type = GSM_BTS_TYPE_RBS2000, + .name = "rbs2000", + .oml_rcvmsg = &abis_om2k_rcvmsg, + .config_write_bts = &config_write_bts, +}; + +int bts_model_rbs2k_init(void) +{ + model_rbs2k.features.data = &model_rbs2k._features_data[0]; + model_rbs2k.features.data_len = sizeof(model_rbs2k._features_data); + + gsm_btsmodel_set_feature(&model_rbs2k, BTS_FEAT_HOPPING); + gsm_btsmodel_set_feature(&model_rbs2k, BTS_FEAT_HSCSD); + + register_signal_handler(SS_INPUT, inp_sig_cb, NULL); + register_signal_handler(SS_GLOBAL, gbl_sig_cb, NULL); + + return gsm_bts_model_register(&model_rbs2k); +} diff --git a/src/libbsc/bts_hsl_femtocell.c b/src/libbsc/bts_hsl_femtocell.c new file mode 100644 index 000000000..e01634c3e --- /dev/null +++ b/src/libbsc/bts_hsl_femtocell.c @@ -0,0 +1,162 @@ +/* OpenBSC support code for HSL Femtocell */ + +/* (C) 2011 by Harald Welte + * (C) 2011 by OnWaves + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include + +static struct gsm_bts_model model_hslfemto = { + .type = GSM_BTS_TYPE_HSL_FEMTO, + .nm_att_tlvdef = { + .def = { + /* no HSL specific OML attributes that we know of */ + }, + }, +}; + + +static const uint8_t l1_msg[] = { +#ifdef HSL_SR_1_0 + 0x80, 0x8a, +#else + 0x81, 0x8a, +#endif + 0xC4, 0x0b, +}; + +static const uint8_t conn_trau_msg[] = { +#ifdef HSL_SR_1_0 + 0x80, 0x81, +#else + 0x81, 0x81, +#endif + 0xC1, 16, + 0x02, 0x00, 0x00, 0x00, 0xC0, 0xA8, 0xEA, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const uint8_t conn_trau_msg2[] = { +#ifdef HSL_SR_1_0 + 0x80, 0x81, +#else + 0x81, 0x81, +#endif + 0xC1, 16, + 0x02, 0x00, 0xd4, 0x07, 0xC0, 0xA8, 0xEA, 0x01, + 0x38, 0xA4, 0x45, 0x00, 0x04, 0x59, 0x40, 0x00 +}; + +static uint8_t oml_arfcn_bsic[] = { +#ifdef HSL_SR_1_0 + 0x81, 0x80, 0x00, 10, +#else + 0x80, 0x80, 0x00, 10, +#endif + NM_MT_SET_BTS_ATTR, NM_OC_BTS, 0xff, 0xff, 0xff, + NM_ATT_BCCH_ARFCN, 0x03, 0x67, + NM_ATT_BSIC, 0x00 +}; + +static inline struct msgb *hsl_alloc_msgb(void) +{ + return msgb_alloc_headroom(1024, 127, "HSL"); +} + +static int hslfemto_bootstrap_om(struct gsm_bts *bts) +{ + struct msgb *msg; + uint8_t *cur; + + msg = hsl_alloc_msgb(); + cur = msgb_put(msg, sizeof(l1_msg)); + memcpy(msg->data, l1_msg, sizeof(l1_msg)); + msg->trx = bts->c0; + abis_rsl_sendmsg(msg); + +#if 1 + msg = hsl_alloc_msgb(); + cur = msgb_put(msg, sizeof(conn_trau_msg)); + memcpy(msg->data, conn_trau_msg, sizeof(conn_trau_msg)); + msg->trx = bts->c0; + abis_rsl_sendmsg(msg); +#endif + msg = hsl_alloc_msgb(); + cur = msgb_put(msg, sizeof(conn_trau_msg2)); + memcpy(msg->data, conn_trau_msg2, sizeof(conn_trau_msg2)); + msg->trx = bts->c0; + abis_rsl_sendmsg(msg); + + *((uint16_t *)oml_arfcn_bsic+10) = htons(bts->c0->arfcn); + oml_arfcn_bsic[13] = bts->bsic; + + msg = hsl_alloc_msgb(); + cur = msgb_put(msg, sizeof(oml_arfcn_bsic)); + memcpy(msg->data, oml_arfcn_bsic, sizeof(oml_arfcn_bsic)); + msg->trx = bts->c0; + _abis_nm_sendmsg(msg, 0); + + /* Delay the OPSTART until after SI have been set via RSL */ + //abis_nm_opstart(bts, NM_OC_BTS, 255, 255, 255); + + return 0; +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int inp_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct input_signal_data *isd = signal_data; + + if (subsys != SS_INPUT) + return 0; + + switch (signal) { + case S_INP_TEI_UP: + switch (isd->link_type) { + case E1INP_SIGN_OML: + hslfemto_bootstrap_om(isd->trx->bts); + break; + } + } + + return 0; +} + +int bts_model_hslfemto_init(void) +{ + model_hslfemto.features.data = &model_hslfemto._features_data[0]; + model_hslfemto.features.data_len = sizeof(model_hslfemto._features_data); + + gsm_btsmodel_set_feature(&model_hslfemto, BTS_FEAT_GPRS); + gsm_btsmodel_set_feature(&model_hslfemto, BTS_FEAT_EGPRS); + + register_signal_handler(SS_INPUT, inp_sig_cb, NULL); + + return gsm_bts_model_register(&model_hslfemto); +} diff --git a/src/libbsc/bts_ipaccess_nanobts.c b/src/libbsc/bts_ipaccess_nanobts.c new file mode 100644 index 000000000..25dc0c8a2 --- /dev/null +++ b/src/libbsc/bts_ipaccess_nanobts.c @@ -0,0 +1,447 @@ +/* ip.access nanoBTS specific code */ + +/* (C) 2009-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include + +#include +#include +#include + +static struct gsm_bts_model model_nanobts = { + .type = GSM_BTS_TYPE_NANOBTS, + .name = "nanobts", + .oml_rcvmsg = &abis_nm_rcvmsg, + .nm_att_tlvdef = { + .def = { + /* ip.access specifics */ + [NM_ATT_IPACC_DST_IP] = { TLV_TYPE_FIXED, 4 }, + [NM_ATT_IPACC_DST_IP_PORT] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_IPACC_STREAM_ID] = { TLV_TYPE_TV, }, + [NM_ATT_IPACC_SEC_OML_CFG] = { TLV_TYPE_FIXED, 6 }, + [NM_ATT_IPACC_IP_IF_CFG] = { TLV_TYPE_FIXED, 8 }, + [NM_ATT_IPACC_IP_GW_CFG] = { TLV_TYPE_FIXED, 12 }, + [NM_ATT_IPACC_IN_SERV_TIME] = { TLV_TYPE_FIXED, 4 }, + [NM_ATT_IPACC_LOCATION] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_PAGING_CFG] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_IPACC_UNIT_ID] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_UNIT_NAME] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_SNMP_CFG] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_PRIM_OML_CFG_LIST] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_NV_FLAGS] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_FREQ_CTRL] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_IPACC_PRIM_OML_FB_TOUT] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_CUR_SW_CFG] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_TIMING_BUS] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_CGI] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_RAC] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_OBJ_VERSION] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_GPRS_PAGING_CFG]= { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_NSEI] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_BVCI] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_NSVCI] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_NS_CFG] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_BSSGP_CFG] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_NS_LINK_CFG] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_RLC_CFG] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_ALM_THRESH_LIST]= { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_MONIT_VAL_LIST] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_TIB_CONTROL] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_SUPP_FEATURES] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_CODING_SCHEMES] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_RLC_CFG_2] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_HEARTB_TOUT] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_UPTIME] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_RLC_CFG_3] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_SSL_CFG] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_SEC_POSSIBLE] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_IML_SSL_STATE] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_REVOC_DATE] = { TLV_TYPE_TL16V }, + }, + }, +}; + +static unsigned char nanobts_attr_bts[] = { + NM_ATT_INTERF_BOUND, 0x55, 0x5b, 0x61, 0x67, 0x6d, 0x73, + /* interference avg. period in numbers of SACCH multifr */ + NM_ATT_INTAVE_PARAM, 0x06, + /* conn fail based on SACCH error rate */ + NM_ATT_CONN_FAIL_CRIT, 0x00, 0x02, 0x01, 0x10, + NM_ATT_T200, 0x1e, 0x24, 0x24, 0xa8, 0x34, 0x21, 0xa8, + NM_ATT_MAX_TA, 0x3f, + NM_ATT_OVERL_PERIOD, 0x00, 0x01, 10, /* seconds */ + NM_ATT_CCCH_L_T, 10, /* percent */ + NM_ATT_CCCH_L_I_P, 1, /* seconds */ + NM_ATT_RACH_B_THRESH, 10, /* busy threshold in - dBm */ + NM_ATT_LDAVG_SLOTS, 0x03, 0xe8, /* rach load averaging 1000 slots */ + NM_ATT_BTS_AIR_TIMER, 128, /* miliseconds */ + NM_ATT_NY1, 10, /* 10 retransmissions of physical config */ + NM_ATT_BCCH_ARFCN, HARDCODED_ARFCN >> 8, HARDCODED_ARFCN & 0xff, + NM_ATT_BSIC, HARDCODED_BSIC, + NM_ATT_IPACC_CGI, 0, 7, 0x00, 0xf1, 0x10, 0x00, 0x01, 0x00, 0x00, +}; + +static unsigned char nanobts_attr_radio[] = { + NM_ATT_RF_MAXPOWR_R, 0x0c, /* number of -2dB reduction steps / Pn */ + NM_ATT_ARFCN_LIST, 0x00, 0x02, HARDCODED_ARFCN >> 8, HARDCODED_ARFCN & 0xff, +}; + +static unsigned char nanobts_attr_nse[] = { + NM_ATT_IPACC_NSEI, 0, 2, 0x03, 0x9d, /* NSEI 925 */ + NM_ATT_IPACC_NS_CFG, 0, 7, 3, /* (un)blocking timer (Tns-block) */ + 3, /* (un)blocking retries */ + 3, /* reset timer (Tns-reset) */ + 3, /* reset retries */ + 30, /* test timer (Tns-test) */ + 3, /* alive timer (Tns-alive) */ + 10, /* alive retrires */ + NM_ATT_IPACC_BSSGP_CFG, 0, 11, + 3, /* blockimg timer (T1) */ + 3, /* blocking retries */ + 3, /* unblocking retries */ + 3, /* reset timer */ + 3, /* reset retries */ + 10, /* suspend timer (T3) in 100ms */ + 3, /* suspend retries */ + 10, /* resume timer (T4) in 100ms */ + 3, /* resume retries */ + 10, /* capability update timer (T5) */ + 3, /* capability update retries */ +}; + +static unsigned char nanobts_attr_cell[] = { + NM_ATT_IPACC_RAC, 0, 1, 1, /* routing area code */ + NM_ATT_IPACC_GPRS_PAGING_CFG, 0, 2, + 5, /* repeat time (50ms) */ + 3, /* repeat count */ + NM_ATT_IPACC_BVCI, 0, 2, 0x03, 0x9d, /* BVCI 925 */ + NM_ATT_IPACC_RLC_CFG, 0, 9, + 20, /* T3142 */ + 5, /* T3169 */ + 5, /* T3191 */ + 200, /* T3193 */ + 5, /* T3195 */ + 10, /* N3101 */ + 4, /* N3103 */ + 8, /* N3105 */ + 15, /* RLC CV countdown */ + NM_ATT_IPACC_CODING_SCHEMES, 0, 2, 0x0f, 0x00, /* CS1..CS4 */ + NM_ATT_IPACC_RLC_CFG_2, 0, 5, + 0x00, 250, /* T downlink TBF extension (0..500) */ + 0x00, 250, /* T uplink TBF extension (0..500) */ + 2, /* CS2 */ +#if 0 + /* EDGE model only, breaks older models. + * Should inquire the BTS capabilities */ + NM_ATT_IPACC_RLC_CFG_3, 0, 1, + 2, /* MCS2 */ +#endif +}; + +static unsigned char nanobts_attr_nsvc0[] = { + NM_ATT_IPACC_NSVCI, 0, 2, 0x03, 0x9d, /* 925 */ + NM_ATT_IPACC_NS_LINK_CFG, 0, 8, + 0x59, 0xd8, /* remote udp port (23000) */ + 192, 168, 100, 11, /* remote ip address */ + 0x59, 0xd8, /* local udp port (23000) */ +}; + +static void patch_16(uint8_t *data, const uint16_t val) +{ + memcpy(data, &val, sizeof(val)); +} + +static void patch_32(uint8_t *data, const uint32_t val) +{ + memcpy(data, &val, sizeof(val)); +} + +/* + * Patch the various SYSTEM INFORMATION tables to update + * the LAI + */ +static void patch_nm_tables(struct gsm_bts *bts) +{ + u_int8_t arfcn_low = bts->c0->arfcn & 0xff; + u_int8_t arfcn_high = (bts->c0->arfcn >> 8) & 0x0f; + + /* patch ARFCN into BTS Attributes */ + nanobts_attr_bts[42] &= 0xf0; + nanobts_attr_bts[42] |= arfcn_high; + nanobts_attr_bts[43] = arfcn_low; + + /* patch the RACH attributes */ + if (bts->rach_b_thresh != -1) { + nanobts_attr_bts[33] = bts->rach_b_thresh & 0xff; + } + + if (bts->rach_ldavg_slots != -1) { + u_int8_t avg_high = bts->rach_ldavg_slots & 0xff; + u_int8_t avg_low = (bts->rach_ldavg_slots >> 8) & 0x0f; + + nanobts_attr_bts[35] = avg_high; + nanobts_attr_bts[36] = avg_low; + } + + /* patch BSIC */ + nanobts_attr_bts[sizeof(nanobts_attr_bts)-11] = bts->bsic; + + /* patch CGI */ + abis_nm_ipaccess_cgi(nanobts_attr_bts+sizeof(nanobts_attr_bts)-7, bts); + + /* patch the power reduction */ + nanobts_attr_radio[1] = bts->c0->max_power_red / 2; + + /* patch NSEI */ + nanobts_attr_nse[3] = bts->gprs.nse.nsei >> 8; + nanobts_attr_nse[4] = bts->gprs.nse.nsei & 0xff; + memcpy(nanobts_attr_nse+8, bts->gprs.nse.timer, + ARRAY_SIZE(bts->gprs.nse.timer)); + memcpy(nanobts_attr_nse+18, bts->gprs.cell.timer, + ARRAY_SIZE(bts->gprs.cell.timer)); + + /* patch NSVCI */ + nanobts_attr_nsvc0[3] = bts->gprs.nsvc[0].nsvci >> 8; + nanobts_attr_nsvc0[4] = bts->gprs.nsvc[0].nsvci & 0xff; + + /* patch IP address as SGSN IP */ + patch_16(nanobts_attr_nsvc0 + 8, + htons(bts->gprs.nsvc[0].remote_port)); + patch_32(nanobts_attr_nsvc0 + 10, + htonl(bts->gprs.nsvc[0].remote_ip)); + patch_16(nanobts_attr_nsvc0 + 14, + htons(bts->gprs.nsvc[0].local_port)); + + /* patch BVCI */ + nanobts_attr_cell[12] = bts->gprs.cell.bvci >> 8; + nanobts_attr_cell[13] = bts->gprs.cell.bvci & 0xff; + /* patch RAC */ + nanobts_attr_cell[3] = bts->gprs.rac; + + if (bts->gprs.mode == BTS_GPRS_EGPRS) { + /* patch EGPRS coding schemes MCS 1..9 */ + nanobts_attr_cell[29] = 0x8f; + nanobts_attr_cell[30] = 0xff; + } +} + + +/* Callback function to be called whenever we get a GSM 12.21 state change event */ +static int nm_statechg_event(int evt, struct nm_statechg_signal_data *nsd) +{ + u_int8_t obj_class = nsd->obj_class; + void *obj = nsd->obj; + struct gsm_nm_state *new_state = nsd->new_state; + + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + struct gsm_bts_gprs_nsvc *nsvc; + + /* This event-driven BTS setup is currently only required on nanoBTS */ + + /* S_NM_STATECHG_ADM is called after we call chg_adm_state() and would create + * endless loop */ + if (evt != S_NM_STATECHG_OPER) + return 0; + + switch (obj_class) { + case NM_OC_SITE_MANAGER: + bts = container_of(obj, struct gsm_bts, site_mgr); + if ((new_state->operational == NM_OPSTATE_ENABLED && + new_state->availability == NM_AVSTATE_OK) || + (new_state->operational == NM_OPSTATE_DISABLED && + new_state->availability == NM_AVSTATE_OFF_LINE)) + abis_nm_opstart(bts, obj_class, 0xff, 0xff, 0xff); + break; + case NM_OC_BTS: + bts = obj; + if (new_state->availability == NM_AVSTATE_DEPENDENCY) { + patch_nm_tables(bts); + abis_nm_set_bts_attr(bts, nanobts_attr_bts, + sizeof(nanobts_attr_bts)); + abis_nm_chg_adm_state(bts, obj_class, + bts->bts_nr, 0xff, 0xff, + NM_STATE_UNLOCKED); + abis_nm_opstart(bts, obj_class, + bts->bts_nr, 0xff, 0xff); + } + break; + case NM_OC_CHANNEL: + ts = obj; + trx = ts->trx; + if (new_state->operational == NM_OPSTATE_DISABLED && + new_state->availability == NM_AVSTATE_DEPENDENCY) { + patch_nm_tables(trx->bts); + enum abis_nm_chan_comb ccomb = + abis_nm_chcomb4pchan(ts->pchan); + abis_nm_set_channel_attr(ts, ccomb); + abis_nm_chg_adm_state(trx->bts, obj_class, + trx->bts->bts_nr, trx->nr, ts->nr, + NM_STATE_UNLOCKED); + abis_nm_opstart(trx->bts, obj_class, + trx->bts->bts_nr, trx->nr, ts->nr); + } + break; + case NM_OC_RADIO_CARRIER: + trx = obj; + if (new_state->operational == NM_OPSTATE_DISABLED && + new_state->availability == NM_AVSTATE_OK) + abis_nm_opstart(trx->bts, obj_class, trx->bts->bts_nr, + trx->nr, 0xff); + break; + case NM_OC_GPRS_NSE: + bts = container_of(obj, struct gsm_bts, gprs.nse); + if (bts->gprs.mode == BTS_GPRS_NONE) + break; + if (new_state->availability == NM_AVSTATE_DEPENDENCY) { + abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr, + 0xff, 0xff, nanobts_attr_nse, + sizeof(nanobts_attr_nse)); + abis_nm_opstart(bts, obj_class, bts->bts_nr, + 0xff, 0xff); + } + break; + case NM_OC_GPRS_CELL: + bts = container_of(obj, struct gsm_bts, gprs.cell); + if (bts->gprs.mode == BTS_GPRS_NONE) + break; + if (new_state->availability == NM_AVSTATE_DEPENDENCY) { + abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr, + 0, 0xff, nanobts_attr_cell, + sizeof(nanobts_attr_cell)); + abis_nm_opstart(bts, obj_class, bts->bts_nr, + 0, 0xff); + abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr, + 0, 0xff, NM_STATE_UNLOCKED); + abis_nm_chg_adm_state(bts, NM_OC_GPRS_NSE, bts->bts_nr, + 0xff, 0xff, NM_STATE_UNLOCKED); + } + break; + case NM_OC_GPRS_NSVC: + nsvc = obj; + bts = nsvc->bts; + if (bts->gprs.mode == BTS_GPRS_NONE) + break; + /* We skip NSVC1 since we only use NSVC0 */ + if (nsvc->id == 1) + break; + if (new_state->availability == NM_AVSTATE_OFF_LINE) { + abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr, + nsvc->id, 0xff, + nanobts_attr_nsvc0, + sizeof(nanobts_attr_nsvc0)); + abis_nm_opstart(bts, obj_class, bts->bts_nr, + nsvc->id, 0xff); + abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr, + nsvc->id, 0xff, + NM_STATE_UNLOCKED); + } + default: + break; + } + return 0; +} + +/* Callback function to be called every time we receive a 12.21 SW activated report */ +static int sw_activ_rep(struct msgb *mb) +{ + struct abis_om_fom_hdr *foh = msgb_l3(mb); + struct gsm_bts *bts = mb->trx->bts; + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + + if (!trx) + return -EINVAL; + + if (trx->bts->type != GSM_BTS_TYPE_NANOBTS) + return 0; + + switch (foh->obj_class) { + case NM_OC_BASEB_TRANSC: + abis_nm_chg_adm_state(trx->bts, foh->obj_class, + trx->bts->bts_nr, trx->nr, 0xff, + NM_STATE_UNLOCKED); + abis_nm_opstart(trx->bts, foh->obj_class, + trx->bts->bts_nr, trx->nr, 0xff); + /* TRX software is active, tell it to initiate RSL Link */ + abis_nm_ipaccess_rsl_connect(trx, 0, 3003, trx->rsl_tei); + break; + case NM_OC_RADIO_CARRIER: { + /* + * Locking the radio carrier will make it go + * offline again and we would come here. The + * framework should determine that there was + * no change and avoid recursion. + * + * This code is here to make sure that on start + * a TRX remains locked. + */ + int rc_state = trx->nm_state.administrative; + /* Patch ARFCN into radio attribute */ + nanobts_attr_radio[5] &= 0xf0; + nanobts_attr_radio[5] |= trx->arfcn >> 8; + nanobts_attr_radio[6] = trx->arfcn & 0xff; + abis_nm_set_radio_attr(trx, nanobts_attr_radio, + sizeof(nanobts_attr_radio)); + abis_nm_chg_adm_state(trx->bts, foh->obj_class, + trx->bts->bts_nr, trx->nr, 0xff, + rc_state); + abis_nm_opstart(trx->bts, foh->obj_class, trx->bts->bts_nr, + trx->nr, 0xff); + break; + } + } + return 0; +} + +/* Callback function to be called every time we receive a signal from NM */ +static int nm_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + if (subsys != SS_NM) + return 0; + + switch (signal) { + case S_NM_SW_ACTIV_REP: + return sw_activ_rep(signal_data); + case S_NM_STATECHG_OPER: + case S_NM_STATECHG_ADM: + return nm_statechg_event(signal, signal_data); + default: + break; + } + return 0; +} + +int bts_model_nanobts_init(void) +{ + model_nanobts.features.data = &model_nanobts._features_data[0]; + model_nanobts.features.data_len = sizeof(model_nanobts._features_data); + + gsm_btsmodel_set_feature(&model_nanobts, BTS_FEAT_GPRS); + gsm_btsmodel_set_feature(&model_nanobts, BTS_FEAT_EGPRS); + + register_signal_handler(SS_NM, nm_sig_cb, NULL); + + return gsm_bts_model_register(&model_nanobts); +} diff --git a/src/libbsc/bts_siemens_bs11.c b/src/libbsc/bts_siemens_bs11.c new file mode 100644 index 000000000..5a5f88306 --- /dev/null +++ b/src/libbsc/bts_siemens_bs11.c @@ -0,0 +1,591 @@ +/* Siemens BS-11 specific code */ + +/* (C) 2009-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include + +#include +#include +#include +#include +#include + +static struct gsm_bts_model model_bs11 = { + .type = GSM_BTS_TYPE_BS11, + .name = "bs11", + .oml_rcvmsg = &abis_nm_rcvmsg, + .nm_att_tlvdef = { + .def = { + [NM_ATT_AVAIL_STATUS] = { TLV_TYPE_TLV }, + /* BS11 specifics */ + [NM_ATT_BS11_ESN_FW_CODE_NO] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_ESN_HW_CODE_NO] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_ESN_PCB_SERIAL] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_BOOT_SW_VERS] = { TLV_TYPE_TLV }, + [0xd5] = { TLV_TYPE_TLV }, + [0xa8] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_PASSWORD] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_TXPWR] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_RSSI_OFFS] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_LINE_CFG] = { TLV_TYPE_TV }, + [NM_ATT_BS11_L1_PROT_TYPE] = { TLV_TYPE_TV }, + [NM_ATT_BS11_BIT_ERR_THESH] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_BS11_DIVERSITY] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_LMT_LOGON_SESSION]={ TLV_TYPE_TLV }, + [NM_ATT_BS11_LMT_LOGIN_TIME] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_LMT_USER_ACC_LEV] ={ TLV_TYPE_TLV }, + [NM_ATT_BS11_LMT_USER_NAME] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_BTS_STATE] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_E1_STATE] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_PLL_MODE] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_PLL] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_CCLK_ACCURACY] = { TLV_TYPE_TV }, + [NM_ATT_BS11_CCLK_TYPE] = { TLV_TYPE_TV }, + [0x95] = { TLV_TYPE_FIXED, 2 }, + }, + }, +}; + +/* The following definitions are for OM and NM packets that we cannot yet + * generate by code but we just pass on */ + +// BTS Site Manager, SET ATTRIBUTES + +/* + Object Class: BTS Site Manager + Instance 1: FF + Instance 2: FF + Instance 3: FF +SET ATTRIBUTES + sAbisExternalTime: 2007/09/08 14:36:11 + omLAPDRelTimer: 30sec + shortLAPDIntTimer: 5sec + emergencyTimer1: 10 minutes + emergencyTimer2: 0 minutes +*/ + +unsigned char msg_1[] = +{ + NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER, 0xFF, 0xFF, 0xFF, + NM_ATT_BS11_ABIS_EXT_TIME, 0x07, + 0xD7, 0x09, 0x08, 0x0E, 0x24, 0x0B, 0xCE, + 0x02, + 0x00, 0x1E, + NM_ATT_BS11_SH_LAPD_INT_TIMER, + 0x01, 0x05, + 0x42, 0x02, 0x00, 0x0A, + 0x44, 0x02, 0x00, 0x00 +}; + +// BTS, SET BTS ATTRIBUTES + +/* + Object Class: BTS + BTS relat. Number: 0 + Instance 2: FF + Instance 3: FF +SET BTS ATTRIBUTES + bsIdentityCode / BSIC: + PLMN_colour_code: 7h + BS_colour_code: 7h + BTS Air Timer T3105: 4 ,unit 10 ms + btsIsHopping: FALSE + periodCCCHLoadIndication: 1sec + thresholdCCCHLoadIndication: 0% + cellAllocationNumber: 00h = GSM 900 + enableInterferenceClass: 00h = Disabled + fACCHQual: 6 (FACCH stealing flags minus 1) + intaveParameter: 31 SACCH multiframes + interferenceLevelBoundaries: + Interference Boundary 1: 0Ah + Interference Boundary 2: 0Fh + Interference Boundary 3: 14h + Interference Boundary 4: 19h + Interference Boundary 5: 1Eh + mSTxPwrMax: 11 + GSM range: 2=39dBm, 15=13dBm, stepsize 2 dBm + DCS1800 range: 0=30dBm, 15=0dBm, stepsize 2 dBm + PCS1900 range: 0=30dBm, 15=0dBm, stepsize 2 dBm + 30=33dBm, 31=32dBm + ny1: + Maximum number of repetitions for PHYSICAL INFORMATION message (GSM 04.08): 20 + powerOutputThresholds: + Out Power Fault Threshold: -10 dB + Red Out Power Threshold: - 6 dB + Excessive Out Power Threshold: 5 dB + rACHBusyThreshold: -127 dBm + rACHLoadAveragingSlots: 250 ,number of RACH burst periods + rfResourceIndicationPeriod: 125 SACCH multiframes + T200: + SDCCH: 044 in 5 ms + FACCH/Full rate: 031 in 5 ms + FACCH/Half rate: 041 in 5 ms + SACCH with TCH SAPI0: 090 in 10 ms + SACCH with SDCCH: 090 in 10 ms + SDCCH with SAPI3: 090 in 5 ms + SACCH with TCH SAPI3: 135 in 10 ms + tSync: 9000 units of 10 msec + tTrau: 9000 units of 10 msec + enableUmLoopTest: 00h = disabled + enableExcessiveDistance: 00h = Disabled + excessiveDistance: 64km + hoppingMode: 00h = baseband hopping + cellType: 00h = Standard Cell + BCCH ARFCN / bCCHFrequency: 1 +*/ + +static unsigned char bs11_attr_bts[] = +{ + NM_ATT_BSIC, HARDCODED_BSIC, + NM_ATT_BTS_AIR_TIMER, 0x04, + NM_ATT_BS11_BTSLS_HOPPING, 0x00, + NM_ATT_CCCH_L_I_P, 0x01, + NM_ATT_CCCH_L_T, 0x00, + NM_ATT_BS11_CELL_ALLOC_NR, NM_BS11_CANR_GSM, + NM_ATT_BS11_ENA_INTERF_CLASS, 0x01, + NM_ATT_BS11_FACCH_QUAL, 0x06, + /* interference avg. period in numbers of SACCH multifr */ + NM_ATT_INTAVE_PARAM, 0x1F, + NM_ATT_INTERF_BOUND, 0x0A, 0x0F, 0x14, 0x19, 0x1E, 0x7B, + NM_ATT_CCCH_L_T, 0x23, + NM_ATT_GSM_TIME, 0x28, 0x00, + NM_ATT_ADM_STATE, 0x03, + NM_ATT_RACH_B_THRESH, 0x7F, + NM_ATT_LDAVG_SLOTS, 0x00, 0xFA, + NM_ATT_BS11_RF_RES_IND_PER, 0x7D, + NM_ATT_T200, 0x2C, 0x1F, 0x29, 0x5A, 0x5A, 0x5A, 0x87, + NM_ATT_BS11_TSYNC, 0x23, 0x28, + NM_ATT_BS11_TTRAU, 0x23, 0x28, + NM_ATT_TEST_DUR, 0x01, 0x00, + NM_ATT_OUTST_ALARM, 0x01, 0x00, + NM_ATT_BS11_EXCESSIVE_DISTANCE, 0x01, 0x40, + NM_ATT_BS11_HOPPING_MODE, 0x01, 0x00, + NM_ATT_BS11_PLL, 0x01, 0x00, + NM_ATT_BCCH_ARFCN, 0x00, HARDCODED_ARFCN/*0x01*/, +}; + +// Handover Recognition, SET ATTRIBUTES + +/* +Illegal Contents GSM Formatted O&M Msg + Object Class: Handover Recognition + BTS relat. Number: 0 + Instance 2: FF + Instance 3: FF +SET ATTRIBUTES + enableDelayPowerBudgetHO: 00h = Disabled + enableDistanceHO: 00h = Disabled + enableInternalInterCellHandover: 00h = Disabled + enableInternalIntraCellHandover: 00h = Disabled + enablePowerBudgetHO: 00h = Disabled + enableRXLEVHO: 00h = Disabled + enableRXQUALHO: 00h = Disabled + hoAveragingDistance: 8 SACCH multiframes + hoAveragingLev: + A_LEV_HO: 8 SACCH multiframes + W_LEV_HO: 1 SACCH multiframes + hoAveragingPowerBudget: 16 SACCH multiframes + hoAveragingQual: + A_QUAL_HO: 8 SACCH multiframes + W_QUAL_HO: 2 SACCH multiframes + hoLowerThresholdLevDL: (10 - 110) dBm + hoLowerThresholdLevUL: (5 - 110) dBm + hoLowerThresholdQualDL: 06h = 6.4% < BER < 12.8% + hoLowerThresholdQualUL: 06h = 6.4% < BER < 12.8% + hoThresholdLevDLintra : (20 - 110) dBm + hoThresholdLevULintra: (20 - 110) dBm + hoThresholdMsRangeMax: 20 km + nCell: 06h + timerHORequest: 3 ,unit 2 SACCH multiframes +*/ + +unsigned char msg_3[] = +{ + NM_MT_BS11_SET_ATTR, NM_OC_BS11_HANDOVER, 0x00, 0xFF, 0xFF, + 0xD0, 0x00, /* enableDelayPowerBudgetHO */ + 0x64, 0x00, /* enableDistanceHO */ + 0x67, 0x00, /* enableInternalInterCellHandover */ + 0x68, 0x00, /* enableInternalInterCellHandover */ + 0x6A, 0x00, /* enablePowerBudgetHO */ + 0x6C, 0x00, /* enableRXLEVHO */ + 0x6D, 0x00, /* enableRXQUALHO */ + 0x6F, 0x08, /* hoAveragingDistance */ + 0x70, 0x08, 0x01, /* hoAveragingLev */ + 0x71, 0x10, 0x10, 0x10, + 0x72, 0x08, 0x02, /* hoAveragingQual */ + 0x73, 0x0A, /* hoLowerThresholdLevDL */ + 0x74, 0x05, /* hoLowerThresholdLevUL */ + 0x75, 0x06, /* hoLowerThresholdQualDL */ + 0x76, 0x06, /* hoLowerThresholdQualUL */ + 0x78, 0x14, /* hoThresholdLevDLintra */ + 0x79, 0x14, /* hoThresholdLevULintra */ + 0x7A, 0x14, /* hoThresholdMsRangeMax */ + 0x7D, 0x06, /* nCell */ + NM_ATT_BS11_TIMER_HO_REQUEST, 0x03, + 0x20, 0x01, 0x00, + 0x45, 0x01, 0x00, + 0x48, 0x01, 0x00, + 0x5A, 0x01, 0x00, + 0x5B, 0x01, 0x05, + 0x5E, 0x01, 0x1A, + 0x5F, 0x01, 0x20, + 0x9D, 0x01, 0x00, + 0x47, 0x01, 0x00, + 0x5C, 0x01, 0x64, + 0x5D, 0x01, 0x1E, + 0x97, 0x01, 0x20, + 0xF7, 0x01, 0x3C, +}; + +// Power Control, SET ATTRIBUTES + +/* + Object Class: Power Control + BTS relat. Number: 0 + Instance 2: FF + Instance 3: FF +SET ATTRIBUTES + enableMsPowerControl: 00h = Disabled + enablePowerControlRLFW: 00h = Disabled + pcAveragingLev: + A_LEV_PC: 4 SACCH multiframes + W_LEV_PC: 1 SACCH multiframes + pcAveragingQual: + A_QUAL_PC: 4 SACCH multiframes + W_QUAL_PC: 2 SACCH multiframes + pcLowerThresholdLevDL: 0Fh + pcLowerThresholdLevUL: 0Ah + pcLowerThresholdQualDL: 05h = 3.2% < BER < 6.4% + pcLowerThresholdQualUL: 05h = 3.2% < BER < 6.4% + pcRLFThreshold: 0Ch + pcUpperThresholdLevDL: 14h + pcUpperThresholdLevUL: 0Fh + pcUpperThresholdQualDL: 04h = 1.6% < BER < 3.2% + pcUpperThresholdQualUL: 04h = 1.6% < BER < 3.2% + powerConfirm: 2 ,unit 2 SACCH multiframes + powerControlInterval: 2 ,unit 2 SACCH multiframes + powerIncrStepSize: 02h = 4 dB + powerRedStepSize: 01h = 2 dB + radioLinkTimeoutBs: 64 SACCH multiframes + enableBSPowerControl: 00h = disabled +*/ + +unsigned char msg_4[] = +{ + NM_MT_BS11_SET_ATTR, NM_OC_BS11_PWR_CTRL, 0x00, 0xFF, 0xFF, + NM_ATT_BS11_ENA_MS_PWR_CTRL, 0x00, + NM_ATT_BS11_ENA_PWR_CTRL_RLFW, 0x00, + 0x7E, 0x04, 0x01, /* pcAveragingLev */ + 0x7F, 0x04, 0x02, /* pcAveragingQual */ + 0x80, 0x0F, /* pcLowerThresholdLevDL */ + 0x81, 0x0A, /* pcLowerThresholdLevUL */ + 0x82, 0x05, /* pcLowerThresholdQualDL */ + 0x83, 0x05, /* pcLowerThresholdQualUL */ + 0x84, 0x0C, /* pcRLFThreshold */ + 0x85, 0x14, /* pcUpperThresholdLevDL */ + 0x86, 0x0F, /* pcUpperThresholdLevUL */ + 0x87, 0x04, /* pcUpperThresholdQualDL */ + 0x88, 0x04, /* pcUpperThresholdQualUL */ + 0x89, 0x02, /* powerConfirm */ + 0x8A, 0x02, /* powerConfirmInterval */ + 0x8B, 0x02, /* powerIncrStepSize */ + 0x8C, 0x01, /* powerRedStepSize */ + 0x8D, 0x40, /* radioLinkTimeoutBs */ + 0x65, 0x01, 0x00 // set to 0x01 to enable BSPowerControl +}; + + +// Transceiver, SET TRX ATTRIBUTES (TRX 0) + +/* + Object Class: Transceiver + BTS relat. Number: 0 + Tranceiver number: 0 + Instance 3: FF +SET TRX ATTRIBUTES + aRFCNList (HEX): 0001 + txPwrMaxReduction: 00h = 30dB + radioMeasGran: 254 SACCH multiframes + radioMeasRep: 01h = enabled + memberOfEmergencyConfig: 01h = TRUE + trxArea: 00h = TRX doesn't belong to a concentric cell +*/ + +static unsigned char bs11_attr_radio[] = +{ + NM_ATT_ARFCN_LIST, 0x01, 0x00, HARDCODED_ARFCN /*0x01*/, + NM_ATT_RF_MAXPOWR_R, 0x00, + NM_ATT_BS11_RADIO_MEAS_GRAN, 0x01, 0x05, + NM_ATT_BS11_RADIO_MEAS_REP, 0x01, 0x01, + NM_ATT_BS11_EMRG_CFG_MEMBER, 0x01, 0x01, + NM_ATT_BS11_TRX_AREA, 0x01, 0x00, +}; + +/* + * Patch the various SYSTEM INFORMATION tables to update + * the LAI + */ +static void patch_nm_tables(struct gsm_bts *bts) +{ + u_int8_t arfcn_low = bts->c0->arfcn & 0xff; + u_int8_t arfcn_high = (bts->c0->arfcn >> 8) & 0x0f; + + /* patch ARFCN into BTS Attributes */ + bs11_attr_bts[69] &= 0xf0; + bs11_attr_bts[69] |= arfcn_high; + bs11_attr_bts[70] = arfcn_low; + + /* patch ARFCN into TRX Attributes */ + bs11_attr_radio[2] &= 0xf0; + bs11_attr_radio[2] |= arfcn_high; + bs11_attr_radio[3] = arfcn_low; + + /* patch the RACH attributes */ + if (bts->rach_b_thresh != -1) + bs11_attr_bts[33] = bts->rach_b_thresh & 0xff; + + if (bts->rach_ldavg_slots != -1) { + u_int8_t avg_high = bts->rach_ldavg_slots & 0xff; + u_int8_t avg_low = (bts->rach_ldavg_slots >> 8) & 0x0f; + + bs11_attr_bts[35] = avg_high; + bs11_attr_bts[36] = avg_low; + } + + /* patch BSIC */ + bs11_attr_bts[1] = bts->bsic; + + /* patch the power reduction */ + bs11_attr_radio[5] = bts->c0->max_power_red / 2; +} + + +static void nm_reconfig_ts(struct gsm_bts_trx_ts *ts) +{ + enum abis_nm_chan_comb ccomb = abis_nm_chcomb4pchan(ts->pchan); + struct gsm_e1_subslot *e1l = &ts->e1_link; + + abis_nm_set_channel_attr(ts, ccomb); + + if (is_ipaccess_bts(ts->trx->bts)) + return; + + switch (ts->pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + abis_nm_conn_terr_traf(ts, e1l->e1_nr, e1l->e1_ts, + e1l->e1_ts_ss); + break; + default: + break; + } +} + +static void nm_reconfig_trx(struct gsm_bts_trx *trx) +{ + struct gsm_e1_subslot *e1l = &trx->rsl_e1_link; + int i; + + patch_nm_tables(trx->bts); + + switch (trx->bts->type) { + case GSM_BTS_TYPE_BS11: + /* FIXME: discover this by fetching an attribute */ +#if 0 + trx->nominal_power = 15; /* 15dBm == 30mW PA configuration */ +#else + trx->nominal_power = 24; /* 24dBm == 250mW PA configuration */ +#endif + abis_nm_conn_terr_sign(trx, e1l->e1_nr, e1l->e1_ts, + e1l->e1_ts_ss); + abis_nm_establish_tei(trx->bts, trx->nr, e1l->e1_nr, + e1l->e1_ts, e1l->e1_ts_ss, trx->rsl_tei); + + /* Set Radio Attributes */ + if (trx == trx->bts->c0) + abis_nm_set_radio_attr(trx, bs11_attr_radio, + sizeof(bs11_attr_radio)); + else { + u_int8_t trx1_attr_radio[sizeof(bs11_attr_radio)]; + u_int8_t arfcn_low = trx->arfcn & 0xff; + u_int8_t arfcn_high = (trx->arfcn >> 8) & 0x0f; + memcpy(trx1_attr_radio, bs11_attr_radio, + sizeof(trx1_attr_radio)); + + /* patch ARFCN into TRX Attributes */ + trx1_attr_radio[2] &= 0xf0; + trx1_attr_radio[2] |= arfcn_high; + trx1_attr_radio[3] = arfcn_low; + + abis_nm_set_radio_attr(trx, trx1_attr_radio, + sizeof(trx1_attr_radio)); + } + break; + case GSM_BTS_TYPE_NANOBTS: + switch (trx->bts->band) { + case GSM_BAND_850: + case GSM_BAND_900: + trx->nominal_power = 20; + break; + case GSM_BAND_1800: + case GSM_BAND_1900: + trx->nominal_power = 23; + break; + default: + LOGP(DNM, LOGL_ERROR, "Unsupported nanoBTS GSM band %s\n", + gsm_band_name(trx->bts->band)); + break; + } + break; + default: + break; + } + + for (i = 0; i < TRX_NR_TS; i++) + nm_reconfig_ts(&trx->ts[i]); +} + +static void nm_reconfig_bts(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + + switch (bts->type) { + case GSM_BTS_TYPE_BS11: + patch_nm_tables(bts); + abis_nm_raw_msg(bts, sizeof(msg_1), msg_1); /* set BTS SiteMgr attr*/ + abis_nm_set_bts_attr(bts, bs11_attr_bts, sizeof(bs11_attr_bts)); + abis_nm_raw_msg(bts, sizeof(msg_3), msg_3); /* set BTS handover attr */ + abis_nm_raw_msg(bts, sizeof(msg_4), msg_4); /* set BTS power control attr */ + break; + default: + break; + } + + llist_for_each_entry(trx, &bts->trx_list, list) + nm_reconfig_trx(trx); +} + + +static void bootstrap_om_bs11(struct gsm_bts *bts) +{ + LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr); + + /* stop sending event reports */ + abis_nm_event_reports(bts, 0); + + /* begin DB transmission */ + abis_nm_bs11_db_transmission(bts, 1); + + /* end DB transmission */ + abis_nm_bs11_db_transmission(bts, 0); + + /* Reset BTS Site manager resource */ + abis_nm_bs11_reset_resource(bts); + + /* begin DB transmission */ + abis_nm_bs11_db_transmission(bts, 1); + + /* reconfigure BTS with all TRX and all TS */ + nm_reconfig_bts(bts); + + /* end DB transmission */ + abis_nm_bs11_db_transmission(bts, 0); + + /* Reset BTS Site manager resource */ + abis_nm_bs11_reset_resource(bts); + + /* restart sending event reports */ + abis_nm_event_reports(bts, 1); +} + +static int shutdown_om(struct gsm_bts *bts) +{ + /* stop sending event reports */ + abis_nm_event_reports(bts, 0); + + /* begin DB transmission */ + abis_nm_bs11_db_transmission(bts, 1); + + /* end DB transmission */ + abis_nm_bs11_db_transmission(bts, 0); + + /* Reset BTS Site manager resource */ + abis_nm_bs11_reset_resource(bts); + + return 0; +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int gbl_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_bts *bts; + + if (subsys != SS_GLOBAL) + return 0; + + switch (signal) { + case S_GLOBAL_BTS_CLOSE_OM: + bts = signal_data; + if (bts->type == GSM_BTS_TYPE_BS11) + shutdown_om(signal_data); + break; + } + + return 0; +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int inp_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct input_signal_data *isd = signal_data; + + if (subsys != SS_INPUT) + return 0; + + switch (signal) { + case S_INP_TEI_UP: + switch (isd->link_type) { + case E1INP_SIGN_OML: + if (isd->trx->bts->type == GSM_BTS_TYPE_BS11) + bootstrap_om_bs11(isd->trx->bts); + break; + } + } + + return 0; +} + +int bts_model_bs11_init(void) +{ + model_bs11.features.data = &model_bs11._features_data[0]; + model_bs11.features.data_len = sizeof(model_bs11._features_data); + + gsm_btsmodel_set_feature(&model_bs11, BTS_FEAT_HOPPING); + gsm_btsmodel_set_feature(&model_bs11, BTS_FEAT_HSCSD); + + register_signal_handler(SS_INPUT, inp_sig_cb, NULL); + register_signal_handler(SS_GLOBAL, gbl_sig_cb, NULL); + + return gsm_bts_model_register(&model_bs11); +} diff --git a/src/libbsc/bts_unknown.c b/src/libbsc/bts_unknown.c new file mode 100644 index 000000000..f95459959 --- /dev/null +++ b/src/libbsc/bts_unknown.c @@ -0,0 +1,41 @@ +/* Generic BTS - VTY code tries to allocate this BTS before type is known */ + +/* (C) 2010 by Daniel Willmann + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include +#include +#include + +static struct gsm_bts_model model_unknown = { + .type = GSM_BTS_TYPE_UNKNOWN, + .name = "unknown", + .oml_rcvmsg = &abis_nm_rcvmsg, + .nm_att_tlvdef = { + .def = { + }, + }, +}; + +int bts_model_unknown_init(void) +{ + return gsm_bts_model_register(&model_unknown); +} diff --git a/src/libbsc/chan_alloc.c b/src/libbsc/chan_alloc.c new file mode 100644 index 000000000..167381b37 --- /dev/null +++ b/src/libbsc/chan_alloc.c @@ -0,0 +1,507 @@ +/* GSM Channel allocation routines + * + * (C) 2008 by Harald Welte + * (C) 2008, 2009 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +static int ts_is_usable(struct gsm_bts_trx_ts *ts) +{ + /* FIXME: How does this behave for BS-11 ? */ + if (is_ipaccess_bts(ts->trx->bts)) { + if (!nm_is_running(&ts->nm_state)) + return 0; + } + + return 1; +} + +int trx_is_usable(struct gsm_bts_trx *trx) +{ + /* FIXME: How does this behave for BS-11 ? */ + if (is_ipaccess_bts(trx->bts)) { + if (!nm_is_running(&trx->nm_state) || + !nm_is_running(&trx->bb_transc.nm_state)) + return 0; + } + + return 1; +} + +struct gsm_bts_trx_ts *ts_c0_alloc(struct gsm_bts *bts, + enum gsm_phys_chan_config pchan) +{ + struct gsm_bts_trx *trx = bts->c0; + struct gsm_bts_trx_ts *ts = &trx->ts[0]; + + if (pchan != GSM_PCHAN_CCCH && + pchan != GSM_PCHAN_CCCH_SDCCH4) + return NULL; + + if (ts->pchan != GSM_PCHAN_NONE) + return NULL; + + ts->pchan = pchan; + + return ts; +} + +/* Allocate a physical channel (TS) */ +struct gsm_bts_trx_ts *ts_alloc(struct gsm_bts *bts, + enum gsm_phys_chan_config pchan) +{ + int j; + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + int from, to; + + if (!trx_is_usable(trx)) + continue; + + /* the following constraints are pure policy, + * no requirement to put this restriction in place */ + if (trx == bts->c0) { + /* On the first TRX we run one CCCH and one SDCCH8 */ + switch (pchan) { + case GSM_PCHAN_CCCH: + case GSM_PCHAN_CCCH_SDCCH4: + from = 0; to = 0; + break; + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + from = 1; to = 7; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + default: + return NULL; + } + } else { + /* Every secondary TRX is configured for TCH/F + * and TCH/H only */ + switch (pchan) { + case GSM_PCHAN_SDCCH8_SACCH8C: + from = 1; to = 1; + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + from = 1; to = 7; + break; + default: + return NULL; + } + } + + for (j = from; j <= to; j++) { + struct gsm_bts_trx_ts *ts = &trx->ts[j]; + + if (!ts_is_usable(ts)) + continue; + + if (ts->pchan == GSM_PCHAN_NONE) { + ts->pchan = pchan; + /* set channel attribute on OML */ + abis_nm_set_channel_attr(ts, abis_nm_chcomb4pchan(pchan)); + return ts; + } + } + } + return NULL; +} + +/* Free a physical channel (TS) */ +void ts_free(struct gsm_bts_trx_ts *ts) +{ + ts->pchan = GSM_PCHAN_NONE; +} + +static const u_int8_t subslots_per_pchan[] = { + [GSM_PCHAN_NONE] = 0, + [GSM_PCHAN_CCCH] = 0, + [GSM_PCHAN_CCCH_SDCCH4] = 4, + [GSM_PCHAN_TCH_F] = 1, + [GSM_PCHAN_TCH_H] = 2, + [GSM_PCHAN_SDCCH8_SACCH8C] = 8, + /* FIXME: what about dynamic TCH_F_TCH_H ? */ + [GSM_PCHAN_TCH_F_PDCH] = 1, +}; + +static struct gsm_lchan * +_lc_find_trx(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan) +{ + struct gsm_bts_trx_ts *ts; + int j, ss; + + if (!trx_is_usable(trx)) + return NULL; + + for (j = 0; j < 8; j++) { + ts = &trx->ts[j]; + if (!ts_is_usable(ts)) + continue; + /* ip.access dynamic TCH/F + PDCH combination */ + if (ts->pchan == GSM_PCHAN_TCH_F_PDCH && + pchan == GSM_PCHAN_TCH_F) { + /* we can only consider such a dynamic channel + * if the PDCH is currently inactive */ + if (ts->flags & TS_F_PDCH_MODE) + continue; + } else if (ts->pchan != pchan) + continue; + /* check if all sub-slots are allocated yet */ + for (ss = 0; ss < subslots_per_pchan[pchan]; ss++) { + struct gsm_lchan *lc = &ts->lchan[ss]; + if (lc->type == GSM_LCHAN_NONE && + lc->state == LCHAN_S_NONE) + return lc; + } + } + + return NULL; +} + +static struct gsm_lchan * +_lc_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan) +{ + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + struct gsm_lchan *lc; + + if (bts->chan_alloc_reverse) { + llist_for_each_entry_reverse(trx, &bts->trx_list, list) { + lc = _lc_find_trx(trx, pchan); + if (lc) + return lc; + } + } else { + llist_for_each_entry(trx, &bts->trx_list, list) { + lc = _lc_find_trx(trx, pchan); + if (lc) + return lc; + } + } + + /* we cannot allocate more of these */ + if (pchan == GSM_PCHAN_CCCH_SDCCH4) + return NULL; + + /* if we've reached here, we need to allocate a new physical + * channel for the logical channel type requested */ + ts = ts_alloc(bts, pchan); + if (!ts) { + /* no more radio resources */ + return NULL; + } + return &ts->lchan[0]; +} + +/* Allocate a logical channel */ +struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type, + int allow_bigger) +{ + struct gsm_lchan *lchan = NULL; + enum gsm_phys_chan_config first, second; + + switch (type) { + case GSM_LCHAN_SDCCH: + if (bts->chan_alloc_reverse) { + first = GSM_PCHAN_SDCCH8_SACCH8C; + second = GSM_PCHAN_CCCH_SDCCH4; + } else { + first = GSM_PCHAN_CCCH_SDCCH4; + second = GSM_PCHAN_SDCCH8_SACCH8C; + } + + lchan = _lc_find_bts(bts, first); + if (lchan == NULL) + lchan = _lc_find_bts(bts, second); + + /* allow to assign bigger channels */ + if (allow_bigger) { + if (lchan == NULL) { + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H); + type = GSM_LCHAN_TCH_H; + } + + if (lchan == NULL) { + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F); + type = GSM_LCHAN_TCH_F; + } + } + break; + case GSM_LCHAN_TCH_F: + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F); + break; + case GSM_LCHAN_TCH_H: + lchan =_lc_find_bts(bts, GSM_PCHAN_TCH_H); + /* If we don't have TCH/H available, fall-back to TCH/F */ + if (!lchan) { + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F); + type = GSM_LCHAN_TCH_F; + } + break; + default: + LOGP(DRLL, LOGL_ERROR, "Unknown gsm_chan_t %u\n", type); + } + + if (lchan) { + lchan->type = type; + + /* clear sapis */ + memset(lchan->sapis, 0, ARRAY_SIZE(lchan->sapis)); + + /* clear multi rate config */ + memset(&lchan->mr_conf, 0, sizeof(lchan->mr_conf)); + } else { + struct challoc_signal_data sig; + sig.bts = bts; + sig.type = type; + dispatch_signal(SS_CHALLOC, S_CHALLOC_ALLOC_FAIL, &sig); + } + + return lchan; +} + +/* Free a logical channel */ +void lchan_free(struct gsm_lchan *lchan) +{ + struct challoc_signal_data sig; + int i; + + sig.type = lchan->type; + lchan->type = GSM_LCHAN_NONE; + + + if (lchan->conn) { + struct lchan_signal_data sig; + + /* We might kill an active channel... */ + sig.lchan = lchan; + sig.mr = NULL; + dispatch_signal(SS_LCHAN, S_LCHAN_UNEXPECTED_RELEASE, &sig); + } + + + /* stop the timer */ + bsc_del_timer(&lchan->T3101); + + /* clear cached measuement reports */ + lchan->meas_rep_idx = 0; + for (i = 0; i < ARRAY_SIZE(lchan->meas_rep); i++) { + lchan->meas_rep[i].flags = 0; + lchan->meas_rep[i].nr = 0; + } + for (i = 0; i < ARRAY_SIZE(lchan->neigh_meas); i++) + lchan->neigh_meas[i].arfcn = 0; + + if (lchan->rqd_ref) { + talloc_free(lchan->rqd_ref); + lchan->rqd_ref = NULL; + lchan->rqd_ta = 0; + } + + sig.lchan = lchan; + sig.bts = lchan->ts->trx->bts; + dispatch_signal(SS_CHALLOC, S_CHALLOC_FREED, &sig); + + if (lchan->conn) { + LOGP(DRLL, LOGL_ERROR, "the subscriber connection should be gone.\n"); + lchan->conn = NULL; + } + + lchan->sach_deact = 0; + lchan->release_reason = 0; + + /* FIXME: ts_free() the timeslot, if we're the last logical + * channel using it */ +} + +/* + * There was an error with the TRX and we need to forget + * any state so that a lchan can be allocated again after + * the trx is fully usable. + * + * This should be called after lchan_free to force a channel + * be available for allocation again. This means that this + * method will stop the "delay after error"-timer and set the + * state to LCHAN_S_NONE. + */ +void lchan_reset(struct gsm_lchan *lchan) +{ + bsc_del_timer(&lchan->T3101); + bsc_del_timer(&lchan->T3111); + bsc_del_timer(&lchan->error_timer); + + lchan->type = GSM_LCHAN_NONE; + lchan->state = LCHAN_S_NONE; +} + +/* release the next allocated SAPI or return 0 */ +static int _lchan_release_next_sapi(struct gsm_lchan *lchan) +{ + int sapi; + + for (sapi = 1; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) { + u_int8_t link_id; + if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) + continue; + + link_id = sapi; + if (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H) + link_id |= 0x40; + rsl_release_request(lchan, link_id, lchan->release_reason); + return 0; + } + + return 1; +} + +/* Drive the release process of the lchan */ +static void _lchan_handle_release(struct gsm_lchan *lchan) +{ + /* Ask for SAPI != 0 to be freed first and stop if we need to wait */ + if (_lchan_release_next_sapi(lchan) == 0) + return; + + if (lchan->sach_deact) { + gsm48_send_rr_release(lchan); + return; + } + + rsl_release_request(lchan, 0, lchan->release_reason); + rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ); +} + +/* called from abis rsl */ +int rsl_lchan_rll_release(struct gsm_lchan *lchan, u_int8_t link_id) +{ + if (lchan->state != LCHAN_S_REL_REQ) + return -1; + + if ((link_id & 0x7) != 0) + _lchan_handle_release(lchan); + return 0; +} + +/* Consider releasing the channel now */ +int lchan_release(struct gsm_lchan *lchan, int sach_deact, int reason) +{ + DEBUGP(DRLL, "%s starting release sequence\n", gsm_lchan_name(lchan)); + rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ); + + lchan->conn = NULL; + lchan->release_reason = reason; + lchan->sach_deact = sach_deact; + _lchan_handle_release(lchan); + return 1; +} + +static struct gsm_lchan* lchan_find(struct gsm_bts *bts, struct gsm_subscriber *subscr) { + struct gsm_bts_trx *trx; + int ts_no, lchan_no; + + llist_for_each_entry(trx, &bts->trx_list, list) { + for (ts_no = 0; ts_no < 8; ++ts_no) { + for (lchan_no = 0; lchan_no < TS_MAX_LCHAN; ++lchan_no) { + struct gsm_lchan *lchan = + &trx->ts[ts_no].lchan[lchan_no]; + if (lchan->conn && subscr == lchan->conn->subscr) + return lchan; + } + } + } + + return NULL; +} + +struct gsm_subscriber_connection *connection_for_subscr(struct gsm_subscriber *subscr) +{ + struct gsm_bts *bts; + struct gsm_network *net = subscr->net; + struct gsm_lchan *lchan; + + llist_for_each_entry(bts, &net->bts_list, list) { + lchan = lchan_find(bts, subscr); + if (lchan) + return lchan->conn; + } + + return NULL; +} + +void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + int i; + + /* skip administratively deactivated tranxsceivers */ + if (!nm_is_running(&trx->nm_state) || + !nm_is_running(&trx->bb_transc.nm_state)) + continue; + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + struct load_counter *pl = &cl->pchan[ts->pchan]; + int j; + + /* skip administratively deactivated timeslots */ + if (!nm_is_running(&ts->nm_state)) + continue; + + for (j = 0; j < subslots_per_pchan[ts->pchan]; j++) { + struct gsm_lchan *lchan = &ts->lchan[j]; + + pl->total++; + + switch (lchan->state) { + case LCHAN_S_NONE: + break; + default: + pl->used++; + break; + } + } + } + } +} + +void network_chan_load(struct pchan_load *pl, struct gsm_network *net) +{ + struct gsm_bts *bts; + + memset(pl, 0, sizeof(*pl)); + + llist_for_each_entry(bts, &net->bts_list, list) + bts_chan_load(pl, bts); +} + diff --git a/src/libbsc/e1_config.c b/src/libbsc/e1_config.c new file mode 100644 index 000000000..958839dcc --- /dev/null +++ b/src/libbsc/e1_config.c @@ -0,0 +1,296 @@ +/* OpenBSC E1 Input code */ + +/* (C) 2008-2010 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SAPI_L2ML 0 +#define SAPI_OML 62 +#define SAPI_RSL 0 /* 63 ? */ + +/* The e1_reconfig_*() functions below tale the configuration present in the + * bts/trx/ts data structures and ensure the E1 configuration reflects the + * timeslot/subslot/TEI configuration */ + +int e1_reconfig_ts(struct gsm_bts_trx_ts *ts) +{ + struct gsm_e1_subslot *e1_link = &ts->e1_link; + struct e1inp_line *line; + struct e1inp_ts *e1_ts; + + DEBUGP(DMI, "e1_reconfig_ts(%u,%u,%u)\n", ts->trx->bts->nr, ts->trx->nr, ts->nr); + + if (!e1_link->e1_ts) { + LOGP(DINP, LOGL_ERROR, "TS (%u/%u/%u) without E1 timeslot?\n", + ts->nr, ts->trx->nr, ts->trx->bts->nr); + return 0; + } + + line = e1inp_line_get(e1_link->e1_nr); + if (!line) { + LOGP(DINP, LOGL_ERROR, "TS (%u/%u/%u) referring to " + "non-existing E1 line %u\n", ts->nr, ts->trx->nr, + ts->trx->bts->nr, e1_link->e1_nr); + return -ENOMEM; + } + + switch (ts->pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + e1_ts = &line->ts[e1_link->e1_ts-1]; + e1inp_ts_config(e1_ts, line, E1INP_TS_TYPE_TRAU); + subch_demux_activate(&e1_ts->trau.demux, e1_link->e1_ts_ss); + break; + default: + break; + } + + return 0; +} + +int e1_reconfig_trx(struct gsm_bts_trx *trx) +{ + struct gsm_e1_subslot *e1_link = &trx->rsl_e1_link; + struct e1inp_ts *sign_ts; + struct e1inp_line *line; + struct e1inp_sign_link *rsl_link; + int i; + + if (!e1_link->e1_ts) { + LOGP(DINP, LOGL_ERROR, "TRX (%u/%u) RSL link without " + "timeslot?\n", trx->bts->nr, trx->nr); + return -EINVAL; + } + + /* RSL Link */ + line = e1inp_line_get(e1_link->e1_nr); + if (!line) { + LOGP(DINP, LOGL_ERROR, "TRX (%u/%u) RSL link referring " + "to non-existing E1 line %u\n", trx->bts->nr, + trx->nr, e1_link->e1_nr); + return -ENOMEM; + } + sign_ts = &line->ts[e1_link->e1_ts-1]; + e1inp_ts_config(sign_ts, line, E1INP_TS_TYPE_SIGN); + /* Ericsson RBS have a per-TRX OML link in parallel to RSL */ + if (trx->bts->type == GSM_BTS_TYPE_RBS2000) { + struct e1inp_sign_link *oml_link; + oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, trx, + trx->rsl_tei, SAPI_OML); + if (!oml_link) { + LOGP(DINP, LOGL_ERROR, "TRX (%u/%u) OML link creation " + "failed\n", trx->bts->nr, trx->nr); + return -ENOMEM; + } + if (trx->oml_link) + e1inp_sign_link_destroy(trx->oml_link); + trx->oml_link = oml_link; + } + rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL, + trx, trx->rsl_tei, SAPI_RSL); + if (!rsl_link) { + LOGP(DINP, LOGL_ERROR, "TRX (%u/%u) RSL link creation " + "failed\n", trx->bts->nr, trx->nr); + return -ENOMEM; + } + if (trx->rsl_link) + e1inp_sign_link_destroy(trx->rsl_link); + trx->rsl_link = rsl_link; + + for (i = 0; i < TRX_NR_TS; i++) + e1_reconfig_ts(&trx->ts[i]); + + return 0; +} + +int e1_reconfig_bts(struct gsm_bts *bts) +{ + struct gsm_e1_subslot *e1_link = &bts->oml_e1_link; + struct e1inp_ts *sign_ts; + struct e1inp_line *line; + struct e1inp_sign_link *oml_link; + struct gsm_bts_trx *trx; + + DEBUGP(DMI, "e1_reconfig_bts(%u)\n", bts->nr); + + if (!e1_link->e1_ts) { + LOGP(DINP, LOGL_ERROR, "BTS %u OML link without timeslot?\n", + bts->nr); + return -EINVAL; + } + + /* OML link */ + line = e1inp_line_get(e1_link->e1_nr); + if (!line) { + LOGP(DINP, LOGL_ERROR, "BTS %u OML link referring to " + "non-existing E1 line %u\n", bts->nr, e1_link->e1_nr); + return -ENOMEM; + } + sign_ts = &line->ts[e1_link->e1_ts-1]; + e1inp_ts_config(sign_ts, line, E1INP_TS_TYPE_SIGN); + oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, + bts->c0, bts->oml_tei, SAPI_OML); + if (!oml_link) { + LOGP(DINP, LOGL_ERROR, "BTS %u OML link creation failed\n", + bts->nr); + return -ENOMEM; + } + if (bts->oml_link) + e1inp_sign_link_destroy(bts->oml_link); + bts->oml_link = oml_link; + + llist_for_each_entry(trx, &bts->trx_list, list) + e1_reconfig_trx(trx); + + /* notify E1 input something has changed */ + return e1inp_line_update(line); +} + +#if 0 +/* do some compiled-in configuration for our BTS/E1 setup */ +int e1_config(struct gsm_bts *bts, int cardnr, int release_l2) +{ + struct e1inp_line *line; + struct e1inp_ts *sign_ts; + struct e1inp_sign_link *oml_link, *rsl_link; + struct gsm_bts_trx *trx = bts->c0; + int base_ts; + + switch (bts->nr) { + case 0: + /* First BTS uses E1 TS 01,02,03,04,05 */ + base_ts = HARDCODED_BTS0_TS - 1; + break; + case 1: + /* Second BTS uses E1 TS 06,07,08,09,10 */ + base_ts = HARDCODED_BTS1_TS - 1; + break; + case 2: + /* Third BTS uses E1 TS 11,12,13,14,15 */ + base_ts = HARDCODED_BTS2_TS - 1; + default: + return -EINVAL; + } + + line = talloc_zero(tall_bsc_ctx, struct e1inp_line); + if (!line) + return -ENOMEM; + + /* create E1 timeslots for signalling and TRAU frames */ + e1inp_ts_config(&line->ts[base_ts+1-1], line, E1INP_TS_TYPE_SIGN); + e1inp_ts_config(&line->ts[base_ts+2-1], line, E1INP_TS_TYPE_TRAU); + e1inp_ts_config(&line->ts[base_ts+3-1], line, E1INP_TS_TYPE_TRAU); + + /* create signalling links for TS1 */ + sign_ts = &line->ts[base_ts+1-1]; + oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, + trx, TEI_OML, SAPI_OML); + rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL, + trx, TEI_RSL, SAPI_RSL); + + /* create back-links from bts/trx */ + bts->oml_link = oml_link; + trx->rsl_link = rsl_link; + + /* enable subchannel demuxer on TS2 */ + subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 1); + subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 2); + subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 3); + + /* enable subchannel demuxer on TS3 */ + subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 0); + subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 1); + subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 2); + subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 3); + + trx = gsm_bts_trx_num(bts, 1); + if (trx) { + /* create E1 timeslots for TRAU frames of TRX1 */ + e1inp_ts_config(&line->ts[base_ts+4-1], line, E1INP_TS_TYPE_TRAU); + e1inp_ts_config(&line->ts[base_ts+5-1], line, E1INP_TS_TYPE_TRAU); + + /* create RSL signalling link for TRX1 */ + sign_ts = &line->ts[base_ts+1-1]; + rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL, + trx, TEI_RSL+1, SAPI_RSL); + /* create back-links from trx */ + trx->rsl_link = rsl_link; + + /* enable subchannel demuxer on TS2 */ + subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 0); + subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 1); + subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 2); + subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 3); + + /* enable subchannel demuxer on TS3 */ + subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 0); + subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 1); + subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 2); + subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 3); + } + + return mi_setup(cardnr, line, release_l2); +} +#endif + +/* configure pseudo E1 line in ip.access style and connect to BTS */ +int ia_config_connect(struct gsm_bts *bts, struct sockaddr_in *sin) +{ + struct e1inp_line *line; + struct e1inp_ts *sign_ts, *rsl_ts; + struct e1inp_sign_link *oml_link, *rsl_link; + + line = talloc_zero(tall_bsc_ctx, struct e1inp_line); + if (!line) + return -ENOMEM; + + /* create E1 timeslots for signalling and TRAU frames */ + e1inp_ts_config(&line->ts[1-1], line, E1INP_TS_TYPE_SIGN); + e1inp_ts_config(&line->ts[2-1], line, E1INP_TS_TYPE_SIGN); + + /* create signalling links for TS1 */ + sign_ts = &line->ts[1-1]; + rsl_ts = &line->ts[2-1]; + oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, + bts->c0, 0xff, 0); + rsl_link = e1inp_sign_link_create(rsl_ts, E1INP_SIGN_RSL, + bts->c0, 0, 0); + + /* create back-links from bts/trx */ + bts->oml_link = oml_link; + bts->c0->rsl_link = rsl_link; + + /* default port at BTS for incoming connections is 3006 */ + if (sin->sin_port == 0) + sin->sin_port = htons(3006); + + return ipaccess_connect(line, sin); +} diff --git a/src/libbsc/gsm_04_08_utils.c b/src/libbsc/gsm_04_08_utils.c new file mode 100644 index 000000000..6d12cc08e --- /dev/null +++ b/src/libbsc/gsm_04_08_utils.c @@ -0,0 +1,655 @@ +/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface + * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 + * utility functions + */ + +/* (C) 2008-2009 by Harald Welte + * (C) 2008, 2009 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +/* should ip.access BTS use direct RTP streams between each other (1), + * or should OpenBSC always act as RTP relay/proxy in between (0) ? */ +int ipacc_rtp_direct = 1; + +static int gsm48_sendmsg(struct msgb *msg) +{ + if (msg->lchan) + msg->trx = msg->lchan->ts->trx; + + msg->l3h = msg->data; + return rsl_data_request(msg, 0); +} + +/* Section 9.1.8 / Table 9.9 */ +struct chreq { + u_int8_t val; + u_int8_t mask; + enum chreq_type type; +}; + +/* If SYSTEM INFORMATION TYPE 4 NECI bit == 1 */ +static const struct chreq chreq_type_neci1[] = { + { 0xa0, 0xe0, CHREQ_T_EMERG_CALL }, + { 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_F }, + { 0x68, 0xfc, CHREQ_T_CALL_REEST_TCH_H }, + { 0x6c, 0xfc, CHREQ_T_CALL_REEST_TCH_H_DBL }, + { 0xe0, 0xe0, CHREQ_T_TCH_F }, + { 0x40, 0xf0, CHREQ_T_VOICE_CALL_TCH_H }, + { 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H }, + { 0x00, 0xf0, CHREQ_T_LOCATION_UPD }, + { 0x10, 0xf0, CHREQ_T_SDCCH }, + { 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI1 }, + { 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F }, + { 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH }, + { 0x67, 0xff, CHREQ_T_LMU }, + { 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH }, + { 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH }, + { 0x63, 0xff, CHREQ_T_RESERVED_SDCCH }, + { 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE }, +}; + +/* If SYSTEM INFORMATION TYPE 4 NECI bit == 0 */ +static const struct chreq chreq_type_neci0[] = { + { 0xa0, 0xe0, CHREQ_T_EMERG_CALL }, + { 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_H }, + { 0xe0, 0xe0, CHREQ_T_TCH_F }, + { 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H }, + { 0x00, 0xe0, CHREQ_T_LOCATION_UPD }, + { 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI0 }, + { 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F }, + { 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH }, + { 0x67, 0xff, CHREQ_T_LMU }, + { 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH }, + { 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH }, + { 0x63, 0xff, CHREQ_T_RESERVED_SDCCH }, + { 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE }, +}; + +static const enum gsm_chan_t ctype_by_chreq[] = { + [CHREQ_T_EMERG_CALL] = GSM_LCHAN_TCH_F, + [CHREQ_T_CALL_REEST_TCH_F] = GSM_LCHAN_TCH_F, + [CHREQ_T_CALL_REEST_TCH_H] = GSM_LCHAN_TCH_H, + [CHREQ_T_CALL_REEST_TCH_H_DBL] = GSM_LCHAN_TCH_H, + [CHREQ_T_SDCCH] = GSM_LCHAN_SDCCH, + [CHREQ_T_TCH_F] = GSM_LCHAN_TCH_F, + [CHREQ_T_VOICE_CALL_TCH_H] = GSM_LCHAN_TCH_H, + [CHREQ_T_DATA_CALL_TCH_H] = GSM_LCHAN_TCH_H, + [CHREQ_T_LOCATION_UPD] = GSM_LCHAN_SDCCH, + [CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_SDCCH, + [CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_SDCCH, + [CHREQ_T_PAG_R_TCH_F] = GSM_LCHAN_TCH_F, + [CHREQ_T_PAG_R_TCH_FH] = GSM_LCHAN_TCH_F, + [CHREQ_T_LMU] = GSM_LCHAN_SDCCH, + [CHREQ_T_RESERVED_SDCCH] = GSM_LCHAN_SDCCH, + [CHREQ_T_RESERVED_IGNORE] = GSM_LCHAN_UNKNOWN, +}; + +static const enum gsm_chreq_reason_t reason_by_chreq[] = { + [CHREQ_T_EMERG_CALL] = GSM_CHREQ_REASON_EMERG, + [CHREQ_T_CALL_REEST_TCH_F] = GSM_CHREQ_REASON_CALL, + [CHREQ_T_CALL_REEST_TCH_H] = GSM_CHREQ_REASON_CALL, + [CHREQ_T_CALL_REEST_TCH_H_DBL] = GSM_CHREQ_REASON_CALL, + [CHREQ_T_SDCCH] = GSM_CHREQ_REASON_OTHER, + [CHREQ_T_TCH_F] = GSM_CHREQ_REASON_OTHER, + [CHREQ_T_VOICE_CALL_TCH_H] = GSM_CHREQ_REASON_CALL, + [CHREQ_T_DATA_CALL_TCH_H] = GSM_CHREQ_REASON_OTHER, + [CHREQ_T_LOCATION_UPD] = GSM_CHREQ_REASON_LOCATION_UPD, + [CHREQ_T_PAG_R_ANY_NECI1] = GSM_CHREQ_REASON_PAG, + [CHREQ_T_PAG_R_ANY_NECI0] = GSM_CHREQ_REASON_PAG, + [CHREQ_T_PAG_R_TCH_F] = GSM_CHREQ_REASON_PAG, + [CHREQ_T_PAG_R_TCH_FH] = GSM_CHREQ_REASON_PAG, + [CHREQ_T_LMU] = GSM_CHREQ_REASON_OTHER, + [CHREQ_T_RESERVED_SDCCH] = GSM_CHREQ_REASON_OTHER, + [CHREQ_T_RESERVED_IGNORE] = GSM_CHREQ_REASON_OTHER, +}; + +/* verify that the two tables match */ +static_assert(sizeof(ctype_by_chreq) == + sizeof(((struct gsm_network *) NULL)->ctype_by_chreq), assert_size); + +/* + * Update channel types for request based on policy. E.g. in the + * case of a TCH/H network/bsc use TCH/H for the emergency calls, + * for early assignment assign a SDCCH and some other options. + */ +void gsm_net_update_ctype(struct gsm_network *network) +{ + /* copy over the data */ + memcpy(network->ctype_by_chreq, ctype_by_chreq, sizeof(ctype_by_chreq)); + + /* + * Use TCH/H for emergency calls when this cell allows TCH/H. Maybe it + * is better to iterate over the BTS/TRX and check if no TCH/F is available + * and then set it to TCH/H. + */ + if (network->neci) + network->ctype_by_chreq[CHREQ_T_EMERG_CALL] = GSM_LCHAN_TCH_H; + + if (network->pag_any_tch) { + if (network->neci) { + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_H; + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_H; + } else { + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_F; + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_F; + } + } +} + +enum gsm_chan_t get_ctype_by_chreq(struct gsm_network *network, u_int8_t ra) +{ + int i; + int length; + const struct chreq *chreq; + + if (network->neci) { + chreq = chreq_type_neci1; + length = ARRAY_SIZE(chreq_type_neci1); + } else { + chreq = chreq_type_neci0; + length = ARRAY_SIZE(chreq_type_neci0); + } + + + for (i = 0; i < length; i++) { + const struct chreq *chr = &chreq[i]; + if ((ra & chr->mask) == chr->val) + return network->ctype_by_chreq[chr->type]; + } + LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST RQD 0x%02x\n", ra); + return GSM_LCHAN_SDCCH; +} + +enum gsm_chreq_reason_t get_reason_by_chreq(u_int8_t ra, int neci) +{ + int i; + int length; + const struct chreq *chreq; + + if (neci) { + chreq = chreq_type_neci1; + length = ARRAY_SIZE(chreq_type_neci1); + } else { + chreq = chreq_type_neci0; + length = ARRAY_SIZE(chreq_type_neci0); + } + + for (i = 0; i < length; i++) { + const struct chreq *chr = &chreq[i]; + if ((ra & chr->mask) == chr->val) + return reason_by_chreq[chr->type]; + } + LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST REASON 0x%02x\n", ra); + return GSM_CHREQ_REASON_OTHER; +} + +/* 7.1.7 and 9.1.7: RR CHANnel RELease */ +int gsm48_send_rr_release(struct gsm_lchan *lchan) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + u_int8_t *cause; + + msg->lchan = lchan; + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CHAN_REL; + + cause = msgb_put(msg, 1); + cause[0] = GSM48_RR_CAUSE_NORMAL; + + DEBUGP(DRR, "Sending Channel Release: Chan: Number: %d Type: %d\n", + lchan->nr, lchan->type); + + /* Send actual release request to MS */ + gsm48_sendmsg(msg); + /* FIXME: Start Timer T3109 */ + + /* Deactivate the SACCH on the BTS side */ + return rsl_deact_sacch(lchan); +} + +int send_siemens_mrpci(struct gsm_lchan *lchan, + u_int8_t *classmark2_lv) +{ + struct rsl_mrpci mrpci; + + if (classmark2_lv[0] < 2) + return -EINVAL; + + mrpci.power_class = classmark2_lv[1] & 0x7; + mrpci.vgcs_capable = classmark2_lv[2] & (1 << 1); + mrpci.vbs_capable = classmark2_lv[2] & (1 <<2); + mrpci.gsm_phase = (classmark2_lv[1]) >> 5 & 0x3; + + return rsl_siemens_mrpci(lchan, &mrpci); +} + +int gsm48_extract_mi(uint8_t *classmark2_lv, int length, char *mi_string, uint8_t *mi_type) +{ + /* Check the size for the classmark */ + if (length < 1 + *classmark2_lv) + return -1; + + u_int8_t *mi_lv = classmark2_lv + *classmark2_lv + 1; + if (length < 2 + *classmark2_lv + mi_lv[0]) + return -2; + + *mi_type = mi_lv[1] & GSM_MI_TYPE_MASK; + return gsm48_mi_to_string(mi_string, GSM48_MI_SIZE, mi_lv+1, *mi_lv); +} + +int gsm48_paging_extract_mi(struct gsm48_pag_resp *resp, int length, + char *mi_string, u_int8_t *mi_type) +{ + static const uint32_t classmark_offset = + offsetof(struct gsm48_pag_resp, classmark2); + u_int8_t *classmark2_lv = (uint8_t *) &resp->classmark2; + return gsm48_extract_mi(classmark2_lv, length - classmark_offset, + mi_string, mi_type); +} + +int gsm48_handle_paging_resp(struct gsm_subscriber_connection *conn, + struct msgb *msg, struct gsm_subscriber *subscr) +{ + struct gsm_bts *bts = msg->lchan->ts->trx->bts; + struct gsm48_hdr *gh = msgb_l3(msg); + u_int8_t *classmark2_lv = gh->data + 1; + + if (is_siemens_bts(bts)) + send_siemens_mrpci(msg->lchan, classmark2_lv); + + if (!conn->subscr) { + conn->subscr = subscr; + } else if (conn->subscr != subscr) { + LOGP(DRR, LOGL_ERROR, "<- Channel already owned by someone else?\n"); + subscr_put(subscr); + return -EINVAL; + } else { + DEBUGP(DRR, "<- Channel already owned by us\n"); + subscr_put(subscr); + subscr = conn->subscr; + } + + counter_inc(bts->network->stats.paging.completed); + + /* Stop paging on the bts we received the paging response */ + paging_request_stop(conn->bts, subscr, conn, msg); + return 0; +} + +/* Chapter 9.1.9: Ciphering Mode Command */ +int gsm48_send_rr_ciph_mode(struct gsm_lchan *lchan, int want_imeisv) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + u_int8_t ciph_mod_set; + + msg->lchan = lchan; + + DEBUGP(DRR, "TX CIPHERING MODE CMD\n"); + + if (lchan->encr.alg_id <= RSL_ENC_ALG_A5(0)) + ciph_mod_set = 0; + else + ciph_mod_set = (lchan->encr.alg_id-2)<<1 | 1; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CIPH_M_CMD; + gh->data[0] = (want_imeisv & 0x1) << 4 | (ciph_mod_set & 0xf); + + return rsl_encryption_cmd(msg); +} + +static void gsm48_cell_desc(struct gsm48_cell_desc *cd, + const struct gsm_bts *bts) +{ + cd->ncc = (bts->bsic >> 3 & 0x7); + cd->bcc = (bts->bsic & 0x7); + cd->arfcn_hi = bts->c0->arfcn >> 8; + cd->arfcn_lo = bts->c0->arfcn & 0xff; +} + +void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd, + const struct gsm_lchan *lchan) +{ + u_int16_t arfcn = lchan->ts->trx->arfcn & 0x3ff; + + cd->chan_nr = lchan2chan_nr(lchan); + if (!lchan->ts->hopping.enabled) { + cd->h0.tsc = lchan->ts->trx->bts->tsc; + cd->h0.h = 0; + cd->h0.arfcn_high = arfcn >> 8; + cd->h0.arfcn_low = arfcn & 0xff; + } else { + cd->h1.tsc = lchan->ts->trx->bts->tsc; + cd->h1.h = 1; + cd->h1.maio_high = lchan->ts->hopping.maio >> 2; + cd->h1.maio_low = lchan->ts->hopping.maio & 0x03; + cd->h1.hsn = lchan->ts->hopping.hsn; + } +} + +#define GSM48_HOCMD_CCHDESC_LEN 16 + +/* Chapter 9.1.15: Handover Command */ +int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan, + u_int8_t power_command, u_int8_t ho_ref) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + struct gsm48_ho_cmd *ho = + (struct gsm48_ho_cmd *) msgb_put(msg, sizeof(*ho)); + + msg->lchan = old_lchan; + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_HANDO_CMD; + + /* mandatory bits */ + gsm48_cell_desc(&ho->cell_desc, new_lchan->ts->trx->bts); + gsm48_lchan2chan_desc(&ho->chan_desc, new_lchan); + ho->ho_ref = ho_ref; + ho->power_command = power_command; + + if (new_lchan->ts->hopping.enabled) { + struct gsm_bts *bts = new_lchan->ts->trx->bts; + struct gsm48_system_information_type_1 *si1; + uint8_t *cur; + + si1 = GSM_BTS_SI(bts, SYSINFO_TYPE_1); + /* Copy the Cell Chan Desc (ARFCNS in this cell) */ + msgb_put_u8(msg, GSM48_IE_CELL_CH_DESC); + cur = msgb_put(msg, GSM48_HOCMD_CCHDESC_LEN); + memcpy(cur, si1->cell_channel_description, + GSM48_HOCMD_CCHDESC_LEN); + /* Copy the Mobile Allocation */ + msgb_tlv_put(msg, GSM48_IE_MA_BEFORE, + new_lchan->ts->hopping.ma_len, + new_lchan->ts->hopping.ma_data); + } + /* FIXME: optional bits for type of synchronization? */ + + return gsm48_sendmsg(msg); +} + +/* Chapter 9.1.2: Assignment Command */ +int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, u_int8_t power_command) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + struct gsm48_ass_cmd *ass = + (struct gsm48_ass_cmd *) msgb_put(msg, sizeof(*ass)); + + DEBUGP(DRR, "-> ASSIGNMENT COMMAND tch_mode=0x%02x\n", lchan->tch_mode); + + msg->lchan = dest_lchan; + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_ASS_CMD; + + /* + * fill the channel information element, this code + * should probably be shared with rsl_rx_chan_rqd(), + * gsm48_tx_chan_mode_modify. But beware that 10.5.2.5 + * 10.5.2.5.a have slightly different semantic for + * the chan_desc. But as long as multi-slot configurations + * are not used we seem to be fine. + */ + gsm48_lchan2chan_desc(&ass->chan_desc, lchan); + ass->power_command = power_command; + + /* optional: cell channel description */ + + msgb_tv_put(msg, GSM48_IE_CHANMODE_1, lchan->tch_mode); + + /* mobile allocation in case of hopping */ + if (lchan->ts->hopping.enabled) { + msgb_tlv_put(msg, GSM48_IE_MA_BEFORE, lchan->ts->hopping.ma_len, + lchan->ts->hopping.ma_data); + } + + /* in case of multi rate we need to attach a config */ + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) { + if (lchan->mr_conf.ver == 0) { + LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec " + "without multirate config.\n"); + } else { + u_int8_t *data = msgb_put(msg, 4); + data[0] = GSM48_IE_MUL_RATE_CFG; + data[1] = 0x2; + memcpy(&data[2], &lchan->mr_conf, 2); + } + } + + return gsm48_sendmsg(msg); +} + +/* 9.1.5 Channel mode modify: Modify the mode on the MS side */ +int gsm48_tx_chan_mode_modify(struct gsm_lchan *lchan, u_int8_t mode) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + struct gsm48_chan_mode_modify *cmm = + (struct gsm48_chan_mode_modify *) msgb_put(msg, sizeof(*cmm)); + + DEBUGP(DRR, "-> CHANNEL MODE MODIFY mode=0x%02x\n", mode); + + lchan->tch_mode = mode; + msg->lchan = lchan; + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CHAN_MODE_MODIF; + + /* fill the channel information element, this code + * should probably be shared with rsl_rx_chan_rqd() */ + gsm48_lchan2chan_desc(&cmm->chan_desc, lchan); + cmm->mode = mode; + + /* in case of multi rate we need to attach a config */ + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) { + if (lchan->mr_conf.ver == 0) { + LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec " + "without multirate config.\n"); + } else { + u_int8_t *data = msgb_put(msg, 4); + data[0] = GSM48_IE_MUL_RATE_CFG; + data[1] = 0x2; + memcpy(&data[2], &lchan->mr_conf, 2); + } + } + + return gsm48_sendmsg(msg); +} + +int gsm48_lchan_modify(struct gsm_lchan *lchan, u_int8_t lchan_mode) +{ + int rc; + + rc = gsm48_tx_chan_mode_modify(lchan, lchan_mode); + if (rc < 0) + return rc; + + return rc; +} + +int gsm48_rx_rr_modif_ack(struct msgb *msg) +{ + int rc; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_chan_mode_modify *mod = + (struct gsm48_chan_mode_modify *) gh->data; + + DEBUGP(DRR, "CHANNEL MODE MODIFY ACK\n"); + + if (mod->mode != msg->lchan->tch_mode) { + LOGP(DRR, LOGL_ERROR, "CHANNEL MODE change failed. Wanted: %d Got: %d\n", + msg->lchan->tch_mode, mod->mode); + return -1; + } + + /* update the channel type */ + switch (mod->mode) { + case GSM48_CMODE_SIGN: + msg->lchan->rsl_cmode = RSL_CMOD_SPD_SIGN; + break; + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + msg->lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH; + break; + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + msg->lchan->rsl_cmode = RSL_CMOD_SPD_DATA; + break; + } + + /* We've successfully modified the MS side of the channel, + * now go on to modify the BTS side of the channel */ + rc = rsl_chan_mode_modify_req(msg->lchan); + + /* FIXME: we not only need to do this after mode modify, but + * also after channel activation */ + if (is_ipaccess_bts(msg->lchan->ts->trx->bts) && mod->mode != GSM48_CMODE_SIGN) + rsl_ipacc_crcx(msg->lchan); + return rc; +} + +int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + u_int8_t *data = gh->data; + struct gsm_bts *bts = msg->lchan->ts->trx->bts; + struct bitvec *nbv = &bts->si_common.neigh_list; + struct gsm_meas_rep_cell *mrc; + + if (gh->msg_type != GSM48_MT_RR_MEAS_REP) + return -EINVAL; + + if (data[0] & 0x80) + rep->flags |= MEAS_REP_F_BA1; + if (data[0] & 0x40) + rep->flags |= MEAS_REP_F_UL_DTX; + if ((data[1] & 0x40) == 0x00) + rep->flags |= MEAS_REP_F_DL_VALID; + + rep->dl.full.rx_lev = data[0] & 0x3f; + rep->dl.sub.rx_lev = data[1] & 0x3f; + rep->dl.full.rx_qual = (data[3] >> 4) & 0x7; + rep->dl.sub.rx_qual = (data[3] >> 1) & 0x7; + + rep->num_cell = ((data[3] >> 6) & 0x3) | ((data[2] & 0x01) << 2); + if (rep->num_cell < 1 || rep->num_cell > 6) + return 0; + + /* an encoding nightmare in perfection */ + mrc = &rep->cell[0]; + mrc->rxlev = data[3] & 0x3f; + mrc->neigh_idx = data[4] >> 3; + mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + mrc->bsic = ((data[4] & 0x07) << 3) | (data[5] >> 5); + if (rep->num_cell < 2) + return 0; + + mrc = &rep->cell[1]; + mrc->rxlev = ((data[5] & 0x1f) << 1) | (data[6] >> 7); + mrc->neigh_idx = (data[6] >> 2) & 0x1f; + mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + mrc->bsic = ((data[6] & 0x03) << 4) | (data[7] >> 4); + if (rep->num_cell < 3) + return 0; + + mrc = &rep->cell[2]; + mrc->rxlev = ((data[7] & 0x0f) << 2) | (data[8] >> 6); + mrc->neigh_idx = (data[8] >> 1) & 0x1f; + mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + mrc->bsic = ((data[8] & 0x01) << 5) | (data[9] >> 3); + if (rep->num_cell < 4) + return 0; + + mrc = &rep->cell[3]; + mrc->rxlev = ((data[9] & 0x07) << 3) | (data[10] >> 5); + mrc->neigh_idx = data[10] & 0x1f; + mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + mrc->bsic = data[11] >> 2; + if (rep->num_cell < 5) + return 0; + + mrc = &rep->cell[4]; + mrc->rxlev = ((data[11] & 0x03) << 4) | (data[12] >> 4); + mrc->neigh_idx = ((data[12] & 0xf) << 1) | (data[13] >> 7); + mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + mrc->bsic = (data[13] >> 1) & 0x3f; + if (rep->num_cell < 6) + return 0; + + mrc = &rep->cell[5]; + mrc->rxlev = ((data[13] & 0x01) << 5) | (data[14] >> 3); + mrc->neigh_idx = ((data[14] & 0x07) << 2) | (data[15] >> 6); + mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + mrc->bsic = data[15] & 0x3f; + + return 0; +} + +struct msgb *gsm48_create_mm_serv_rej(enum gsm48_reject_value value) +{ + struct msgb *msg; + struct gsm48_hdr *gh; + + msg = gsm48_msgb_alloc(); + if (!msg) + return NULL; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_MM; + gh->msg_type = GSM48_MT_MM_CM_SERV_REJ; + gh->data[0] = value; + + return msg; +} + +struct msgb *gsm48_create_loc_upd_rej(uint8_t cause) +{ + struct gsm48_hdr *gh; + struct msgb *msg; + + msg = gsm48_msgb_alloc(); + if (!msg) + return NULL; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_MM; + gh->msg_type = GSM48_MT_MM_LOC_UPD_REJECT; + gh->data[0] = cause; + return msg; +} diff --git a/src/libbsc/gsm_subscriber_base.c b/src/libbsc/gsm_subscriber_base.c new file mode 100644 index 000000000..caf84e7bb --- /dev/null +++ b/src/libbsc/gsm_subscriber_base.c @@ -0,0 +1,149 @@ +/* The concept of a subscriber as seen by the BSC */ + +/* (C) 2008 by Harald Welte + * (C) 2009-2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +LLIST_HEAD(active_subscribers); +void *tall_subscr_ctx; + +/* for the gsm_subscriber.c */ +struct llist_head *subscr_bsc_active_subscriber(void) +{ + return &active_subscribers; +} + + +char *subscr_name(struct gsm_subscriber *subscr) +{ + if (strlen(subscr->name)) + return subscr->name; + + return subscr->imsi; +} + +struct gsm_subscriber *subscr_alloc(void) +{ + struct gsm_subscriber *s; + + s = talloc_zero(tall_subscr_ctx, struct gsm_subscriber); + if (!s) + return NULL; + + llist_add_tail(&s->entry, &active_subscribers); + s->use_count = 1; + s->tmsi = GSM_RESERVED_TMSI; + + INIT_LLIST_HEAD(&s->requests); + + return s; +} + +static void subscr_free(struct gsm_subscriber *subscr) +{ + llist_del(&subscr->entry); + talloc_free(subscr); +} + +struct gsm_subscriber *subscr_get(struct gsm_subscriber *subscr) +{ + subscr->use_count++; + DEBUGP(DREF, "subscr %s usage increases usage to: %d\n", + subscr->extension, subscr->use_count); + return subscr; +} + +struct gsm_subscriber *subscr_put(struct gsm_subscriber *subscr) +{ + subscr->use_count--; + DEBUGP(DREF, "subscr %s usage decreased usage to: %d\n", + subscr->extension, subscr->use_count); + if (subscr->use_count <= 0 && !subscr->net->keep_subscr) + subscr_free(subscr); + return NULL; +} + +struct gsm_subscriber *subscr_get_or_create(struct gsm_network *net, + const char *imsi) +{ + struct gsm_subscriber *subscr; + + llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) { + if (strcmp(subscr->imsi, imsi) == 0 && subscr->net == net) + return subscr_get(subscr); + } + + subscr = subscr_alloc(); + if (!subscr) + return NULL; + + strcpy(subscr->imsi, imsi); + subscr->net = net; + return subscr; +} + +struct gsm_subscriber *subscr_active_by_tmsi(struct gsm_network *net, uint32_t tmsi) +{ + struct gsm_subscriber *subscr; + + llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) { + if (subscr->tmsi == tmsi && subscr->net == net) + return subscr_get(subscr); + } + + return NULL; +} + +struct gsm_subscriber *subscr_active_by_imsi(struct gsm_network *net, const char *imsi) +{ + struct gsm_subscriber *subscr; + + llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) { + if (strcmp(subscr->imsi, imsi) == 0 && subscr->net == net) + return subscr_get(subscr); + } + + return NULL; +} + +int subscr_purge_inactive(struct gsm_network *net) +{ + struct gsm_subscriber *subscr, *tmp; + int purged = 0; + + llist_for_each_entry_safe(subscr, tmp, subscr_bsc_active_subscriber(), entry) { + if (subscr->net == net && subscr->use_count <= 0) { + subscr_free(subscr); + purged += 1; + } + } + + return purged; +} diff --git a/src/libbsc/handover_decision.c b/src/libbsc/handover_decision.c new file mode 100644 index 000000000..d3f843afb --- /dev/null +++ b/src/libbsc/handover_decision.c @@ -0,0 +1,297 @@ +/* Handover Decision making for Inter-BTS (Intra-BSC) Handover. This + * only implements the handover algorithm/decision, but not execution + * of it */ + +/* (C) 2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/* issue handover to a cell identified by ARFCN and BSIC */ +static int handover_to_arfcn_bsic(struct gsm_lchan *lchan, + u_int16_t arfcn, u_int8_t bsic) +{ + struct gsm_bts *new_bts; + + /* resolve the gsm_bts structure for the best neighbor */ + new_bts = gsm_bts_neighbor(lchan->ts->trx->bts, arfcn, bsic); + if (!new_bts) { + LOGP(DHO, LOGL_NOTICE, "unable to determine neighbor BTS " + "for ARFCN %u BSIC %u ?!?\n", arfcn, bsic); + return -EINVAL; + } + + /* and actually try to handover to that cell */ + return bsc_handover_start(lchan, new_bts); +} + +/* did we get a RXLEV for a given cell in the given report? */ +static int rxlev_for_cell_in_rep(struct gsm_meas_rep *mr, + u_int16_t arfcn, u_int8_t bsic) +{ + int i; + + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + + /* search for matching report */ + if (!(mrc->arfcn == arfcn && mrc->bsic == bsic)) + continue; + + mrc->flags |= MRC_F_PROCESSED; + return mrc->rxlev; + } + return -ENODEV; +} + +/* obtain averaged rxlev for given neighbor */ +static int neigh_meas_avg(struct neigh_meas_proc *nmp, int window) +{ + unsigned int i, idx; + int avg = 0; + + idx = calc_initial_idx(ARRAY_SIZE(nmp->rxlev), + nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev), + window); + + for (i = 0; i < window; i++) { + int j = (idx+i) % ARRAY_SIZE(nmp->rxlev); + + avg += nmp->rxlev[j]; + } + + return avg / window; +} + +/* find empty or evict bad neighbor */ +static struct neigh_meas_proc *find_evict_neigh(struct gsm_lchan *lchan) +{ + int j, worst = 999999; + struct neigh_meas_proc *nmp_worst; + + /* first try to find an empty/unused slot */ + for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; + if (!nmp->arfcn) + return nmp; + } + + /* no empty slot found. evict worst neighbor from list */ + for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; + int avg = neigh_meas_avg(nmp, MAX_WIN_NEIGH_AVG); + if (avg < worst) { + worst = avg; + nmp_worst = nmp; + } + } + + return nmp_worst; +} + +/* process neighbor cell measurement reports */ +static void process_meas_neigh(struct gsm_meas_rep *mr) +{ + int i, j, idx; + + /* for each reported cell, try to update global state */ + for (j = 0; j < ARRAY_SIZE(mr->lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[j]; + unsigned int idx; + int rxlev; + + /* skip unused entries */ + if (!nmp->arfcn) + continue; + + rxlev = rxlev_for_cell_in_rep(mr, nmp->arfcn, nmp->bsic); + idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); + if (rxlev >= 0) { + nmp->rxlev[idx] = rxlev; + nmp->last_seen_nr = mr->nr; + } else + nmp->rxlev[idx] = 0; + nmp->rxlev_cnt++; + } + + /* iterate over list of reported cells, check if we did not + * process all of them */ + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + struct neigh_meas_proc *nmp; + + if (mrc->flags & MRC_F_PROCESSED) + continue; + + nmp = find_evict_neigh(mr->lchan); + + nmp->arfcn = mrc->arfcn; + nmp->bsic = mrc->bsic; + + idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); + nmp->rxlev[idx] = mrc->rxlev; + nmp->rxlev_cnt++; + nmp->last_seen_nr = mr->nr; + + mrc->flags |= MRC_F_PROCESSED; + } +} + +/* attempt to do a handover */ +static int attempt_handover(struct gsm_meas_rep *mr) +{ + struct gsm_network *net = mr->lchan->ts->trx->bts->network; + struct neigh_meas_proc *best_cell = NULL; + unsigned int best_better_db = 0; + int i, rc; + + /* find the best cell in this report that is at least RXLEV_HYST + * better than the current serving cell */ + + for (i = 0; i < ARRAY_SIZE(mr->lchan->neigh_meas); i++) { + struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[i]; + int avg, better; + + /* skip empty slots */ + if (nmp->arfcn == 0) + continue; + + /* caculate average rxlev for this cell over the window */ + avg = neigh_meas_avg(nmp, net->handover.win_rxlev_avg_neigh); + + /* check if hysteresis is fulfilled */ + if (avg < mr->dl.full.rx_lev + net->handover.pwr_hysteresis) + continue; + + better = avg - mr->dl.full.rx_lev; + if (better > best_better_db) { + best_cell = nmp; + best_better_db = better; + } + } + + if (!best_cell) + return 0; + + LOGP(DHO, LOGL_INFO, "%s: Cell on ARFCN %u is better: ", + gsm_ts_name(mr->lchan->ts), best_cell->arfcn); + if (!net->handover.active) { + LOGPC(DHO, LOGL_INFO, "Skipping, Handover disabled\n"); + return 0; + } + + rc = handover_to_arfcn_bsic(mr->lchan, best_cell->arfcn, best_cell->bsic); + switch (rc) { + case 0: + LOGPC(DHO, LOGL_INFO, "Starting handover\n"); + break; + case -ENOSPC: + LOGPC(DHO, LOGL_INFO, "No channel available\n"); + break; + case -EBUSY: + LOGPC(DHO, LOGL_INFO, "Handover already active\n"); + break; + default: + LOGPC(DHO, LOGL_ERROR, "Unknown error\n"); + } + return rc; +} + +/* process an already parsed measurement report and decide if we want to + * attempt a handover */ +static int process_meas_rep(struct gsm_meas_rep *mr) +{ + struct gsm_network *net = mr->lchan->ts->trx->bts->network; + int av_rxlev; + + /* we currently only do handover for TCH channels */ + switch (mr->lchan->type) { + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_TCH_H: + break; + default: + return 0; + } + + /* parse actual neighbor cell info */ + if (mr->num_cell > 0 && mr->num_cell < 7) + process_meas_neigh(mr); + + av_rxlev = get_meas_rep_avg(mr->lchan, MEAS_REP_DL_RXLEV_FULL, + net->handover.win_rxlev_avg); + + /* Interference HO */ + if (rxlev2dbm(av_rxlev) > -85 && + meas_rep_n_out_of_m_be(mr->lchan, MEAS_REP_DL_RXQUAL_FULL, + 3, 4, 5)) + return attempt_handover(mr); + + /* Bad Quality */ + if (meas_rep_n_out_of_m_be(mr->lchan, MEAS_REP_DL_RXQUAL_FULL, + 3, 4, 5)) + return attempt_handover(mr); + + /* Low Level */ + if (rxlev2dbm(av_rxlev) <= -110) + return attempt_handover(mr); + + /* Distance */ + if (mr->ms_l1.ta > net->handover.max_distance) + return attempt_handover(mr); + + /* Power Budget AKA Better Cell */ + if ((mr->nr % net->handover.pwr_interval) == 0) + return attempt_handover(mr); + + return 0; + +} + +static int ho_dec_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct lchan_signal_data *lchan_data; + + if (subsys != SS_LCHAN) + return 0; + + lchan_data = signal_data; + switch (signal) { + case S_LCHAN_MEAS_REP: + process_meas_rep(lchan_data->mr); + break; + } + + return 0; +} + +void on_dso_load_ho_dec(void) +{ + register_signal_handler(SS_LCHAN, ho_dec_sig_cb, NULL); +} diff --git a/src/libbsc/handover_logic.c b/src/libbsc/handover_logic.c new file mode 100644 index 000000000..c2e3f8c72 --- /dev/null +++ b/src/libbsc/handover_logic.c @@ -0,0 +1,393 @@ +/* Handover Logic for Inter-BTS (Intra-BSC) Handover. This does not + * actually implement the handover algorithm/decision, but executes a + * handover decision */ + +/* (C) 2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct bsc_handover { + struct llist_head list; + + struct gsm_lchan *old_lchan; + struct gsm_lchan *new_lchan; + + struct timer_list T3103; + + u_int8_t ho_ref; +}; + +static LLIST_HEAD(bsc_handovers); + +static struct bsc_handover *bsc_ho_by_new_lchan(struct gsm_lchan *new_lchan) +{ + struct bsc_handover *ho; + + llist_for_each_entry(ho, &bsc_handovers, list) { + if (ho->new_lchan == new_lchan) + return ho; + } + + return NULL; +} + +static struct bsc_handover *bsc_ho_by_old_lchan(struct gsm_lchan *old_lchan) +{ + struct bsc_handover *ho; + + llist_for_each_entry(ho, &bsc_handovers, list) { + if (ho->old_lchan == old_lchan) + return ho; + } + + return NULL; +} + +/* Hand over the specified logical channel to the specified new BTS. + * This is the main entry point for the actual handover algorithm, + * after it has decided it wants to initiate HO to a specific BTS */ +int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts) +{ + struct gsm_lchan *new_lchan; + struct bsc_handover *ho; + static u_int8_t ho_ref; + int rc; + + /* don't attempt multiple handovers for the same lchan at + * the same time */ + if (bsc_ho_by_old_lchan(old_lchan)) + return -EBUSY; + + DEBUGP(DHO, "(old_lchan on BTS %u, new BTS %u)\n", + old_lchan->ts->trx->bts->nr, bts->nr); + + counter_inc(bts->network->stats.handover.attempted); + + if (!old_lchan->conn) { + LOGP(DHO, LOGL_ERROR, "Old lchan lacks connection data.\n"); + return -ENOSPC; + } + + new_lchan = lchan_alloc(bts, old_lchan->type, 0); + if (!new_lchan) { + LOGP(DHO, LOGL_NOTICE, "No free channel\n"); + counter_inc(bts->network->stats.handover.no_channel); + return -ENOSPC; + } + + ho = talloc_zero(tall_bsc_ctx, struct bsc_handover); + if (!ho) { + LOGP(DHO, LOGL_FATAL, "Out of Memory\n"); + lchan_free(new_lchan); + return -ENOMEM; + } + ho->old_lchan = old_lchan; + ho->new_lchan = new_lchan; + ho->ho_ref = ho_ref++; + + /* copy some parameters from old lchan */ + memcpy(&new_lchan->encr, &old_lchan->encr, sizeof(new_lchan->encr)); + new_lchan->ms_power = old_lchan->ms_power; + new_lchan->bs_power = old_lchan->bs_power; + new_lchan->rsl_cmode = old_lchan->rsl_cmode; + new_lchan->tch_mode = old_lchan->tch_mode; + + new_lchan->conn = old_lchan->conn; + new_lchan->conn->ho_lchan = new_lchan; + + /* FIXME: do we have a better idea of the timing advance? */ + rc = rsl_chan_activate_lchan(new_lchan, RSL_ACT_INTER_ASYNC, 0, + ho->ho_ref); + if (rc < 0) { + LOGP(DHO, LOGL_ERROR, "could not activate channel\n"); + new_lchan->conn->ho_lchan = NULL; + new_lchan->conn = NULL; + talloc_free(ho); + lchan_free(new_lchan); + return rc; + } + + rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ); + llist_add(&ho->list, &bsc_handovers); + /* we continue in the SS_LCHAN handler / ho_chan_activ_ack */ + + return 0; +} + +void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan) +{ + struct bsc_handover *ho; + + ho = bsc_ho_by_new_lchan(conn->ho_lchan); + + + if (!ho && conn->ho_lchan) + LOGP(DHO, LOGL_ERROR, "BUG: We lost some state.\n"); + + if (!ho) { + LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); + return; + } + + conn->ho_lchan->conn = NULL; + conn->ho_lchan = NULL; + + if (free_lchan) + lchan_release(ho->new_lchan, 0, 1); + + bsc_del_timer(&ho->T3103); + llist_del(&ho->list); + talloc_free(ho); +} + +/* T3103 expired: Handover has failed without HO COMPLETE or HO FAIL */ +static void ho_T3103_cb(void *_ho) +{ + struct bsc_handover *ho = _ho; + struct gsm_network *net = ho->new_lchan->ts->trx->bts->network; + + DEBUGP(DHO, "HO T3103 expired\n"); + counter_inc(net->stats.handover.timeout); + + ho->new_lchan->conn->ho_lchan = NULL; + ho->new_lchan->conn = NULL; + lchan_release(ho->new_lchan, 0, 1); + llist_del(&ho->list); + talloc_free(ho); +} + +/* RSL has acknowledged activation of the new lchan */ +static int ho_chan_activ_ack(struct gsm_lchan *new_lchan) +{ + struct bsc_handover *ho; + int rc; + + /* we need to check if this channel activation is related to + * a handover at all (and if, which particular handover) */ + ho = bsc_ho_by_new_lchan(new_lchan); + if (!ho) + return -ENODEV; + + DEBUGP(DHO, "handover activate ack, send HO Command\n"); + + /* we can now send the 04.08 HANDOVER COMMAND to the MS + * using the old lchan */ + + rc = gsm48_send_ho_cmd(ho->old_lchan, new_lchan, 0, ho->ho_ref); + + /* start T3103. We can continue either with T3103 expiration, + * 04.08 HANDOVER COMPLETE or 04.08 HANDOVER FAIL */ + ho->T3103.cb = ho_T3103_cb; + ho->T3103.data = ho; + bsc_schedule_timer(&ho->T3103, 10, 0); + + /* create a RTP connection */ + if (is_ipaccess_bts(new_lchan->ts->trx->bts)) + rsl_ipacc_crcx(new_lchan); + + return 0; +} + +/* RSL has not acknowledged activation of the new lchan */ +static int ho_chan_activ_nack(struct gsm_lchan *new_lchan) +{ + struct bsc_handover *ho; + + ho = bsc_ho_by_new_lchan(new_lchan); + if (!ho) { + LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); + return -ENODEV; + } + + new_lchan->conn->ho_lchan = NULL; + new_lchan->conn = NULL; + llist_del(&ho->list); + talloc_free(ho); + + /* FIXME: maybe we should try to allocate a new LCHAN here? */ + + return 0; +} + +/* GSM 04.08 HANDOVER COMPLETE has been received on new channel */ +static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan) +{ + struct gsm_network *net; + struct bsc_handover *ho; + + ho = bsc_ho_by_new_lchan(new_lchan); + if (!ho) { + LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); + return -ENODEV; + } + + net = new_lchan->ts->trx->bts->network; + LOGP(DHO, LOGL_INFO, "Subscriber %s HO from BTS %u->%u on ARFCN " + "%u->%u\n", subscr_name(ho->old_lchan->conn->subscr), + ho->old_lchan->ts->trx->bts->nr, new_lchan->ts->trx->bts->nr, + ho->old_lchan->ts->trx->arfcn, new_lchan->ts->trx->arfcn); + + counter_inc(net->stats.handover.completed); + + bsc_del_timer(&ho->T3103); + + /* Replace the ho lchan with the primary one */ + if (ho->old_lchan != new_lchan->conn->lchan) + LOGP(DHO, LOGL_ERROR, "Primary lchan changed during handover.\n"); + + if (new_lchan != new_lchan->conn->ho_lchan) + LOGP(DHO, LOGL_ERROR, "Handover channel changed during this handover.\n"); + + new_lchan->conn->ho_lchan = NULL; + new_lchan->conn->lchan = new_lchan; + ho->old_lchan->conn = NULL; + + rsl_lchan_set_state(ho->old_lchan, LCHAN_S_INACTIVE); + lchan_release(ho->old_lchan, 0, 1); + + /* do something to re-route the actual speech frames ! */ + + llist_del(&ho->list); + talloc_free(ho); + + return 0; +} + +/* GSM 04.08 HANDOVER FAIL has been received */ +static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan) +{ + struct gsm_network *net = old_lchan->ts->trx->bts->network; + struct bsc_handover *ho; + + ho = bsc_ho_by_old_lchan(old_lchan); + if (!ho) { + LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); + return -ENODEV; + } + + counter_inc(net->stats.handover.failed); + + bsc_del_timer(&ho->T3103); + llist_del(&ho->list); + + /* release the channel and forget about it */ + ho->new_lchan->conn->ho_lchan = NULL; + ho->new_lchan->conn = NULL; + lchan_release(ho->new_lchan, 0, 1); + + talloc_free(ho); + + return 0; +} + +/* GSM 08.58 HANDOVER DETECT has been received */ +static int ho_rsl_detect(struct gsm_lchan *new_lchan) +{ + struct bsc_handover *ho; + + ho = bsc_ho_by_new_lchan(new_lchan); + if (!ho) { + LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); + return -ENODEV; + } + + /* FIXME: do we actually want to do something here ? */ + + return 0; +} + +static int ho_ipac_crcx_ack(struct gsm_lchan *new_lchan) +{ + struct bsc_handover *ho; + struct ho_signal_data sig_ho; + + ho = bsc_ho_by_new_lchan(new_lchan); + if (!ho) { + /* it is perfectly normal, we have CRCX even in non-HO cases */ + return 0; + } + + sig_ho.old_lchan = ho->old_lchan; + sig_ho.new_lchan = new_lchan; + dispatch_signal(SS_HO, S_HANDOVER_ACK, &sig_ho); + return 0; +} + +static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct lchan_signal_data *lchan_data; + struct gsm_lchan *lchan; + + lchan_data = signal_data; + switch (subsys) { + case SS_LCHAN: + lchan = lchan_data->lchan; + switch (signal) { + case S_LCHAN_ACTIVATE_ACK: + return ho_chan_activ_ack(lchan); + case S_LCHAN_ACTIVATE_NACK: + return ho_chan_activ_nack(lchan); + case S_LCHAN_HANDOVER_DETECT: + return ho_rsl_detect(lchan); + case S_LCHAN_HANDOVER_COMPL: + return ho_gsm48_ho_compl(lchan); + case S_LCHAN_HANDOVER_FAIL: + return ho_gsm48_ho_fail(lchan); + } + break; + case SS_ABISIP: + lchan = signal_data; + switch (signal) { + case S_ABISIP_CRCX_ACK: + return ho_ipac_crcx_ack(lchan); + break; + } + break; + default: + break; + } + + return 0; +} + +static __attribute__((constructor)) void on_dso_load_ho_logic(void) +{ + register_signal_handler(SS_LCHAN, ho_logic_sig_cb, NULL); + register_signal_handler(SS_ABISIP, ho_logic_sig_cb, NULL); +} diff --git a/src/libbsc/meas_rep.c b/src/libbsc/meas_rep.c new file mode 100644 index 000000000..788a9baed --- /dev/null +++ b/src/libbsc/meas_rep.c @@ -0,0 +1,116 @@ +/* Measurement Report Processing */ + +/* (C) 2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include +#include + +static int get_field(const struct gsm_meas_rep *rep, + enum meas_rep_field field) +{ + switch (field) { + case MEAS_REP_DL_RXLEV_FULL: + return rep->dl.full.rx_lev; + case MEAS_REP_DL_RXLEV_SUB: + return rep->dl.sub.rx_lev; + case MEAS_REP_DL_RXQUAL_FULL: + return rep->dl.full.rx_qual; + case MEAS_REP_DL_RXQUAL_SUB: + return rep->dl.sub.rx_qual; + case MEAS_REP_UL_RXLEV_FULL: + return rep->ul.full.rx_lev; + case MEAS_REP_UL_RXLEV_SUB: + return rep->ul.sub.rx_lev; + case MEAS_REP_UL_RXQUAL_FULL: + return rep->ul.full.rx_qual; + case MEAS_REP_UL_RXQUAL_SUB: + return rep->ul.sub.rx_qual; + } + + return 0; +} + + +unsigned int calc_initial_idx(unsigned int array_size, + unsigned int meas_rep_idx, + unsigned int num_values) +{ + int offs, idx; + + /* from which element do we need to start if we're interested + * in an average of 'num' elements */ + offs = meas_rep_idx - num_values; + + if (offs < 0) + idx = array_size + offs; + else + idx = offs; + + return idx; +} + +/* obtain an average over the last 'num' fields in the meas reps */ +int get_meas_rep_avg(const struct gsm_lchan *lchan, + enum meas_rep_field field, unsigned int num) +{ + unsigned int i, idx; + int avg = 0; + + if (num < 1) + return 0; + + idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), + lchan->meas_rep_idx, num); + + for (i = 0; i < num; i++) { + int j = (idx+i) % ARRAY_SIZE(lchan->meas_rep); + + avg += get_field(&lchan->meas_rep[j], field); + } + + return avg / num; +} + +/* Check if N out of M last values for FIELD are >= bd */ +int meas_rep_n_out_of_m_be(const struct gsm_lchan *lchan, + enum meas_rep_field field, + unsigned int n, unsigned int m, int be) +{ + unsigned int i, idx; + int count = 0; + + idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), + lchan->meas_rep_idx, m); + + for (i = 0; i < m; i++) { + int j = (idx + i) % ARRAY_SIZE(lchan->meas_rep); + int val = get_field(&lchan->meas_rep[j], field); + + if (val >= be) + count++; + + if (count >= n) + return 1; + } + + return 0; +} diff --git a/src/libbsc/paging.c b/src/libbsc/paging.c new file mode 100644 index 000000000..650254575 --- /dev/null +++ b/src/libbsc/paging.c @@ -0,0 +1,395 @@ +/* Paging helper and manager.... */ +/* (C) 2009 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +/* + * Relevant specs: + * 12.21: + * - 9.4.12 for CCCH Local Threshold + * + * 05.58: + * - 8.5.2 CCCH Load indication + * - 9.3.15 Paging Load + * + * Approach: + * - Send paging command to subscriber + * - On Channel Request we will remember the reason + * - After the ACK we will request the identity + * - Then we will send assign the gsm_subscriber and + * - and call a callback + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +void *tall_paging_ctx; + +#define PAGING_TIMER 0, 500000 + +static unsigned int calculate_group(struct gsm_bts *bts, struct gsm_subscriber *subscr) +{ + int ccch_conf; + int bs_cc_chans; + int blocks; + unsigned int group; + + ccch_conf = bts->si_common.chan_desc.ccch_conf; + bs_cc_chans = rsl_ccch_conf_to_bs_cc_chans(ccch_conf); + /* code word + 2, as 2 channels equals 0x0 */ + blocks = rsl_number_of_paging_subchannels(bts); + group = get_paging_group(str_to_imsi(subscr->imsi), + bs_cc_chans, blocks); + return group; +} + +/* + * Kill one paging request update the internal list... + */ +static void paging_remove_request(struct gsm_bts_paging_state *paging_bts, + struct gsm_paging_request *to_be_deleted) +{ + bsc_del_timer(&to_be_deleted->T3113); + llist_del(&to_be_deleted->entry); + subscr_put(to_be_deleted->subscr); + talloc_free(to_be_deleted); +} + +static void page_ms(struct gsm_paging_request *request) +{ + u_int8_t mi[128]; + unsigned int mi_len; + unsigned int page_group; + + LOGP(DPAG, LOGL_INFO, "Going to send paging commands: imsi: '%s' tmsi: '0x%x'\n", + request->subscr->imsi, request->subscr->tmsi); + + if (request->subscr->tmsi == GSM_RESERVED_TMSI) + mi_len = gsm48_generate_mid_from_imsi(mi, request->subscr->imsi); + else + mi_len = gsm48_generate_mid_from_tmsi(mi, request->subscr->tmsi); + + page_group = calculate_group(request->bts, request->subscr); + gsm0808_page(request->bts, page_group, mi_len, mi, request->chan_type); +} + +static void paging_schedule_if_needed(struct gsm_bts_paging_state *paging_bts) +{ + if (llist_empty(&paging_bts->pending_requests)) + return; + + if (!bsc_timer_pending(&paging_bts->work_timer)) + bsc_schedule_timer(&paging_bts->work_timer, PAGING_TIMER); +} + + +static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts); +static void paging_give_credit(void *data) +{ + struct gsm_bts_paging_state *paging_bts = data; + + LOGP(DPAG, LOGL_NOTICE, "No slots available on bts nr %d\n", paging_bts->bts->nr); + paging_bts->available_slots = 20; + paging_handle_pending_requests(paging_bts); +} + +static int can_send_pag_req(struct gsm_bts *bts, int rsl_type) +{ + struct pchan_load pl; + int count; + + memset(&pl, 0, sizeof(pl)); + bts_chan_load(&pl, bts); + + switch (rsl_type) { + case RSL_CHANNEED_TCH_F: + case RSL_CHANNEED_TCH_ForH: + goto count_tch; + break; + case RSL_CHANNEED_SDCCH: + goto count_sdcch; + break; + case RSL_CHANNEED_ANY: + default: + if (bts->network->pag_any_tch) + goto count_tch; + else + goto count_sdcch; + break; + } + + return 0; + + /* could available SDCCH */ +count_sdcch: + count = 0; + count += pl.pchan[GSM_PCHAN_SDCCH8_SACCH8C].total + - pl.pchan[GSM_PCHAN_SDCCH8_SACCH8C].used; + count += pl.pchan[GSM_PCHAN_CCCH_SDCCH4].total + - pl.pchan[GSM_PCHAN_CCCH_SDCCH4].used; + return bts->paging.free_chans_need > count; + +count_tch: + count = 0; + count += pl.pchan[GSM_PCHAN_TCH_F].total + - pl.pchan[GSM_PCHAN_TCH_F].used; + if (bts->network->neci) + count += pl.pchan[GSM_PCHAN_TCH_H].total + - pl.pchan[GSM_PCHAN_TCH_H].used; + return bts->paging.free_chans_need > count; +} + +/* + * This is kicked by the periodic PAGING LOAD Indicator + * coming from abis_rsl.c + * + * We attempt to iterate once over the list of items but + * only upto available_slots. + */ +static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts) +{ + struct gsm_paging_request *request = NULL; + + /* + * Determine if the pending_requests list is empty and + * return then. + */ + if (llist_empty(&paging_bts->pending_requests)) { + /* since the list is empty, no need to reschedule the timer */ + return; + } + + /* + * In case the BTS does not provide us with load indication and we + * ran out of slots, call an autofill routine. It might be that the + * BTS did not like our paging messages and then we have counted down + * to zero and we do not get any messages. + */ + if (paging_bts->available_slots == 0) { + paging_bts->credit_timer.cb = paging_give_credit; + paging_bts->credit_timer.data = paging_bts; + bsc_schedule_timer(&paging_bts->credit_timer, 5, 0); + return; + } + + request = llist_entry(paging_bts->pending_requests.next, + struct gsm_paging_request, entry); + + /* we need to determine the number of free channels */ + if (paging_bts->free_chans_need != -1) { + if (can_send_pag_req(request->bts, request->chan_type) != 0) + goto skip_paging; + } + + /* handle the paging request now */ + page_ms(request); + paging_bts->available_slots--; + request->attempts++; + + /* take the current and add it to the back */ + llist_del(&request->entry); + llist_add_tail(&request->entry, &paging_bts->pending_requests); + +skip_paging: + bsc_schedule_timer(&paging_bts->work_timer, PAGING_TIMER); +} + +static void paging_worker(void *data) +{ + struct gsm_bts_paging_state *paging_bts = data; + + paging_handle_pending_requests(paging_bts); +} + +void paging_init(struct gsm_bts *bts) +{ + bts->paging.bts = bts; + INIT_LLIST_HEAD(&bts->paging.pending_requests); + bts->paging.work_timer.cb = paging_worker; + bts->paging.work_timer.data = &bts->paging; + + /* Large number, until we get a proper message */ + bts->paging.available_slots = 20; +} + +static int paging_pending_request(struct gsm_bts_paging_state *bts, + struct gsm_subscriber *subscr) { + struct gsm_paging_request *req; + + llist_for_each_entry(req, &bts->pending_requests, entry) { + if (subscr == req->subscr) + return 1; + } + + return 0; +} + +static void paging_T3113_expired(void *data) +{ + struct gsm_paging_request *req = (struct gsm_paging_request *)data; + void *cbfn_param; + gsm_cbfn *cbfn; + int msg; + + LOGP(DPAG, LOGL_INFO, "T3113 expired for request %p (%s)\n", + req, req->subscr->imsi); + + /* must be destroyed before calling cbfn, to prevent double free */ + counter_inc(req->bts->network->stats.paging.expired); + cbfn_param = req->cbfn_param; + cbfn = req->cbfn; + + /* did we ever manage to page the subscriber */ + msg = req->attempts > 0 ? GSM_PAGING_EXPIRED : GSM_PAGING_BUSY; + + /* destroy it now. Do not access req afterwards */ + paging_remove_request(&req->bts->paging, req); + + if (cbfn) + cbfn(GSM_HOOK_RR_PAGING, msg, NULL, NULL, + cbfn_param); +} + +static int _paging_request(struct gsm_bts *bts, struct gsm_subscriber *subscr, + int type, gsm_cbfn *cbfn, void *data) +{ + struct gsm_bts_paging_state *bts_entry = &bts->paging; + struct gsm_paging_request *req; + + if (paging_pending_request(bts_entry, subscr)) { + LOGP(DPAG, LOGL_INFO, "Paging request already pending for %s\n", subscr->imsi); + return -EEXIST; + } + + LOGP(DPAG, LOGL_DEBUG, "Start paging of subscriber %llu on bts %d.\n", + subscr->id, bts->nr); + req = talloc_zero(tall_paging_ctx, struct gsm_paging_request); + req->subscr = subscr_get(subscr); + req->bts = bts; + req->chan_type = type; + req->cbfn = cbfn; + req->cbfn_param = data; + req->T3113.cb = paging_T3113_expired; + req->T3113.data = req; + bsc_schedule_timer(&req->T3113, bts->network->T3113, 0); + llist_add_tail(&req->entry, &bts_entry->pending_requests); + paging_schedule_if_needed(bts_entry); + + return 0; +} + +int paging_request(struct gsm_network *network, struct gsm_subscriber *subscr, + int type, gsm_cbfn *cbfn, void *data) +{ + struct gsm_bts *bts = NULL; + int num_pages = 0; + + counter_inc(network->stats.paging.attempted); + + /* start paging subscriber on all BTS within Location Area */ + do { + int rc; + + bts = gsm_bts_by_lac(network, subscr->lac, bts); + if (!bts) + break; + + /* skip all currently inactive TRX */ + if (!trx_is_usable(bts->c0)) + continue; + + num_pages++; + + /* Trigger paging, pass any error to caller */ + rc = _paging_request(bts, subscr, type, cbfn, data); + if (rc < 0) + return rc; + } while (1); + + if (num_pages == 0) + counter_inc(network->stats.paging.detached); + + return num_pages; +} + + +/* we consciously ignore the type of the request here */ +static void _paging_request_stop(struct gsm_bts *bts, struct gsm_subscriber *subscr, + struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm_bts_paging_state *bts_entry = &bts->paging; + struct gsm_paging_request *req, *req2; + + llist_for_each_entry_safe(req, req2, &bts_entry->pending_requests, + entry) { + if (req->subscr == subscr) { + if (conn && req->cbfn) { + LOGP(DPAG, LOGL_DEBUG, "Stop paging on bts %d, calling cbfn.\n", bts->nr); + req->cbfn(GSM_HOOK_RR_PAGING, GSM_PAGING_SUCCEEDED, + msg, conn, req->cbfn_param); + } else + LOGP(DPAG, LOGL_DEBUG, "Stop paging on bts %d silently.\n", bts->nr); + paging_remove_request(&bts->paging, req); + break; + } + } +} + +/* Stop paging on all other bts' */ +void paging_request_stop(struct gsm_bts *_bts, struct gsm_subscriber *subscr, + struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm_bts *bts = NULL; + + if (_bts) + _paging_request_stop(_bts, subscr, conn, msg); + + do { + /* + * FIXME: Don't use the lac of the subscriber... + * as it might have magically changed the lac.. use the + * location area of the _bts as reconfiguration of the + * network is probably happening less often. + */ + bts = gsm_bts_by_lac(subscr->net, subscr->lac, bts); + if (!bts) + break; + + /* Stop paging */ + if (bts != _bts) + _paging_request_stop(bts, subscr, NULL, NULL); + } while (1); +} + +void paging_update_buffer_space(struct gsm_bts *bts, u_int16_t free_slots) +{ + bsc_del_timer(&bts->paging.credit_timer); + bts->paging.available_slots = free_slots; + paging_schedule_if_needed(&bts->paging); +} diff --git a/src/libbsc/rest_octets.c b/src/libbsc/rest_octets.c new file mode 100644 index 000000000..084f14498 --- /dev/null +++ b/src/libbsc/rest_octets.c @@ -0,0 +1,432 @@ +/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface, + * rest octet handling according to + * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */ + +/* (C) 2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include +#include + +/* generate SI1 rest octets */ +int rest_octets_si1(u_int8_t *data, u_int8_t *nch_pos) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = data; + bv.data_len = 1; + + if (nch_pos) { + bitvec_set_bit(&bv, H); + bitvec_set_uint(&bv, *nch_pos, 5); + } else + bitvec_set_bit(&bv, L); + + bitvec_spare_padding(&bv, 7); + return bv.data_len; +} + +/* Append selection parameters to bitvec */ +static void append_selection_params(struct bitvec *bv, + const struct gsm48_si_selection_params *sp) +{ + if (sp->present) { + bitvec_set_bit(bv, H); + bitvec_set_bit(bv, sp->cbq); + bitvec_set_uint(bv, sp->cell_resel_off, 6); + bitvec_set_uint(bv, sp->temp_offs, 3); + bitvec_set_uint(bv, sp->penalty_time, 5); + } else + bitvec_set_bit(bv, L); +} + +/* Append power offset to bitvec */ +static void append_power_offset(struct bitvec *bv, + const struct gsm48_si_power_offset *po) +{ + if (po->present) { + bitvec_set_bit(bv, H); + bitvec_set_uint(bv, po->power_offset, 2); + } else + bitvec_set_bit(bv, L); +} + +/* Append GPRS indicator to bitvec */ +static void append_gprs_ind(struct bitvec *bv, + const struct gsm48_si3_gprs_ind *gi) +{ + if (gi->present) { + bitvec_set_bit(bv, H); + bitvec_set_uint(bv, gi->ra_colour, 3); + /* 0 == SI13 in BCCH Norm, 1 == SI13 sent on BCCH Ext */ + bitvec_set_bit(bv, gi->si13_position); + } else + bitvec_set_bit(bv, L); +} + + +/* Generate SI3 Rest Octests (Chapter 10.5.2.34 / Table 10.4.72) */ +int rest_octets_si3(u_int8_t *data, const struct gsm48_si_ro_info *si3) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = data; + bv.data_len = 4; + + /* Optional Selection Parameters */ + append_selection_params(&bv, &si3->selection_params); + + /* Optional Power Offset */ + append_power_offset(&bv, &si3->power_offset); + + /* Do we have a SI2ter on the BCCH? */ + if (si3->si2ter_indicator) + bitvec_set_bit(&bv, H); + else + bitvec_set_bit(&bv, L); + + /* Early Classmark Sending Control */ + if (si3->early_cm_ctrl) + bitvec_set_bit(&bv, H); + else + bitvec_set_bit(&bv, L); + + /* Do we have a SI Type 9 on the BCCH? */ + if (si3->scheduling.present) { + bitvec_set_bit(&bv, H); + bitvec_set_uint(&bv, si3->scheduling.where, 3); + } else + bitvec_set_bit(&bv, L); + + /* GPRS Indicator */ + append_gprs_ind(&bv, &si3->gprs_ind); + + bitvec_spare_padding(&bv, (bv.data_len*8)-1); + return bv.data_len; +} + +static int append_lsa_params(struct bitvec *bv, + const struct gsm48_lsa_params *lsa_params) +{ + /* FIXME */ + return -1; +} + +/* Generate SI4 Rest Octets (Chapter 10.5.2.35) */ +int rest_octets_si4(u_int8_t *data, const struct gsm48_si_ro_info *si4) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = data; + bv.data_len = 10; /* FIXME: up to ? */ + + /* SI4 Rest Octets O */ + append_selection_params(&bv, &si4->selection_params); + append_power_offset(&bv, &si4->power_offset); + append_gprs_ind(&bv, &si4->gprs_ind); + + if (0 /* FIXME */) { + /* H and SI4 Rest Octets S */ + bitvec_set_bit(&bv, H); + + /* LSA Parameters */ + if (si4->lsa_params.present) { + bitvec_set_bit(&bv, H); + append_lsa_params(&bv, &si4->lsa_params); + } else + bitvec_set_bit(&bv, L); + + /* Cell Identity */ + if (1) { + bitvec_set_bit(&bv, H); + bitvec_set_uint(&bv, si4->cell_id, 16); + } else + bitvec_set_bit(&bv, L); + + /* LSA ID Information */ + if (0) { + bitvec_set_bit(&bv, H); + /* FIXME */ + } else + bitvec_set_bit(&bv, L); + } else { + /* L and break indicator */ + bitvec_set_bit(&bv, L); + bitvec_set_bit(&bv, si4->break_ind ? H : L); + } + + return bv.data_len; +} + +/* GPRS Mobile Allocation as per TS 04.60 Chapter 12.10a: + < GPRS Mobile Allocation IE > ::= + < HSN : bit (6) > + { 0 | 1 < RFL number list : < RFL number list struct > > } + { 0 < MA_LENGTH : bit (6) > + < MA_BITMAP: bit (val(MA_LENGTH) + 1) > + | 1 { 0 | 1 > } } ; + + < RFL number list struct > :: = + < RFL_NUMBER : bit (4) > + { 0 | 1 < RFL number list struct > } ; + < ARFCN index list struct > ::= + < ARFCN_INDEX : bit(6) > + { 0 | 1 < ARFCN index list struct > } ; + */ +static int append_gprs_mobile_alloc(struct bitvec *bv) +{ + /* Hopping Sequence Number */ + bitvec_set_uint(bv, 0, 6); + + if (0) { + /* We want to use a RFL number list */ + bitvec_set_bit(bv, 1); + /* FIXME: RFL number list */ + } else + bitvec_set_bit(bv, 0); + + if (0) { + /* We want to use a MA_BITMAP */ + bitvec_set_bit(bv, 0); + /* FIXME: MA_LENGTH, MA_BITMAP, ... */ + } else { + bitvec_set_bit(bv, 1); + if (0) { + /* We want to provide an ARFCN index list */ + bitvec_set_bit(bv, 1); + /* FIXME */ + } else + bitvec_set_bit(bv, 0); + } + return 0; +} + +static int encode_t3192(unsigned int t3192) +{ + if (t3192 == 0) + return 3; + else if (t3192 <= 80) + return 4; + else if (t3192 <= 120) + return 5; + else if (t3192 <= 160) + return 6; + else if (t3192 <= 200) + return 7; + else if (t3192 <= 500) + return 0; + else if (t3192 <= 1000) + return 1; + else if (t3192 <= 1500) + return 2; + else + return -EINVAL; +} + +static int encode_drx_timer(unsigned int drx) +{ + if (drx == 0) + return 0; + else if (drx == 1) + return 1; + else if (drx == 2) + return 2; + else if (drx <= 4) + return 3; + else if (drx <= 8) + return 4; + else if (drx <= 16) + return 5; + else if (drx <= 32) + return 6; + else if (drx <= 64) + return 7; + else + return -EINVAL; +} + +/* GPRS Cell Options as per TS 04.60 Chapter 12.24 + < GPRS Cell Options IE > ::= + < NMO : bit(2) > + < T3168 : bit(3) > + < T3192 : bit(3) > + < DRX_TIMER_MAX: bit(3) > + < ACCESS_BURST_TYPE: bit > + < CONTROL_ACK_TYPE : bit > + < BS_CV_MAX: bit(4) > + { 0 | 1 < PAN_DEC : bit(3) > + < PAN_INC : bit(3) > + < PAN_MAX : bit(3) > + { 0 | 1 < Extension Length : bit(6) > + < bit (val(Extension Length) + 1 + & { < Extension Information > ! { bit ** = } } ; + < Extension Information > ::= + { 0 | 1 < EGPRS_PACKET_CHANNEL_REQUEST : bit > + < BEP_PERIOD : bit(4) > } + < PFC_FEATURE_MODE : bit > + < DTM_SUPPORT : bit > + + ** ; + */ +static int append_gprs_cell_opt(struct bitvec *bv, + const struct gprs_cell_options *gco) +{ + int t3192, drx_timer_max; + + t3192 = encode_t3192(gco->t3192); + if (t3192 < 0) + return t3192; + + drx_timer_max = encode_drx_timer(gco->drx_timer_max); + if (drx_timer_max < 0) + return drx_timer_max; + + bitvec_set_uint(bv, gco->nmo, 2); + bitvec_set_uint(bv, gco->t3168 / 500, 3); + bitvec_set_uint(bv, t3192, 3); + bitvec_set_uint(bv, drx_timer_max, 3); + /* ACCESS_BURST_TYPE: Hard-code 8bit */ + bitvec_set_bit(bv, 0); + /* CONTROL_ACK_TYPE: Hard-code to RLC/MAC control block */ + bitvec_set_bit(bv, 1); + bitvec_set_uint(bv, gco->bs_cv_max, 4); + + if (0) { + /* hard-code no PAN_{DEC,INC,MAX} */ + bitvec_set_bit(bv, 0); + } else { + /* copied from ip.access BSC protocol trace */ + bitvec_set_bit(bv, 1); + bitvec_set_uint(bv, 1, 3); /* DEC */ + bitvec_set_uint(bv, 1, 3); /* INC */ + bitvec_set_uint(bv, 15, 3); /* MAX */ + } + + if (!gco->ext_info_present) { + /* no extension information */ + bitvec_set_bit(bv, 0); + } else { + /* extension information */ + bitvec_set_bit(bv, 1); + if (!gco->ext_info.egprs_supported) { + /* 6bit length of extension */ + bitvec_set_uint(bv, (1 + 3)-1, 6); + /* EGPRS supported in the cell */ + bitvec_set_bit(bv, 0); + } else { + /* 6bit length of extension */ + bitvec_set_uint(bv, (1 + 5 + 3)-1, 6); + /* EGPRS supported in the cell */ + bitvec_set_bit(bv, 1); + /* 1bit EGPRS PACKET CHANNEL REQUEST */ + bitvec_set_bit(bv, gco->ext_info.use_egprs_p_ch_req); + /* 4bit BEP PERIOD */ + bitvec_set_uint(bv, gco->ext_info.bep_period, 4); + } + bitvec_set_bit(bv, gco->ext_info.pfc_supported); + bitvec_set_bit(bv, gco->ext_info.dtm_supported); + bitvec_set_bit(bv, gco->ext_info.bss_paging_coordination); + } + + return 0; +} + +static void append_gprs_pwr_ctrl_pars(struct bitvec *bv, + const struct gprs_power_ctrl_pars *pcp) +{ + bitvec_set_uint(bv, pcp->alpha, 4); + bitvec_set_uint(bv, pcp->t_avg_w, 5); + bitvec_set_uint(bv, pcp->t_avg_t, 5); + bitvec_set_uint(bv, pcp->pc_meas_chan, 1); + bitvec_set_uint(bv, pcp->n_avg_i, 4); +} + +/* Generate SI13 Rest Octests (04.08 Chapter 10.5.2.37b) */ +int rest_octets_si13(u_int8_t *data, const struct gsm48_si13_info *si13) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = data; + bv.data_len = 20; + + if (0) { + /* No rest octets */ + bitvec_set_bit(&bv, L); + } else { + bitvec_set_bit(&bv, H); + bitvec_set_uint(&bv, si13->bcch_change_mark, 3); + bitvec_set_uint(&bv, si13->si_change_field, 4); + if (1) { + bitvec_set_bit(&bv, 0); + } else { + bitvec_set_bit(&bv, 1); + bitvec_set_uint(&bv, si13->bcch_change_mark, 2); + append_gprs_mobile_alloc(&bv); + } + if (!si13->pbcch_present) { + /* PBCCH not present in cell */ + bitvec_set_bit(&bv, 0); + bitvec_set_uint(&bv, si13->no_pbcch.rac, 8); + bitvec_set_bit(&bv, si13->no_pbcch.spgc_ccch_sup); + bitvec_set_uint(&bv, si13->no_pbcch.prio_acc_thr, 3); + bitvec_set_uint(&bv, si13->no_pbcch.net_ctrl_ord, 2); + append_gprs_cell_opt(&bv, &si13->cell_opts); + append_gprs_pwr_ctrl_pars(&bv, &si13->pwr_ctrl_pars); + } else { + /* PBCCH present in cell */ + bitvec_set_bit(&bv, 1); + bitvec_set_uint(&bv, si13->pbcch.psi1_rep_per, 4); + /* PBCCH Descripiton */ + bitvec_set_uint(&bv, si13->pbcch.pb, 4); + bitvec_set_uint(&bv, si13->pbcch.tsc, 3); + bitvec_set_uint(&bv, si13->pbcch.tn, 3); + switch (si13->pbcch.carrier_type) { + case PBCCH_BCCH: + bitvec_set_bit(&bv, 0); + bitvec_set_bit(&bv, 0); + break; + case PBCCH_ARFCN: + bitvec_set_bit(&bv, 0); + bitvec_set_bit(&bv, 1); + bitvec_set_uint(&bv, si13->pbcch.arfcn, 10); + break; + case PBCCH_MAIO: + bitvec_set_bit(&bv, 1); + bitvec_set_uint(&bv, si13->pbcch.maio, 6); + break; + } + } + /* 3GPP TS 44.018 Release 6 / 10.5.2.37b */ + bitvec_set_bit(&bv, H); /* added Release 99 */ + /* claim our SGSN is compatible with Release 99, as EDGE and EGPRS + * was only added in this Release */ + bitvec_set_bit(&bv, 1); + } + bitvec_spare_padding(&bv, (bv.data_len*8)-1); + return bv.data_len; +} diff --git a/src/libbsc/system_information.c b/src/libbsc/system_information.c new file mode 100644 index 000000000..dc719388b --- /dev/null +++ b/src/libbsc/system_information.c @@ -0,0 +1,616 @@ +/* GSM 04.08 System Information (SI) encoding and decoding + * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */ + +/* (C) 2008-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define GSM48_CELL_CHAN_DESC_SIZE 16 +#define GSM_MACBLOCK_PADDING 0x2b + +/* verify the sizes of the system information type structs */ + +/* rest octets are not part of the struct */ +static_assert(sizeof(struct gsm48_system_information_type_header) == 3, _si_header_size); +static_assert(sizeof(struct gsm48_rach_control) == 3, _si_rach_control); +static_assert(sizeof(struct gsm48_system_information_type_1) == 22, _si1_size); +static_assert(sizeof(struct gsm48_system_information_type_2) == 23, _si2_size); +static_assert(sizeof(struct gsm48_system_information_type_3) == 19, _si3_size); +static_assert(sizeof(struct gsm48_system_information_type_4) == 13, _si4_size); + +/* bs11 forgot the l2 len, 0-6 rest octets */ +static_assert(sizeof(struct gsm48_system_information_type_5) == 18, _si5_size); +static_assert(sizeof(struct gsm48_system_information_type_6) == 11, _si6_size); + +static_assert(sizeof(struct gsm48_system_information_type_13) == 3, _si13_size); + +/* Frequency Lists as per TS 04.08 10.5.2.13 */ + +/* 10.5.2.13.2: Bit map 0 format */ +static int freq_list_bm0_set_arfcn(u_int8_t *chan_list, unsigned int arfcn) +{ + unsigned int byte, bit; + + if (arfcn > 124 || arfcn < 1) { + LOGP(DRR, LOGL_ERROR, "Bitmap 0 only supports ARFCN 1...124\n"); + return -EINVAL; + } + + /* the bitmask is from 1..124, not from 0..123 */ + arfcn--; + + byte = arfcn / 8; + bit = arfcn % 8; + + chan_list[GSM48_CELL_CHAN_DESC_SIZE-1-byte] |= (1 << bit); + + return 0; +} + +/* 10.5.2.13.7: Variable bit map format */ +static int freq_list_bmrel_set_arfcn(u_int8_t *chan_list, unsigned int arfcn) +{ + unsigned int byte, bit; + unsigned int min_arfcn; + unsigned int bitno; + + min_arfcn = (chan_list[0] & 1) << 9; + min_arfcn |= chan_list[1] << 1; + min_arfcn |= (chan_list[2] >> 7) & 1; + + /* The lower end of our bitmaks is always implicitly included */ + if (arfcn == min_arfcn) + return 0; + + if (arfcn < min_arfcn) { + LOGP(DRR, LOGL_ERROR, "arfcn(%u) < min(%u)\n", arfcn, min_arfcn); + return -EINVAL; + } + if (arfcn > min_arfcn + 111) { + LOGP(DRR, LOGL_ERROR, "arfcn(%u) > min(%u) + 111\n", arfcn, min_arfcn); + return -EINVAL; + } + + bitno = (arfcn - min_arfcn); + byte = bitno / 8; + bit = bitno % 8; + + chan_list[2 + byte] |= 1 << (7 - bit); + + return 0; +} + +/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */ +static int bitvec2freq_list(u_int8_t *chan_list, struct bitvec *bv, + const struct gsm_bts *bts) +{ + int i, rc, min = 1024, max = -1; + + memset(chan_list, 0, 16); + + /* GSM900-only handsets only support 'bit map 0 format' */ + if (bts->band == GSM_BAND_900) { + chan_list[0] = 0; + + for (i = 0; i < bv->data_len*8; i++) { + if (bitvec_get_bit_pos(bv, i)) { + rc = freq_list_bm0_set_arfcn(chan_list, i); + if (rc < 0) + return rc; + } + } + return 0; + } + + /* We currently only support the 'Variable bitmap format' */ + chan_list[0] = 0x8e; + + for (i = 0; i < bv->data_len*8; i++) { + if (bitvec_get_bit_pos(bv, i)) { + if (i < min) + min = i; + if (i > max) + max = i; + } + } + + if (max == -1) { + /* Empty set, use 'bit map 0 format' */ + chan_list[0] = 0; + return 0; + } + + if ((max - min) > 111) { + LOGP(DRR, LOGL_ERROR, "min_arfcn=%u, max_arfcn=%u, " + "distance > 111\n", min, max); + return -EINVAL; + } + + chan_list[0] |= (min >> 9) & 1; + chan_list[1] = (min >> 1); + chan_list[2] = (min & 1) << 7; + + for (i = 0; i < bv->data_len*8; i++) { + if (bitvec_get_bit_pos(bv, i)) { + rc = freq_list_bmrel_set_arfcn(chan_list, i); + if (rc < 0) + return rc; + } + } + + return 0; +} + +/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */ +static int generate_cell_chan_list(u_int8_t *chan_list, struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + struct bitvec *bv = &bts->si_common.cell_alloc; + + /* Zero-initialize the bit-vector */ + memset(bv->data, 0, bv->data_len); + + /* first we generate a bitvec of all TRX ARFCN's in our BTS */ + llist_for_each_entry(trx, &bts->trx_list, list) { + unsigned int i, j; + /* Always add the TRX's ARFCN */ + bitvec_set_bit_pos(bv, trx->arfcn, 1); + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + /* Add any ARFCNs present in hopping channels */ + for (j = 0; j < 1024; j++) { + if (bitvec_get_bit_pos(&ts->hopping.arfcns, j)) + bitvec_set_bit_pos(bv, j, 1); + } + } + } + + /* then we generate a GSM 04.08 frequency list from the bitvec */ + return bitvec2freq_list(chan_list, bv, bts); +} + +/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */ +static int generate_bcch_chan_list(u_int8_t *chan_list, struct gsm_bts *bts, int si5) +{ + struct gsm_bts *cur_bts; + struct bitvec *bv; + + if (si5 && bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) + bv = &bts->si_common.si5_neigh_list; + else + bv = &bts->si_common.neigh_list; + + /* Generate list of neighbor cells if we are in automatic mode */ + if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) { + /* Zero-initialize the bit-vector */ + memset(bv->data, 0, bv->data_len); + + /* first we generate a bitvec of the BCCH ARFCN's in our BSC */ + llist_for_each_entry(cur_bts, &bts->network->bts_list, list) { + if (cur_bts == bts) + continue; + bitvec_set_bit_pos(bv, cur_bts->c0->arfcn, 1); + } + } + + /* then we generate a GSM 04.08 frequency list from the bitvec */ + return bitvec2freq_list(chan_list, bv, bts); +} + +static int generate_si1(u_int8_t *output, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_1 *si1 = + (struct gsm48_system_information_type_1 *) output; + + memset(si1, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si1->header.l2_plen = (21 << 2) | 1; + si1->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si1->header.skip_indicator = 0; + si1->header.system_information = GSM48_MT_RR_SYSINFO_1; + + rc = generate_cell_chan_list(si1->cell_channel_description, bts); + if (rc < 0) + return rc; + + si1->rach_control = bts->si_common.rach_control; + + /* SI1 Rest Octets (10.5.2.32), contains NCH position */ + rc = rest_octets_si1(si1->rest_octets, NULL); + + return sizeof(*si1) + rc; +} + +static int generate_si2(u_int8_t *output, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_2 *si2 = + (struct gsm48_system_information_type_2 *) output; + + memset(si2, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si2->header.l2_plen = (22 << 2) | 1; + si2->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si2->header.skip_indicator = 0; + si2->header.system_information = GSM48_MT_RR_SYSINFO_2; + + rc = generate_bcch_chan_list(si2->bcch_frequency_list, bts, 0); + if (rc < 0) + return rc; + + si2->ncc_permitted = bts->si_common.ncc_permitted; + si2->rach_control = bts->si_common.rach_control; + + return sizeof(*si2); +} + +static struct gsm48_si_ro_info si_info = { + .selection_params = { + .present = 0, + }, + .power_offset = { + .present = 0, + }, + .si2ter_indicator = 0, + .early_cm_ctrl = 1, + .scheduling = { + .present = 0, + }, + .gprs_ind = { + .si13_position = 0, + .ra_colour = 0, + .present = 1, + }, + .lsa_params = { + .present = 0, + }, + .cell_id = 0, /* FIXME: doesn't the bts have this? */ + .break_ind = 0, +}; + +static int generate_si3(u_int8_t *output, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_3 *si3 = + (struct gsm48_system_information_type_3 *) output; + + memset(si3, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si3->header.l2_plen = (18 << 2) | 1; + si3->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si3->header.skip_indicator = 0; + si3->header.system_information = GSM48_MT_RR_SYSINFO_3; + + si3->cell_identity = htons(bts->cell_identity); + gsm48_generate_lai(&si3->lai, bts->network->country_code, + bts->network->network_code, + bts->location_area_code); + si3->control_channel_desc = bts->si_common.chan_desc; + si3->cell_options = bts->si_common.cell_options; + si3->cell_sel_par = bts->si_common.cell_sel_par; + si3->rach_control = bts->si_common.rach_control; + + /* SI3 Rest Octets (10.5.2.34), containing + CBQ, CELL_RESELECT_OFFSET, TEMPORARY_OFFSET, PENALTY_TIME + Power Offset, 2ter Indicator, Early Classmark Sending, + Scheduling if and WHERE, GPRS Indicator, SI13 position */ + rc = rest_octets_si3(si3->rest_octets, &si_info); + + return sizeof(*si3) + rc; +} + +static int generate_si4(u_int8_t *output, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_4 *si4 = + (struct gsm48_system_information_type_4 *) output; + + /* length of all IEs present except SI4 rest octets and l2_plen */ + int l2_plen = sizeof(*si4) - 1; + + memset(si4, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si4->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si4->header.skip_indicator = 0; + si4->header.system_information = GSM48_MT_RR_SYSINFO_4; + + gsm48_generate_lai(&si4->lai, bts->network->country_code, + bts->network->network_code, + bts->location_area_code); + si4->cell_sel_par = bts->si_common.cell_sel_par; + si4->rach_control = bts->si_common.rach_control; + + /* Optional: CBCH Channel Description + CBCH Mobile Allocation */ + + si4->header.l2_plen = (l2_plen << 2) | 1; + + /* SI4 Rest Octets (10.5.2.35), containing + Optional Power offset, GPRS Indicator, + Cell Identity, LSA ID, Selection Parameter */ + rc = rest_octets_si4(si4->data, &si_info); + + return sizeof(*si4) + rc; +} + +static int generate_si5(u_int8_t *output, struct gsm_bts *bts) +{ + struct gsm48_system_information_type_5 *si5; + int rc, l2_plen = 18; + + memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + /* ip.access nanoBTS needs l2_plen!! */ + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + case GSM_BTS_TYPE_HSL_FEMTO: + *output++ = (l2_plen << 2) | 1; + l2_plen++; + break; + default: + break; + } + + si5 = (struct gsm48_system_information_type_5 *) output; + + /* l2 pseudo length, not part of msg: 18 */ + si5->rr_protocol_discriminator = GSM48_PDISC_RR; + si5->skip_indicator = 0; + si5->system_information = GSM48_MT_RR_SYSINFO_5; + rc = generate_bcch_chan_list(si5->bcch_frequency_list, bts, 1); + if (rc < 0) + return rc; + + /* 04.08 9.1.37: L2 Pseudo Length of 18 */ + return l2_plen; +} + +static int generate_si6(u_int8_t *output, struct gsm_bts *bts) +{ + struct gsm48_system_information_type_6 *si6; + int l2_plen = 11; + + memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + /* ip.access nanoBTS needs l2_plen!! */ + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + case GSM_BTS_TYPE_HSL_FEMTO: + *output++ = (l2_plen << 2) | 1; + l2_plen++; + break; + default: + break; + } + + si6 = (struct gsm48_system_information_type_6 *) output; + + /* l2 pseudo length, not part of msg: 11 */ + si6->rr_protocol_discriminator = GSM48_PDISC_RR; + si6->skip_indicator = 0; + si6->system_information = GSM48_MT_RR_SYSINFO_6; + si6->cell_identity = htons(bts->cell_identity); + gsm48_generate_lai(&si6->lai, bts->network->country_code, + bts->network->network_code, + bts->location_area_code); + si6->cell_options = bts->si_common.cell_options; + si6->ncc_permitted = bts->si_common.ncc_permitted; + + /* SI6 Rest Octets: 10.5.2.35a: PCH / NCH info, VBS/VGCS options */ + + return l2_plen; +} + +static struct gsm48_si13_info si13_default = { + .cell_opts = { + .nmo = GPRS_NMO_II, + .t3168 = 2000, + .t3192 = 200, + .drx_timer_max = 3, + .bs_cv_max = 15, + .ext_info_present = 1, + .ext_info = { + /* The values below are just guesses ! */ + .egprs_supported = 0, + .use_egprs_p_ch_req = 1, + .bep_period = 5, + .pfc_supported = 0, + .dtm_supported = 0, + .bss_paging_coordination = 0, + }, + }, + .pwr_ctrl_pars = { + .alpha = 10, /* a = 1.0 */ + .t_avg_w = 16, + .t_avg_t = 16, + .pc_meas_chan = 0, /* downling measured on CCCH */ + .n_avg_i = 8, + }, + .bcch_change_mark = 1, + .si_change_field = 0, + .pbcch_present = 0, + { + .no_pbcch = { + .rac = 0, /* needs to be patched */ + .spgc_ccch_sup = 0, + .net_ctrl_ord = 0, + .prio_acc_thr = 6, + }, + }, +}; + +static int generate_si13(u_int8_t *output, struct gsm_bts *bts) +{ + struct gsm48_system_information_type_13 *si13 = + (struct gsm48_system_information_type_13 *) output; + int ret; + + memset(si13, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si13->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si13->header.skip_indicator = 0; + si13->header.system_information = GSM48_MT_RR_SYSINFO_13; + + si13_default.no_pbcch.rac = bts->gprs.rac; + + ret = rest_octets_si13(si13->rest_octets, &si13_default); + if (ret < 0) + return ret; + + /* length is coded in bit 2 an up */ + si13->header.l2_plen = 0x01; + + return sizeof (*si13) + ret; +} + +static const uint8_t sitype2rsl[_MAX_SYSINFO_TYPE] = { + [SYSINFO_TYPE_1] = RSL_SYSTEM_INFO_1, + [SYSINFO_TYPE_2] = RSL_SYSTEM_INFO_2, + [SYSINFO_TYPE_3] = RSL_SYSTEM_INFO_3, + [SYSINFO_TYPE_4] = RSL_SYSTEM_INFO_4, + [SYSINFO_TYPE_5] = RSL_SYSTEM_INFO_5, + [SYSINFO_TYPE_6] = RSL_SYSTEM_INFO_6, + [SYSINFO_TYPE_7] = RSL_SYSTEM_INFO_7, + [SYSINFO_TYPE_8] = RSL_SYSTEM_INFO_8, + [SYSINFO_TYPE_9] = RSL_SYSTEM_INFO_9, + [SYSINFO_TYPE_10] = RSL_SYSTEM_INFO_10, + [SYSINFO_TYPE_13] = RSL_SYSTEM_INFO_13, + [SYSINFO_TYPE_16] = RSL_SYSTEM_INFO_16, + [SYSINFO_TYPE_17] = RSL_SYSTEM_INFO_17, + [SYSINFO_TYPE_18] = RSL_SYSTEM_INFO_18, + [SYSINFO_TYPE_19] = RSL_SYSTEM_INFO_19, + [SYSINFO_TYPE_20] = RSL_SYSTEM_INFO_20, + [SYSINFO_TYPE_2bis] = RSL_SYSTEM_INFO_2bis, + [SYSINFO_TYPE_2ter] = RSL_SYSTEM_INFO_2ter, + [SYSINFO_TYPE_2quater] = RSL_SYSTEM_INFO_2quater, + [SYSINFO_TYPE_5bis] = RSL_SYSTEM_INFO_5bis, + [SYSINFO_TYPE_5ter] = RSL_SYSTEM_INFO_5ter, +}; + +static const uint8_t rsl2sitype[0xff] = { + [RSL_SYSTEM_INFO_1] = SYSINFO_TYPE_1, + [RSL_SYSTEM_INFO_2] = SYSINFO_TYPE_2, + [RSL_SYSTEM_INFO_3] = SYSINFO_TYPE_3, + [RSL_SYSTEM_INFO_4] = SYSINFO_TYPE_4, + [RSL_SYSTEM_INFO_5] = SYSINFO_TYPE_5, + [RSL_SYSTEM_INFO_6] = SYSINFO_TYPE_6, + [RSL_SYSTEM_INFO_7] = SYSINFO_TYPE_7, + [RSL_SYSTEM_INFO_8] = SYSINFO_TYPE_8, + [RSL_SYSTEM_INFO_9] = SYSINFO_TYPE_9, + [RSL_SYSTEM_INFO_10] = SYSINFO_TYPE_10, + [RSL_SYSTEM_INFO_13] = SYSINFO_TYPE_13, + [RSL_SYSTEM_INFO_16] = SYSINFO_TYPE_16, + [RSL_SYSTEM_INFO_17] = SYSINFO_TYPE_17, + [RSL_SYSTEM_INFO_18] = SYSINFO_TYPE_18, + [RSL_SYSTEM_INFO_19] = SYSINFO_TYPE_19, + [RSL_SYSTEM_INFO_20] = SYSINFO_TYPE_20, + [RSL_SYSTEM_INFO_2bis] = SYSINFO_TYPE_2bis, + [RSL_SYSTEM_INFO_2ter] = SYSINFO_TYPE_2ter, + [RSL_SYSTEM_INFO_2quater] = SYSINFO_TYPE_2quater, + [RSL_SYSTEM_INFO_5bis] = SYSINFO_TYPE_5bis, + [RSL_SYSTEM_INFO_5ter] = SYSINFO_TYPE_5ter, +}; + +typedef int (*gen_si_fn_t)(uint8_t *output, struct gsm_bts *bts); + +static const gen_si_fn_t gen_si_fn[_MAX_SYSINFO_TYPE] = { + [SYSINFO_TYPE_1] = &generate_si1, + [SYSINFO_TYPE_2] = &generate_si2, + [SYSINFO_TYPE_3] = &generate_si3, + [SYSINFO_TYPE_4] = &generate_si4, + [SYSINFO_TYPE_5] = &generate_si5, + [SYSINFO_TYPE_6] = &generate_si6, + [SYSINFO_TYPE_13] = &generate_si13, +}; + +const struct value_string osmo_sitype_strs[_MAX_SYSINFO_TYPE] = { + { SYSINFO_TYPE_1, "1" }, + { SYSINFO_TYPE_2, "2" }, + { SYSINFO_TYPE_3, "3" }, + { SYSINFO_TYPE_4, "4" }, + { SYSINFO_TYPE_5, "5" }, + { SYSINFO_TYPE_6, "6" }, + { SYSINFO_TYPE_7, "7" }, + { SYSINFO_TYPE_8, "8" }, + { SYSINFO_TYPE_9, "9" }, + { SYSINFO_TYPE_10, "10" }, + { SYSINFO_TYPE_13, "13" }, + { SYSINFO_TYPE_16, "16" }, + { SYSINFO_TYPE_17, "17" }, + { SYSINFO_TYPE_18, "18" }, + { SYSINFO_TYPE_19, "19" }, + { SYSINFO_TYPE_20, "20" }, + { SYSINFO_TYPE_2bis, "2bis" }, + { SYSINFO_TYPE_2ter, "2ter" }, + { SYSINFO_TYPE_2quater, "2quater" }, + { SYSINFO_TYPE_5bis, "5bis" }, + { SYSINFO_TYPE_5ter, "5ter" }, + { 0, NULL } +}; + +uint8_t gsm_sitype2rsl(enum osmo_sysinfo_type si_type) +{ + return sitype2rsl[si_type]; +} + +const char *gsm_sitype_name(enum osmo_sysinfo_type si_type) +{ + return get_value_string(osmo_sitype_strs, si_type); +} + +int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type) +{ + gen_si_fn_t gen_si; + + switch (bts->gprs.mode) { + case BTS_GPRS_EGPRS: + si13_default.cell_opts.ext_info_present = 1; + si13_default.cell_opts.ext_info.egprs_supported = 1; + /* fallthrough */ + case BTS_GPRS_GPRS: + si_info.gprs_ind.present = 1; + break; + case BTS_GPRS_NONE: + si_info.gprs_ind.present = 0; + break; + } + + memcpy(&si_info.selection_params, + &bts->si_common.cell_ro_sel_par, + sizeof(struct gsm48_si_selection_params)); + + gen_si = gen_si_fn[si_type]; + if (!gen_si) + return -EINVAL; + + return gen_si(bts->si_buf[si_type], bts); +} diff --git a/src/libbsc/transaction.c b/src/libbsc/transaction.c new file mode 100644 index 000000000..9b4af1aac --- /dev/null +++ b/src/libbsc/transaction.c @@ -0,0 +1,152 @@ +/* GSM 04.07 Transaction handling */ + +/* (C) 2009 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void *tall_trans_ctx; + +void _gsm48_cc_trans_free(struct gsm_trans *trans); + +struct gsm_trans *trans_find_by_id(struct gsm_subscriber *subscr, + u_int8_t proto, u_int8_t trans_id) +{ + struct gsm_trans *trans; + struct gsm_network *net = subscr->net; + + llist_for_each_entry(trans, &net->trans_list, entry) { + if (trans->subscr == subscr && + trans->protocol == proto && + trans->transaction_id == trans_id) + return trans; + } + return NULL; +} + +struct gsm_trans *trans_find_by_callref(struct gsm_network *net, + u_int32_t callref) +{ + struct gsm_trans *trans; + + llist_for_each_entry(trans, &net->trans_list, entry) { + if (trans->callref == callref) + return trans; + } + return NULL; +} + +struct gsm_trans *trans_alloc(struct gsm_subscriber *subscr, + u_int8_t protocol, u_int8_t trans_id, + u_int32_t callref) +{ + struct gsm_trans *trans; + + DEBUGP(DCC, "subscr=%p, subscr->net=%p\n", subscr, subscr->net); + + trans = talloc_zero(tall_trans_ctx, struct gsm_trans); + if (!trans) + return NULL; + + trans->subscr = subscr; + subscr_get(trans->subscr); + + trans->protocol = protocol; + trans->transaction_id = trans_id; + trans->callref = callref; + + llist_add_tail(&trans->entry, &subscr->net->trans_list); + + return trans; +} + +void trans_free(struct gsm_trans *trans) +{ + switch (trans->protocol) { + case GSM48_PDISC_CC: + _gsm48_cc_trans_free(trans); + break; + case GSM48_PDISC_SMS: + _gsm411_sms_trans_free(trans); + break; + } + + /* FIXME: implement a sane way to stop this. */ + if (!trans->conn && trans->paging_request) { + LOGP(DNM, LOGL_ERROR, + "Transaction freed while paging for sub: %llu\n", + trans->subscr->id); + trans->paging_request = NULL; + } + + if (trans->subscr) + subscr_put(trans->subscr); + + llist_del(&trans->entry); + + if (trans->conn) + msc_release_connection(trans->conn); + + + talloc_free(trans); +} + +/* allocate an unused transaction ID for the given subscriber + * in the given protocol using the ti_flag specified */ +int trans_assign_trans_id(struct gsm_subscriber *subscr, + u_int8_t protocol, u_int8_t ti_flag) +{ + struct gsm_network *net = subscr->net; + struct gsm_trans *trans; + unsigned int used_tid_bitmask = 0; + int i, j, h; + + if (ti_flag) + ti_flag = 0x8; + + /* generate bitmask of already-used TIDs for this (subscr,proto) */ + llist_for_each_entry(trans, &net->trans_list, entry) { + if (trans->subscr != subscr || + trans->protocol != protocol || + trans->transaction_id == 0xff) + continue; + used_tid_bitmask |= (1 << trans->transaction_id); + } + + /* find a new one, trying to go in a 'circular' pattern */ + for (h = 6; h > 0; h--) + if (used_tid_bitmask & (1 << (h | ti_flag))) + break; + for (i = 0; i < 7; i++) { + j = ((h + i) % 7) | ti_flag; + if ((used_tid_bitmask & (1 << j)) == 0) + return j; + } + + return -1; +} + diff --git a/src/libcommon/Makefile.am b/src/libcommon/Makefile.am new file mode 100644 index 000000000..2895452ea --- /dev/null +++ b/src/libcommon/Makefile.am @@ -0,0 +1,7 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) + +noinst_LIBRARIES = libcommon.a + +libcommon_a_SOURCES = bsc_version.c common_vty.c debug.c gsm_data.c socket.c talloc_ctx.c diff --git a/src/libcommon/Makefile.in b/src/libcommon/Makefile.in new file mode 100644 index 000000000..41d52fc6d --- /dev/null +++ b/src/libcommon/Makefile.in @@ -0,0 +1,457 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = src/libcommon +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LIBRARIES = $(noinst_LIBRARIES) +AR = ar +ARFLAGS = cru +AM_V_AR = $(am__v_AR_$(V)) +am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY)) +am__v_AR_0 = @echo " AR " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +libcommon_a_AR = $(AR) $(ARFLAGS) +libcommon_a_LIBADD = +am_libcommon_a_OBJECTS = bsc_version.$(OBJEXT) common_vty.$(OBJEXT) \ + debug.$(OBJEXT) gsm_data.$(OBJEXT) socket.$(OBJEXT) \ + talloc_ctx.$(OBJEXT) +libcommon_a_OBJECTS = $(am_libcommon_a_OBJECTS) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(libcommon_a_SOURCES) +DIST_SOURCES = $(libcommon_a_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) +noinst_LIBRARIES = libcommon.a +libcommon_a_SOURCES = bsc_version.c common_vty.c debug.c gsm_data.c socket.c talloc_ctx.c +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libcommon/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/libcommon/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) +libcommon.a: $(libcommon_a_OBJECTS) $(libcommon_a_DEPENDENCIES) + $(AM_V_at)-rm -f libcommon.a + $(AM_V_AR)$(libcommon_a_AR) libcommon.a $(libcommon_a_OBJECTS) $(libcommon_a_LIBADD) + $(AM_V_at)$(RANLIB) libcommon.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_version.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/common_vty.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/debug.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_data.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socket.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/talloc_ctx.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LIBRARIES) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-noinstLIBRARIES ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/libcommon/bsc_version.c b/src/libcommon/bsc_version.c new file mode 100644 index 000000000..be5d28c22 --- /dev/null +++ b/src/libcommon/bsc_version.c @@ -0,0 +1,30 @@ +/* Hold the copyright and version string */ +/* (C) 2010 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "../../bscconfig.h" + +const char *openbsc_copyright = + "Copyright (C) 2008-2010 Harald Welte, Holger Freyther\r\n" + "Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\r\n" + "Dieter Spaar, Andreas Eversberg, Sylvain Munaut\r\n\r\n" + "License AGPLv3+: GNU AGPL version 3 or later \r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + + diff --git a/src/libcommon/common_vty.c b/src/libcommon/common_vty.c new file mode 100644 index 000000000..84375a22d --- /dev/null +++ b/src/libcommon/common_vty.c @@ -0,0 +1,225 @@ +/* OpenBSC VTY common helpers */ +/* (C) 2009-2010 by Harald Welte + * (C) 2009-2010 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +enum node_type bsc_vty_go_parent(struct vty *vty) +{ + switch (vty->node) { + case GSMNET_NODE: + vty->node = CONFIG_NODE; + vty->index = NULL; + break; + case BTS_NODE: + vty->node = GSMNET_NODE; + { + /* set vty->index correctly ! */ + struct gsm_bts *bts = vty->index; + vty->index = bts->network; + } + break; + case TRX_NODE: + vty->node = BTS_NODE; + { + /* set vty->index correctly ! */ + struct gsm_bts_trx *trx = vty->index; + vty->index = trx->bts; + } + break; + case TS_NODE: + vty->node = TRX_NODE; + { + /* set vty->index correctly ! */ + struct gsm_bts_trx_ts *ts = vty->index; + vty->index = ts->trx; + } + break; + case OML_NODE: + case OM2K_NODE: + vty->node = ENABLE_NODE; + talloc_free(vty->index); + vty->index = NULL; + break; + case NAT_NODE: + vty->node = CONFIG_NODE; + vty->index = NULL; + break; + case NAT_BSC_NODE: + vty->node = NAT_NODE; + { + struct bsc_config *bsc_config = vty->index; + vty->index = bsc_config->nat; + } + break; + case MSC_NODE: + vty->node = GSMNET_NODE; + break; + case TRUNK_NODE: + vty->node = MGCP_NODE; + break; + default: + vty->node = CONFIG_NODE; + } + + return vty->node; +} + +/* Down vty node level. */ +gDEFUN(ournode_exit, + ournode_exit_cmd, "exit", "Exit current mode and down to previous mode\n") +{ + switch (vty->node) { + case GSMNET_NODE: + vty->node = CONFIG_NODE; + vty->index = NULL; + break; + case BTS_NODE: + vty->node = GSMNET_NODE; + { + /* set vty->index correctly ! */ + struct gsm_bts *bts = vty->index; + vty->index = bts->network; + vty->index_sub = NULL; + } + break; + case TRX_NODE: + vty->node = BTS_NODE; + { + /* set vty->index correctly ! */ + struct gsm_bts_trx *trx = vty->index; + vty->index = trx->bts; + vty->index_sub = &trx->bts->description; + } + break; + case TS_NODE: + vty->node = TRX_NODE; + { + /* set vty->index correctly ! */ + struct gsm_bts_trx_ts *ts = vty->index; + vty->index = ts->trx; + vty->index_sub = &ts->trx->description; + } + break; + case NAT_BSC_NODE: + vty->node = NAT_NODE; + { + struct bsc_config *bsc_config = vty->index; + vty->index = bsc_config->nat; + } + break; + case MGCP_NODE: + case GBPROXY_NODE: + case SGSN_NODE: + case NS_NODE: + case BSSGP_NODE: + case NAT_NODE: + vty->node = CONFIG_NODE; + vty->index = NULL; + break; + case OML_NODE: + case OM2K_NODE: + vty->node = ENABLE_NODE; + talloc_free(vty->index); + vty->index = NULL; + break; + case MSC_NODE: + vty->node = GSMNET_NODE; + break; + case TRUNK_NODE: + vty->node = MGCP_NODE; + vty->index = NULL; + break; + default: + break; + } + return CMD_SUCCESS; +} + +/* End of configuration. */ +gDEFUN(ournode_end, + ournode_end_cmd, "end", "End current mode and change to enable mode.") +{ + switch (vty->node) { + case VIEW_NODE: + case ENABLE_NODE: + /* Nothing to do. */ + break; + case CONFIG_NODE: + case GSMNET_NODE: + case BTS_NODE: + case TRX_NODE: + case TS_NODE: + case MGCP_NODE: + case TRUNK_NODE: + case GBPROXY_NODE: + case SGSN_NODE: + case NS_NODE: + case VTY_NODE: + case NAT_NODE: + case NAT_BSC_NODE: + case MSC_NODE: + vty_config_unlock(vty); + vty->node = ENABLE_NODE; + vty->index = NULL; + vty->index_sub = NULL; + break; + default: + break; + } + return CMD_SUCCESS; +} + +int bsc_vty_is_config_node(struct vty *vty, int node) +{ + switch (node) { + /* add items that are not config */ + case OML_NODE: + case OM2K_NODE: + case SUBSCR_NODE: + case CONFIG_NODE: + return 0; + + default: + return 1; + } +} + +/* a talloc string replace routine */ +void bsc_replace_string(void *ctx, char **dst, const char *newstr) +{ + if (*dst) + talloc_free(*dst); + *dst = talloc_strdup(ctx, newstr); +} diff --git a/src/libcommon/debug.c b/src/libcommon/debug.c new file mode 100644 index 000000000..ea5db49e5 --- /dev/null +++ b/src/libcommon/debug.c @@ -0,0 +1,249 @@ +/* OpenBSC Debugging/Logging support code */ + +/* (C) 2008-2010 by Harald Welte + * (C) 2008 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* default categories */ +static const struct log_info_cat default_categories[] = { + [DRLL] = { + .name = "DRLL", + .description = "A-bis Radio Link Layer (RLL)", + .color = "\033[1;31m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DCC] = { + .name = "DCC", + .description = "Layer3 Call Control (CC)", + .color = "\033[1;32m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DMM] = { + .name = "DMM", + .description = "Layer3 Mobility Management (MM)", + .color = "\033[1;33m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DRR] = { + .name = "DRR", + .description = "Layer3 Radio Resource (RR)", + .color = "\033[1;34m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DRSL] = { + .name = "DRSL", + .description = "A-bis Radio Siganlling Link (RSL)", + .color = "\033[1;35m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DNM] = { + .name = "DNM", + .description = "A-bis Network Management / O&M (NM/OML)", + .color = "\033[1;36m", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DMNCC] = { + .name = "DMNCC", + .description = "MNCC API for Call Control application", + .color = "\033[1;39m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DSMS] = { + .name = "DSMS", + .description = "Layer3 Short Message Service (SMS)", + .color = "\033[1;37m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DPAG] = { + .name = "DPAG", + .description = "Paging Subsystem", + .color = "\033[1;38m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DMEAS] = { + .name = "DMEAS", + .description = "Radio Measurement Processing", + .enabled = 0, .loglevel = LOGL_NOTICE, + }, + [DMI] = { + .name = "DMI", + .description = "A-bis Input Driver for Signalling", + .enabled = 0, .loglevel = LOGL_NOTICE, + }, + [DMIB] = { + .name = "DMIB", + .description = "A-bis Input Driver for B-Channels (voice)", + .enabled = 0, .loglevel = LOGL_NOTICE, + }, + [DMUX] = { + .name = "DMUX", + .description = "A-bis B-Subchannel TRAU Frame Multiplex", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DINP] = { + .name = "DINP", + .description = "A-bis Intput Subsystem", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DSCCP] = { + .name = "DSCCP", + .description = "SCCP Protocol", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DMSC] = { + .name = "DMSC", + .description = "Mobile Switching Center", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DMGCP] = { + .name = "DMGCP", + .description = "Media Gateway Control Protocol", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DHO] = { + .name = "DHO", + .description = "Hand-Over", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DDB] = { + .name = "DDB", + .description = "Database Layer", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DREF] = { + .name = "DREF", + .description = "Reference Counting", + .enabled = 0, .loglevel = LOGL_NOTICE, + }, + [DGPRS] = { + .name = "DGPRS", + .description = "GPRS Packet Service", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DNS] = { + .name = "DNS", + .description = "GPRS Network Service (NS)", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DBSSGP] = { + .name = "DBSSGP", + .description = "GPRS BSS Gateway Protocol (BSSGP)", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DLLC] = { + .name = "DLLC", + .description = "GPRS Logical Link Control Protocol (LLC)", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DSNDCP] = { + .name = "DSNDCP", + .description = "GPRS Sub-Network Dependent Control Protocol (SNDCP)", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DNAT] = { + .name = "DNAT", + .description = "GSM 08.08 NAT/Multipkexer", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, +}; + +enum log_filter { + _FLT_ALL = LOG_FILTER_ALL, /* libosmocore */ + FLT_IMSI = 1, + FLT_NSVC = 2, + FLT_BVC = 3, +}; + +static int filter_fn(const struct log_context *ctx, + struct log_target *tar) +{ + struct gsm_subscriber *subscr = ctx->ctx[BSC_CTX_SUBSCR]; + const struct gprs_nsvc *nsvc = ctx->ctx[BSC_CTX_NSVC]; + const struct gprs_nsvc *bvc = ctx->ctx[BSC_CTX_BVC]; + + if ((tar->filter_map & (1 << FLT_IMSI)) != 0 + && subscr && strcmp(subscr->imsi, tar->filter_data[FLT_IMSI]) == 0) + return 1; + + /* Filter on the NS Virtual Connection */ + if ((tar->filter_map & (1 << FLT_NSVC)) != 0 + && nsvc && (nsvc == tar->filter_data[FLT_NSVC])) + return 1; + + /* Filter on the NS Virtual Connection */ + if ((tar->filter_map & (1 << FLT_BVC)) != 0 + && bvc && (bvc == tar->filter_data[FLT_BVC])) + return 1; + + return 0; +} + +const struct log_info log_info = { + .filter_fn = filter_fn, + .cat = default_categories, + .num_cat = ARRAY_SIZE(default_categories), +}; + +void log_set_imsi_filter(struct log_target *target, const char *imsi) +{ + if (imsi) { + target->filter_map |= (1 << FLT_IMSI); + target->filter_data[FLT_IMSI] = talloc_strdup(target, imsi); + } else if (target->filter_data[FLT_IMSI]) { + target->filter_map &= ~(1 << FLT_IMSI); + talloc_free(target->filter_data[FLT_IMSI]); + target->filter_data[FLT_IMSI] = NULL; + } +} + +void log_set_nsvc_filter(struct log_target *target, struct gprs_nsvc *nsvc) +{ + if (nsvc) { + target->filter_map |= (1 << FLT_NSVC); + target->filter_data[FLT_NSVC] = nsvc; + } else if (target->filter_data[FLT_NSVC]) { + target->filter_map = ~(1 << FLT_NSVC); + target->filter_data[FLT_NSVC] = NULL; + } +} + +void log_set_bvc_filter(struct log_target *target, struct bssgp_bvc_ctx *bctx) +{ + if (bctx) { + target->filter_map |= (1 << FLT_BVC); + target->filter_data[FLT_BVC] = bctx; + } else if (target->filter_data[FLT_NSVC]) { + target->filter_map = ~(1 << FLT_BVC); + target->filter_data[FLT_BVC] = NULL; + } +} diff --git a/src/libcommon/gsm_data.c b/src/libcommon/gsm_data.c new file mode 100644 index 000000000..b2c52e4b1 --- /dev/null +++ b/src/libcommon/gsm_data.c @@ -0,0 +1,592 @@ +/* (C) 2008-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +void *tall_bsc_ctx; + +static LLIST_HEAD(bts_models); + +void set_ts_e1link(struct gsm_bts_trx_ts *ts, u_int8_t e1_nr, + u_int8_t e1_ts, u_int8_t e1_ts_ss) +{ + ts->e1_link.e1_nr = e1_nr; + ts->e1_link.e1_ts = e1_ts; + ts->e1_link.e1_ts_ss = e1_ts_ss; +} + +static const struct value_string pchan_names[] = { + { GSM_PCHAN_NONE, "NONE" }, + { GSM_PCHAN_CCCH, "CCCH" }, + { GSM_PCHAN_CCCH_SDCCH4,"CCCH+SDCCH4" }, + { GSM_PCHAN_TCH_F, "TCH/F" }, + { GSM_PCHAN_TCH_H, "TCH/H" }, + { GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" }, + { GSM_PCHAN_PDCH, "PDCH" }, + { GSM_PCHAN_TCH_F_PDCH, "TCH/F_PDCH" }, + { GSM_PCHAN_UNKNOWN, "UNKNOWN" }, + { 0, NULL } +}; + +const char *gsm_pchan_name(enum gsm_phys_chan_config c) +{ + return get_value_string(pchan_names, c); +} + +enum gsm_phys_chan_config gsm_pchan_parse(const char *name) +{ + return get_string_value(pchan_names, name); +} + +static const struct value_string lchant_names[] = { + { GSM_LCHAN_NONE, "NONE" }, + { GSM_LCHAN_SDCCH, "SDCCH" }, + { GSM_LCHAN_TCH_F, "TCH/F" }, + { GSM_LCHAN_TCH_H, "TCH/H" }, + { GSM_LCHAN_UNKNOWN, "UNKNOWN" }, + { 0, NULL } +}; + +const char *gsm_lchant_name(enum gsm_chan_t c) +{ + return get_value_string(lchant_names, c); +} + +static const struct value_string lchan_s_names[] = { + { LCHAN_S_NONE, "NONE" }, + { LCHAN_S_ACT_REQ, "ACTIVATION REQUESTED" }, + { LCHAN_S_ACTIVE, "ACTIVE" }, + { LCHAN_S_INACTIVE, "INACTIVE" }, + { LCHAN_S_REL_REQ, "RELEASE REQUESTED" }, + { LCHAN_S_REL_ERR, "RELEASE DUE ERROR" }, + { 0, NULL } +}; + +const char *gsm_lchans_name(enum gsm_lchan_state s) +{ + return get_value_string(lchan_s_names, s); +} + +static const struct value_string chreq_names[] = { + { GSM_CHREQ_REASON_EMERG, "EMERGENCY" }, + { GSM_CHREQ_REASON_PAG, "PAGING" }, + { GSM_CHREQ_REASON_CALL, "CALL" }, + { GSM_CHREQ_REASON_LOCATION_UPD,"LOCATION_UPDATE" }, + { GSM_CHREQ_REASON_OTHER, "OTHER" }, + { 0, NULL } +}; + +const char *gsm_chreq_name(enum gsm_chreq_reason_t c) +{ + return get_value_string(chreq_names, c); +} + +static struct gsm_bts_model *bts_model_find(enum gsm_bts_type type) +{ + struct gsm_bts_model *model; + + llist_for_each_entry(model, &bts_models, list) { + if (model->type == type) + return model; + } + + return NULL; +} + +int gsm_bts_model_register(struct gsm_bts_model *model) +{ + if (bts_model_find(model->type)) + return -EEXIST; + + tlv_def_patch(&model->nm_att_tlvdef, &nm_att_tlvdef); + llist_add_tail(&model->list, &bts_models); + return 0; +} + + +struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx = talloc_zero(bts, struct gsm_bts_trx); + int k; + + if (!trx) + return NULL; + + trx->bts = bts; + trx->nr = bts->num_trx++; + trx->nm_state.administrative = NM_STATE_UNLOCKED; + + for (k = 0; k < TRX_NR_TS; k++) { + struct gsm_bts_trx_ts *ts = &trx->ts[k]; + int l; + + ts->trx = trx; + ts->nr = k; + ts->pchan = GSM_PCHAN_NONE; + + ts->hopping.arfcns.data_len = sizeof(ts->hopping.arfcns_data); + ts->hopping.arfcns.data = ts->hopping.arfcns_data; + ts->hopping.ma.data_len = sizeof(ts->hopping.ma_data); + ts->hopping.ma.data = ts->hopping.ma_data; + + for (l = 0; l < TS_MAX_LCHAN; l++) { + struct gsm_lchan *lchan; + lchan = &ts->lchan[l]; + + lchan->ts = ts; + lchan->nr = l; + lchan->type = GSM_LCHAN_NONE; + } + } + + if (trx->nr != 0) + trx->nominal_power = bts->c0->nominal_power; + + llist_add_tail(&trx->list, &bts->trx_list); + + return trx; +} + +static const uint8_t bts_nse_timer_default[] = { 3, 3, 3, 3, 30, 3, 10 }; +static const uint8_t bts_cell_timer_default[] = + { 3, 3, 3, 3, 3, 10, 3, 10, 3, 10, 3 }; + +struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, enum gsm_bts_type type, + u_int8_t tsc, u_int8_t bsic) +{ + struct gsm_bts *bts = talloc_zero(net, struct gsm_bts); + struct gsm_bts_model *model = bts_model_find(type); + int i; + + if (!bts) + return NULL; + + if (!model && type != GSM_BTS_TYPE_UNKNOWN) { + talloc_free(bts); + return NULL; + } + + bts->network = net; + bts->nr = net->num_bts++; + bts->type = type; + bts->model = model; + bts->tsc = tsc; + bts->bsic = bsic; + bts->num_trx = 0; + INIT_LLIST_HEAD(&bts->trx_list); + bts->ms_max_power = 15; /* dBm */ + + bts->neigh_list_manual_mode = 0; + bts->si_common.cell_sel_par.cell_resel_hyst = 2; /* 4 dB */ + bts->si_common.cell_sel_par.rxlev_acc_min = 0; + bts->si_common.neigh_list.data = bts->si_common.data.neigh_list; + bts->si_common.neigh_list.data_len = + sizeof(bts->si_common.data.neigh_list); + bts->si_common.si5_neigh_list.data = bts->si_common.data.si5_neigh_list; + bts->si_common.si5_neigh_list.data_len = + sizeof(bts->si_common.data.si5_neigh_list); + bts->si_common.cell_alloc.data = bts->si_common.data.cell_alloc; + bts->si_common.cell_alloc.data_len = + sizeof(bts->si_common.data.cell_alloc); + bts->si_common.rach_control.re = 1; /* no re-establishment */ + bts->si_common.rach_control.tx_integer = 9; /* 12 slots spread - 217/115 slots delay */ + bts->si_common.rach_control.max_trans = 3; /* 7 retransmissions */ + bts->si_common.rach_control.t2 = 4; /* no emergency calls */ + + for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) { + bts->gprs.nsvc[i].bts = bts; + bts->gprs.nsvc[i].id = i; + } + memcpy(&bts->gprs.nse.timer, bts_nse_timer_default, + sizeof(bts->gprs.nse.timer)); + memcpy(&bts->gprs.cell.timer, bts_cell_timer_default, + sizeof(bts->gprs.cell.timer)); + + /* create our primary TRX */ + bts->c0 = gsm_bts_trx_alloc(bts); + if (!bts->c0) { + talloc_free(bts); + return NULL; + } + bts->c0->ts[0].pchan = GSM_PCHAN_CCCH_SDCCH4; + + bts->rach_b_thresh = -1; + bts->rach_ldavg_slots = -1; + bts->paging.free_chans_need = -1; + INIT_LLIST_HEAD(&bts->abis_queue); + + llist_add_tail(&bts->list, &net->bts_list); + + return bts; +} + +struct gsm_network *gsm_network_init(u_int16_t country_code, u_int16_t network_code, + int (*mncc_recv)(struct gsm_network *, struct msgb *)) +{ + struct gsm_network *net; + + net = talloc_zero(tall_bsc_ctx, struct gsm_network); + if (!net) + return NULL; + + net->msc_data = talloc_zero(net, struct osmo_msc_data); + if (!net->msc_data) { + talloc_free(net); + return NULL; + } + + net->country_code = country_code; + net->network_code = network_code; + net->num_bts = 0; + net->reject_cause = GSM48_REJECT_ROAMING_NOT_ALLOWED; + net->T3101 = GSM_T3101_DEFAULT; + net->T3113 = GSM_T3113_DEFAULT; + /* FIXME: initialize all other timers! */ + + /* default set of handover parameters */ + net->handover.win_rxlev_avg = 10; + net->handover.win_rxqual_avg = 1; + net->handover.win_rxlev_avg_neigh = 10; + net->handover.pwr_interval = 6; + net->handover.pwr_hysteresis = 3; + net->handover.max_distance = 9999; + + INIT_LLIST_HEAD(&net->trans_list); + INIT_LLIST_HEAD(&net->upqueue); + INIT_LLIST_HEAD(&net->bts_list); + + net->stats.chreq.total = counter_alloc("net.chreq.total"); + net->stats.chreq.no_channel = counter_alloc("net.chreq.no_channel"); + net->stats.handover.attempted = counter_alloc("net.handover.attempted"); + net->stats.handover.no_channel = counter_alloc("net.handover.no_channel"); + net->stats.handover.timeout = counter_alloc("net.handover.timeout"); + net->stats.handover.completed = counter_alloc("net.handover.completed"); + net->stats.handover.failed = counter_alloc("net.handover.failed"); + net->stats.loc_upd_type.attach = counter_alloc("net.loc_upd_type.attach"); + net->stats.loc_upd_type.normal = counter_alloc("net.loc_upd_type.normal"); + net->stats.loc_upd_type.periodic = counter_alloc("net.loc_upd_type.periodic"); + net->stats.loc_upd_type.detach = counter_alloc("net.imsi_detach.count"); + net->stats.loc_upd_resp.reject = counter_alloc("net.loc_upd_resp.reject"); + net->stats.loc_upd_resp.accept = counter_alloc("net.loc_upd_resp.accept"); + net->stats.paging.attempted = counter_alloc("net.paging.attempted"); + net->stats.paging.detached = counter_alloc("net.paging.detached"); + net->stats.paging.completed = counter_alloc("net.paging.completed"); + net->stats.paging.expired = counter_alloc("net.paging.expired"); + net->stats.sms.submitted = counter_alloc("net.sms.submitted"); + net->stats.sms.no_receiver = counter_alloc("net.sms.no_receiver"); + net->stats.sms.delivered = counter_alloc("net.sms.delivered"); + net->stats.sms.rp_err_mem = counter_alloc("net.sms.rp_err_mem"); + net->stats.sms.rp_err_other = counter_alloc("net.sms.rp_err_other"); + net->stats.call.mo_setup = counter_alloc("net.call.mo_setup"); + net->stats.call.mo_connect_ack = counter_alloc("net.call.mo_connect_ack"); + net->stats.call.mt_setup = counter_alloc("net.call.mt_setup"); + net->stats.call.mt_connect = counter_alloc("net.call.mt_connect"); + net->stats.chan.rf_fail = counter_alloc("net.chan.rf_fail"); + net->stats.chan.rll_err = counter_alloc("net.chan.rll_err"); + net->stats.bts.oml_fail = counter_alloc("net.bts.oml_fail"); + net->stats.bts.rsl_fail = counter_alloc("net.bts.rsl_fail"); + + net->mncc_recv = mncc_recv; + + net->msc_data->msc_ip = talloc_strdup(net, "127.0.0.1"); + net->msc_data->msc_port = 5000; + net->msc_data->ping_timeout = 20; + net->msc_data->pong_timeout = 5; + net->msc_data->core_ncc = -1; + net->msc_data->core_mcc = -1; + net->msc_data->rtp_base = 4000; + + gsm_net_update_ctype(net); + + return net; +} + +struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num) +{ + struct gsm_bts *bts; + + if (num >= net->num_bts) + return NULL; + + llist_for_each_entry(bts, &net->bts_list, list) { + if (bts->nr == num) + return bts; + } + + return NULL; +} + +/* Get reference to a neighbor cell on a given BCCH ARFCN */ +struct gsm_bts *gsm_bts_neighbor(const struct gsm_bts *bts, + u_int16_t arfcn, u_int8_t bsic) +{ + struct gsm_bts *neigh; + /* FIXME: use some better heuristics here to determine which cell + * using this ARFCN really is closest to the target cell. For + * now we simply assume that each ARFCN will only be used by one + * cell */ + + llist_for_each_entry(neigh, &bts->network->bts_list, list) { + if (neigh->c0->arfcn == arfcn && + neigh->bsic == bsic) + return neigh; + } + + return NULL; +} + +struct gsm_bts_trx *gsm_bts_trx_num(struct gsm_bts *bts, int num) +{ + struct gsm_bts_trx *trx; + + if (num >= bts->num_trx) + return NULL; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->nr == num) + return trx; + } + + return NULL; +} + +static char ts2str[255]; + +char *gsm_trx_name(struct gsm_bts_trx *trx) +{ + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d)", + trx->bts->nr, trx->nr); + + return ts2str; +} + + +char *gsm_ts_name(struct gsm_bts_trx_ts *ts) +{ + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d)", + ts->trx->bts->nr, ts->trx->nr, ts->nr); + + return ts2str; +} + +char *gsm_lchan_name(struct gsm_lchan *lchan) +{ + struct gsm_bts_trx_ts *ts = lchan->ts; + + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,ss=%d)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, lchan->nr); + + return ts2str; +} + +static const struct value_string bts_types[] = { + { GSM_BTS_TYPE_UNKNOWN, "unknown" }, + { GSM_BTS_TYPE_BS11, "bs11" }, + { GSM_BTS_TYPE_NANOBTS, "nanobts" }, + { GSM_BTS_TYPE_RBS2000, "rbs2000" }, + { GSM_BTS_TYPE_HSL_FEMTO, "hsl_femto" }, + { 0, NULL } +}; + +enum gsm_bts_type parse_btstype(const char *arg) +{ + return get_string_value(bts_types, arg); +} + +const char *btstype2str(enum gsm_bts_type type) +{ + return get_value_string(bts_types, type); +} + +struct gsm_bts_trx *gsm_bts_trx_by_nr(struct gsm_bts *bts, int nr) +{ + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->nr == nr) + return trx; + } + return NULL; +} + +/* Search for a BTS in the given Location Area; optionally start searching + * with start_bts (for continuing to search after the first result) */ +struct gsm_bts *gsm_bts_by_lac(struct gsm_network *net, unsigned int lac, + struct gsm_bts *start_bts) +{ + int i; + struct gsm_bts *bts; + int skip = 0; + + if (start_bts) + skip = 1; + + for (i = 0; i < net->num_bts; i++) { + bts = gsm_bts_num(net, i); + + if (skip) { + if (start_bts == bts) + skip = 0; + continue; + } + + if (lac == GSM_LAC_RESERVED_ALL_BTS || bts->location_area_code == lac) + return bts; + } + return NULL; +} + +static const struct value_string auth_policy_names[] = { + { GSM_AUTH_POLICY_CLOSED, "closed" }, + { GSM_AUTH_POLICY_ACCEPT_ALL, "accept-all" }, + { GSM_AUTH_POLICY_TOKEN, "token" }, + { 0, NULL } +}; + +enum gsm_auth_policy gsm_auth_policy_parse(const char *arg) +{ + return get_string_value(auth_policy_names, arg); +} + +const char *gsm_auth_policy_name(enum gsm_auth_policy policy) +{ + return get_value_string(auth_policy_names, policy); +} + +void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts) +{ + raid->mcc = bts->network->country_code; + raid->mnc = bts->network->network_code; + raid->lac = bts->location_area_code; + raid->rac = bts->gprs.rac; +} + +int gsm48_ra_id_by_bts(u_int8_t *buf, struct gsm_bts *bts) +{ + struct gprs_ra_id raid; + + gprs_ra_id_by_bts(&raid, bts); + + return gsm48_construct_ra(buf, &raid); +} + +static const struct value_string rrlp_mode_names[] = { + { RRLP_MODE_NONE, "none" }, + { RRLP_MODE_MS_BASED, "ms-based" }, + { RRLP_MODE_MS_PREF, "ms-preferred" }, + { RRLP_MODE_ASS_PREF, "ass-preferred" }, + { 0, NULL } +}; + +enum rrlp_mode rrlp_mode_parse(const char *arg) +{ + return get_string_value(rrlp_mode_names, arg); +} + +const char *rrlp_mode_name(enum rrlp_mode mode) +{ + return get_value_string(rrlp_mode_names, mode); +} + +static const struct value_string bts_gprs_mode_names[] = { + { BTS_GPRS_NONE, "none" }, + { BTS_GPRS_GPRS, "gprs" }, + { BTS_GPRS_EGPRS, "egprs" }, + { 0, NULL } +}; + +enum bts_gprs_mode bts_gprs_mode_parse(const char *arg) +{ + return get_string_value(bts_gprs_mode_names, arg); +} + +const char *bts_gprs_mode_name(enum bts_gprs_mode mode) +{ + return get_value_string(bts_gprs_mode_names, mode); +} + +struct gsm_meas_rep *lchan_next_meas_rep(struct gsm_lchan *lchan) +{ + struct gsm_meas_rep *meas_rep; + + meas_rep = &lchan->meas_rep[lchan->meas_rep_idx]; + memset(meas_rep, 0, sizeof(*meas_rep)); + meas_rep->lchan = lchan; + lchan->meas_rep_idx = (lchan->meas_rep_idx + 1) + % ARRAY_SIZE(lchan->meas_rep); + + return meas_rep; +} + +int gsm_btsmodel_set_feature(struct gsm_bts_model *bts, enum gsm_bts_features feat) +{ + return bitvec_set_bit_pos(&bts->features, feat, 1); +} + +int gsm_bts_has_feature(struct gsm_bts *bts, enum gsm_bts_features feat) +{ + return bitvec_get_bit_pos(&bts->model->features, feat); +} + +int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type) +{ + struct gsm_bts_model *model; + + model = bts_model_find(type); + if (!model) + return -EINVAL; + + bts->type = type; + bts->model = model; + + switch (bts->type) { + case GSM_BTS_TYPE_HSL_FEMTO: + bts->c0->rsl_tei = 0; + case GSM_BTS_TYPE_NANOBTS: + /* Set the default OML Stream ID to 0xff */ + bts->oml_tei = 0xff; + bts->c0->nominal_power = 23; + break; + case GSM_BTS_TYPE_BS11: + case GSM_BTS_TYPE_UNKNOWN: + break; + case GSM_BTS_TYPE_RBS2000: + INIT_LLIST_HEAD(&bts->rbs2000.is.conn_groups); + INIT_LLIST_HEAD(&bts->rbs2000.con.conn_groups); + break; + } + + return 0; +} diff --git a/src/libcommon/socket.c b/src/libcommon/socket.c new file mode 100644 index 000000000..47778e746 --- /dev/null +++ b/src/libcommon/socket.c @@ -0,0 +1,108 @@ +/* OpenBSC sokcet code, taken from Abis input driver for ip.access */ + +/* (C) 2009 by Harald Welte + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +int make_sock(struct bsc_fd *bfd, int proto, u_int32_t ip, u_int16_t port, + int (*cb)(struct bsc_fd *fd, unsigned int what)) +{ + struct sockaddr_in addr; + int ret, on = 1; + int type = SOCK_STREAM; + + switch (proto) { + case IPPROTO_TCP: + type = SOCK_STREAM; + break; + case IPPROTO_UDP: + type = SOCK_DGRAM; + break; + case IPPROTO_GRE: + type = SOCK_RAW; + break; + default: + return -EINVAL; + } + + bfd->fd = socket(AF_INET, type, proto); + bfd->cb = cb; + bfd->when = BSC_FD_READ; + //bfd->data = line; + + if (bfd->fd < 0) { + LOGP(DINP, LOGL_ERROR, "could not create socket.\n"); + return -EIO; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (ip) + addr.sin_addr.s_addr = htonl(ip); + else + addr.sin_addr.s_addr = INADDR_ANY; + + setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr)); + if (ret < 0) { + LOGP(DINP, LOGL_ERROR, "could not bind socket %s\n", + strerror(errno)); + close(bfd->fd); + return -EIO; + } + + if (proto == IPPROTO_TCP) { + ret = listen(bfd->fd, 1); + if (ret < 0) { + perror("listen"); + close(bfd->fd); + return ret; + } + } + + ret = bsc_register_fd(bfd); + if (ret < 0) { + perror("register_listen_fd"); + close(bfd->fd); + return ret; + } + return 0; +} diff --git a/src/libcommon/talloc_ctx.c b/src/libcommon/talloc_ctx.c new file mode 100644 index 000000000..8e7ec230f --- /dev/null +++ b/src/libcommon/talloc_ctx.c @@ -0,0 +1,38 @@ +#include +#include + +extern void *tall_msgb_ctx; +extern void *tall_fle_ctx; +extern void *tall_locop_ctx; +extern void *tall_authciphop_ctx; +extern void *tall_gsms_ctx; +extern void *tall_subscr_ctx; +extern void *tall_sub_req_ctx; +extern void *tall_call_ctx; +extern void *tall_paging_ctx; +extern void *tall_sigh_ctx; +extern void *tall_tqe_ctx; +extern void *tall_trans_ctx; +extern void *tall_map_ctx; +extern void *tall_upq_ctx; +extern void *tall_ctr_ctx; + +void talloc_ctx_init(void) +{ + tall_msgb_ctx = talloc_named_const(tall_bsc_ctx, 0, "msgb"); + tall_fle_ctx = talloc_named_const(tall_bsc_ctx, 0, + "bs11_file_list_entry"); + tall_locop_ctx = talloc_named_const(tall_bsc_ctx, 0, "loc_updating_oper"); + tall_authciphop_ctx = talloc_named_const(tall_bsc_ctx, 0, "auth_ciph_oper"); + tall_gsms_ctx = talloc_named_const(tall_bsc_ctx, 0, "sms"); + tall_subscr_ctx = talloc_named_const(tall_bsc_ctx, 0, "subscriber"); + tall_sub_req_ctx = talloc_named_const(tall_bsc_ctx, 0, "subscr_request"); + tall_call_ctx = talloc_named_const(tall_bsc_ctx, 0, "gsm_call"); + tall_paging_ctx = talloc_named_const(tall_bsc_ctx, 0, "paging_request"); + tall_sigh_ctx = talloc_named_const(tall_bsc_ctx, 0, "signal_handler"); + tall_tqe_ctx = talloc_named_const(tall_bsc_ctx, 0, "subch_txq_entry"); + tall_trans_ctx = talloc_named_const(tall_bsc_ctx, 0, "transaction"); + tall_map_ctx = talloc_named_const(tall_bsc_ctx, 0, "trau_map_entry"); + tall_upq_ctx = talloc_named_const(tall_bsc_ctx, 0, "trau_upq_entry"); + tall_ctr_ctx = talloc_named_const(tall_bsc_ctx, 0, "counter"); +} diff --git a/src/libgb/Makefile.am b/src/libgb/Makefile.am new file mode 100644 index 000000000..b48b17791 --- /dev/null +++ b/src/libgb/Makefile.am @@ -0,0 +1,9 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS) + +noinst_LIBRARIES = libgb.a + +libgb_a_SOURCES = gprs_ns.c gprs_ns_frgre.c gprs_ns_vty.c \ + gprs_bssgp.c gprs_bssgp_util.c gprs_bssgp_vty.c +#gprs_llc.c gprs_llc_vty.c crc24.c diff --git a/src/libgb/Makefile.in b/src/libgb/Makefile.in new file mode 100644 index 000000000..7b9dbd98c --- /dev/null +++ b/src/libgb/Makefile.in @@ -0,0 +1,460 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = src/libgb +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LIBRARIES = $(noinst_LIBRARIES) +AR = ar +ARFLAGS = cru +AM_V_AR = $(am__v_AR_$(V)) +am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY)) +am__v_AR_0 = @echo " AR " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +libgb_a_AR = $(AR) $(ARFLAGS) +libgb_a_LIBADD = +am_libgb_a_OBJECTS = gprs_ns.$(OBJEXT) gprs_ns_frgre.$(OBJEXT) \ + gprs_ns_vty.$(OBJEXT) gprs_bssgp.$(OBJEXT) \ + gprs_bssgp_util.$(OBJEXT) gprs_bssgp_vty.$(OBJEXT) +libgb_a_OBJECTS = $(am_libgb_a_OBJECTS) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(libgb_a_SOURCES) +DIST_SOURCES = $(libgb_a_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS) +noinst_LIBRARIES = libgb.a +libgb_a_SOURCES = gprs_ns.c gprs_ns_frgre.c gprs_ns_vty.c \ + gprs_bssgp.c gprs_bssgp_util.c gprs_bssgp_vty.c + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libgb/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/libgb/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) +libgb.a: $(libgb_a_OBJECTS) $(libgb_a_DEPENDENCIES) + $(AM_V_at)-rm -f libgb.a + $(AM_V_AR)$(libgb_a_AR) libgb.a $(libgb_a_OBJECTS) $(libgb_a_LIBADD) + $(AM_V_at)$(RANLIB) libgb.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_bssgp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_bssgp_util.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_bssgp_vty.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_ns.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_ns_frgre.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_ns_vty.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LIBRARIES) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-noinstLIBRARIES ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am + +#gprs_llc.c gprs_llc_vty.c crc24.c + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/libgb/gprs_bssgp.c b/src/libgb/gprs_bssgp.c new file mode 100644 index 000000000..eca34b989 --- /dev/null +++ b/src/libgb/gprs_bssgp.c @@ -0,0 +1,856 @@ +/* GPRS BSSGP protocol implementation as per 3GPP TS 08.18 */ + +/* (C) 2009-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * TODO: + * o properly count incoming BVC-RESET packets in counter group + * o set log context as early as possible for outgoing packets + */ + +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +void *bssgp_tall_ctx = NULL; + +#define BVC_F_BLOCKED 0x0001 + +enum bssgp_ctr { + BSSGP_CTR_PKTS_IN, + BSSGP_CTR_PKTS_OUT, + BSSGP_CTR_BYTES_IN, + BSSGP_CTR_BYTES_OUT, + BSSGP_CTR_BLOCKED, + BSSGP_CTR_DISCARDED, +}; + +static const struct rate_ctr_desc bssgp_ctr_description[] = { + { "packets.in", "Packets at BSSGP Level ( In)" }, + { "packets.out","Packets at BSSGP Level (Out)" }, + { "bytes.in", "Bytes at BSSGP Level ( In)" }, + { "bytes.out", "Bytes at BSSGP Level (Out)" }, + { "blocked", "BVC Blocking count" }, + { "discarded", "BVC LLC Discarded count" }, +}; + +static const struct rate_ctr_group_desc bssgp_ctrg_desc = { + .group_name_prefix = "bssgp.bss_ctx", + .group_description = "BSSGP Peer Statistics", + .num_ctr = ARRAY_SIZE(bssgp_ctr_description), + .ctr_desc = bssgp_ctr_description, +}; + +LLIST_HEAD(bssgp_bvc_ctxts); + +/* Find a BTS Context based on parsed RA ID and Cell ID */ +struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t cid) +{ + struct bssgp_bvc_ctx *bctx; + + llist_for_each_entry(bctx, &bssgp_bvc_ctxts, list) { + if (!memcmp(&bctx->ra_id, raid, sizeof(bctx->ra_id)) && + bctx->cell_id == cid) + return bctx; + } + return NULL; +} + +/* Find a BTS context based on BVCI+NSEI tuple */ +struct bssgp_bvc_ctx *btsctx_by_bvci_nsei(uint16_t bvci, uint16_t nsei) +{ + struct bssgp_bvc_ctx *bctx; + + llist_for_each_entry(bctx, &bssgp_bvc_ctxts, list) { + if (bctx->nsei == nsei && bctx->bvci == bvci) + return bctx; + } + return NULL; +} + +struct bssgp_bvc_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei) +{ + struct bssgp_bvc_ctx *ctx; + + ctx = talloc_zero(bssgp_tall_ctx, struct bssgp_bvc_ctx); + if (!ctx) + return NULL; + ctx->bvci = bvci; + ctx->nsei = nsei; + /* FIXME: BVCI is not unique, only BVCI+NSEI ?!? */ + ctx->ctrg = rate_ctr_group_alloc(ctx, &bssgp_ctrg_desc, bvci); + + llist_add(&ctx->list, &bssgp_bvc_ctxts); + + return ctx; +} + +/* Chapter 10.4.5: Flow Control BVC ACK */ +static int bssgp_tx_fc_bvc_ack(uint16_t nsei, uint8_t tag, uint16_t ns_bvci) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph = + (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + + msgb_nsei(msg) = nsei; + msgb_bvci(msg) = ns_bvci; + + bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK; + msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag); + + return gprs_ns_sendmsg(bssgp_nsi, msg); +} + +/* 10.3.7 SUSPEND-ACK PDU */ +int bssgp_tx_suspend_ack(uint16_t nsei, uint32_t tlli, + const struct gprs_ra_id *ra_id, uint8_t suspend_ref) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph = + (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + uint32_t _tlli; + uint8_t ra[6]; + + msgb_nsei(msg) = nsei; + msgb_bvci(msg) = 0; /* Signalling */ + bgph->pdu_type = BSSGP_PDUT_SUSPEND_ACK; + + _tlli = htonl(tlli); + msgb_tvlv_put(msg, BSSGP_IE_TLLI, 4, (uint8_t *) &_tlli); + gsm48_construct_ra(ra, ra_id); + msgb_tvlv_put(msg, BSSGP_IE_ROUTEING_AREA, 6, ra); + msgb_tvlv_put(msg, BSSGP_IE_SUSPEND_REF_NR, 1, &suspend_ref); + + return gprs_ns_sendmsg(bssgp_nsi, msg); +} + +/* 10.3.8 SUSPEND-NACK PDU */ +int bssgp_tx_suspend_nack(uint16_t nsei, uint32_t tlli, + const struct gprs_ra_id *ra_id, + uint8_t *cause) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph = + (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + uint32_t _tlli; + uint8_t ra[6]; + + msgb_nsei(msg) = nsei; + msgb_bvci(msg) = 0; /* Signalling */ + bgph->pdu_type = BSSGP_PDUT_SUSPEND_NACK; + + _tlli = htonl(tlli); + msgb_tvlv_put(msg, BSSGP_IE_TLLI, 4, (uint8_t *) &_tlli); + gsm48_construct_ra(ra, ra_id); + msgb_tvlv_put(msg, BSSGP_IE_ROUTEING_AREA, 6, ra); + if (cause) + msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, cause); + + return gprs_ns_sendmsg(bssgp_nsi, msg); +} + +/* 10.3.10 RESUME-ACK PDU */ +int bssgp_tx_resume_ack(uint16_t nsei, uint32_t tlli, + const struct gprs_ra_id *ra_id) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph = + (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + uint32_t _tlli; + uint8_t ra[6]; + + msgb_nsei(msg) = nsei; + msgb_bvci(msg) = 0; /* Signalling */ + bgph->pdu_type = BSSGP_PDUT_RESUME_ACK; + + _tlli = htonl(tlli); + msgb_tvlv_put(msg, BSSGP_IE_TLLI, 4, (uint8_t *) &_tlli); + gsm48_construct_ra(ra, ra_id); + msgb_tvlv_put(msg, BSSGP_IE_ROUTEING_AREA, 6, ra); + + return gprs_ns_sendmsg(bssgp_nsi, msg); +} + +/* 10.3.11 RESUME-NACK PDU */ +int bssgp_tx_resume_nack(uint16_t nsei, uint32_t tlli, + const struct gprs_ra_id *ra_id, uint8_t *cause) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph = + (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + uint32_t _tlli; + uint8_t ra[6]; + + msgb_nsei(msg) = nsei; + msgb_bvci(msg) = 0; /* Signalling */ + bgph->pdu_type = BSSGP_PDUT_SUSPEND_NACK; + + _tlli = htonl(tlli); + msgb_tvlv_put(msg, BSSGP_IE_TLLI, 4, (uint8_t *) &_tlli); + gsm48_construct_ra(ra, ra_id); + msgb_tvlv_put(msg, BSSGP_IE_ROUTEING_AREA, 6, ra); + if (cause) + msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, cause); + + return gprs_ns_sendmsg(bssgp_nsi, msg); +} + +uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf) +{ + /* 6 octets RAC */ + gsm48_parse_ra(raid, buf); + /* 2 octets CID */ + return ntohs(*(uint16_t *) (buf+6)); +} + +/* Chapter 8.4 BVC-Reset Procedure */ +static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp, + uint16_t ns_bvci) +{ + struct bssgp_bvc_ctx *bctx; + uint16_t nsei = msgb_nsei(msg); + uint16_t bvci; + int rc; + + bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI)); + DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RESET cause=%s\n", bvci, + bssgp_cause_str(*TLVP_VAL(tp, BSSGP_IE_CAUSE))); + + /* look-up or create the BTS context for this BVC */ + bctx = btsctx_by_bvci_nsei(bvci, nsei); + if (!bctx) + bctx = btsctx_alloc(bvci, nsei); + + /* As opposed to NS-VCs, BVCs are NOT blocked after RESET */ + bctx->state &= ~BVC_S_BLOCKED; + + /* When we receive a BVC-RESET PDU (at least of a PTP BVCI), the BSS + * informs us about its RAC + Cell ID, so we can create a mapping */ + if (bvci != 0 && bvci != 1) { + if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID)) { + LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u RESET " + "missing mandatory IE\n", bvci); + return -EINVAL; + } + /* actually extract RAC / CID */ + bctx->cell_id = bssgp_parse_cell_id(&bctx->ra_id, + TLVP_VAL(tp, BSSGP_IE_CELL_ID)); + LOGP(DBSSGP, LOGL_NOTICE, "Cell %u-%u-%u-%u CI %u on BVCI %u\n", + bctx->ra_id.mcc, bctx->ra_id.mnc, bctx->ra_id.lac, + bctx->ra_id.rac, bctx->cell_id, bvci); + } + + /* Acknowledge the RESET to the BTS */ + rc = bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK, + nsei, bvci, ns_bvci); + return 0; +} + +static int bssgp_rx_bvc_block(struct msgb *msg, struct tlv_parsed *tp) +{ + uint16_t bvci; + struct bssgp_bvc_ctx *ptp_ctx; + + bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI)); + if (bvci == BVCI_SIGNALLING) { + /* 8.3.2: Signalling BVC shall never be blocked */ + LOGP(DBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u " + "received block for signalling BVC!?!\n", + msgb_nsei(msg), msgb_bvci(msg)); + return 0; + } + + LOGP(DBSSGP, LOGL_INFO, "BSSGP BVCI=%u BVC-BLOCK\n", bvci); + + ptp_ctx = btsctx_by_bvci_nsei(bvci, msgb_nsei(msg)); + if (!ptp_ctx) + return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg); + + ptp_ctx->state |= BVC_S_BLOCKED; + rate_ctr_inc(&ptp_ctx->ctrg->ctr[BSSGP_CTR_BLOCKED]); + + /* FIXME: Send NM_BVC_BLOCK.ind to NM */ + + /* We always acknowledge the BLOCKing */ + return bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_BLOCK_ACK, msgb_nsei(msg), + bvci, msgb_bvci(msg)); +}; + +static int bssgp_rx_bvc_unblock(struct msgb *msg, struct tlv_parsed *tp) +{ + uint16_t bvci; + struct bssgp_bvc_ctx *ptp_ctx; + + bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI)); + if (bvci == BVCI_SIGNALLING) { + /* 8.3.2: Signalling BVC shall never be blocked */ + LOGP(DBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u " + "received unblock for signalling BVC!?!\n", + msgb_nsei(msg), msgb_bvci(msg)); + return 0; + } + + DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx BVC-UNBLOCK\n", bvci); + + ptp_ctx = btsctx_by_bvci_nsei(bvci, msgb_nsei(msg)); + if (!ptp_ctx) + return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg); + + ptp_ctx->state &= ~BVC_S_BLOCKED; + + /* FIXME: Send NM_BVC_UNBLOCK.ind to NM */ + + /* We always acknowledge the unBLOCKing */ + return bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_UNBLOCK_ACK, msgb_nsei(msg), + bvci, msgb_bvci(msg)); +}; + +/* Uplink unit-data */ +static int bssgp_rx_ul_ud(struct msgb *msg, struct tlv_parsed *tp, + struct bssgp_bvc_ctx *ctx) +{ + struct bssgp_ud_hdr *budh = (struct bssgp_ud_hdr *) msgb_bssgph(msg); + + /* extract TLLI and parse TLV IEs */ + msgb_tlli(msg) = ntohl(budh->tlli); + + DEBUGP(DBSSGP, "BSSGP TLLI=0x%08x UPLINK-UNITDATA\n", msgb_tlli(msg)); + + /* Cell ID and LLC_PDU are the only mandatory IE */ + if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID) || + !TLVP_PRESENT(tp, BSSGP_IE_LLC_PDU)) { + LOGP(DBSSGP, LOGL_ERROR, "BSSGP TLLI=0x%08x Rx UL-UD " + "missing mandatory IE\n", msgb_tlli(msg)); + return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); + } + + /* store pointer to LLC header and CELL ID in msgb->cb */ + msgb_llch(msg) = (uint8_t *) TLVP_VAL(tp, BSSGP_IE_LLC_PDU); + msgb_bcid(msg) = (uint8_t *) TLVP_VAL(tp, BSSGP_IE_CELL_ID); + + return gprs_llc_rcvmsg(msg, tp); +} + +static int bssgp_rx_suspend(struct msgb *msg, struct tlv_parsed *tp, + struct bssgp_bvc_ctx *ctx) +{ + struct bssgp_normal_hdr *bgph = + (struct bssgp_normal_hdr *) msgb_bssgph(msg); + struct gprs_ra_id raid; + uint32_t tlli; + int rc; + + if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) || + !TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA)) { + LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx SUSPEND " + "missing mandatory IE\n", ctx->bvci); + return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); + } + + tlli = ntohl(*(uint32_t *)TLVP_VAL(tp, BSSGP_IE_TLLI)); + + DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx SUSPEND\n", + ctx->bvci, tlli); + + gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA)); + + /* Inform GMM about the SUSPEND request */ + rc = gprs_gmm_rx_suspend(&raid, tlli); + if (rc < 0) + return bssgp_tx_suspend_nack(msgb_nsei(msg), tlli, &raid, NULL); + + bssgp_tx_suspend_ack(msgb_nsei(msg), tlli, &raid, 0); + + return 0; +} + +static int bssgp_rx_resume(struct msgb *msg, struct tlv_parsed *tp, + struct bssgp_bvc_ctx *ctx) +{ + struct bssgp_normal_hdr *bgph = + (struct bssgp_normal_hdr *) msgb_bssgph(msg); + struct gprs_ra_id raid; + uint32_t tlli; + uint8_t suspend_ref; + int rc; + + if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) || + !TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA) || + !TLVP_PRESENT(tp, BSSGP_IE_SUSPEND_REF_NR)) { + LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESUME " + "missing mandatory IE\n", ctx->bvci); + return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); + } + + tlli = ntohl(*(uint32_t *)TLVP_VAL(tp, BSSGP_IE_TLLI)); + suspend_ref = *TLVP_VAL(tp, BSSGP_IE_SUSPEND_REF_NR); + + DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=0x%08x RESUME\n", ctx->bvci, tlli); + + gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA)); + + /* Inform GMM about the RESUME request */ + rc = gprs_gmm_rx_resume(&raid, tlli, suspend_ref); + if (rc < 0) + return bssgp_tx_resume_nack(msgb_nsei(msg), tlli, &raid, + NULL); + + bssgp_tx_resume_ack(msgb_nsei(msg), tlli, &raid); + return 0; +} + + +static int bssgp_rx_llc_disc(struct msgb *msg, struct tlv_parsed *tp, + struct bssgp_bvc_ctx *ctx) +{ + uint32_t tlli = 0; + + if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) || + !TLVP_PRESENT(tp, BSSGP_IE_LLC_FRAMES_DISCARDED) || + !TLVP_PRESENT(tp, BSSGP_IE_BVCI) || + !TLVP_PRESENT(tp, BSSGP_IE_NUM_OCT_AFF)) { + LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx LLC DISCARDED " + "missing mandatory IE\n", ctx->bvci); + } + + if (TLVP_PRESENT(tp, BSSGP_IE_TLLI)) + tlli = ntohl(*(uint32_t *)TLVP_VAL(tp, BSSGP_IE_TLLI)); + + DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=%08x LLC DISCARDED\n", + ctx->bvci, tlli); + + rate_ctr_inc(&ctx->ctrg->ctr[BSSGP_CTR_DISCARDED]); + + /* FIXME: send NM_LLC_DISCARDED to NM */ + return 0; +} + +static int bssgp_rx_fc_bvc(struct msgb *msg, struct tlv_parsed *tp, + struct bssgp_bvc_ctx *bctx) +{ + + DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx Flow Control BVC\n", + bctx->bvci); + + if (!TLVP_PRESENT(tp, BSSGP_IE_TAG) || + !TLVP_PRESENT(tp, BSSGP_IE_BVC_BUCKET_SIZE) || + !TLVP_PRESENT(tp, BSSGP_IE_BUCKET_LEAK_RATE) || + !TLVP_PRESENT(tp, BSSGP_IE_BMAX_DEFAULT_MS) || + !TLVP_PRESENT(tp, BSSGP_IE_R_DEFAULT_MS)) { + LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx FC BVC " + "missing mandatory IE\n", bctx->bvci); + return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); + } + + /* FIXME: actually implement flow control */ + + /* Send FLOW_CONTROL_BVC_ACK */ + return bssgp_tx_fc_bvc_ack(msgb_nsei(msg), *TLVP_VAL(tp, BSSGP_IE_TAG), + msgb_bvci(msg)); +} + +/* Receive a BSSGP PDU from a BSS on a PTP BVCI */ +static int gprs_bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp, + struct bssgp_bvc_ctx *bctx) +{ + struct bssgp_normal_hdr *bgph = + (struct bssgp_normal_hdr *) msgb_bssgph(msg); + uint8_t pdu_type = bgph->pdu_type; + int rc = 0; + + /* If traffic is received on a BVC that is marked as blocked, the + * received PDU shall not be accepted and a STATUS PDU (Cause value: + * BVC Blocked) shall be sent to the peer entity on the signalling BVC */ + if (bctx->state & BVC_S_BLOCKED && pdu_type != BSSGP_PDUT_STATUS) { + uint16_t bvci = msgb_bvci(msg); + return bssgp_tx_status(BSSGP_CAUSE_BVCI_BLOCKED, &bvci, msg); + } + + switch (pdu_type) { + case BSSGP_PDUT_UL_UNITDATA: + /* some LLC data from the MS */ + rc = bssgp_rx_ul_ud(msg, tp, bctx); + break; + case BSSGP_PDUT_RA_CAPABILITY: + /* BSS requests RA capability or IMSI */ + DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RA CAPABILITY UPDATE\n", + bctx->bvci); + /* FIXME: send GMM_RA_CAPABILITY_UPDATE.ind to GMM */ + /* FIXME: send RA_CAPA_UPDATE_ACK */ + break; + case BSSGP_PDUT_RADIO_STATUS: + DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RADIO STATUS\n", bctx->bvci); + /* BSS informs us of some exception */ + /* FIXME: send GMM_RADIO_STATUS.ind to GMM */ + break; + case BSSGP_PDUT_FLOW_CONTROL_BVC: + /* BSS informs us of available bandwidth in Gb interface */ + rc = bssgp_rx_fc_bvc(msg, tp, bctx); + break; + case BSSGP_PDUT_FLOW_CONTROL_MS: + /* BSS informs us of available bandwidth to one MS */ + DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx Flow Control MS\n", + bctx->bvci); + /* FIXME: actually implement flow control */ + /* FIXME: Send FLOW_CONTROL_MS_ACK */ + break; + case BSSGP_PDUT_STATUS: + /* Some exception has occurred */ + /* FIXME: send NM_STATUS.ind to NM */ + case BSSGP_PDUT_DOWNLOAD_BSS_PFC: + case BSSGP_PDUT_CREATE_BSS_PFC_ACK: + case BSSGP_PDUT_CREATE_BSS_PFC_NACK: + case BSSGP_PDUT_MODIFY_BSS_PFC: + case BSSGP_PDUT_DELETE_BSS_PFC_ACK: + DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type 0x%02x not [yet] " + "implemented\n", bctx->bvci, pdu_type); + rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg); + break; + /* those only exist in the SGSN -> BSS direction */ + case BSSGP_PDUT_DL_UNITDATA: + case BSSGP_PDUT_PAGING_PS: + case BSSGP_PDUT_PAGING_CS: + case BSSGP_PDUT_RA_CAPA_UPDATE_ACK: + case BSSGP_PDUT_FLOW_CONTROL_BVC_ACK: + case BSSGP_PDUT_FLOW_CONTROL_MS_ACK: + DEBUGP(DBSSGP, "BSSGP BVCI=%u PDU type 0x%02x only exists " + "in DL\n", bctx->bvci, pdu_type); + bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); + rc = -EINVAL; + break; + default: + DEBUGP(DBSSGP, "BSSGP BVCI=%u PDU type 0x%02x unknown\n", + bctx->bvci, pdu_type); + rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); + break; + } + + return rc; +} + +/* Receive a BSSGP PDU from a BSS on a SIGNALLING BVCI */ +static int gprs_bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp, + struct bssgp_bvc_ctx *bctx) +{ + struct bssgp_normal_hdr *bgph = + (struct bssgp_normal_hdr *) msgb_bssgph(msg); + uint8_t pdu_type = bgph->pdu_type; + int rc = 0; + uint16_t ns_bvci = msgb_bvci(msg); + uint16_t bvci; + + switch (bgph->pdu_type) { + case BSSGP_PDUT_SUSPEND: + /* MS wants to suspend */ + rc = bssgp_rx_suspend(msg, tp, bctx); + break; + case BSSGP_PDUT_RESUME: + /* MS wants to resume */ + rc = bssgp_rx_resume(msg, tp, bctx); + break; + case BSSGP_PDUT_FLUSH_LL_ACK: + /* BSS informs us it has performed LL FLUSH */ + DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx FLUSH LL ACK\n", bctx->bvci); + /* FIXME: send NM_FLUSH_LL.res to NM */ + break; + case BSSGP_PDUT_LLC_DISCARD: + /* BSS informs that some LLC PDU's have been discarded */ + rc = bssgp_rx_llc_disc(msg, tp, bctx); + break; + case BSSGP_PDUT_BVC_BLOCK: + /* BSS tells us that BVC shall be blocked */ + if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI) || + !TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) { + LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-BLOCK " + "missing mandatory IE\n"); + goto err_mand_ie; + } + rc = bssgp_rx_bvc_block(msg, tp); + break; + case BSSGP_PDUT_BVC_UNBLOCK: + /* BSS tells us that BVC shall be unblocked */ + if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI)) { + LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-UNBLOCK " + "missing mandatory IE\n"); + goto err_mand_ie; + } + rc = bssgp_rx_bvc_unblock(msg, tp); + break; + case BSSGP_PDUT_BVC_RESET: + /* BSS tells us that BVC init is required */ + if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI) || + !TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) { + LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-RESET " + "missing mandatory IE\n"); + goto err_mand_ie; + } + rc = bssgp_rx_bvc_reset(msg, tp, ns_bvci); + break; + case BSSGP_PDUT_STATUS: + /* Some exception has occurred */ + DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx BVC STATUS\n", bctx->bvci); + /* FIXME: send NM_STATUS.ind to NM */ + break; + /* those only exist in the SGSN -> BSS direction */ + case BSSGP_PDUT_PAGING_PS: + case BSSGP_PDUT_PAGING_CS: + case BSSGP_PDUT_SUSPEND_ACK: + case BSSGP_PDUT_SUSPEND_NACK: + case BSSGP_PDUT_RESUME_ACK: + case BSSGP_PDUT_RESUME_NACK: + case BSSGP_PDUT_FLUSH_LL: + case BSSGP_PDUT_BVC_BLOCK_ACK: + case BSSGP_PDUT_BVC_UNBLOCK_ACK: + case BSSGP_PDUT_SGSN_INVOKE_TRACE: + DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type 0x%02x only exists " + "in DL\n", bctx->bvci, pdu_type); + bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); + rc = -EINVAL; + break; + default: + DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type 0x%02x unknown\n", + bctx->bvci, pdu_type); + rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); + break; + } + + return rc; +err_mand_ie: + return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); +} + +/* We expect msgb_bssgph() to point to the BSSGP header */ +int gprs_bssgp_rcvmsg(struct msgb *msg) +{ + struct bssgp_normal_hdr *bgph = + (struct bssgp_normal_hdr *) msgb_bssgph(msg); + struct bssgp_ud_hdr *budh = (struct bssgp_ud_hdr *) msgb_bssgph(msg); + struct tlv_parsed tp; + struct bssgp_bvc_ctx *bctx; + uint8_t pdu_type = bgph->pdu_type; + uint16_t ns_bvci = msgb_bvci(msg); + int data_len; + int rc = 0; + + /* Identifiers from DOWN: NSEI, BVCI (both in msg->cb) */ + + /* UNITDATA BSSGP headers have TLLI in front */ + if (pdu_type != BSSGP_PDUT_UL_UNITDATA && + pdu_type != BSSGP_PDUT_DL_UNITDATA) { + data_len = msgb_bssgp_len(msg) - sizeof(*bgph); + rc = bssgp_tlv_parse(&tp, bgph->data, data_len); + } else { + data_len = msgb_bssgp_len(msg) - sizeof(*budh); + rc = bssgp_tlv_parse(&tp, budh->data, data_len); + } + + /* look-up or create the BTS context for this BVC */ + bctx = btsctx_by_bvci_nsei(ns_bvci, msgb_nsei(msg)); + /* Only a RESET PDU can create a new BVC context */ + if (!bctx && pdu_type != BSSGP_PDUT_BVC_RESET) { + LOGP(DBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Rejecting PDU " + "type %u for unknown BVCI\n", msgb_nsei(msg), ns_bvci, + pdu_type); + return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, NULL, msg); + } + + if (bctx) { + log_set_context(BSC_CTX_BVC, bctx); + rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_IN]); + rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_IN], + msgb_bssgp_len(msg)); + } + + if (ns_bvci == BVCI_SIGNALLING) + rc = gprs_bssgp_rx_sign(msg, &tp, bctx); + else if (ns_bvci == BVCI_PTM) + rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg); + else + rc = gprs_bssgp_rx_ptp(msg, &tp, bctx); + + return rc; +} + +/* Entry function from upper level (LLC), asking us to transmit a BSSGP PDU + * to a remote MS (identified by TLLI) at a BTS identified by its BVCI and NSEI */ +int gprs_bssgp_tx_dl_ud(struct msgb *msg, struct sgsn_mm_ctx *mmctx) +{ + struct bssgp_bvc_ctx *bctx; + struct bssgp_ud_hdr *budh; + uint8_t llc_pdu_tlv_hdr_len = 2; + uint8_t *llc_pdu_tlv, *qos_profile; + uint16_t pdu_lifetime = 1000; /* centi-seconds */ + uint8_t qos_profile_default[3] = { 0x00, 0x00, 0x20 }; + uint16_t msg_len = msg->len; + uint16_t bvci = msgb_bvci(msg); + uint16_t nsei = msgb_nsei(msg); + uint16_t drx_params; + + /* Identifiers from UP: TLLI, BVCI, NSEI (all in msgb->cb) */ + if (bvci <= BVCI_PTM ) { + LOGP(DBSSGP, LOGL_ERROR, "Cannot send DL-UD to BVCI %u\n", + bvci); + return -EINVAL; + } + + bctx = btsctx_by_bvci_nsei(bvci, nsei); + if (!bctx) { + /* FIXME: don't simply create missing context, but reject message */ + bctx = btsctx_alloc(bvci, nsei); + } + + if (msg->len > TVLV_MAX_ONEBYTE) + llc_pdu_tlv_hdr_len += 1; + + /* prepend the tag and length of the LLC-PDU TLV */ + llc_pdu_tlv = msgb_push(msg, llc_pdu_tlv_hdr_len); + llc_pdu_tlv[0] = BSSGP_IE_LLC_PDU; + if (llc_pdu_tlv_hdr_len > 2) { + llc_pdu_tlv[1] = msg_len >> 8; + llc_pdu_tlv[2] = msg_len & 0xff; + } else { + llc_pdu_tlv[1] = msg_len & 0x7f; + llc_pdu_tlv[1] |= 0x80; + } + + /* FIXME: optional elements: Alignment, UTRAN CCO, LSA, PFI */ + + if (mmctx) { + /* Old TLLI to help BSS map from old->new */ +#if 0 + if (mmctx->tlli_old) + msgb_tvlv_push(msg, BSSGP_IE_TLLI, 4, htonl(*tlli_old)); +#endif + + /* IMSI */ + if (strlen(mmctx->imsi)) { + uint8_t mi[10]; + int imsi_len = gsm48_generate_mid_from_imsi(mi, mmctx->imsi); + if (imsi_len > 2) + msgb_tvlv_push(msg, BSSGP_IE_IMSI, + imsi_len-2, mi+2); + } + + /* DRX parameters */ + drx_params = htons(mmctx->drx_parms); + msgb_tvlv_push(msg, BSSGP_IE_DRX_PARAMS, 2, + (uint8_t *) &drx_params); + + /* FIXME: Priority */ + + /* MS Radio Access Capability */ + if (mmctx->ms_radio_access_capa.len) + msgb_tvlv_push(msg, BSSGP_IE_MS_RADIO_ACCESS_CAP, + mmctx->ms_radio_access_capa.len, + mmctx->ms_radio_access_capa.buf); + } + + /* prepend the pdu lifetime */ + pdu_lifetime = htons(pdu_lifetime); + msgb_tvlv_push(msg, BSSGP_IE_PDU_LIFETIME, 2, (uint8_t *)&pdu_lifetime); + + /* prepend the QoS profile, TLLI and pdu type */ + budh = (struct bssgp_ud_hdr *) msgb_push(msg, sizeof(*budh)); + memcpy(budh->qos_profile, qos_profile_default, sizeof(qos_profile_default)); + budh->tlli = htonl(msgb_tlli(msg)); + budh->pdu_type = BSSGP_PDUT_DL_UNITDATA; + + rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_OUT]); + rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_OUT], msg->len); + + /* Identifiers down: BVCI, NSEI (in msgb->cb) */ + + return gprs_ns_sendmsg(bssgp_nsi, msg); +} + +/* Send a single GMM-PAGING.req to a given NSEI/NS-BVCI */ +int gprs_bssgp_tx_paging(uint16_t nsei, uint16_t ns_bvci, + struct bssgp_paging_info *pinfo) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph = + (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + uint16_t drx_params = htons(pinfo->drx_params); + uint8_t mi[10]; + int imsi_len = gsm48_generate_mid_from_imsi(mi, pinfo->imsi); + uint8_t ra[6]; + + if (imsi_len < 2) + return -EINVAL; + + msgb_nsei(msg) = nsei; + msgb_bvci(msg) = ns_bvci; + + if (pinfo->mode == BSSGP_PAGING_PS) + bgph->pdu_type = BSSGP_PDUT_PAGING_PS; + else + bgph->pdu_type = BSSGP_PDUT_PAGING_CS; + /* IMSI */ + msgb_tvlv_put(msg, BSSGP_IE_IMSI, imsi_len-2, mi+2); + /* DRX Parameters */ + msgb_tvlv_put(msg, BSSGP_IE_DRX_PARAMS, 2, + (uint8_t *) &drx_params); + /* Scope */ + switch (pinfo->scope) { + case BSSGP_PAGING_BSS_AREA: + { + uint8_t null = 0; + msgb_tvlv_put(msg, BSSGP_IE_BSS_AREA_ID, 1, &null); + } + break; + case BSSGP_PAGING_LOCATION_AREA: + gsm48_construct_ra(ra, &pinfo->raid); + msgb_tvlv_put(msg, BSSGP_IE_LOCATION_AREA, 4, ra); + break; + case BSSGP_PAGING_ROUTEING_AREA: + gsm48_construct_ra(ra, &pinfo->raid); + msgb_tvlv_put(msg, BSSGP_IE_ROUTEING_AREA, 6, ra); + break; + case BSSGP_PAGING_BVCI: + { + uint16_t bvci = htons(pinfo->bvci); + msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *)&bvci); + } + break; + } + /* QoS profile mandatory for PS */ + if (pinfo->mode == BSSGP_PAGING_PS) + msgb_tvlv_put(msg, BSSGP_IE_QOS_PROFILE, 3, pinfo->qos); + + /* Optional (P-)TMSI */ + if (pinfo->ptmsi) { + uint32_t ptmsi = htonl(*pinfo->ptmsi); + msgb_tvlv_put(msg, BSSGP_IE_TMSI, 4, (uint8_t *) &ptmsi); + } + + return gprs_ns_sendmsg(bssgp_nsi, msg); +} diff --git a/src/libgb/gprs_bssgp_util.c b/src/libgb/gprs_bssgp_util.c new file mode 100644 index 000000000..f8e3b5699 --- /dev/null +++ b/src/libgb/gprs_bssgp_util.c @@ -0,0 +1,119 @@ +/* GPRS BSSGP protocol implementation as per 3GPP TS 08.18 */ + +/* (C) 2009-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +struct gprs_ns_inst *bssgp_nsi; + +/* BSSGP Protocol specific, not implementation specific */ +/* FIXME: This needs to go into libosmocore after finished */ + +/* Chapter 11.3.9 / Table 11.10: Cause coding */ +static const struct value_string bssgp_cause_strings[] = { + { BSSGP_CAUSE_PROC_OVERLOAD, "Processor overload" }, + { BSSGP_CAUSE_EQUIP_FAIL, "Equipment Failure" }, + { BSSGP_CAUSE_TRASIT_NET_FAIL, "Transit netowkr service failure" }, + { BSSGP_CAUSE_CAPA_GREATER_0KPBS,"Transmission capacity modified" }, + { BSSGP_CAUSE_UNKNOWN_MS, "Unknown MS" }, + { BSSGP_CAUSE_UNKNOWN_BVCI, "Unknown BVCI" }, + { BSSGP_CAUSE_CELL_TRAF_CONG, "Cell traffic congestion" }, + { BSSGP_CAUSE_SGSN_CONG, "SGSN congestion" }, + { BSSGP_CAUSE_OML_INTERV, "O&M intervention" }, + { BSSGP_CAUSE_BVCI_BLOCKED, "BVCI blocked" }, + { BSSGP_CAUSE_PFC_CREATE_FAIL, "PFC create failure" }, + { BSSGP_CAUSE_SEM_INCORR_PDU, "Semantically incorrect PDU" }, + { BSSGP_CAUSE_INV_MAND_INF, "Invalid mandatory information" }, + { BSSGP_CAUSE_MISSING_MAND_IE, "Missing mandatory IE" }, + { BSSGP_CAUSE_MISSING_COND_IE, "Missing conditional IE" }, + { BSSGP_CAUSE_UNEXP_COND_IE, "Unexpected conditional IE" }, + { BSSGP_CAUSE_COND_IE_ERR, "Conditional IE error" }, + { BSSGP_CAUSE_PDU_INCOMP_STATE, "PDU incompatible with protocol state" }, + { BSSGP_CAUSE_PROTO_ERR_UNSPEC, "Protocol error - unspecified" }, + { BSSGP_CAUSE_PDU_INCOMP_FEAT, "PDU not compatible with feature set" }, + { 0, NULL }, +}; + +const char *bssgp_cause_str(enum gprs_bssgp_cause cause) +{ + return get_value_string(bssgp_cause_strings, cause); +} + + +struct msgb *bssgp_msgb_alloc(void) +{ + return msgb_alloc_headroom(4096, 128, "BSSGP"); +} + +/* Transmit a simple response such as BLOCK/UNBLOCK/RESET ACK/NACK */ +int bssgp_tx_simple_bvci(uint8_t pdu_type, uint16_t nsei, + uint16_t bvci, uint16_t ns_bvci) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph = + (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + uint16_t _bvci; + + msgb_nsei(msg) = nsei; + msgb_bvci(msg) = ns_bvci; + + bgph->pdu_type = pdu_type; + _bvci = htons(bvci); + msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci); + + return gprs_ns_sendmsg(bssgp_nsi, msg); +} + +/* Chapter 10.4.14: Status */ +int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph = + (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + + LOGP(DBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Tx STATUS, cause=%s\n", + bvci ? *bvci : 0, bssgp_cause_str(cause)); + msgb_nsei(msg) = msgb_nsei(orig_msg); + msgb_bvci(msg) = 0; + + bgph->pdu_type = BSSGP_PDUT_STATUS; + msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause); + if (bvci) { + uint16_t _bvci = htons(*bvci); + msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci); + } + if (orig_msg) + msgb_tvlv_put(msg, BSSGP_IE_PDU_IN_ERROR, + msgb_bssgp_len(orig_msg), msgb_bssgph(orig_msg)); + + return gprs_ns_sendmsg(bssgp_nsi, msg); +} diff --git a/src/libgb/gprs_bssgp_vty.c b/src/libgb/gprs_bssgp_vty.c new file mode 100644 index 000000000..9ebd09004 --- /dev/null +++ b/src/libgb/gprs_bssgp_vty.c @@ -0,0 +1,176 @@ +/* VTY interface for our GPRS BSS Gateway Protocol (BSSGP) implementation */ + +/* (C) 2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* FIXME: this should go to some common file as it is copied + * in vty_interface.c of the BSC */ +static const struct value_string gprs_bssgp_timer_strs[] = { + { 0, NULL } +}; + +static struct cmd_node bssgp_node = { + BSSGP_NODE, + "%s(bssgp)#", + 1, +}; + +static int config_write_bssgp(struct vty *vty) +{ + vty_out(vty, "bssgp%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bssgp, cfg_bssgp_cmd, + "bssgp", + "Configure the GPRS BSS Gateway Protocol") +{ + vty->node = BSSGP_NODE; + return CMD_SUCCESS; +} + +static void dump_bvc(struct vty *vty, struct bssgp_bvc_ctx *bvc, int stats) +{ + vty_out(vty, "NSEI %5u, BVCI %5u, RA-ID: %u-%u-%u-%u, CID: %u, " + "STATE: %s%s", bvc->nsei, bvc->bvci, bvc->ra_id.mcc, + bvc->ra_id.mnc, bvc->ra_id.lac, bvc->ra_id.rac, bvc->cell_id, + bvc->state & BVC_S_BLOCKED ? "BLOCKED" : "UNBLOCKED", + VTY_NEWLINE); + if (stats) + vty_out_rate_ctr_group(vty, " ", bvc->ctrg); +} + +static void dump_bssgp(struct vty *vty, int stats) +{ + struct bssgp_bvc_ctx *bvc; + + llist_for_each_entry(bvc, &bssgp_bvc_ctxts, list) { + dump_bvc(vty, bvc, stats); + } +} + +#define BSSGP_STR "Show information about the BSSGP protocol\n" + +DEFUN(show_bssgp, show_bssgp_cmd, "show bssgp", + SHOW_STR BSSGP_STR) +{ + dump_bssgp(vty, 0); + return CMD_SUCCESS; +} + +DEFUN(show_bssgp_stats, show_bssgp_stats_cmd, "show bssgp stats", + SHOW_STR BSSGP_STR + "Include statistics\n") +{ + dump_bssgp(vty, 1); + return CMD_SUCCESS; +} + +DEFUN(show_bvc, show_bvc_cmd, "show bssgp nsei <0-65535> [stats]", + SHOW_STR BSSGP_STR + "Show all BVCs on one NSE\n" + "The NSEI\n" "Include Statistics\n") +{ + struct bssgp_bvc_ctx *bvc; + uint16_t nsei = atoi(argv[1]); + int show_stats = 0; + + if (argc >= 2) + show_stats = 1; + + llist_for_each_entry(bvc, &bssgp_bvc_ctxts, list) { + if (bvc->nsei != nsei) + continue; + dump_bvc(vty, bvc, show_stats); + } + + return CMD_SUCCESS; +} + +DEFUN(logging_fltr_bvc, + logging_fltr_bvc_cmd, + "logging filter bvc nsei <0-65535> bvci <0-65535>", + LOGGING_STR FILTER_STR + "Filter based on BSSGP Virtual Connection\n" + "NSEI of the BVC to be filtered\n" + "Network Service Entity Identifier (NSEI)\n" + "BVCI of the BVC to be filtered\n" + "BSSGP Virtual Connection Identifier (BVCI)\n") +{ + struct log_target *tgt = osmo_log_vty2tgt(vty); + struct bssgp_bvc_ctx *bvc; + uint16_t nsei = atoi(argv[0]); + uint16_t bvci = atoi(argv[1]); + + if (!tgt) + return CMD_WARNING; + + bvc = btsctx_by_bvci_nsei(bvci, nsei); + if (!bvc) { + vty_out(vty, "No BVC by that identifier%s", VTY_NEWLINE); + return CMD_WARNING; + } + + log_set_bvc_filter(tgt, bvc); + return CMD_SUCCESS; +} + +int gprs_bssgp_vty_init(void) +{ + install_element_ve(&show_bssgp_cmd); + install_element_ve(&show_bssgp_stats_cmd); + install_element_ve(&show_bvc_cmd); + install_element_ve(&logging_fltr_bvc_cmd); + + install_element(CFG_LOG_NODE, &logging_fltr_bvc_cmd); + + install_element(CONFIG_NODE, &cfg_bssgp_cmd); + install_node(&bssgp_node, config_write_bssgp); + install_default(BSSGP_NODE); + install_element(BSSGP_NODE, &ournode_exit_cmd); + install_element(BSSGP_NODE, &ournode_end_cmd); + //install_element(BSSGP_NODE, &cfg_bssgp_timer_cmd); + + return 0; +} diff --git a/src/libgb/gprs_ns.c b/src/libgb/gprs_ns.c new file mode 100644 index 000000000..5a8e35860 --- /dev/null +++ b/src/libgb/gprs_ns.c @@ -0,0 +1,992 @@ +/* GPRS Networks Service (NS) messages on the Gb interfacebvci = msgb_bvci(msg); + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) */ + +/* (C) 2009-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +/* Some introduction into NS: NS is used typically on top of frame relay, + * but in the ip.access world it is encapsulated in UDP packets. It serves + * as an intermediate shim betwen BSSGP and the underlying medium. It doesn't + * do much, apart from providing congestion notification and status indication. + * + * Terms: + * NS Network Service + * NSVC NS Virtual Connection + * NSEI NS Entity Identifier + * NSVL NS Virtual Link + * NSVLI NS Virtual Link Identifier + * BVC BSSGP Virtual Connection + * BVCI BSSGP Virtual Connection Identifier + * NSVCG NS Virtual Connection Goup + * Blocked NS-VC cannot be used for user traffic + * Alive Ability of a NS-VC to provide communication + * + * There can be multiple BSSGP virtual connections over one (group of) NSVC's. BSSGP will + * therefore identify the BSSGP virtual connection by a BVCI passed down to NS. + * NS then has to firgure out which NSVC's are responsible for this BVCI. + * Those mappings are administratively configured. + */ + +/* This implementation has the following limitations: + * o Only one NS-VC for each NSE: No load-sharing function + * o NSVCI 65535 and 65534 are reserved for internal use + * o Only UDP is supported as of now, no frame relay support + * o The IP Sub-Network-Service (SNS) as specified in 48.016 is not implemented + * o There are no BLOCK and UNBLOCK timers (yet?) + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct tlv_definition ns_att_tlvdef = { + .def = { + [NS_IE_CAUSE] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_VCI] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_PDU] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_BVCI] = { TLV_TYPE_TvLV, 0 }, + [NS_IE_NSEI] = { TLV_TYPE_TvLV, 0 }, + }, +}; + +enum ns_ctr { + NS_CTR_PKTS_IN, + NS_CTR_PKTS_OUT, + NS_CTR_BYTES_IN, + NS_CTR_BYTES_OUT, + NS_CTR_BLOCKED, + NS_CTR_DEAD, +}; + +static const struct rate_ctr_desc nsvc_ctr_description[] = { + { "packets.in", "Packets at NS Level ( In)" }, + { "packets.out","Packets at NS Level (Out)" }, + { "bytes.in", "Bytes at NS Level ( In)" }, + { "bytes.out", "Bytes at NS Level (Out)" }, + { "blocked", "NS-VC Block count " }, + { "dead", "NS-VC gone dead count " }, +}; + +static const struct rate_ctr_group_desc nsvc_ctrg_desc = { + .group_name_prefix = "ns.nsvc", + .group_description = "NSVC Peer Statistics", + .num_ctr = ARRAY_SIZE(nsvc_ctr_description), + .ctr_desc = nsvc_ctr_description, +}; + +/* Lookup struct gprs_nsvc based on NSVCI */ +struct gprs_nsvc *nsvc_by_nsvci(struct gprs_ns_inst *nsi, uint16_t nsvci) +{ + struct gprs_nsvc *nsvc; + llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) { + if (nsvc->nsvci == nsvci) + return nsvc; + } + return NULL; +} + +/* Lookup struct gprs_nsvc based on NSVCI */ +struct gprs_nsvc *nsvc_by_nsei(struct gprs_ns_inst *nsi, uint16_t nsei) +{ + struct gprs_nsvc *nsvc; + llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) { + if (nsvc->nsei == nsei) + return nsvc; + } + return NULL; +} + +/* Lookup struct gprs_nsvc based on remote peer socket addr */ +static struct gprs_nsvc *nsvc_by_rem_addr(struct gprs_ns_inst *nsi, + struct sockaddr_in *sin) +{ + struct gprs_nsvc *nsvc; + llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) { + if (nsvc->ip.bts_addr.sin_addr.s_addr == + sin->sin_addr.s_addr && + nsvc->ip.bts_addr.sin_port == sin->sin_port) + return nsvc; + } + return NULL; +} + +static void gprs_ns_timer_cb(void *data); + +struct gprs_nsvc *nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci) +{ + struct gprs_nsvc *nsvc; + + LOGP(DNS, LOGL_INFO, "NSVCI=%u Creating NS-VC\n", nsvci); + + nsvc = talloc_zero(nsi, struct gprs_nsvc); + nsvc->nsvci = nsvci; + /* before RESET procedure: BLOCKED and DEAD */ + nsvc->state = NSE_S_BLOCKED; + nsvc->nsi = nsi; + nsvc->timer.cb = gprs_ns_timer_cb; + nsvc->timer.data = nsvc; + nsvc->ctrg = rate_ctr_group_alloc(nsvc, &nsvc_ctrg_desc, nsvci); + + llist_add(&nsvc->list, &nsi->gprs_nsvcs); + + return nsvc; +} + +void nsvc_delete(struct gprs_nsvc *nsvc) +{ + if (bsc_timer_pending(&nsvc->timer)) + bsc_del_timer(&nsvc->timer); + llist_del(&nsvc->list); + talloc_free(nsvc); +} + +static void ns_dispatch_signal(struct gprs_nsvc *nsvc, unsigned int signal, + uint8_t cause) +{ + struct ns_signal_data nssd; + + nssd.nsvc = nsvc; + nssd.cause = cause; + + dispatch_signal(SS_NS, signal, &nssd); +} + +/* Section 10.3.2, Table 13 */ +static const struct value_string ns_cause_str[] = { + { NS_CAUSE_TRANSIT_FAIL, "Transit network failure" }, + { NS_CAUSE_OM_INTERVENTION, "O&M intervention" }, + { NS_CAUSE_EQUIP_FAIL, "Equipment failure" }, + { NS_CAUSE_NSVC_BLOCKED, "NS-VC blocked" }, + { NS_CAUSE_NSVC_UNKNOWN, "NS-VC unknown" }, + { NS_CAUSE_BVCI_UNKNOWN, "BVCI unknown" }, + { NS_CAUSE_SEM_INCORR_PDU, "Semantically incorrect PDU" }, + { NS_CAUSE_PDU_INCOMP_PSTATE, "PDU not compatible with protocol state" }, + { NS_CAUSE_PROTO_ERR_UNSPEC, "Protocol error, unspecified" }, + { NS_CAUSE_INVAL_ESSENT_IE, "Invalid essential IE" }, + { NS_CAUSE_MISSING_ESSENT_IE, "Missing essential IE" }, + { 0, NULL } +}; + +const char *gprs_ns_cause_str(enum ns_cause cause) +{ + return get_value_string(ns_cause_str, cause); +} + +static int nsip_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg); +extern int grps_ns_frgre_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg); + +static int gprs_ns_tx(struct gprs_nsvc *nsvc, struct msgb *msg) +{ + int ret; + + log_set_context(BSC_CTX_NSVC, nsvc); + + /* Increment number of Uplink bytes */ + rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_PKTS_OUT]); + rate_ctr_add(&nsvc->ctrg->ctr[NS_CTR_BYTES_OUT], msgb_l2len(msg)); + + switch (nsvc->ll) { + case GPRS_NS_LL_UDP: + ret = nsip_sendmsg(nsvc, msg); + break; + case GPRS_NS_LL_FR_GRE: + ret = gprs_ns_frgre_sendmsg(nsvc, msg); + break; + default: + LOGP(DNS, LOGL_ERROR, "unsupported NS linklayer %u\n", nsvc->ll); + msgb_free(msg); + ret = -EIO; + break; + } + return ret; +} + +static int gprs_ns_tx_simple(struct gprs_nsvc *nsvc, uint8_t pdu_type) +{ + struct msgb *msg = gprs_ns_msgb_alloc(); + struct gprs_ns_hdr *nsh; + + log_set_context(BSC_CTX_NSVC, nsvc); + + if (!msg) + return -ENOMEM; + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + + nsh->pdu_type = pdu_type; + + return gprs_ns_tx(nsvc, msg); +} + +int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause) +{ + struct msgb *msg = gprs_ns_msgb_alloc(); + struct gprs_ns_hdr *nsh; + uint16_t nsvci = htons(nsvc->nsvci); + uint16_t nsei = htons(nsvc->nsei); + + log_set_context(BSC_CTX_NSVC, nsvc); + + if (!msg) + return -ENOMEM; + + LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS RESET (NSVCI=%u, cause=%s)\n", + nsvc->nsei, nsvc->nsvci, gprs_ns_cause_str(cause)); + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + nsh->pdu_type = NS_PDUT_RESET; + + msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause); + msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci); + msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *) &nsei); + + return gprs_ns_tx(nsvc, msg); + +} + +int gprs_ns_tx_status(struct gprs_nsvc *nsvc, uint8_t cause, + uint16_t bvci, struct msgb *orig_msg) +{ + struct msgb *msg = gprs_ns_msgb_alloc(); + struct gprs_ns_hdr *nsh; + uint16_t nsvci = htons(nsvc->nsvci); + + log_set_context(BSC_CTX_NSVC, nsvc); + + bvci = htons(bvci); + + if (!msg) + return -ENOMEM; + + LOGP(DNS, LOGL_NOTICE, "NSEI=%u Tx NS STATUS (NSVCI=%u, cause=%s)\n", + nsvc->nsei, nsvc->nsvci, gprs_ns_cause_str(cause)); + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + nsh->pdu_type = NS_PDUT_STATUS; + + msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause); + + /* Section 9.2.7.1: Static conditions for NS-VCI */ + if (cause == NS_CAUSE_NSVC_BLOCKED || + cause == NS_CAUSE_NSVC_UNKNOWN) + msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci); + + /* Section 9.2.7.2: Static conditions for NS PDU */ + switch (cause) { + case NS_CAUSE_SEM_INCORR_PDU: + case NS_CAUSE_PDU_INCOMP_PSTATE: + case NS_CAUSE_PROTO_ERR_UNSPEC: + case NS_CAUSE_INVAL_ESSENT_IE: + case NS_CAUSE_MISSING_ESSENT_IE: + msgb_tvlv_put(msg, NS_IE_PDU, msgb_l2len(orig_msg), + orig_msg->l2h); + break; + default: + break; + } + + /* Section 9.2.7.3: Static conditions for BVCI */ + if (cause == NS_CAUSE_BVCI_UNKNOWN) + msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&bvci); + + return gprs_ns_tx(nsvc, msg); +} + +int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause) +{ + struct msgb *msg = gprs_ns_msgb_alloc(); + struct gprs_ns_hdr *nsh; + uint16_t nsvci = htons(nsvc->nsvci); + + log_set_context(BSC_CTX_NSVC, nsvc); + + if (!msg) + return -ENOMEM; + + LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS BLOCK (NSVCI=%u, cause=%s)\n", + nsvc->nsei, nsvc->nsvci, gprs_ns_cause_str(cause)); + + /* be conservative and mark it as blocked even now! */ + nsvc->state |= NSE_S_BLOCKED; + rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]); + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + nsh->pdu_type = NS_PDUT_BLOCK; + + msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause); + msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci); + + return gprs_ns_tx(nsvc, msg); +} + +int gprs_ns_tx_unblock(struct gprs_nsvc *nsvc) +{ + log_set_context(BSC_CTX_NSVC, nsvc); + LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS UNBLOCK (NSVCI=%u)\n", + nsvc->nsei, nsvc->nsvci); + + return gprs_ns_tx_simple(nsvc, NS_PDUT_UNBLOCK); +} + +int gprs_ns_tx_alive(struct gprs_nsvc *nsvc) +{ + log_set_context(BSC_CTX_NSVC, nsvc); + LOGP(DNS, LOGL_DEBUG, "NSEI=%u Tx NS ALIVE (NSVCI=%u)\n", + nsvc->nsei, nsvc->nsvci); + + return gprs_ns_tx_simple(nsvc, NS_PDUT_ALIVE); +} + +int gprs_ns_tx_alive_ack(struct gprs_nsvc *nsvc) +{ + log_set_context(BSC_CTX_NSVC, nsvc); + LOGP(DNS, LOGL_DEBUG, "NSEI=%u Tx NS ALIVE_ACK (NSVCI=%u)\n", + nsvc->nsei, nsvc->nsvci); + + return gprs_ns_tx_simple(nsvc, NS_PDUT_ALIVE_ACK); +} + +static const enum ns_timeout timer_mode_tout[_NSVC_TIMER_NR] = { + [NSVC_TIMER_TNS_RESET] = NS_TOUT_TNS_RESET, + [NSVC_TIMER_TNS_ALIVE] = NS_TOUT_TNS_ALIVE, + [NSVC_TIMER_TNS_TEST] = NS_TOUT_TNS_TEST, +}; + +static const struct value_string timer_mode_strs[] = { + { NSVC_TIMER_TNS_RESET, "tns-reset" }, + { NSVC_TIMER_TNS_ALIVE, "tns-alive" }, + { NSVC_TIMER_TNS_TEST, "tns-test" }, + { 0, NULL } +}; + +static void nsvc_start_timer(struct gprs_nsvc *nsvc, enum nsvc_timer_mode mode) +{ + enum ns_timeout tout = timer_mode_tout[mode]; + unsigned int seconds = nsvc->nsi->timeout[tout]; + + log_set_context(BSC_CTX_NSVC, nsvc); + DEBUGP(DNS, "NSEI=%u Starting timer in mode %s (%u seconds)\n", + nsvc->nsei, get_value_string(timer_mode_strs, mode), + seconds); + + if (bsc_timer_pending(&nsvc->timer)) + bsc_del_timer(&nsvc->timer); + + nsvc->timer_mode = mode; + bsc_schedule_timer(&nsvc->timer, seconds, 0); +} + +static void gprs_ns_timer_cb(void *data) +{ + struct gprs_nsvc *nsvc = data; + enum ns_timeout tout = timer_mode_tout[nsvc->timer_mode]; + unsigned int seconds = nsvc->nsi->timeout[tout]; + + log_set_context(BSC_CTX_NSVC, nsvc); + DEBUGP(DNS, "NSEI=%u Timer expired in mode %s (%u seconds)\n", + nsvc->nsei, get_value_string(timer_mode_strs, nsvc->timer_mode), + seconds); + + switch (nsvc->timer_mode) { + case NSVC_TIMER_TNS_ALIVE: + /* Tns-alive case: we expired without response ! */ + nsvc->alive_retries++; + if (nsvc->alive_retries > + nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) { + /* mark as dead and blocked */ + nsvc->state = NSE_S_BLOCKED; + rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]); + rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_DEAD]); + LOGP(DNS, LOGL_NOTICE, + "NSEI=%u Tns-alive expired more then " + "%u times, blocking NS-VC\n", nsvc->nsei, + nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]); + ns_dispatch_signal(nsvc, S_NS_ALIVE_EXP, 0); + ns_dispatch_signal(nsvc, S_NS_BLOCK, NS_CAUSE_NSVC_BLOCKED); + return; + } + /* Tns-test case: send NS-ALIVE PDU */ + gprs_ns_tx_alive(nsvc); + /* start Tns-alive timer */ + nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE); + break; + case NSVC_TIMER_TNS_TEST: + /* Tns-test case: send NS-ALIVE PDU */ + gprs_ns_tx_alive(nsvc); + /* start Tns-alive timer (transition into faster + * alive retransmissions) */ + nsvc->alive_retries = 0; + nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE); + break; + case NSVC_TIMER_TNS_RESET: + /* Chapter 7.3: Re-send the RESET */ + gprs_ns_tx_reset(nsvc, NS_CAUSE_OM_INTERVENTION); + /* Re-start Tns-reset timer */ + nsvc_start_timer(nsvc, NSVC_TIMER_TNS_RESET); + break; + case _NSVC_TIMER_NR: + break; + } +} + +/* Section 9.2.6 */ +static int gprs_ns_tx_reset_ack(struct gprs_nsvc *nsvc) +{ + struct msgb *msg = gprs_ns_msgb_alloc(); + struct gprs_ns_hdr *nsh; + uint16_t nsvci, nsei; + + log_set_context(BSC_CTX_NSVC, nsvc); + if (!msg) + return -ENOMEM; + + nsvci = htons(nsvc->nsvci); + nsei = htons(nsvc->nsei); + + msg->l2h = msgb_put(msg, sizeof(*nsh)); + nsh = (struct gprs_ns_hdr *) msg->l2h; + + nsh->pdu_type = NS_PDUT_RESET_ACK; + + LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS RESET ACK (NSVCI=%u)\n", + nsvc->nsei, nsvc->nsvci); + + msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci); + msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei); + + return gprs_ns_tx(nsvc, msg); +} + +/* Section 9.2.10: transmit side / NS-UNITDATA-REQUEST primitive */ +int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg) +{ + struct gprs_nsvc *nsvc; + struct gprs_ns_hdr *nsh; + uint16_t bvci = msgb_bvci(msg); + + nsvc = nsvc_by_nsei(nsi, msgb_nsei(msg)); + if (!nsvc) { + LOGP(DNS, LOGL_ERROR, "Unable to resolve NSEI %u " + "to NS-VC!\n", msgb_nsei(msg)); + msgb_free(msg); + return -EINVAL; + } + log_set_context(BSC_CTX_NSVC, nsvc); + + if (!(nsvc->state & NSE_S_ALIVE)) { + LOGP(DNS, LOGL_ERROR, "NSEI=%u is not alive, cannot send\n", + nsvc->nsei); + msgb_free(msg); + return -EBUSY; + } + if (nsvc->state & NSE_S_BLOCKED) { + LOGP(DNS, LOGL_ERROR, "NSEI=%u is blocked, cannot send\n", + nsvc->nsei); + msgb_free(msg); + return -EBUSY; + } + + msg->l2h = msgb_push(msg, sizeof(*nsh) + 3); + nsh = (struct gprs_ns_hdr *) msg->l2h; + if (!nsh) { + LOGP(DNS, LOGL_ERROR, "Not enough headroom for NS header\n"); + msgb_free(msg); + return -EIO; + } + + nsh->pdu_type = NS_PDUT_UNITDATA; + /* spare octet in data[0] */ + nsh->data[1] = bvci >> 8; + nsh->data[2] = bvci & 0xff; + + return gprs_ns_tx(nsvc, msg); +} + +/* Section 9.2.10: receive side */ +static int gprs_ns_rx_unitdata(struct gprs_nsvc *nsvc, struct msgb *msg) +{ + struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *)msg->l2h; + uint16_t bvci; + + if (nsvc->state & NSE_S_BLOCKED) + return gprs_ns_tx_status(nsvc, NS_CAUSE_NSVC_BLOCKED, + 0, msg); + + /* spare octet in data[0] */ + bvci = nsh->data[1] << 8 | nsh->data[2]; + msgb_bssgph(msg) = &nsh->data[3]; + msgb_bvci(msg) = bvci; + + /* call upper layer (BSSGP) */ + return nsvc->nsi->cb(GPRS_NS_EVT_UNIT_DATA, nsvc, msg, bvci); +} + +/* Section 9.2.7 */ +static int gprs_ns_rx_status(struct gprs_nsvc *nsvc, struct msgb *msg) +{ + struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; + struct tlv_parsed tp; + uint8_t cause; + int rc; + + LOGP(DNS, LOGL_NOTICE, "NSEI=%u Rx NS STATUS ", nsvc->nsei); + + rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data, + msgb_l2len(msg) - sizeof(*nsh), 0, 0); + if (rc < 0) { + LOGPC(DNS, LOGL_NOTICE, "Error during TLV Parse\n"); + LOGP(DNS, LOGL_ERROR, "NSEI=%u Rx NS STATUS: " + "Error during TLV Parse\n", nsvc->nsei); + return rc; + } + + if (!TLVP_PRESENT(&tp, NS_IE_CAUSE)) { + LOGPC(DNS, LOGL_INFO, "missing cause IE\n"); + return -EINVAL; + } + + cause = *TLVP_VAL(&tp, NS_IE_CAUSE); + LOGPC(DNS, LOGL_NOTICE, "cause=%s\n", gprs_ns_cause_str(cause)); + + return 0; +} + +/* Section 7.3 */ +static int gprs_ns_rx_reset(struct gprs_nsvc *nsvc, struct msgb *msg) +{ + struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; + struct tlv_parsed tp; + uint8_t *cause; + uint16_t *nsvci, *nsei; + int rc; + + rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data, + msgb_l2len(msg) - sizeof(*nsh), 0, 0); + if (rc < 0) { + LOGP(DNS, LOGL_ERROR, "NSEI=%u Rx NS RESET " + "Error during TLV Parse\n", nsvc->nsei); + return rc; + } + + if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) || + !TLVP_PRESENT(&tp, NS_IE_VCI) || + !TLVP_PRESENT(&tp, NS_IE_NSEI)) { + LOGP(DNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n"); + gprs_ns_tx_status(nsvc, NS_CAUSE_MISSING_ESSENT_IE, 0, msg); + return -EINVAL; + } + + cause = (uint8_t *) TLVP_VAL(&tp, NS_IE_CAUSE); + nsvci = (uint16_t *) TLVP_VAL(&tp, NS_IE_VCI); + nsei = (uint16_t *) TLVP_VAL(&tp, NS_IE_NSEI); + + LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS RESET (NSVCI=%u, cause=%s)\n", + nsvc->nsvci, nsvc->nsei, gprs_ns_cause_str(*cause)); + + /* Mark NS-VC as blocked and alive */ + nsvc->state = NSE_S_BLOCKED | NSE_S_ALIVE; + + nsvc->nsei = ntohs(*nsei); + nsvc->nsvci = ntohs(*nsvci); + + /* start the test procedure */ + gprs_ns_tx_simple(nsvc, NS_PDUT_ALIVE); + nsvc_start_timer(nsvc, NSVC_TIMER_TNS_TEST); + + /* inform interested parties about the fact that this NSVC + * has received RESET */ + ns_dispatch_signal(nsvc, S_NS_RESET, *cause); + + return gprs_ns_tx_reset_ack(nsvc); +} + +static int gprs_ns_rx_block(struct gprs_nsvc *nsvc, struct msgb *msg) +{ + struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; + struct tlv_parsed tp; + uint8_t *cause; + int rc; + + LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS BLOCK\n", nsvc->nsei); + + nsvc->state |= NSE_S_BLOCKED; + + rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data, + msgb_l2len(msg) - sizeof(*nsh), 0, 0); + if (rc < 0) { + LOGP(DNS, LOGL_ERROR, "NSEI=%u Rx NS BLOCK " + "Error during TLV Parse\n", nsvc->nsei); + return rc; + } + + if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) || + !TLVP_PRESENT(&tp, NS_IE_VCI)) { + LOGP(DNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n"); + gprs_ns_tx_status(nsvc, NS_CAUSE_MISSING_ESSENT_IE, 0, msg); + return -EINVAL; + } + + cause = (uint8_t *) TLVP_VAL(&tp, NS_IE_CAUSE); + //nsvci = (uint16_t *) TLVP_VAL(&tp, NS_IE_VCI); + + ns_dispatch_signal(nsvc, S_NS_BLOCK, *cause); + rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]); + + return gprs_ns_tx_simple(nsvc, NS_PDUT_BLOCK_ACK); +} + +/* main entry point, here incoming NS frames enter */ +int gprs_ns_rcvmsg(struct gprs_ns_inst *nsi, struct msgb *msg, + struct sockaddr_in *saddr, enum gprs_ns_ll ll) +{ + struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; + struct gprs_nsvc *nsvc; + int rc = 0; + + /* look up the NSVC based on source address */ + nsvc = nsvc_by_rem_addr(nsi, saddr); + if (!nsvc) { + struct tlv_parsed tp; + uint16_t nsei; + if (nsh->pdu_type == NS_PDUT_STATUS) { + LOGP(DNS, LOGL_INFO, "Ignoring NS STATUS from %s:%u " + "for non-existing NS-VC\n", + inet_ntoa(saddr->sin_addr), ntohs(saddr->sin_port)); + return 0; + } + /* Only the RESET procedure creates a new NSVC */ + if (nsh->pdu_type != NS_PDUT_RESET) { + /* Since we have no NSVC, we have to use a fake */ + nsvc = nsi->unknown_nsvc; + log_set_context(BSC_CTX_NSVC, nsvc); + LOGP(DNS, LOGL_INFO, "Rejecting NS PDU type 0x%0x " + "from %s:%u for non-existing NS-VC\n", + nsh->pdu_type, inet_ntoa(saddr->sin_addr), + ntohs(saddr->sin_port)); + nsvc->nsvci = nsvc->nsei = 0xfffe; + nsvc->ip.bts_addr = *saddr; + nsvc->state = NSE_S_ALIVE; + nsvc->ll = ll; +#if 0 + return gprs_ns_tx_reset(nsvc, NS_CAUSE_PDU_INCOMP_PSTATE); +#else + return gprs_ns_tx_status(nsvc, + NS_CAUSE_PDU_INCOMP_PSTATE, 0, + msg); +#endif + } + rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data, + msgb_l2len(msg) - sizeof(*nsh), 0, 0); + if (rc < 0) { + LOGP(DNS, LOGL_ERROR, "Rx NS RESET Error %d during " + "TLV Parse\n", rc); + return rc; + } + if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) || + !TLVP_PRESENT(&tp, NS_IE_VCI) || + !TLVP_PRESENT(&tp, NS_IE_NSEI)) { + LOGP(DNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n"); + gprs_ns_tx_status(nsvc, NS_CAUSE_MISSING_ESSENT_IE, 0, + msg); + return -EINVAL; + } + nsei = ntohs(*(uint16_t *)TLVP_VAL(&tp, NS_IE_NSEI)); + /* Check if we already know this NSEI, the remote end might + * simply have changed addresses, or it is a SGSN */ + nsvc = nsvc_by_nsei(nsi, nsei); + if (!nsvc) { + nsvc = nsvc_create(nsi, 0xffff); + nsvc->ll = ll; + log_set_context(BSC_CTX_NSVC, nsvc); + LOGP(DNS, LOGL_INFO, "Creating NS-VC for BSS at %s:%u\n", + inet_ntoa(saddr->sin_addr), ntohs(saddr->sin_port)); + } + /* Update the remote peer IP address/port */ + nsvc->ip.bts_addr = *saddr; + } else + msgb_nsei(msg) = nsvc->nsei; + + log_set_context(BSC_CTX_NSVC, nsvc); + + /* Increment number of Incoming bytes */ + rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_PKTS_IN]); + rate_ctr_add(&nsvc->ctrg->ctr[NS_CTR_BYTES_IN], msgb_l2len(msg)); + + switch (nsh->pdu_type) { + case NS_PDUT_ALIVE: + /* If we're dead and blocked and suddenly receive a + * NS-ALIVE out of the blue, we might have been re-started + * and should send a NS-RESET to make sure everything recovers + * fine. */ + if (nsvc->state == NSE_S_BLOCKED) + rc = gprs_ns_tx_reset(nsvc, NS_CAUSE_PDU_INCOMP_PSTATE); + else + rc = gprs_ns_tx_alive_ack(nsvc); + break; + case NS_PDUT_ALIVE_ACK: + /* stop Tns-alive and start Tns-test */ + nsvc_start_timer(nsvc, NSVC_TIMER_TNS_TEST); + if (nsvc->remote_end_is_sgsn) { + /* FIXME: this should be one level higher */ + if (nsvc->state & NSE_S_BLOCKED) + rc = gprs_ns_tx_unblock(nsvc); + } + break; + case NS_PDUT_UNITDATA: + /* actual user data */ + rc = gprs_ns_rx_unitdata(nsvc, msg); + break; + case NS_PDUT_STATUS: + rc = gprs_ns_rx_status(nsvc, msg); + break; + case NS_PDUT_RESET: + rc = gprs_ns_rx_reset(nsvc, msg); + break; + case NS_PDUT_RESET_ACK: + LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS RESET ACK\n", nsvc->nsei); + /* mark NS-VC as blocked + active */ + nsvc->state = NSE_S_BLOCKED | NSE_S_ALIVE; + nsvc->remote_state = NSE_S_BLOCKED | NSE_S_ALIVE; + rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]); + if (nsvc->persistent || nsvc->remote_end_is_sgsn) { + /* stop RESET timer */ + bsc_del_timer(&nsvc->timer); + } + /* Initiate TEST proc.: Send ALIVE and start timer */ + rc = gprs_ns_tx_simple(nsvc, NS_PDUT_ALIVE); + nsvc_start_timer(nsvc, NSVC_TIMER_TNS_TEST); + break; + case NS_PDUT_UNBLOCK: + /* Section 7.2: unblocking procedure */ + LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS UNBLOCK\n", nsvc->nsei); + nsvc->state &= ~NSE_S_BLOCKED; + ns_dispatch_signal(nsvc, S_NS_UNBLOCK, 0); + rc = gprs_ns_tx_simple(nsvc, NS_PDUT_UNBLOCK_ACK); + break; + case NS_PDUT_UNBLOCK_ACK: + LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS UNBLOCK ACK\n", nsvc->nsei); + /* mark NS-VC as unblocked + active */ + nsvc->state = NSE_S_ALIVE; + nsvc->remote_state = NSE_S_ALIVE; + ns_dispatch_signal(nsvc, S_NS_UNBLOCK, 0); + break; + case NS_PDUT_BLOCK: + rc = gprs_ns_rx_block(nsvc, msg); + break; + case NS_PDUT_BLOCK_ACK: + LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS BLOCK ACK\n", nsvc->nsei); + /* mark remote NS-VC as blocked + active */ + nsvc->remote_state = NSE_S_BLOCKED | NSE_S_ALIVE; + break; + default: + LOGP(DNS, LOGL_NOTICE, "NSEI=%u Rx Unknown NS PDU type 0x%02x\n", + nsvc->nsei, nsh->pdu_type); + rc = -EINVAL; + break; + } + return rc; +} + +struct gprs_ns_inst *gprs_ns_instantiate(gprs_ns_cb_t *cb) +{ + struct gprs_ns_inst *nsi = talloc_zero(tall_bsc_ctx, struct gprs_ns_inst); + + nsi->cb = cb; + INIT_LLIST_HEAD(&nsi->gprs_nsvcs); + nsi->timeout[NS_TOUT_TNS_BLOCK] = 3; + nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES] = 3; + nsi->timeout[NS_TOUT_TNS_RESET] = 3; + nsi->timeout[NS_TOUT_TNS_RESET_RETRIES] = 3; + nsi->timeout[NS_TOUT_TNS_TEST] = 30; + nsi->timeout[NS_TOUT_TNS_ALIVE] = 3; + nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES] = 10; + + /* Create the dummy NSVC that we use for sending + * messages to non-existant/unknown NS-VC's */ + nsi->unknown_nsvc = nsvc_create(nsi, 0xfffe); + llist_del(&nsi->unknown_nsvc->list); + + return nsi; +} + +void gprs_ns_destroy(struct gprs_ns_inst *nsi) +{ + /* FIXME: clear all timers */ + + /* recursively free the NSI and all its NSVCs */ + talloc_free(nsi); +} + + +/* NS-over-IP code, according to 3GPP TS 48.016 Chapter 6.2 + * We don't support Size Procedure, Configuration Procedure, ChangeWeight Procedure */ + +/* Read a single NS-over-IP message */ +static struct msgb *read_nsip_msg(struct bsc_fd *bfd, int *error, + struct sockaddr_in *saddr) +{ + struct msgb *msg = gprs_ns_msgb_alloc(); + int ret = 0; + socklen_t saddr_len = sizeof(*saddr); + + if (!msg) { + *error = -ENOMEM; + return NULL; + } + + ret = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE - NS_ALLOC_HEADROOM, 0, + (struct sockaddr *)saddr, &saddr_len); + if (ret < 0) { + LOGP(DNS, LOGL_ERROR, "recv error %s during NSIP recv\n", + strerror(errno)); + msgb_free(msg); + *error = ret; + return NULL; + } else if (ret == 0) { + msgb_free(msg); + *error = ret; + return NULL; + } + + msg->l2h = msg->data; + msgb_put(msg, ret); + + return msg; +} + +static int handle_nsip_read(struct bsc_fd *bfd) +{ + int error; + struct sockaddr_in saddr; + struct gprs_ns_inst *nsi = bfd->data; + struct msgb *msg = read_nsip_msg(bfd, &error, &saddr); + + if (!msg) + return error; + + error = gprs_ns_rcvmsg(nsi, msg, &saddr, GPRS_NS_LL_UDP); + + msgb_free(msg); + + return error; +} + +static int handle_nsip_write(struct bsc_fd *bfd) +{ + /* FIXME: actually send the data here instead of nsip_sendmsg() */ + return -EIO; +} + +static int nsip_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg) +{ + int rc; + struct gprs_ns_inst *nsi = nsvc->nsi; + struct sockaddr_in *daddr = &nsvc->ip.bts_addr; + + rc = sendto(nsi->nsip.fd.fd, msg->data, msg->len, 0, + (struct sockaddr *)daddr, sizeof(*daddr)); + + talloc_free(msg); + + return rc; +} + +/* UDP Port 23000 carries the LLC-in-BSSGP-in-NS protocol stack */ +static int nsip_fd_cb(struct bsc_fd *bfd, unsigned int what) +{ + int rc = 0; + + if (what & BSC_FD_READ) + rc = handle_nsip_read(bfd); + if (what & BSC_FD_WRITE) + rc = handle_nsip_write(bfd); + + return rc; +} + +/* Listen for incoming GPRS packets */ +int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi) +{ + int ret; + + ret = make_sock(&nsi->nsip.fd, IPPROTO_UDP, nsi->nsip.local_ip, + nsi->nsip.local_port, nsip_fd_cb); + if (ret < 0) + return ret; + + nsi->nsip.fd.data = nsi; + + return ret; +} + +/* Initiate a RESET procedure */ +void gprs_nsvc_reset(struct gprs_nsvc *nsvc, uint8_t cause) +{ + LOGP(DNS, LOGL_INFO, "NSEI=%u RESET procedure based on API request\n", + nsvc->nsei); + + /* Mark NS-VC locally as blocked and dead */ + nsvc->state = NSE_S_BLOCKED; + /* Send NS-RESET PDU */ + if (gprs_ns_tx_reset(nsvc, cause) < 0) { + LOGP(DNS, LOGL_ERROR, "NSEI=%u, error resetting NS-VC\n", + nsvc->nsei); + } + /* Start Tns-reset */ + nsvc_start_timer(nsvc, NSVC_TIMER_TNS_RESET); +} + +/* Establish a connection (from the BSS) to the SGSN */ +struct gprs_nsvc *nsip_connect(struct gprs_ns_inst *nsi, + struct sockaddr_in *dest, uint16_t nsei, + uint16_t nsvci) +{ + struct gprs_nsvc *nsvc; + + nsvc = nsvc_by_rem_addr(nsi, dest); + if (!nsvc) + nsvc = nsvc_create(nsi, nsvci); + nsvc->ip.bts_addr = *dest; + nsvc->nsei = nsei; + nsvc->nsvci = nsvci; + nsvc->remote_end_is_sgsn = 1; + + gprs_nsvc_reset(nsvc, NS_CAUSE_OM_INTERVENTION); + return nsvc; +} diff --git a/src/libgb/gprs_ns_frgre.c b/src/libgb/gprs_ns_frgre.c new file mode 100644 index 000000000..106f410e6 --- /dev/null +++ b/src/libgb/gprs_ns_frgre.c @@ -0,0 +1,304 @@ +/* GPRS Networks Service (NS) messages on the Gb interface + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) */ + +/* NS-over-FR-over-GRE implementation */ + +/* (C) 2009-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#define GRE_PTYPE_FR 0x6559 +#define GRE_PTYPE_IPv4 0x0800 +#define GRE_PTYPE_KAR 0x0000 /* keepalive response */ + +struct gre_hdr { + uint16_t flags; + uint16_t ptype; +} __attribute__ ((packed)); + +/* IPv4 messages inside the GRE tunnel might be GRE keepalives */ +static int handle_rx_gre_ipv4(struct bsc_fd *bfd, struct msgb *msg, + struct iphdr *iph, struct gre_hdr *greh) +{ + struct gprs_ns_inst *nsi = bfd->data; + int gre_payload_len; + struct iphdr *inner_iph; + struct gre_hdr *inner_greh; + struct sockaddr_in daddr; + struct in_addr ia; + + gre_payload_len = msg->len - (iph->ihl*4 + sizeof(*greh)); + + inner_iph = (struct iphdr *) ((uint8_t *)greh + sizeof(*greh)); + + if (gre_payload_len < inner_iph->ihl*4 + sizeof(*inner_greh)) { + LOGP(DNS, LOGL_ERROR, "GRE keepalive too short\n"); + return -EIO; + } + + if (inner_iph->saddr != iph->daddr || + inner_iph->daddr != iph->saddr) { + LOGP(DNS, LOGL_ERROR, + "GRE keepalive with wrong tunnel addresses\n"); + return -EIO; + } + + if (inner_iph->protocol != IPPROTO_GRE) { + LOGP(DNS, LOGL_ERROR, "GRE keepalive with wrong protocol\n"); + return -EIO; + } + + inner_greh = (struct gre_hdr *) ((uint8_t *)inner_iph + iph->ihl*4); + if (inner_greh->ptype != htons(GRE_PTYPE_KAR)) { + LOGP(DNS, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n"); + return -EIO; + } + + /* Actually send the response back */ + + daddr.sin_family = AF_INET; + daddr.sin_addr.s_addr = inner_iph->daddr; + daddr.sin_port = IPPROTO_GRE; + + ia.s_addr = iph->saddr; + LOGP(DNS, LOGL_DEBUG, "GRE keepalive from %s, responding\n", + inet_ntoa(ia)); + + return sendto(nsi->frgre.fd.fd, inner_greh, + gre_payload_len - inner_iph->ihl*4, 0, + (struct sockaddr *)&daddr, sizeof(daddr)); +} + +static struct msgb *read_nsfrgre_msg(struct bsc_fd *bfd, int *error, + struct sockaddr_in *saddr) +{ + struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR/GRE Rx"); + int ret = 0; + socklen_t saddr_len = sizeof(*saddr); + struct iphdr *iph; + struct gre_hdr *greh; + uint8_t *frh; + uint16_t dlci; + + if (!msg) { + *error = -ENOMEM; + return NULL; + } + + ret = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE, 0, + (struct sockaddr *)saddr, &saddr_len); + if (ret < 0) { + LOGP(DNS, LOGL_ERROR, "recv error %s during NS-FR-GRE recv\n", + strerror(errno)); + *error = ret; + goto out_err; + } else if (ret == 0) { + *error = ret; + goto out_err; + } + + msgb_put(msg, ret); + + if (msg->len < sizeof(*iph) + sizeof(*greh) + 2) { + LOGP(DNS, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len); + *error = -EIO; + goto out_err; + } + + iph = (struct iphdr *) msg->data; + if (msg->len < (iph->ihl*4 + sizeof(*greh) + 2)) { + LOGP(DNS, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len); + *error = -EIO; + goto out_err; + } + + greh = (struct gre_hdr *) (msg->data + iph->ihl*4); + if (greh->flags) { + LOGP(DNS, LOGL_NOTICE, "Unknown GRE flags 0x%04x\n", + ntohs(greh->flags)); + } + + switch (ntohs(greh->ptype)) { + case GRE_PTYPE_IPv4: + /* IPv4 messages might be GRE keepalives */ + *error = handle_rx_gre_ipv4(bfd, msg, iph, greh); + goto out_err; + break; + case GRE_PTYPE_FR: + /* continue as usual */ + break; + default: + LOGP(DNS, LOGL_NOTICE, "Unknown GRE protocol 0x%04x != FR\n", + ntohs(greh->ptype)); + *error = -EIO; + goto out_err; + break; + } + + if (msg->len < sizeof(*greh) + 2) { + LOGP(DNS, LOGL_ERROR, "Short FR header: %u bytes\n", msg->len); + *error = -EIO; + goto out_err; + } + + frh = (uint8_t *)greh + sizeof(*greh); + if (frh[0] & 0x01) { + LOGP(DNS, LOGL_NOTICE, "Unsupported single-byte FR address\n"); + *error = -EIO; + goto out_err; + } + dlci = ((frh[0] & 0xfc) << 2); + if ((frh[1] & 0x0f) != 0x01) { + LOGP(DNS, LOGL_NOTICE, "Unknown second FR octet 0x%02x\n", + frh[1]); + *error = -EIO; + goto out_err; + } + dlci |= (frh[1] >> 4); + + msg->l2h = frh+2; + + /* Store DLCI in NETWORK BYTEORDER in sockaddr port member */ + saddr->sin_port = htons(dlci); + + return msg; + +out_err: + msgb_free(msg); + return NULL; +} + +int gprs_ns_rcvmsg(struct gprs_ns_inst *nsi, struct msgb *msg, + struct sockaddr_in *saddr, enum gprs_ns_ll ll); + +static int handle_nsfrgre_read(struct bsc_fd *bfd) +{ + int rc; + struct sockaddr_in saddr; + struct gprs_ns_inst *nsi = bfd->data; + struct msgb *msg; + uint16_t dlci; + + msg = read_nsfrgre_msg(bfd, &rc, &saddr); + if (!msg) + return rc; + + dlci = ntohs(saddr.sin_port); + if (dlci == 0 || dlci == 1023) { + LOGP(DNS, LOGL_INFO, "Received FR on LMI DLCI %u - ignoring\n", + dlci); + rc = 0; + goto out; + } + + rc = gprs_ns_rcvmsg(nsi, msg, &saddr, GPRS_NS_LL_FR_GRE); +out: + msgb_free(msg); + + return rc; +} + +static int handle_nsfrgre_write(struct bsc_fd *bfd) +{ + /* FIXME: actually send the data here instead of nsip_sendmsg() */ + return -EIO; +} + +int gprs_ns_frgre_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg) +{ + int rc; + struct gprs_ns_inst *nsi = nsvc->nsi; + struct sockaddr_in daddr; + uint16_t dlci = ntohs(nsvc->frgre.bts_addr.sin_port); + uint8_t *frh; + struct gre_hdr *greh; + + /* Build socket address for the packet destionation */ + daddr.sin_family = AF_INET; + daddr.sin_addr = nsvc->frgre.bts_addr.sin_addr; + daddr.sin_port = IPPROTO_GRE; + + /* Prepend the FR header */ + frh = msgb_push(msg, 2); + frh[0] = (dlci >> 2) & 0xfc; + frh[1] = ((dlci & 0xf)<<4) | 0x01; + + /* Prepend the GRE header */ + greh = (struct gre_hdr *) msgb_push(msg, sizeof(*greh)); + greh->flags = 0; + greh->ptype = htons(GRE_PTYPE_FR); + + rc = sendto(nsi->frgre.fd.fd, msg->data, msg->len, 0, + (struct sockaddr *)&daddr, sizeof(daddr)); + + talloc_free(msg); + + return rc; +} + +static int nsfrgre_fd_cb(struct bsc_fd *bfd, unsigned int what) +{ + int rc = 0; + + if (what & BSC_FD_READ) + rc = handle_nsfrgre_read(bfd); + if (what & BSC_FD_WRITE) + rc = handle_nsfrgre_write(bfd); + + return rc; +} + +int gprs_ns_frgre_listen(struct gprs_ns_inst *nsi) +{ + int rc; + + /* Make sure we close any existing socket before changing it */ + if (nsi->frgre.fd.fd) + close(nsi->frgre.fd.fd); + + if (!nsi->frgre.enabled) + return 0; + + rc = make_sock(&nsi->frgre.fd, IPPROTO_GRE, nsi->frgre.local_ip, + 0, nsfrgre_fd_cb); + if (rc < 0) { + LOGP(DNS, LOGL_ERROR, "Error creating GRE socket (%s)\n", + strerror(errno)); + return rc; + } + nsi->frgre.fd.data = nsi; + + return rc; +} diff --git a/src/libgb/gprs_ns_vty.c b/src/libgb/gprs_ns_vty.c new file mode 100644 index 000000000..39277fc71 --- /dev/null +++ b/src/libgb/gprs_ns_vty.c @@ -0,0 +1,569 @@ +/* VTY interface for our GPRS Networks Service (NS) implementation */ + +/* (C) 2009-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static struct gprs_ns_inst *vty_nsi = NULL; + +/* FIXME: this should go to some common file as it is copied + * in vty_interface.c of the BSC */ +static const struct value_string gprs_ns_timer_strs[] = { + { 0, "tns-block" }, + { 1, "tns-block-retries" }, + { 2, "tns-reset" }, + { 3, "tns-reset-retries" }, + { 4, "tns-test" }, + { 5, "tns-alive" }, + { 6, "tns-alive-retries" }, + { 0, NULL } +}; + +static struct cmd_node ns_node = { + NS_NODE, + "%s(ns)#", + 1, +}; + +static int config_write_ns(struct vty *vty) +{ + struct gprs_nsvc *nsvc; + unsigned int i; + struct in_addr ia; + + vty_out(vty, "ns%s", VTY_NEWLINE); + + llist_for_each_entry(nsvc, &vty_nsi->gprs_nsvcs, list) { + if (!nsvc->persistent) + continue; + vty_out(vty, " nse %u nsvci %u%s", + nsvc->nsei, nsvc->nsvci, VTY_NEWLINE); + vty_out(vty, " nse %u remote-role %s%s", + nsvc->nsei, nsvc->remote_end_is_sgsn ? "sgsn" : "bss", + VTY_NEWLINE); + switch (nsvc->ll) { + case GPRS_NS_LL_UDP: + vty_out(vty, " nse %u encapsulation udp%s", nsvc->nsei, + VTY_NEWLINE); + vty_out(vty, " nse %u remote-ip %s%s", + nsvc->nsei, + inet_ntoa(nsvc->ip.bts_addr.sin_addr), + VTY_NEWLINE); + vty_out(vty, " nse %u remote-port %u%s", + nsvc->nsei, ntohs(nsvc->ip.bts_addr.sin_port), + VTY_NEWLINE); + break; + case GPRS_NS_LL_FR_GRE: + vty_out(vty, " nse %u encapsulation framerelay-gre%s", + nsvc->nsei, VTY_NEWLINE); + vty_out(vty, " nse %u remote-ip %s%s", + nsvc->nsei, + inet_ntoa(nsvc->frgre.bts_addr.sin_addr), + VTY_NEWLINE); + vty_out(vty, " nse %u fr-dlci %u%s", + nsvc->nsei, ntohs(nsvc->frgre.bts_addr.sin_port), + VTY_NEWLINE); + default: + break; + } + } + + for (i = 0; i < ARRAY_SIZE(vty_nsi->timeout); i++) + vty_out(vty, " timer %s %u%s", + get_value_string(gprs_ns_timer_strs, i), + vty_nsi->timeout[i], VTY_NEWLINE); + + if (vty_nsi->nsip.local_ip) { + ia.s_addr = htonl(vty_nsi->nsip.local_ip); + vty_out(vty, " encapsulation udp local-ip %s%s", + inet_ntoa(ia), VTY_NEWLINE); + } + if (vty_nsi->nsip.local_port) + vty_out(vty, " encapsulation udp local-port %u%s", + vty_nsi->nsip.local_port, VTY_NEWLINE); + + vty_out(vty, " encapsulation framerelay-gre enabled %u%s", + vty_nsi->frgre.enabled ? 1 : 0, VTY_NEWLINE); + if (vty_nsi->frgre.local_ip) { + ia.s_addr = htonl(vty_nsi->frgre.local_ip); + vty_out(vty, " encapsulation framerelay-gre local-ip %s%s", + inet_ntoa(ia), VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_ns, cfg_ns_cmd, + "ns", + "Configure the GPRS Network Service") +{ + vty->node = NS_NODE; + return CMD_SUCCESS; +} + +static void dump_nse(struct vty *vty, struct gprs_nsvc *nsvc, int stats) +{ + vty_out(vty, "NSEI %5u, NS-VC %5u, Remote: %-4s, %5s %9s", + nsvc->nsei, nsvc->nsvci, + nsvc->remote_end_is_sgsn ? "SGSN" : "BSS", + nsvc->state & NSE_S_ALIVE ? "ALIVE" : "DEAD", + nsvc->state & NSE_S_BLOCKED ? "BLOCKED" : "UNBLOCKED"); + if (nsvc->ll == GPRS_NS_LL_UDP || nsvc->ll == GPRS_NS_LL_FR_GRE) + vty_out(vty, ", %s %15s:%u", + nsvc->ll == GPRS_NS_LL_UDP ? "UDP " : "FR-GRE", + inet_ntoa(nsvc->ip.bts_addr.sin_addr), + ntohs(nsvc->ip.bts_addr.sin_port)); + vty_out(vty, "%s", VTY_NEWLINE); + if (stats) + vty_out_rate_ctr_group(vty, " ", nsvc->ctrg); +} + +static void dump_ns(struct vty *vty, struct gprs_ns_inst *nsi, int stats) +{ + struct gprs_nsvc *nsvc; + struct in_addr ia; + + ia.s_addr = htonl(vty_nsi->nsip.local_ip); + vty_out(vty, "Encapsulation NS-UDP-IP Local IP: %s, UDP Port: %u%s", + inet_ntoa(ia), vty_nsi->nsip.local_port, VTY_NEWLINE); + + ia.s_addr = htonl(vty_nsi->frgre.local_ip); + vty_out(vty, "Encapsulation NS-FR-GRE-IP Local IP: %s%s", + inet_ntoa(ia), VTY_NEWLINE); + + llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) { + if (nsvc == nsi->unknown_nsvc) + continue; + dump_nse(vty, nsvc, stats); + } +} + +DEFUN(show_ns, show_ns_cmd, "show ns", + SHOW_STR "Display information about the NS protocol") +{ + struct gprs_ns_inst *nsi = vty_nsi; + dump_ns(vty, nsi, 0); + return CMD_SUCCESS; +} + +DEFUN(show_ns_stats, show_ns_stats_cmd, "show ns stats", + SHOW_STR + "Display information about the NS protocol\n" + "Include statistics\n") +{ + struct gprs_ns_inst *nsi = vty_nsi; + dump_ns(vty, nsi, 1); + return CMD_SUCCESS; +} + +DEFUN(show_nse, show_nse_cmd, "show ns (nsei|nsvc) <0-65535> [stats]", + SHOW_STR "Display information about the NS protocol\n" + "Select one NSE by its NSE Identifier\n" + "Select one NSE by its NS-VC Identifier\n" + "The Identifier of selected type\n" + "Include Statistics\n") +{ + struct gprs_ns_inst *nsi = vty_nsi; + struct gprs_nsvc *nsvc; + uint16_t id = atoi(argv[1]); + int show_stats = 0; + + if (!strcmp(argv[0], "nsei")) + nsvc = nsvc_by_nsei(nsi, id); + else + nsvc = nsvc_by_nsvci(nsi, id); + + if (!nsvc) { + vty_out(vty, "No such NS Entity%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (argc >= 3) + show_stats = 1; + + dump_nse(vty, nsvc, show_stats); + return CMD_SUCCESS; +} + +#define NSE_CMD_STR "Persistent NS Entity\n" "NS Entity ID (NSEI)\n" + +DEFUN(cfg_nse_nsvc, cfg_nse_nsvci_cmd, + "nse <0-65535> nsvci <0-65534>", + NSE_CMD_STR + "NS Virtual Connection\n" + "NS Virtual Connection ID (NSVCI)\n" + ) +{ + uint16_t nsei = atoi(argv[0]); + uint16_t nsvci = atoi(argv[1]); + struct gprs_nsvc *nsvc; + + nsvc = nsvc_by_nsei(vty_nsi, nsei); + if (!nsvc) { + nsvc = nsvc_create(vty_nsi, nsvci); + nsvc->nsei = nsei; + } + nsvc->nsvci = nsvci; + /* All NSVCs that are explicitly configured by VTY are + * marked as persistent so we can write them to the config + * file at some later point */ + nsvc->persistent = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_nse_remoteip, cfg_nse_remoteip_cmd, + "nse <0-65535> remote-ip A.B.C.D", + NSE_CMD_STR + "Remote IP Address\n" + "Remote IP Address\n") +{ + uint16_t nsei = atoi(argv[0]); + struct gprs_nsvc *nsvc; + + nsvc = nsvc_by_nsei(vty_nsi, nsei); + if (!nsvc) { + vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE); + return CMD_WARNING; + } + inet_aton(argv[1], &nsvc->ip.bts_addr.sin_addr); + + return CMD_SUCCESS; + +} + +DEFUN(cfg_nse_remoteport, cfg_nse_remoteport_cmd, + "nse <0-65535> remote-port <0-65535>", + NSE_CMD_STR + "Remote UDP Port\n" + "Remote UDP Port Number\n") +{ + uint16_t nsei = atoi(argv[0]); + uint16_t port = atoi(argv[1]); + struct gprs_nsvc *nsvc; + + nsvc = nsvc_by_nsei(vty_nsi, nsei); + if (!nsvc) { + vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE); + return CMD_WARNING; + } + + if (nsvc->ll != GPRS_NS_LL_UDP) { + vty_out(vty, "Cannot set UDP Port on non-UDP NSE%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + nsvc->ip.bts_addr.sin_port = htons(port); + + return CMD_SUCCESS; +} + +DEFUN(cfg_nse_fr_dlci, cfg_nse_fr_dlci_cmd, + "nse <0-65535> fr-dlci <16-1007>", + NSE_CMD_STR + "Frame Relay DLCI\n" + "Frame Relay DLCI Number\n") +{ + uint16_t nsei = atoi(argv[0]); + uint16_t dlci = atoi(argv[1]); + struct gprs_nsvc *nsvc; + + nsvc = nsvc_by_nsei(vty_nsi, nsei); + if (!nsvc) { + vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE); + return CMD_WARNING; + } + + if (nsvc->ll != GPRS_NS_LL_FR_GRE) { + vty_out(vty, "Cannot set FR DLCI on non-FR NSE%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + nsvc->frgre.bts_addr.sin_port = htons(dlci); + + return CMD_SUCCESS; +} + +DEFUN(cfg_nse_encaps, cfg_nse_encaps_cmd, + "nse <0-65535> encapsulation (udp|framerelay-gre)", + NSE_CMD_STR + "Encapsulation for NS\n" + "UDP/IP Encapsulation\n" "Frame-Relay/GRE/IP Encapsulation\n") +{ + uint16_t nsei = atoi(argv[0]); + struct gprs_nsvc *nsvc; + + nsvc = nsvc_by_nsei(vty_nsi, nsei); + if (!nsvc) { + vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[1], "udp")) + nsvc->ll = GPRS_NS_LL_UDP; + else + nsvc->ll = GPRS_NS_LL_FR_GRE; + + return CMD_SUCCESS; +} + + +DEFUN(cfg_nse_remoterole, cfg_nse_remoterole_cmd, + "nse <0-65535> remote-role (sgsn|bss)", + NSE_CMD_STR + "Remote NSE Role\n" + "Remote Peer is SGSN\n" + "Remote Peer is BSS\n") +{ + uint16_t nsei = atoi(argv[0]); + struct gprs_nsvc *nsvc; + + nsvc = nsvc_by_nsei(vty_nsi, nsei); + if (!nsvc) { + vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[1], "sgsn")) + nsvc->remote_end_is_sgsn = 1; + else + nsvc->remote_end_is_sgsn = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_nse, cfg_no_nse_cmd, + "no nse <0-65535>", + "Delete Persistent NS Entity\n" + "Delete " NSE_CMD_STR) +{ + uint16_t nsei = atoi(argv[0]); + struct gprs_nsvc *nsvc; + + nsvc = nsvc_by_nsei(vty_nsi, nsei); + if (!nsvc) { + vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!nsvc->persistent) { + vty_out(vty, "NSEI %u is not a persistent NSE%s", + nsei, VTY_NEWLINE); + return CMD_WARNING; + } + + nsvc->persistent = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ns_timer, cfg_ns_timer_cmd, + "timer " NS_TIMERS " <0-65535>", + "Network Service Timer\n" + NS_TIMERS_HELP "Timer Value\n") +{ + int idx = get_string_value(gprs_ns_timer_strs, argv[0]); + int val = atoi(argv[1]); + + if (idx < 0 || idx >= ARRAY_SIZE(vty_nsi->timeout)) + return CMD_WARNING; + + vty_nsi->timeout[idx] = val; + + return CMD_SUCCESS; +} + +#define ENCAPS_STR "NS encapsulation options\n" + +DEFUN(cfg_nsip_local_ip, cfg_nsip_local_ip_cmd, + "encapsulation udp local-ip A.B.C.D", + ENCAPS_STR "NS over UDP Encapsulation\n" + "Set the IP address on which we listen for NS/UDP\n" + "IP Address\n") +{ + struct in_addr ia; + + inet_aton(argv[0], &ia); + vty_nsi->nsip.local_ip = ntohl(ia.s_addr); + + return CMD_SUCCESS; +} + +DEFUN(cfg_nsip_local_port, cfg_nsip_local_port_cmd, + "encapsulation udp local-port <0-65535>", + ENCAPS_STR "NS over UDP Encapsulation\n" + "Set the UDP port on which we listen for NS/UDP\n" + "UDP port number\n") +{ + unsigned int port = atoi(argv[0]); + + vty_nsi->nsip.local_port = port; + + return CMD_SUCCESS; +} + +DEFUN(cfg_frgre_local_ip, cfg_frgre_local_ip_cmd, + "encapsulation framerelay-gre local-ip A.B.C.D", + ENCAPS_STR "NS over Frame Relay over GRE Encapsulation\n" + "Set the IP address on which we listen for NS/FR/GRE\n" + "IP Address\n") +{ + struct in_addr ia; + + if (!vty_nsi->frgre.enabled) { + vty_out(vty, "FR/GRE is not enabled%s", VTY_NEWLINE); + return CMD_WARNING; + } + inet_aton(argv[0], &ia); + vty_nsi->frgre.local_ip = ntohl(ia.s_addr); + + return CMD_SUCCESS; +} + +DEFUN(cfg_frgre_enable, cfg_frgre_enable_cmd, + "encapsulation framerelay-gre enabled (1|0)", + ENCAPS_STR "NS over Frame Relay over GRE Encapsulation\n" + "Enable or disable Frame Relay over GRE\n" + "Enable\n" "Disable\n") +{ + int enabled = atoi(argv[0]); + + vty_nsi->frgre.enabled = enabled; + + return CMD_SUCCESS; +} + +DEFUN(nsvc_nsei, nsvc_nsei_cmd, + "nsvc nsei <0-65535> (block|unblock|reset)", + "Perform an operation on a NSVC\n" + "NS-VC Identifier (NS-VCI)\n" + "Initiate BLOCK procedure\n" + "Initiate UNBLOCK procedure\n" + "Initiate RESET procedure\n") +{ + uint16_t nsvci = atoi(argv[0]); + const char *operation = argv[1]; + struct gprs_nsvc *nsvc; + + nsvc = nsvc_by_nsei(vty_nsi, nsvci); + if (!nsvc) { + vty_out(vty, "No such NSVCI (%u)%s", nsvci, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(operation, "block")) + gprs_ns_tx_block(nsvc, NS_CAUSE_OM_INTERVENTION); + else if (!strcmp(operation, "unblock")) + gprs_ns_tx_unblock(nsvc); + else if (!strcmp(operation, "reset")) + gprs_nsvc_reset(nsvc, NS_CAUSE_OM_INTERVENTION); + else + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFUN(logging_fltr_nsvc, + logging_fltr_nsvc_cmd, + "logging filter nsvc (nsei|nsvci) <0-65535>", + LOGGING_STR FILTER_STR + "Filter based on NS Virtual Connection\n" + "Identify NS-VC by NSEI\n" + "Identify NS-VC by NSVCI\n" + "Numeric identifier\n") +{ + struct log_target *tgt = osmo_log_vty2tgt(vty); + struct gprs_nsvc *nsvc; + uint16_t id = atoi(argv[1]); + + if (!tgt) + return CMD_WARNING; + + if (!strcmp(argv[0], "nsei")) + nsvc = nsvc_by_nsei(vty_nsi, id); + else + nsvc = nsvc_by_nsvci(vty_nsi, id); + + if (!nsvc) { + vty_out(vty, "No NS-VC by that identifier%s", VTY_NEWLINE); + return CMD_WARNING; + } + + log_set_nsvc_filter(tgt, nsvc); + return CMD_SUCCESS; +} + +int gprs_ns_vty_init(struct gprs_ns_inst *nsi) +{ + vty_nsi = nsi; + + install_element_ve(&show_ns_cmd); + install_element_ve(&show_ns_stats_cmd); + install_element_ve(&show_nse_cmd); + install_element_ve(&logging_fltr_nsvc_cmd); + + install_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd); + + install_element(CONFIG_NODE, &cfg_ns_cmd); + install_node(&ns_node, config_write_ns); + install_default(NS_NODE); + install_element(NS_NODE, &ournode_exit_cmd); + install_element(NS_NODE, &ournode_end_cmd); + install_element(NS_NODE, &cfg_nse_nsvci_cmd); + install_element(NS_NODE, &cfg_nse_remoteip_cmd); + install_element(NS_NODE, &cfg_nse_remoteport_cmd); + install_element(NS_NODE, &cfg_nse_fr_dlci_cmd); + install_element(NS_NODE, &cfg_nse_encaps_cmd); + install_element(NS_NODE, &cfg_nse_remoterole_cmd); + install_element(NS_NODE, &cfg_no_nse_cmd); + install_element(NS_NODE, &cfg_ns_timer_cmd); + install_element(NS_NODE, &cfg_nsip_local_ip_cmd); + install_element(NS_NODE, &cfg_nsip_local_port_cmd); + install_element(NS_NODE, &cfg_frgre_enable_cmd); + install_element(NS_NODE, &cfg_frgre_local_ip_cmd); + + install_element(ENABLE_NODE, &nsvc_nsei_cmd); + + return 0; +} diff --git a/src/libmgcp/Makefile.am b/src/libmgcp/Makefile.am new file mode 100644 index 000000000..b1d1d158a --- /dev/null +++ b/src/libmgcp/Makefile.am @@ -0,0 +1,7 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) + +noinst_LIBRARIES = libmgcp.a + +libmgcp_a_SOURCES = mgcp_protocol.c mgcp_network.c mgcp_vty.c diff --git a/src/libmgcp/Makefile.in b/src/libmgcp/Makefile.in new file mode 100644 index 000000000..942aeea95 --- /dev/null +++ b/src/libmgcp/Makefile.in @@ -0,0 +1,453 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = src/libmgcp +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LIBRARIES = $(noinst_LIBRARIES) +AR = ar +ARFLAGS = cru +AM_V_AR = $(am__v_AR_$(V)) +am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY)) +am__v_AR_0 = @echo " AR " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +libmgcp_a_AR = $(AR) $(ARFLAGS) +libmgcp_a_LIBADD = +am_libmgcp_a_OBJECTS = mgcp_protocol.$(OBJEXT) mgcp_network.$(OBJEXT) \ + mgcp_vty.$(OBJEXT) +libmgcp_a_OBJECTS = $(am_libmgcp_a_OBJECTS) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(libmgcp_a_SOURCES) +DIST_SOURCES = $(libmgcp_a_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) +noinst_LIBRARIES = libmgcp.a +libmgcp_a_SOURCES = mgcp_protocol.c mgcp_network.c mgcp_vty.c +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libmgcp/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/libmgcp/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) +libmgcp.a: $(libmgcp_a_OBJECTS) $(libmgcp_a_DEPENDENCIES) + $(AM_V_at)-rm -f libmgcp.a + $(AM_V_AR)$(libmgcp_a_AR) libmgcp.a $(libmgcp_a_OBJECTS) $(libmgcp_a_LIBADD) + $(AM_V_at)$(RANLIB) libmgcp.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mgcp_network.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mgcp_protocol.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mgcp_vty.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LIBRARIES) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-noinstLIBRARIES ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/libmgcp/mgcp_network.c b/src/libmgcp/mgcp_network.c new file mode 100644 index 000000000..51c08d151 --- /dev/null +++ b/src/libmgcp/mgcp_network.c @@ -0,0 +1,579 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The protocol implementation */ + +/* + * (C) 2009-2011 by Holger Hans Peter Freyther + * (C) 2009-2011 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#warning "Make use of the rtp proxy code" + +/* attempt to determine byte order */ +#include +#include +#include + +#ifndef __BYTE_ORDER +#error "__BYTE_ORDER should be defined by someone" +#endif + +/* according to rtp_proxy.c RFC 3550 */ +struct rtp_hdr { +#if __BYTE_ORDER == __LITTLE_ENDIAN + uint8_t csrc_count:4, + extension:1, + padding:1, + version:2; + uint8_t payload_type:7, + marker:1; +#elif __BYTE_ORDER == __BIG_ENDIAN + uint8_t version:2, + padding:1, + extension:1, + csrc_count:4; + uint8_t marker:1, + payload_type:7; +#endif + uint16_t sequence; + uint32_t timestamp; + uint32_t ssrc; +} __attribute__((packed)); + + +enum { + DEST_NETWORK = 0, + DEST_BTS = 1, +}; + +enum { + PROTO_RTP, + PROTO_RTCP, +}; + +#define DUMMY_LOAD 0x23 + + +static int udp_send(int fd, struct in_addr *addr, int port, char *buf, int len) +{ + struct sockaddr_in out; + out.sin_family = AF_INET; + out.sin_port = port; + memcpy(&out.sin_addr, addr, sizeof(*addr)); + + return sendto(fd, buf, len, 0, (struct sockaddr *)&out, sizeof(out)); +} + +int mgcp_send_dummy(struct mgcp_endpoint *endp) +{ + static char buf[] = { DUMMY_LOAD }; + + return udp_send(endp->net_end.rtp.fd, &endp->net_end.addr, + endp->net_end.rtp_port, buf, 1); +} + +static void patch_and_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state, + int payload, struct sockaddr_in *addr, char *data, int len) +{ + uint16_t seq; + uint32_t timestamp; + struct rtp_hdr *rtp_hdr; + + if (len < sizeof(*rtp_hdr)) + return; + + rtp_hdr = (struct rtp_hdr *) data; + seq = ntohs(rtp_hdr->sequence); + timestamp = ntohl(rtp_hdr->timestamp); + + if (!state->initialized) { + state->seq_no = seq - 1; + state->ssrc = state->orig_ssrc = rtp_hdr->ssrc; + state->initialized = 1; + state->last_timestamp = timestamp; + } else if (state->ssrc != rtp_hdr->ssrc) { + state->ssrc = rtp_hdr->ssrc; + state->seq_offset = (state->seq_no + 1) - seq; + state->timestamp_offset = state->last_timestamp - timestamp; + state->patch = endp->allow_patch; + LOGP(DMGCP, LOGL_NOTICE, + "The SSRC changed on 0x%x SSRC: %u offset: %d from %s:%d in %d\n", + ENDPOINT_NUMBER(endp), state->ssrc, state->seq_offset, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), endp->conn_mode); + } + + /* apply the offset and store it back to the packet */ + if (state->patch) { + seq += state->seq_offset; + rtp_hdr->sequence = htons(seq); + rtp_hdr->ssrc = state->orig_ssrc; + + timestamp += state->timestamp_offset; + rtp_hdr->timestamp = htonl(timestamp); + } + + /* seq changed, now compare if we have lost something */ + if (state->seq_no + 1u != seq) + state->lost_no = abs(seq - (state->seq_no + 1)); + state->seq_no = seq; + + state->last_timestamp = timestamp; + + if (payload < 0) + return; + + rtp_hdr->payload_type = payload; +} + +/* + * The below code is for dispatching. We have a dedicated port for + * the data coming from the net and one to discover the BTS. + */ +static int forward_data(int fd, struct mgcp_rtp_tap *tap, const char *buf, int len) +{ + if (!tap->enabled) + return 0; + + return sendto(fd, buf, len, 0, + (struct sockaddr *)&tap->forward, sizeof(tap->forward)); +} + +static int send_transcoder(struct mgcp_rtp_end *end, struct mgcp_config *cfg, + int is_rtp, const char *buf, int len) +{ + int rc; + int port; + struct sockaddr_in addr; + + port = is_rtp ? end->rtp_port : end->rtcp_port; + + addr.sin_family = AF_INET; + addr.sin_addr = cfg->transcoder_in; + addr.sin_port = port; + + rc = sendto(is_rtp ? + end->rtp.fd : + end->rtcp.fd, buf, len, 0, + (struct sockaddr *) &addr, sizeof(addr)); + + if (rc != len) + LOGP(DMGCP, LOGL_ERROR, + "Failed to send data to the transcoder: %s\n", + strerror(errno)); + + return rc; +} + +static int send_to(struct mgcp_endpoint *endp, int dest, int is_rtp, + struct sockaddr_in *addr, char *buf, int rc) +{ + struct mgcp_trunk_config *tcfg = endp->tcfg; + /* For loop toggle the destination and then dispatch. */ + if (tcfg->audio_loop) + dest = !dest; + + /* Loop based on the conn_mode, maybe undoing the above */ + if (endp->conn_mode == MGCP_CONN_LOOPBACK) + dest = !dest; + + if (dest == DEST_NETWORK) { + if (is_rtp) { + patch_and_count(endp, &endp->bts_state, + endp->net_end.payload_type, + addr, buf, rc); + forward_data(endp->net_end.rtp.fd, + &endp->taps[MGCP_TAP_NET_OUT], buf, rc); + return udp_send(endp->net_end.rtp.fd, &endp->net_end.addr, + endp->net_end.rtp_port, buf, rc); + } else { + return udp_send(endp->net_end.rtcp.fd, &endp->net_end.addr, + endp->net_end.rtcp_port, buf, rc); + } + } else { + if (is_rtp) { + patch_and_count(endp, &endp->net_state, + endp->bts_end.payload_type, + addr, buf, rc); + forward_data(endp->bts_end.rtp.fd, + &endp->taps[MGCP_TAP_BTS_OUT], buf, rc); + return udp_send(endp->bts_end.rtp.fd, &endp->bts_end.addr, + endp->bts_end.rtp_port, buf, rc); + } else { + return udp_send(endp->bts_end.rtcp.fd, &endp->bts_end.addr, + endp->bts_end.rtcp_port, buf, rc); + } + } +} + +static int recevice_from(struct mgcp_endpoint *endp, int fd, struct sockaddr_in *addr, + char *buf, int bufsize) +{ + int rc; + socklen_t slen = sizeof(*addr); + + rc = recvfrom(fd, buf, bufsize, 0, + (struct sockaddr *) addr, &slen); + if (rc < 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to receive message on: 0x%x errno: %d/%s\n", + ENDPOINT_NUMBER(endp), errno, strerror(errno)); + return -1; + } + + /* do not forward aynthing... maybe there is a packet from the bts */ + if (!endp->allocated) + return -1; + + #warning "Slight spec violation. With connection mode recvonly we should attempt to forward." + + return rc; +} + +static int rtp_data_net(struct bsc_fd *fd, unsigned int what) +{ + char buf[4096]; + struct sockaddr_in addr; + struct mgcp_endpoint *endp; + int rc, proto; + + endp = (struct mgcp_endpoint *) fd->data; + + rc = recevice_from(endp, fd->fd, &addr, buf, sizeof(buf)); + if (rc <= 0) + return -1; + + if (memcmp(&addr.sin_addr, &endp->net_end.addr, sizeof(addr.sin_addr)) != 0) { + LOGP(DMGCP, LOGL_ERROR, + "Data from wrong address %s on 0x%x\n", + inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(endp)); + return -1; + } + + if (endp->net_end.rtp_port != addr.sin_port && + endp->net_end.rtcp_port != addr.sin_port) { + LOGP(DMGCP, LOGL_ERROR, + "Data from wrong source port %d on 0x%x\n", + ntohs(addr.sin_port), ENDPOINT_NUMBER(endp)); + return -1; + } + + /* throw away the dummy message */ + if (rc == 1 && buf[0] == DUMMY_LOAD) { + LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from network on 0x%x\n", + ENDPOINT_NUMBER(endp)); + return 0; + } + + proto = fd == &endp->net_end.rtp ? PROTO_RTP : PROTO_RTCP; + endp->net_end.packets += 1; + + forward_data(fd->fd, &endp->taps[MGCP_TAP_NET_IN], buf, rc); + if (endp->is_transcoded) + return send_transcoder(&endp->trans_net, endp->cfg, proto == PROTO_RTP, &buf[0], rc); + else + return send_to(endp, DEST_BTS, proto == PROTO_RTP, &addr, &buf[0], rc); +} + +static void discover_bts(struct mgcp_endpoint *endp, int proto, struct sockaddr_in *addr) +{ + struct mgcp_config *cfg = endp->cfg; + + if (proto == PROTO_RTP && endp->bts_end.rtp_port == 0) { + if (!cfg->bts_ip || + memcmp(&addr->sin_addr, + &cfg->bts_in, sizeof(cfg->bts_in)) == 0 || + memcmp(&addr->sin_addr, + &endp->bts_end.addr, sizeof(endp->bts_end.addr)) == 0) { + + endp->bts_end.rtp_port = addr->sin_port; + endp->bts_end.addr = addr->sin_addr; + + LOGP(DMGCP, LOGL_NOTICE, + "Found BTS for endpoint: 0x%x on port: %d/%d of %s\n", + ENDPOINT_NUMBER(endp), ntohs(endp->bts_end.rtp_port), + ntohs(endp->bts_end.rtcp_port), inet_ntoa(addr->sin_addr)); + } + } else if (proto == PROTO_RTCP && endp->bts_end.rtcp_port == 0) { + if (memcmp(&endp->bts_end.addr, &addr->sin_addr, + sizeof(endp->bts_end.addr)) == 0) { + endp->bts_end.rtcp_port = addr->sin_port; + } + } +} + +static int rtp_data_bts(struct bsc_fd *fd, unsigned int what) +{ + char buf[4096]; + struct sockaddr_in addr; + struct mgcp_endpoint *endp; + struct mgcp_config *cfg; + int rc, proto; + + endp = (struct mgcp_endpoint *) fd->data; + cfg = endp->cfg; + + rc = recevice_from(endp, fd->fd, &addr, buf, sizeof(buf)); + if (rc <= 0) + return -1; + + proto = fd == &endp->bts_end.rtp ? PROTO_RTP : PROTO_RTCP; + + /* We have no idea who called us, maybe it is the BTS. */ + /* it was the BTS... */ + discover_bts(endp, proto, &addr); + + if (memcmp(&endp->bts_end.addr, &addr.sin_addr, sizeof(addr.sin_addr)) != 0) { + LOGP(DMGCP, LOGL_ERROR, + "Data from wrong bts %s on 0x%x\n", + inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(endp)); + return -1; + } + + if (endp->bts_end.rtp_port != addr.sin_port && + endp->bts_end.rtcp_port != addr.sin_port) { + LOGP(DMGCP, LOGL_ERROR, + "Data from wrong bts source port %d on 0x%x\n", + ntohs(addr.sin_port), ENDPOINT_NUMBER(endp)); + return -1; + } + + /* throw away the dummy message */ + if (rc == 1 && buf[0] == DUMMY_LOAD) { + LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from bts on 0x%x\n", + ENDPOINT_NUMBER(endp)); + return 0; + } + + /* do this before the loop handling */ + endp->bts_end.packets += 1; + + forward_data(fd->fd, &endp->taps[MGCP_TAP_BTS_IN], buf, rc); + if (endp->is_transcoded) + return send_transcoder(&endp->trans_bts, endp->cfg, proto == PROTO_RTP, &buf[0], rc); + else + return send_to(endp, DEST_NETWORK, proto == PROTO_RTP, &addr, &buf[0], rc); +} + +static int rtp_data_transcoder(struct mgcp_rtp_end *end, struct mgcp_endpoint *_endp, + int dest, struct bsc_fd *fd) +{ + char buf[4096]; + struct sockaddr_in addr; + struct mgcp_config *cfg; + int rc, proto; + + cfg = _endp->cfg; + rc = recevice_from(_endp, fd->fd, &addr, buf, sizeof(buf)); + if (rc <= 0) + return -1; + + proto = fd == &end->rtp ? PROTO_RTP : PROTO_RTCP; + + if (memcmp(&addr.sin_addr, &cfg->transcoder_in, sizeof(addr.sin_addr)) != 0) { + LOGP(DMGCP, LOGL_ERROR, + "Data not coming from transcoder dest: %d %s on 0x%x\n", + dest, inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(_endp)); + return -1; + } + + if (end->rtp_port != addr.sin_port && + end->rtcp_port != addr.sin_port) { + LOGP(DMGCP, LOGL_ERROR, + "Data from wrong transcoder dest %d source port %d on 0x%x\n", + dest, ntohs(addr.sin_port), ENDPOINT_NUMBER(_endp)); + return -1; + } + + /* throw away the dummy message */ + if (rc == 1 && buf[0] == DUMMY_LOAD) { + LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from transcoder dest %d on 0x%x\n", + dest, ENDPOINT_NUMBER(_endp)); + return 0; + } + + end->packets += 1; + return send_to(_endp, dest, proto == PROTO_RTP, &addr, &buf[0], rc); +} + +static int rtp_data_trans_net(struct bsc_fd *fd, unsigned int what) +{ + struct mgcp_endpoint *endp; + endp = (struct mgcp_endpoint *) fd->data; + + return rtp_data_transcoder(&endp->trans_net, endp, DEST_NETWORK, fd); +} + +static int rtp_data_trans_bts(struct bsc_fd *fd, unsigned int what) +{ + struct mgcp_endpoint *endp; + endp = (struct mgcp_endpoint *) fd->data; + + return rtp_data_transcoder(&endp->trans_bts, endp, DEST_BTS, fd); +} + +static int create_bind(const char *source_addr, struct bsc_fd *fd, int port) +{ + struct sockaddr_in addr; + int on = 1; + + fd->fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd->fd < 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to create UDP port.\n"); + return -1; + } + + setsockopt(fd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_aton(source_addr, &addr.sin_addr); + + if (bind(fd->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(fd->fd); + fd->fd = -1; + return -1; + } + + return 0; +} + +static int set_ip_tos(int fd, int tos) +{ + int ret; + ret = setsockopt(fd, IPPROTO_IP, IP_TOS, + &tos, sizeof(tos)); + return ret != 0; +} + +static int bind_rtp(struct mgcp_config *cfg, struct mgcp_rtp_end *rtp_end, int endpno) +{ + if (create_bind(cfg->source_addr, &rtp_end->rtp, rtp_end->local_port) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to create RTP port: %s:%d on 0x%x\n", + cfg->source_addr, rtp_end->local_port, endpno); + goto cleanup0; + } + + if (create_bind(cfg->source_addr, &rtp_end->rtcp, rtp_end->local_port + 1) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to create RTCP port: %s:%d on 0x%x\n", + cfg->source_addr, rtp_end->local_port + 1, endpno); + goto cleanup1; + } + + set_ip_tos(rtp_end->rtp.fd, cfg->endp_dscp); + set_ip_tos(rtp_end->rtcp.fd, cfg->endp_dscp); + + rtp_end->rtp.when = BSC_FD_READ; + if (bsc_register_fd(&rtp_end->rtp) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to register RTP port %d on 0x%x\n", + rtp_end->local_port, endpno); + goto cleanup2; + } + + rtp_end->rtcp.when = BSC_FD_READ; + if (bsc_register_fd(&rtp_end->rtcp) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to register RTCP port %d on 0x%x\n", + rtp_end->local_port + 1, endpno); + goto cleanup3; + } + + return 0; + +cleanup3: + bsc_unregister_fd(&rtp_end->rtp); +cleanup2: + close(rtp_end->rtcp.fd); + rtp_end->rtcp.fd = -1; +cleanup1: + close(rtp_end->rtp.fd); + rtp_end->rtp.fd = -1; +cleanup0: + return -1; +} + +static int int_bind(const char *port, + struct mgcp_rtp_end *end, int (*cb)(struct bsc_fd *, unsigned), + struct mgcp_endpoint *_endp, int rtp_port) +{ + if (end->rtp.fd != -1 || end->rtcp.fd != -1) { + LOGP(DMGCP, LOGL_ERROR, "Previous %s was still bound on %d\n", + port, ENDPOINT_NUMBER(_endp)); + mgcp_free_rtp_port(end); + } + + end->local_port = rtp_port; + end->rtp.cb = cb; + end->rtp.data = _endp; + end->rtcp.data = _endp; + end->rtcp.cb = cb; + return bind_rtp(_endp->cfg, end, ENDPOINT_NUMBER(_endp)); +} + + +int mgcp_bind_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port) +{ + return int_bind("bts-port", &endp->bts_end, + rtp_data_bts, endp, rtp_port); +} + +int mgcp_bind_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port) +{ + return int_bind("net-port", &endp->net_end, + rtp_data_net, endp, rtp_port); +} + +int mgcp_bind_trans_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port) +{ + return int_bind("trans-net", &endp->trans_net, + rtp_data_trans_net, endp, rtp_port); +} + +int mgcp_bind_trans_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port) +{ + return int_bind("trans-bts", &endp->trans_bts, + rtp_data_trans_bts, endp, rtp_port); +} + +int mgcp_free_rtp_port(struct mgcp_rtp_end *end) +{ + if (end->rtp.fd != -1) { + close(end->rtp.fd); + end->rtp.fd = -1; + bsc_unregister_fd(&end->rtp); + } + + if (end->rtcp.fd != -1) { + close(end->rtcp.fd); + end->rtcp.fd = -1; + bsc_unregister_fd(&end->rtcp); + } + + return 0; +} diff --git a/src/libmgcp/mgcp_protocol.c b/src/libmgcp/mgcp_protocol.c new file mode 100644 index 000000000..ba290dd90 --- /dev/null +++ b/src/libmgcp/mgcp_protocol.c @@ -0,0 +1,1102 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The protocol implementation */ + +/* + * (C) 2009-2011 by Holger Hans Peter Freyther + * (C) 2009-2011 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/** + * Macro for tokenizing MGCP messages and SDP in one go. + * + */ +#define MSG_TOKENIZE_START \ + line_start = 0; \ + for (i = 0; i < msgb_l3len(msg); ++i) { \ + /* we have a line end */ \ + if (msg->l3h[i] == '\n') { \ + /* skip the first line */ \ + if (line_start == 0) { \ + line_start = i + 1; \ + continue; \ + } \ + \ + /* check if we have a proper param */ \ + if (i - line_start == 1 && msg->l3h[line_start] == '\r') { \ + } else if (i - line_start > 2 \ + && islower(msg->l3h[line_start]) \ + && msg->l3h[line_start + 1] == '=') { \ + } else if (i - line_start < 3 \ + || msg->l3h[line_start + 1] != ':' \ + || msg->l3h[line_start + 2] != ' ') \ + goto error; \ + \ + msg->l3h[i] = '\0'; \ + if (msg->l3h[i-1] == '\r') \ + msg->l3h[i-1] = '\0'; + +#define MSG_TOKENIZE_END \ + line_start = i + 1; \ + } \ + } + +static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end); + +struct mgcp_request { + char *name; + struct msgb *(*handle_request) (struct mgcp_config *cfg, struct msgb *msg); + char *debug_name; +}; + +#define MGCP_REQUEST(NAME, REQ, DEBUG_NAME) \ + { .name = NAME, .handle_request = REQ, .debug_name = DEBUG_NAME }, + +static struct msgb *handle_audit_endpoint(struct mgcp_config *cfg, struct msgb *msg); +static struct msgb *handle_create_con(struct mgcp_config *cfg, struct msgb *msg); +static struct msgb *handle_delete_con(struct mgcp_config *cfg, struct msgb *msg); +static struct msgb *handle_modify_con(struct mgcp_config *cfg, struct msgb *msg); +static struct msgb *handle_rsip(struct mgcp_config *cfg, struct msgb *msg); +static struct msgb *handle_noti_req(struct mgcp_config *cfg, struct msgb *msg); + +static void create_transcoder(struct mgcp_endpoint *endp); +static void delete_transcoder(struct mgcp_endpoint *endp); + +static uint32_t generate_call_id(struct mgcp_config *cfg) +{ + int i; + + /* use the call id */ + ++cfg->last_call_id; + + /* handle wrap around */ + if (cfg->last_call_id == CI_UNUSED) + ++cfg->last_call_id; + + /* callstack can only be of size number_of_endpoints */ + /* verify that the call id is free, e.g. in case of overrun */ + for (i = 1; i < cfg->trunk.number_endpoints; ++i) + if (cfg->trunk.endpoints[i].ci == cfg->last_call_id) + return generate_call_id(cfg); + + return cfg->last_call_id; +} + +/* + * array of function pointers for handling various + * messages. In the future this might be binary sorted + * for performance reasons. + */ +static const struct mgcp_request mgcp_requests [] = { + MGCP_REQUEST("AUEP", handle_audit_endpoint, "AuditEndpoint") + MGCP_REQUEST("CRCX", handle_create_con, "CreateConnection") + MGCP_REQUEST("DLCX", handle_delete_con, "DeleteConnection") + MGCP_REQUEST("MDCX", handle_modify_con, "ModifiyConnection") + MGCP_REQUEST("RQNT", handle_noti_req, "NotificationRequest") + + /* SPEC extension */ + MGCP_REQUEST("RSIP", handle_rsip, "ReSetInProgress") +}; + +static struct msgb *mgcp_msgb_alloc(void) +{ + struct msgb *msg; + msg = msgb_alloc_headroom(4096, 128, "MGCP msg"); + if (!msg) + LOGP(DMGCP, LOGL_ERROR, "Failed to msgb for MGCP data.\n"); + + return msg; +} + +struct msgb *mgcp_create_response_with_data(int code, const char *txt, + const char *msg, const char *trans, + const char *data) +{ + int len; + struct msgb *res; + + res = mgcp_msgb_alloc(); + if (!res) + return NULL; + + if (data) { + len = snprintf((char *) res->data, 2048, "%d %s%s\r\n%s", code, trans, txt, data); + } else { + len = snprintf((char *) res->data, 2048, "%d %s%s\r\n", code, trans, txt); + } + + res->l2h = msgb_put(res, len); + LOGP(DMGCP, LOGL_DEBUG, "Sending response: code: %d for '%s'\n", code, res->l2h); + return res; +} + +static struct msgb *create_ok_response(int code, const char *msg, const char *trans) +{ + return mgcp_create_response_with_data(code, " OK", msg, trans, NULL); +} + +static struct msgb *create_err_response(int code, const char *msg, const char *trans) +{ + return mgcp_create_response_with_data(code, " FAIL", msg, trans, NULL); +} + +static struct msgb *create_response_with_sdp(struct mgcp_endpoint *endp, + const char *msg, const char *trans_id) +{ + const char *addr = endp->cfg->local_ip; + char sdp_record[4096]; + + if (!addr) + addr = endp->cfg->source_addr; + + snprintf(sdp_record, sizeof(sdp_record) - 1, + "I: %u\n\n" + "v=0\r\n" + "c=IN IP4 %s\r\n" + "m=audio %d RTP/AVP %d\r\n" + "a=rtpmap:%d %s\r\n", + endp->ci, addr, endp->net_end.local_port, + endp->bts_end.payload_type, endp->bts_end.payload_type, + endp->tcfg->audio_name); + return mgcp_create_response_with_data(200, " OK", msg, trans_id, sdp_record); +} + +/* + * handle incoming messages: + * - this can be a command (four letters, space, transaction id) + * - or a response (three numbers, space, transaction id) + */ +struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg) +{ + int code; + struct msgb *resp = NULL; + + if (msgb_l2len(msg) < 4) { + LOGP(DMGCP, LOGL_ERROR, "mgs too short: %d\n", msg->len); + return NULL; + } + + /* attempt to treat it as a response */ + if (sscanf((const char *)&msg->l2h[0], "%3d %*s", &code) == 1) { + LOGP(DMGCP, LOGL_DEBUG, "Response: Code: %d\n", code); + } else { + int i, handled = 0; + msg->l3h = &msg->l2h[4]; + for (i = 0; i < ARRAY_SIZE(mgcp_requests); ++i) + if (strncmp(mgcp_requests[i].name, (const char *) &msg->l2h[0], 4) == 0) { + handled = 1; + resp = mgcp_requests[i].handle_request(cfg, msg); + break; + } + if (!handled) { + LOGP(DMGCP, LOGL_NOTICE, "MSG with type: '%.4s' not handled\n", &msg->l2h[0]); + } + } + + return resp; +} + +/* string tokenizer for the poor */ +static int find_msg_pointers(struct msgb *msg, struct mgcp_msg_ptr *ptrs, int ptrs_length) +{ + int i, found = 0; + + int whitespace = 1; + for (i = 0; i < msgb_l3len(msg) && ptrs_length > 0; ++i) { + /* if we have a space we found an end */ + if (msg->l3h[i] == ' ' || msg->l3h[i] == '\r' || msg->l3h[i] == '\n') { + if (!whitespace) { + ++found; + whitespace = 1; + ptrs->length = i - ptrs->start - 1; + ++ptrs; + --ptrs_length; + } else { + /* skip any number of whitespace */ + } + + /* line end... stop */ + if (msg->l3h[i] == '\r' || msg->l3h[i] == '\n') + break; + } else if (msg->l3h[i] == '\r' || msg->l3h[i] == '\n') { + /* line end, be done */ + break; + } else if (whitespace) { + whitespace = 0; + ptrs->start = i; + } + } + + if (ptrs_length == 0) + return -1; + return found; +} + +/** + * We have a null terminated string with the endpoint name here. We only + * support two kinds. Simple ones as seen on the BSC level and the ones + * seen on the trunk side. + */ +static struct mgcp_endpoint *find_e1_endpoint(struct mgcp_config *cfg, + const char *mgcp) +{ + char *rest = NULL; + struct mgcp_trunk_config *tcfg; + int trunk, endp; + + trunk = strtoul(mgcp + 6, &rest, 10); + if (rest == NULL || rest[0] != '/' || trunk < 1) { + LOGP(DMGCP, LOGL_ERROR, "Wrong trunk name '%s'\n", mgcp); + return NULL; + } + + endp = strtoul(rest + 1, &rest, 10); + if (rest == NULL || rest[0] != '@') { + LOGP(DMGCP, LOGL_ERROR, "Wrong endpoint name '%s'\n", mgcp); + return NULL; + } + + /* signalling is on timeslot 1 */ + if (endp == 1) + return NULL; + + tcfg = mgcp_trunk_num(cfg, trunk); + if (!tcfg) { + LOGP(DMGCP, LOGL_ERROR, "The trunk %d is not declared.\n", trunk); + return NULL; + } + + if (!tcfg->endpoints) { + LOGP(DMGCP, LOGL_ERROR, "Endpoints of trunk %d not allocated.\n", trunk); + return NULL; + } + + if (endp < 1 || endp >= tcfg->number_endpoints) { + LOGP(DMGCP, LOGL_ERROR, "Failed to find endpoint '%s'\n", mgcp); + return NULL; + } + + return &tcfg->endpoints[endp]; +} + +static struct mgcp_endpoint *find_endpoint(struct mgcp_config *cfg, const char *mgcp) +{ + char *endptr = NULL; + unsigned int gw = INT_MAX; + + if (strncmp(mgcp, "ds/e1", 5) == 0) { + return find_e1_endpoint(cfg, mgcp); + } else { + gw = strtoul(mgcp, &endptr, 16); + if (gw > 0 && gw < cfg->trunk.number_endpoints && strcmp(endptr, "@mgw") == 0) + return &cfg->trunk.endpoints[gw]; + } + + LOGP(DMGCP, LOGL_ERROR, "Not able to find endpoint: '%s'\n", mgcp); + return NULL; +} + +int mgcp_analyze_header(struct mgcp_config *cfg, struct msgb *msg, + struct mgcp_msg_ptr *ptr, int size, + const char **transaction_id, struct mgcp_endpoint **endp) +{ + int found; + + *transaction_id = "000000"; + + if (size < 3) { + LOGP(DMGCP, LOGL_ERROR, "Not enough space in ptr\n"); + return -1; + } + + found = find_msg_pointers(msg, ptr, size); + + if (found <= 3) { + LOGP(DMGCP, LOGL_ERROR, "Gateway: Not enough params. Found: %d\n", found); + return -1; + } + + /* + * replace the space with \0. the main method gurantess that + * we still have + 1 for null termination + */ + msg->l3h[ptr[3].start + ptr[3].length + 1] = '\0'; + msg->l3h[ptr[2].start + ptr[2].length + 1] = '\0'; + msg->l3h[ptr[1].start + ptr[1].length + 1] = '\0'; + msg->l3h[ptr[0].start + ptr[0].length + 1] = '\0'; + + if (strncmp("1.0", (const char *)&msg->l3h[ptr[3].start], 3) != 0 + || strncmp("MGCP", (const char *)&msg->l3h[ptr[2].start], 4) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Wrong MGCP version. Not handling: '%s' '%s'\n", + (const char *)&msg->l3h[ptr[3].start], + (const char *)&msg->l3h[ptr[2].start]); + return -1; + } + + *transaction_id = (const char *)&msg->l3h[ptr[0].start]; + if (endp) { + *endp = find_endpoint(cfg, (const char *)&msg->l3h[ptr[1].start]); + return *endp == NULL; + } + return 0; +} + +static int verify_call_id(const struct mgcp_endpoint *endp, + const char *callid) +{ + if (strcmp(endp->callid, callid) != 0) { + LOGP(DMGCP, LOGL_ERROR, "CallIDs does not match on 0x%x. '%s' != '%s'\n", + ENDPOINT_NUMBER(endp), endp->callid, callid); + return -1; + } + + return 0; +} + +static int verify_ci(const struct mgcp_endpoint *endp, + const char *_ci) +{ + uint32_t ci = strtoul(_ci, NULL, 10); + + if (ci != endp->ci) { + LOGP(DMGCP, LOGL_ERROR, "ConnectionIdentifiers do not match on 0x%x. %u != %s\n", + ENDPOINT_NUMBER(endp), endp->ci, _ci); + return -1; + } + + return 0; +} + +static struct msgb *handle_audit_endpoint(struct mgcp_config *cfg, struct msgb *msg) +{ + struct mgcp_msg_ptr data_ptrs[6]; + int found; + const char *trans_id; + struct mgcp_endpoint *endp; + + found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp); + if (found != 0) + return create_err_response(500, "AUEP", trans_id); + else + return create_ok_response(200, "AUEP", trans_id); +} + +static int parse_conn_mode(const char *msg, int *conn_mode) +{ + int ret = 0; + if (strcmp(msg, "recvonly") == 0) + *conn_mode = MGCP_CONN_RECV_ONLY; + else if (strcmp(msg, "sendrecv") == 0) + *conn_mode = MGCP_CONN_RECV_SEND; + else if (strcmp(msg, "sendonly") == 0) + *conn_mode = MGCP_CONN_SEND_ONLY; + else if (strcmp(msg, "loopback") == 0) + *conn_mode = MGCP_CONN_LOOPBACK; + else { + LOGP(DMGCP, LOGL_ERROR, "Unknown connection mode: '%s'\n", msg); + ret = -1; + } + + return ret; +} + +static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_rtp_end *end, + struct mgcp_port_range *range, + int (*alloc)(struct mgcp_endpoint *endp, int port)) +{ + int i; + + if (range->mode == PORT_ALLOC_STATIC) { + end->local_alloc = PORT_ALLOC_STATIC; + return 0; + } + + /* attempt to find a port */ + for (i = 0; i < 200; ++i) { + int rc; + + if (range->last_port >= range->range_end) + range->last_port = range->range_start; + + rc = alloc(endp, range->last_port); + + range->last_port += 2; + if (rc == 0) { + end->local_alloc = PORT_ALLOC_DYNAMIC; + return 0; + } + + } + + LOGP(DMGCP, LOGL_ERROR, "Allocating a RTP/RTCP port failed 200 times 0x%x.\n", + ENDPOINT_NUMBER(endp)); + return -1; +} + +static int allocate_ports(struct mgcp_endpoint *endp) +{ + if (allocate_port(endp, &endp->net_end, &endp->cfg->net_ports, + mgcp_bind_net_rtp_port) != 0) + return -1; + + if (allocate_port(endp, &endp->bts_end, &endp->cfg->bts_ports, + mgcp_bind_bts_rtp_port) != 0) { + mgcp_rtp_end_reset(&endp->net_end); + return -1; + } + + if (endp->cfg->transcoder_ip && endp->tcfg->trunk_type == MGCP_TRUNK_VIRTUAL) { + if (allocate_port(endp, &endp->trans_net, + &endp->cfg->transcoder_ports, + mgcp_bind_trans_net_rtp_port) != 0) { + mgcp_rtp_end_reset(&endp->net_end); + mgcp_rtp_end_reset(&endp->bts_end); + return -1; + } + + if (allocate_port(endp, &endp->trans_bts, + &endp->cfg->transcoder_ports, + mgcp_bind_trans_bts_rtp_port) != 0) { + mgcp_rtp_end_reset(&endp->net_end); + mgcp_rtp_end_reset(&endp->bts_end); + mgcp_rtp_end_reset(&endp->trans_net); + return -1; + } + + /* remember that we have set up transcoding */ + endp->is_transcoded = 1; + } + + return 0; +} + +static struct msgb *handle_create_con(struct mgcp_config *cfg, struct msgb *msg) +{ + struct mgcp_msg_ptr data_ptrs[6]; + int found, i, line_start; + const char *trans_id; + struct mgcp_trunk_config *tcfg; + struct mgcp_endpoint *endp; + int error_code = 400; + + found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp); + if (found != 0) + return create_err_response(510, "CRCX", trans_id); + + tcfg = endp->tcfg; + + if (endp->allocated) { + if (tcfg->force_realloc) { + LOGP(DMGCP, LOGL_NOTICE, "Endpoint 0x%x already allocated. Forcing realloc.\n", + ENDPOINT_NUMBER(endp)); + mgcp_free_endp(endp); + if (cfg->realloc_cb) + cfg->realloc_cb(tcfg, ENDPOINT_NUMBER(endp)); + } else { + LOGP(DMGCP, LOGL_ERROR, "Endpoint is already used. 0x%x\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(400, "CRCX", trans_id); + } + } + + /* parse CallID C: and LocalParameters L: */ + MSG_TOKENIZE_START + switch (msg->l3h[line_start]) { + case 'L': + endp->local_options = talloc_strdup(tcfg->endpoints, + (const char *)&msg->l3h[line_start + 3]); + break; + case 'C': + endp->callid = talloc_strdup(tcfg->endpoints, + (const char *)&msg->l3h[line_start + 3]); + break; + case 'M': + if (parse_conn_mode((const char *)&msg->l3h[line_start + 3], + &endp->conn_mode) != 0) { + error_code = 517; + goto error2; + } + + endp->orig_mode = endp->conn_mode; + break; + default: + LOGP(DMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n", + msg->l3h[line_start], msg->l3h[line_start], + ENDPOINT_NUMBER(endp)); + break; + } + MSG_TOKENIZE_END + + /* initialize */ + endp->net_end.rtp_port = endp->net_end.rtcp_port = endp->bts_end.rtp_port = endp->bts_end.rtcp_port = 0; + + /* set to zero until we get the info */ + memset(&endp->net_end.addr, 0, sizeof(endp->net_end.addr)); + + /* bind to the port now */ + if (allocate_ports(endp) != 0) + goto error2; + + /* assign a local call identifier or fail */ + endp->ci = generate_call_id(cfg); + if (endp->ci == CI_UNUSED) + goto error2; + + endp->allocated = 1; + endp->bts_end.payload_type = tcfg->audio_payload; + + /* policy CB */ + if (cfg->policy_cb) { + switch (cfg->policy_cb(tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_CRCX, trans_id)) { + case MGCP_POLICY_REJECT: + LOGP(DMGCP, LOGL_NOTICE, "CRCX rejected by policy on 0x%x\n", + ENDPOINT_NUMBER(endp)); + mgcp_free_endp(endp); + return create_err_response(400, "CRCX", trans_id); + break; + case MGCP_POLICY_DEFER: + /* stop processing */ + create_transcoder(endp); + return NULL; + break; + case MGCP_POLICY_CONT: + /* just continue */ + break; + } + } + + LOGP(DMGCP, LOGL_DEBUG, "Creating endpoint on: 0x%x CI: %u port: %u/%u\n", + ENDPOINT_NUMBER(endp), endp->ci, + endp->net_end.local_port, endp->bts_end.local_port); + if (cfg->change_cb) + cfg->change_cb(tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_CRCX); + + create_transcoder(endp); + return create_response_with_sdp(endp, "CRCX", trans_id); +error: + LOGP(DMGCP, LOGL_ERROR, "Malformed line: %s on 0x%x with: line_start: %d %d\n", + hexdump(msg->l3h, msgb_l3len(msg)), + ENDPOINT_NUMBER(endp), line_start, i); + return create_err_response(error_code, "CRCX", trans_id); + +error2: + mgcp_free_endp(endp); + LOGP(DMGCP, LOGL_NOTICE, "Resource error on 0x%x\n", ENDPOINT_NUMBER(endp)); + return create_err_response(error_code, "CRCX", trans_id); +} + +static struct msgb *handle_modify_con(struct mgcp_config *cfg, struct msgb *msg) +{ + struct mgcp_msg_ptr data_ptrs[6]; + int found, i, line_start; + const char *trans_id; + struct mgcp_endpoint *endp; + int error_code = 500; + int silent = 0; + + found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp); + if (found != 0) + return create_err_response(510, "MDCX", trans_id); + + if (endp->ci == CI_UNUSED) { + LOGP(DMGCP, LOGL_ERROR, "Endpoint is not holding a connection. 0x%x\n", ENDPOINT_NUMBER(endp)); + return create_err_response(400, "MDCX", trans_id); + } + + MSG_TOKENIZE_START + switch (msg->l3h[line_start]) { + case 'C': { + if (verify_call_id(endp, (const char *)&msg->l3h[line_start + 3]) != 0) + goto error3; + break; + } + case 'I': { + if (verify_ci(endp, (const char *)&msg->l3h[line_start + 3]) != 0) + goto error3; + break; + } + case 'L': + /* skip */ + break; + case 'M': + if (parse_conn_mode((const char *)&msg->l3h[line_start + 3], + &endp->conn_mode) != 0) { + error_code = 517; + goto error3; + } + endp->orig_mode = endp->conn_mode; + break; + case 'Z': + silent = strcmp("noanswer", (const char *)&msg->l3h[line_start + 3]) == 0; + break; + case '\0': + /* SDP file begins */ + break; + case 'a': + case 'o': + case 's': + case 't': + case 'v': + /* skip these SDP attributes */ + break; + case 'm': { + int port; + int payload; + const char *param = (const char *)&msg->l3h[line_start]; + + if (sscanf(param, "m=audio %d RTP/AVP %d", &port, &payload) == 2) { + endp->net_end.rtp_port = htons(port); + endp->net_end.rtcp_port = htons(port + 1); + endp->net_end.payload_type = payload; + } + break; + } + case 'c': { + char ipv4[16]; + const char *param = (const char *)&msg->l3h[line_start]; + + if (sscanf(param, "c=IN IP4 %15s", ipv4) == 1) { + inet_aton(ipv4, &endp->net_end.addr); + } + break; + } + default: + LOGP(DMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n", + msg->l3h[line_start], msg->l3h[line_start], + ENDPOINT_NUMBER(endp)); + break; + } + MSG_TOKENIZE_END + + /* policy CB */ + if (cfg->policy_cb) { + switch (cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_MDCX, trans_id)) { + case MGCP_POLICY_REJECT: + LOGP(DMGCP, LOGL_NOTICE, "MDCX rejected by policy on 0x%x\n", + ENDPOINT_NUMBER(endp)); + if (silent) + goto out_silent; + return create_err_response(400, "MDCX", trans_id); + break; + case MGCP_POLICY_DEFER: + /* stop processing */ + return NULL; + break; + case MGCP_POLICY_CONT: + /* just continue */ + break; + } + } + + /* modify */ + LOGP(DMGCP, LOGL_DEBUG, "Modified endpoint on: 0x%x Server: %s:%u\n", + ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port)); + if (cfg->change_cb) + cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_MDCX); + if (silent) + goto out_silent; + + return create_response_with_sdp(endp, "MDCX", trans_id); + +error: + LOGP(DMGCP, LOGL_ERROR, "Malformed line: %s on 0x%x with: line_start: %d %d %d\n", + hexdump(msg->l3h, msgb_l3len(msg)), + ENDPOINT_NUMBER(endp), line_start, i, msg->l3h[line_start]); + return create_err_response(error_code, "MDCX", trans_id); + +error3: + return create_err_response(error_code, "MDCX", trans_id); + + +out_silent: + return NULL; +} + +static struct msgb *handle_delete_con(struct mgcp_config *cfg, struct msgb *msg) +{ + struct mgcp_msg_ptr data_ptrs[6]; + int found, i, line_start; + const char *trans_id; + struct mgcp_endpoint *endp; + int error_code = 400; + int silent = 0; + + found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp); + if (found != 0) + return create_err_response(error_code, "DLCX", trans_id); + + if (!endp->allocated) { + LOGP(DMGCP, LOGL_ERROR, "Endpoint is not used. 0x%x\n", ENDPOINT_NUMBER(endp)); + return create_err_response(400, "DLCX", trans_id); + } + + MSG_TOKENIZE_START + switch (msg->l3h[line_start]) { + case 'C': { + if (verify_call_id(endp, (const char *)&msg->l3h[line_start + 3]) != 0) + goto error3; + break; + } + case 'I': { + if (verify_ci(endp, (const char *)&msg->l3h[line_start + 3]) != 0) + goto error3; + break; + case 'Z': + silent = strcmp("noanswer", (const char *)&msg->l3h[line_start + 3]) == 0; + break; + } + default: + LOGP(DMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n", + msg->l3h[line_start], msg->l3h[line_start], + ENDPOINT_NUMBER(endp)); + break; + } + MSG_TOKENIZE_END + + /* policy CB */ + if (cfg->policy_cb) { + switch (cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_DLCX, trans_id)) { + case MGCP_POLICY_REJECT: + LOGP(DMGCP, LOGL_NOTICE, "DLCX rejected by policy on 0x%x\n", + ENDPOINT_NUMBER(endp)); + if (silent) + goto out_silent; + return create_err_response(400, "DLCX", trans_id); + break; + case MGCP_POLICY_DEFER: + /* stop processing */ + delete_transcoder(endp); + return NULL; + break; + case MGCP_POLICY_CONT: + /* just continue */ + break; + } + } + + /* free the connection */ + LOGP(DMGCP, LOGL_DEBUG, "Deleted endpoint on: 0x%x Server: %s:%u\n", + ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port)); + + delete_transcoder(endp); + mgcp_free_endp(endp); + if (cfg->change_cb) + cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_DLCX); + + if (silent) + goto out_silent; + return create_ok_response(250, "DLCX", trans_id); + +error: + LOGP(DMGCP, LOGL_ERROR, "Malformed line: %s on 0x%x with: line_start: %d %d\n", + hexdump(msg->l3h, msgb_l3len(msg)), + ENDPOINT_NUMBER(endp), line_start, i); + return create_err_response(error_code, "DLCX", trans_id); + +error3: + return create_err_response(error_code, "DLCX", trans_id); + +out_silent: + return NULL; +} + +static struct msgb *handle_rsip(struct mgcp_config *cfg, struct msgb *msg) +{ + if (cfg->reset_cb) + cfg->reset_cb(cfg); + return NULL; +} + +/* + * This can request like DTMF detection and forward, fax detection... it + * can also request when the notification should be send and such. We don't + * do this right now. + */ +static struct msgb *handle_noti_req(struct mgcp_config *cfg, struct msgb *msg) +{ + struct mgcp_msg_ptr data_ptrs[6]; + const char *trans_id; + struct mgcp_endpoint *endp; + int found; + + found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp); + if (found != 0) + return create_err_response(400, "RQNT", trans_id); + + if (!endp->allocated) { + LOGP(DMGCP, LOGL_ERROR, "Endpoint is not used. 0x%x\n", ENDPOINT_NUMBER(endp)); + return create_err_response(400, "RQNT", trans_id); + } + return create_ok_response(200, "RQNT", trans_id); +} + +struct mgcp_config *mgcp_config_alloc(void) +{ + struct mgcp_config *cfg; + + cfg = talloc_zero(NULL, struct mgcp_config); + if (!cfg) { + LOGP(DMGCP, LOGL_FATAL, "Failed to allocate config.\n"); + return NULL; + } + + cfg->source_port = 2427; + cfg->source_addr = talloc_strdup(cfg, "0.0.0.0"); + + cfg->transcoder_remote_base = 4000; + + cfg->bts_ports.base_port = RTP_PORT_DEFAULT; + cfg->net_ports.base_port = RTP_PORT_NET_DEFAULT; + + /* default trunk handling */ + cfg->trunk.cfg = cfg; + cfg->trunk.trunk_nr = 0; + cfg->trunk.trunk_type = MGCP_TRUNK_VIRTUAL; + cfg->trunk.audio_name = talloc_strdup(cfg, "AMR/8000"); + cfg->trunk.audio_payload = 126; + + INIT_LLIST_HEAD(&cfg->trunks); + + return cfg; +} + +struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int nr) +{ + struct mgcp_trunk_config *trunk; + + trunk = talloc_zero(cfg, struct mgcp_trunk_config); + if (!trunk) { + LOGP(DMGCP, LOGL_ERROR, "Failed to allocate.\n"); + return NULL; + } + + trunk->cfg = cfg; + trunk->trunk_type = MGCP_TRUNK_E1; + trunk->trunk_nr = nr; + trunk->audio_name = talloc_strdup(cfg, "AMR/8000"); + trunk->audio_payload = 126; + trunk->number_endpoints = 33; + llist_add_tail(&trunk->entry, &cfg->trunks); + return trunk; +} + +struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index) +{ + struct mgcp_trunk_config *trunk; + + llist_for_each_entry(trunk, &cfg->trunks, entry) + if (trunk->trunk_nr == index) + return trunk; + + return NULL; +} + +static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end) +{ + if (end->local_alloc == PORT_ALLOC_DYNAMIC) { + mgcp_free_rtp_port(end); + end->local_port = 0; + } + + end->packets = 0; + memset(&end->addr, 0, sizeof(end->addr)); + end->rtp_port = end->rtcp_port = 0; + end->payload_type = -1; + end->local_alloc = -1; +} + +static void mgcp_rtp_end_init(struct mgcp_rtp_end *end) +{ + mgcp_rtp_end_reset(end); + end->rtp.fd = -1; + end->rtcp.fd = -1; +} + +int mgcp_endpoints_allocate(struct mgcp_trunk_config *tcfg) +{ + int i; + + /* Initialize all endpoints */ + tcfg->endpoints = _talloc_zero_array(tcfg->cfg, + sizeof(struct mgcp_endpoint), + tcfg->number_endpoints, "endpoints"); + if (!tcfg->endpoints) + return -1; + + for (i = 0; i < tcfg->number_endpoints; ++i) { + tcfg->endpoints[i].ci = CI_UNUSED; + tcfg->endpoints[i].cfg = tcfg->cfg; + tcfg->endpoints[i].tcfg = tcfg; + mgcp_rtp_end_init(&tcfg->endpoints[i].net_end); + mgcp_rtp_end_init(&tcfg->endpoints[i].bts_end); + mgcp_rtp_end_init(&tcfg->endpoints[i].trans_net); + mgcp_rtp_end_init(&tcfg->endpoints[i].trans_bts); + } + + return 0; +} + +void mgcp_free_endp(struct mgcp_endpoint *endp) +{ + LOGP(DMGCP, LOGL_DEBUG, "Deleting endpoint on: 0x%x\n", ENDPOINT_NUMBER(endp)); + endp->ci = CI_UNUSED; + endp->allocated = 0; + + if (endp->callid) { + talloc_free(endp->callid); + endp->callid = NULL; + } + + if (endp->local_options) { + talloc_free(endp->local_options); + endp->local_options = NULL; + } + + mgcp_rtp_end_reset(&endp->bts_end); + mgcp_rtp_end_reset(&endp->net_end); + mgcp_rtp_end_reset(&endp->trans_net); + mgcp_rtp_end_reset(&endp->trans_bts); + endp->is_transcoded = 0; + + memset(&endp->net_state, 0, sizeof(endp->net_state)); + memset(&endp->bts_state, 0, sizeof(endp->bts_state)); + + endp->conn_mode = endp->orig_mode = MGCP_CONN_NONE; + endp->allow_patch = 0; + + memset(&endp->taps, 0, sizeof(endp->taps)); +} + +static int send_trans(struct mgcp_config *cfg, const char *buf, int len) +{ + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr = cfg->transcoder_in; + addr.sin_port = htons(2427); + return sendto(cfg->gw_fd.bfd.fd, buf, len, 0, + (struct sockaddr *) &addr, sizeof(addr)); +} + +static void send_msg(struct mgcp_endpoint *endp, int endpoint, int port, + const char *msg, const char *mode) +{ + char buf[2096]; + int len; + + /* hardcoded to AMR right now, we do not know the real type at this point */ + len = snprintf(buf, sizeof(buf), + "%s 42 %x@mgw MGCP 1.0\r\n" + "C: 4256\r\n" + "M: %s\r\n" + "\r\n" + "c=IN IP4 %s\r\n" + "m=audio %d RTP/AVP %d\r\n" + "a=rtpmap:%d %s\r\n", + msg, endpoint, mode, endp->cfg->source_addr, + port, endp->tcfg->audio_payload, + endp->tcfg->audio_payload, endp->tcfg->audio_name); + + if (len < 0) + return; + + buf[sizeof(buf) - 1] = '\0'; + + send_trans(endp->cfg, buf, len); +} + +static void send_dlcx(struct mgcp_endpoint *endp, int endpoint) +{ + char buf[2096]; + int len; + + len = snprintf(buf, sizeof(buf), + "DLCX 43 %x@mgw MGCP 1.0\r\n" + "C: 4256\r\n" + , endpoint); + + if (len < 0) + return; + + buf[sizeof(buf) - 1] = '\0'; + + send_trans(endp->cfg, buf, len); +} + +static void create_transcoder(struct mgcp_endpoint *endp) +{ + int port; + int in_endp = ENDPOINT_NUMBER(endp); + int out_endp = endp_back_channel(in_endp); + + if (!endp->is_transcoded) + return; + + send_msg(endp, in_endp, endp->trans_bts.local_port, "CRCX", "sendrecv"); + send_msg(endp, in_endp, endp->trans_bts.local_port, "MDCX", "sendrecv"); + send_msg(endp, out_endp, endp->trans_net.local_port, "CRCX", "sendrecv"); + send_msg(endp, out_endp, endp->trans_net.local_port, "MDCX", "sendrecv"); + + port = rtp_calculate_port(in_endp, endp->cfg->transcoder_remote_base); + endp->trans_bts.rtp_port = htons(port); + endp->trans_bts.rtcp_port = htons(port + 1); + + port = rtp_calculate_port(out_endp, endp->cfg->transcoder_remote_base); + endp->trans_net.rtp_port = htons(port); + endp->trans_net.rtcp_port = htons(port + 1); +} + +static void delete_transcoder(struct mgcp_endpoint *endp) +{ + int in_endp = ENDPOINT_NUMBER(endp); + int out_endp = endp_back_channel(in_endp); + + if (!endp->is_transcoded) + return; + + send_dlcx(endp, in_endp); + send_dlcx(endp, out_endp); +} + +int mgcp_reset_transcoder(struct mgcp_config *cfg) +{ + if (!cfg->transcoder_ip) + return 0; + + static const char mgcp_reset[] = { + "RSIP 1 13@mgw MGCP 1.0\r\n" + }; + + return send_trans(cfg, mgcp_reset, sizeof mgcp_reset -1); +} diff --git a/src/libmgcp/mgcp_vty.c b/src/libmgcp/mgcp_vty.c new file mode 100644 index 000000000..c299a98cc --- /dev/null +++ b/src/libmgcp/mgcp_vty.c @@ -0,0 +1,742 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The protocol implementation */ + +/* + * (C) 2009-2011 by Holger Hans Peter Freyther + * (C) 2009-2011 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include + +static struct mgcp_config *g_cfg = NULL; + +static struct mgcp_trunk_config *find_trunk(struct mgcp_config *cfg, int nr) +{ + struct mgcp_trunk_config *trunk; + + if (nr == 0) + trunk = &cfg->trunk; + else + trunk = mgcp_trunk_num(cfg, nr); + + return trunk; +} + +/* + * vty code for mgcp below + */ +struct cmd_node mgcp_node = { + MGCP_NODE, + "%s(mgcp)#", + 1, +}; + +struct cmd_node trunk_node = { + TRUNK_NODE, + "%s(trunk)#", + 1, +}; + +static int config_write_mgcp(struct vty *vty) +{ + vty_out(vty, "mgcp%s", VTY_NEWLINE); + if (g_cfg->local_ip) + vty_out(vty, " local ip %s%s", g_cfg->local_ip, VTY_NEWLINE); + if (g_cfg->bts_ip && strlen(g_cfg->bts_ip) != 0) + vty_out(vty, " bts ip %s%s", g_cfg->bts_ip, VTY_NEWLINE); + vty_out(vty, " bind ip %s%s", g_cfg->source_addr, VTY_NEWLINE); + vty_out(vty, " bind port %u%s", g_cfg->source_port, VTY_NEWLINE); + + if (g_cfg->bts_ports.mode == PORT_ALLOC_STATIC) + vty_out(vty, " rtp bts-base %u%s", g_cfg->bts_ports.base_port, VTY_NEWLINE); + else + vty_out(vty, " rtp bts-range %u %u%s", + g_cfg->bts_ports.range_start, g_cfg->bts_ports.range_end, VTY_NEWLINE); + + if (g_cfg->net_ports.mode == PORT_ALLOC_STATIC) + vty_out(vty, " rtp net-base %u%s", g_cfg->net_ports.base_port, VTY_NEWLINE); + else + vty_out(vty, " rtp net-range %u %u%s", + g_cfg->net_ports.range_start, g_cfg->net_ports.range_end, VTY_NEWLINE); + + vty_out(vty, " rtp ip-dscp %d%s", g_cfg->endp_dscp, VTY_NEWLINE); + if (g_cfg->trunk.audio_payload != -1) + vty_out(vty, " sdp audio payload number %d%s", + g_cfg->trunk.audio_payload, VTY_NEWLINE); + if (g_cfg->trunk.audio_name) + vty_out(vty, " sdp audio payload name %s%s", + g_cfg->trunk.audio_name, VTY_NEWLINE); + vty_out(vty, " loop %u%s", !!g_cfg->trunk.audio_loop, VTY_NEWLINE); + vty_out(vty, " number endpoints %u%s", g_cfg->trunk.number_endpoints - 1, VTY_NEWLINE); + if (g_cfg->call_agent_addr) + vty_out(vty, " call agent ip %s%s", g_cfg->call_agent_addr, VTY_NEWLINE); + if (g_cfg->transcoder_ip) + vty_out(vty, " transcoder-mgw %s%s", g_cfg->transcoder_ip, VTY_NEWLINE); + + if (g_cfg->transcoder_ports.mode == PORT_ALLOC_STATIC) + vty_out(vty, " rtp transcoder-base %u%s", g_cfg->transcoder_ports.base_port, VTY_NEWLINE); + else + vty_out(vty, " rtp transcoder-range %u %u%s", + g_cfg->transcoder_ports.range_start, g_cfg->transcoder_ports.range_end, VTY_NEWLINE); + vty_out(vty, " transcoder-remote-base %u%s", g_cfg->transcoder_remote_base, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +static void dump_trunk(struct vty *vty, struct mgcp_trunk_config *cfg) +{ + int i; + + vty_out(vty, "%s trunk nr %d with %d endpoints:%s", + cfg->trunk_type == MGCP_TRUNK_VIRTUAL ? "Virtual" : "E1", + cfg->trunk_nr, cfg->number_endpoints - 1, VTY_NEWLINE); + + if (!cfg->endpoints) { + vty_out(vty, "No endpoints allocated yet.%s", VTY_NEWLINE); + return; + } + + for (i = 1; i < cfg->number_endpoints; ++i) { + struct mgcp_endpoint *endp = &cfg->endpoints[i]; + vty_out(vty, + " Endpoint 0x%.2x: CI: %d net: %u/%u bts: %u/%u on %s " + "traffic received bts: %u/%u remote: %u/%u transcoder: %u/%u%s", + i, endp->ci, + ntohs(endp->net_end.rtp_port), ntohs(endp->net_end.rtcp_port), + ntohs(endp->bts_end.rtp_port), ntohs(endp->bts_end.rtcp_port), + inet_ntoa(endp->bts_end.addr), + endp->bts_end.packets, endp->bts_state.lost_no, + endp->net_end.packets, endp->net_state.lost_no, + endp->trans_net.packets, endp->trans_bts.packets, + VTY_NEWLINE); + } +} + +DEFUN(show_mcgp, show_mgcp_cmd, "show mgcp", + SHOW_STR "Display information about the MGCP Media Gateway") +{ + struct mgcp_trunk_config *trunk; + + dump_trunk(vty, &g_cfg->trunk); + + llist_for_each_entry(trunk, &g_cfg->trunks, entry) + dump_trunk(vty, trunk); + + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp, + cfg_mgcp_cmd, + "mgcp", + "Configure the MGCP") +{ + vty->node = MGCP_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_local_ip, + cfg_mgcp_local_ip_cmd, + "local ip A.B.C.D", + "Set the IP to be used in SDP records") +{ + bsc_replace_string(g_cfg, &g_cfg->local_ip, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_bts_ip, + cfg_mgcp_bts_ip_cmd, + "bts ip A.B.C.D", + "Set the IP of the BTS for RTP forwarding") +{ + bsc_replace_string(g_cfg, &g_cfg->bts_ip, argv[0]); + inet_aton(g_cfg->bts_ip, &g_cfg->bts_in); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_bind_ip, + cfg_mgcp_bind_ip_cmd, + "bind ip A.B.C.D", + "Bind the MGCP to this local addr") +{ + bsc_replace_string(g_cfg, &g_cfg->source_addr, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_bind_port, + cfg_mgcp_bind_port_cmd, + "bind port <0-65534>", + "Bind the MGCP to this port") +{ + unsigned int port = atoi(argv[0]); + g_cfg->source_port = port; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_bind_early, + cfg_mgcp_bind_early_cmd, + "bind early (0|1)", + "Bind all RTP ports early") +{ + vty_out(vty, "bind early is deprecated, remove it from the config.\n"); + return CMD_WARNING; +} + +static void parse_base(struct mgcp_port_range *range, const char **argv) +{ + unsigned int port = atoi(argv[0]); + range->mode = PORT_ALLOC_STATIC; + range->base_port = port; +} + +static void parse_range(struct mgcp_port_range *range, const char **argv) +{ + range->mode = PORT_ALLOC_DYNAMIC; + range->range_start = atoi(argv[0]); + range->range_end = atoi(argv[1]); + range->last_port = g_cfg->bts_ports.range_start; +} + + +DEFUN(cfg_mgcp_rtp_bts_base_port, + cfg_mgcp_rtp_bts_base_port_cmd, + "rtp bts-base <0-65534>", + "Base port to use") +{ + parse_base(&g_cfg->bts_ports, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_bts_range, + cfg_mgcp_rtp_bts_range_cmd, + "rtp bts-range <0-65534> <0-65534>", + "Range of ports to allocate for endpoints\n" + "Start of the range of ports\n" "End of the range of ports\n") +{ + parse_range(&g_cfg->bts_ports, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_net_range, + cfg_mgcp_rtp_net_range_cmd, + "rtp net-range <0-65534> <0-65534>", + "Range of ports to allocate for endpoints\n" + "Start of the range of ports\n" "End of the range of ports\n") +{ + parse_range(&g_cfg->net_ports, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_net_base_port, + cfg_mgcp_rtp_net_base_port_cmd, + "rtp net-base <0-65534>", + "Base port to use for network port\n" "Port\n") +{ + parse_base(&g_cfg->net_ports, argv); + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_rtp_bts_base_port, cfg_mgcp_rtp_base_port_cmd, + "rtp base <0-65534>", "Base port to use") + +DEFUN(cfg_mgcp_rtp_transcoder_range, + cfg_mgcp_rtp_transcoder_range_cmd, + "rtp transcoder-range <0-65534> <0-65534>", + "Range of ports to allocate for the transcoder\n" + "Start of the range of ports\n" "End of the range of ports\n") +{ + parse_range(&g_cfg->transcoder_ports, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_transcoder_base, + cfg_mgcp_rtp_transcoder_base_cmd, + "rtp transcoder-base <0-65534>", + "Base port for the transcoder range\n" "Port\n") +{ + parse_base(&g_cfg->transcoder_ports, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_ip_dscp, + cfg_mgcp_rtp_ip_dscp_cmd, + "rtp ip-dscp <0-255>", + "Set the IP_TOS socket attribute on the RTP/RTCP sockets.\n" "The DSCP value.") +{ + int dscp = atoi(argv[0]); + g_cfg->endp_dscp = dscp; + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_rtp_ip_dscp, cfg_mgcp_rtp_ip_tos_cmd, + "rtp ip-tos <0-255>", + "Set the IP_TOS socket attribute on the RTP/RTCP sockets.\n" "The DSCP value.") + + +DEFUN(cfg_mgcp_sdp_payload_number, + cfg_mgcp_sdp_payload_number_cmd, + "sdp audio payload number <1-255>", + "Set the audio codec to use") +{ + unsigned int payload = atoi(argv[0]); + g_cfg->trunk.audio_payload = payload; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_sdp_payload_name, + cfg_mgcp_sdp_payload_name_cmd, + "sdp audio payload name NAME", + "Set the audio name to use") +{ + bsc_replace_string(g_cfg, &g_cfg->trunk.audio_name, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_loop, + cfg_mgcp_loop_cmd, + "loop (0|1)", + "Loop the audio") +{ + g_cfg->trunk.audio_loop = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_number_endp, + cfg_mgcp_number_endp_cmd, + "number endpoints <0-65534>", + "The number of endpoints to allocate. This is not dynamic.") +{ + /* + 1 as we start counting at one */ + g_cfg->trunk.number_endpoints = atoi(argv[0]) + 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_agent_addr, + cfg_mgcp_agent_addr_cmd, + "call agent ip IP", + "Set the address of the call agent.") +{ + bsc_replace_string(g_cfg, &g_cfg->call_agent_addr, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_transcoder, + cfg_mgcp_transcoder_cmd, + "transcoder-mgw A.B.C.D", + "Use a MGW to detranscoder RTP\n" + "The IP address of the MGW") +{ + bsc_replace_string(g_cfg, &g_cfg->transcoder_ip, argv[0]); + inet_aton(g_cfg->transcoder_ip, &g_cfg->transcoder_in); + + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_transcoder, + cfg_mgcp_no_transcoder_cmd, + NO_STR "transcoder-mgw", + "Disable the transcoding\n") +{ + if (g_cfg->transcoder_ip) { + LOGP(DMGCP, LOGL_NOTICE, "Disabling transcoding on future calls.\n"); + talloc_free(g_cfg->transcoder_ip); + g_cfg->transcoder_ip = NULL; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_transcoder_remote_base, + cfg_mgcp_transcoder_remote_base_cmd, + "transcoder-remote-base <0-65534>", + "Set the base port for the transcoder\n" "The RTP base port on the transcoder") +{ + g_cfg->transcoder_remote_base = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_trunk, cfg_mgcp_trunk_cmd, + "trunk <1-64>", + "Configure a SS7 trunk\n" "Trunk Nr\n") +{ + struct mgcp_trunk_config *trunk; + int index = atoi(argv[0]); + + trunk = mgcp_trunk_num(g_cfg, index); + if (!trunk) + trunk = mgcp_trunk_alloc(g_cfg, index); + + if (!trunk) { + vty_out(vty, "%%Unable to allocate trunk %u.%s", + index, VTY_NEWLINE); + return CMD_WARNING; + } + + vty->node = TRUNK_NODE; + vty->index = trunk; + return CMD_SUCCESS; +} + +static int config_write_trunk(struct vty *vty) +{ + struct mgcp_trunk_config *trunk; + + llist_for_each_entry(trunk, &g_cfg->trunks, entry) { + vty_out(vty, " trunk %d%s", trunk->trunk_nr, VTY_NEWLINE); + vty_out(vty, " sdp audio payload number %d%s", + trunk->audio_payload, VTY_NEWLINE); + vty_out(vty, " sdp audio payload name %s%s", + trunk->audio_name, VTY_NEWLINE); + vty_out(vty, " loop %d%s", + trunk->audio_loop, VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_payload_number, + cfg_trunk_payload_number_cmd, + "sdp audio payload number <1-255>", + "SDP related\n" "Audio\n" "Payload\n" "Payload Number\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + unsigned int payload = atoi(argv[0]); + + trunk->audio_payload = payload; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_payload_name, + cfg_trunk_payload_name_cmd, + "sdp audio payload name NAME", + "SDP related\n" "Audio\n" "Payload\n" "Payload Name\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + + bsc_replace_string(g_cfg, &trunk->audio_name, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_loop, + cfg_trunk_loop_cmd, + "loop (0|1)", + "Loop the audio") +{ + struct mgcp_trunk_config *trunk = vty->index; + + trunk->audio_loop = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(loop_endp, + loop_endp_cmd, + "loop-endpoint <0-64> NAME (0|1)", + "Loop a given endpoint\n" "Trunk number\n" + "The name in hex of the endpoint\n" "Disable the loop\n" "Enable the loop\n") +{ + struct mgcp_trunk_config *trunk; + struct mgcp_endpoint *endp; + + trunk = find_trunk(g_cfg, atoi(argv[0])); + if (!trunk) { + vty_out(vty, "%%Trunk %d not found in the config.%s", + atoi(argv[0]), VTY_NEWLINE); + return CMD_WARNING; + } + + if (!trunk->endpoints) { + vty_out(vty, "%%Trunk %d has no endpoints allocated.%s", + trunk->trunk_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + int endp_no = strtoul(argv[1], NULL, 16); + if (endp_no < 1 || endp_no >= trunk->number_endpoints) { + vty_out(vty, "Loopback number %s/%d is invalid.%s", + argv[1], endp_no, VTY_NEWLINE); + return CMD_WARNING; + } + + + endp = &trunk->endpoints[endp_no]; + int loop = atoi(argv[2]); + + if (loop) + endp->conn_mode = MGCP_CONN_LOOPBACK; + else + endp->conn_mode = endp->orig_mode; + endp->allow_patch = 1; + + return CMD_SUCCESS; +} + +DEFUN(tap_call, + tap_call_cmd, + "tap-call <0-64> ENDPOINT (bts-in|bts-out|net-in|net-out) A.B.C.D <0-65534>", + "Forward data on endpoint to a different system\n" "Trunk number\n" + "The endpoint in hex\n" + "Forward the data coming from the bts\n" + "Forward the data coming from the bts leaving to the network\n" + "Forward the data coming from the net\n" + "Forward the data coming from the net leaving to the bts\n" + "destination IP of the data\n" "destination port\n") +{ + struct mgcp_rtp_tap *tap; + struct mgcp_trunk_config *trunk; + struct mgcp_endpoint *endp; + int port = 0; + + trunk = find_trunk(g_cfg, atoi(argv[0])); + if (!trunk) { + vty_out(vty, "%%Trunk %d not found in the config.%s", + atoi(argv[0]), VTY_NEWLINE); + return CMD_WARNING; + } + + if (!trunk->endpoints) { + vty_out(vty, "%%Trunk %d has no endpoints allocated.%s", + trunk->trunk_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + int endp_no = strtoul(argv[1], NULL, 16); + if (endp_no < 1 || endp_no >= trunk->number_endpoints) { + vty_out(vty, "Endpoint number %s/%d is invalid.%s", + argv[1], endp_no, VTY_NEWLINE); + return CMD_WARNING; + } + + endp = &trunk->endpoints[endp_no]; + + if (strcmp(argv[2], "bts-in") == 0) { + port = MGCP_TAP_BTS_IN; + } else if (strcmp(argv[2], "bts-out") == 0) { + port = MGCP_TAP_BTS_OUT; + } else if (strcmp(argv[2], "net-in") == 0) { + port = MGCP_TAP_NET_IN; + } else if (strcmp(argv[2], "net-out") == 0) { + port = MGCP_TAP_NET_OUT; + } else { + vty_out(vty, "Unknown mode... tricked vty?%s", VTY_NEWLINE); + return CMD_WARNING; + } + + tap = &endp->taps[port]; + memset(&tap->forward, 0, sizeof(tap->forward)); + inet_aton(argv[3], &tap->forward.sin_addr); + tap->forward.sin_port = htons(atoi(argv[4])); + tap->enabled = 1; + return CMD_SUCCESS; +} + +DEFUN(free_endp, free_endp_cmd, + "free-endpoint <0-64> NUMBER", + "Free the given endpoint\n" "Trunk number\n" + "Endpoint number in hex.\n") +{ + struct mgcp_trunk_config *trunk; + struct mgcp_endpoint *endp; + + trunk = find_trunk(g_cfg, atoi(argv[0])); + if (!trunk) { + vty_out(vty, "%%Trunk %d not found in the config.%s", + atoi(argv[0]), VTY_NEWLINE); + return CMD_WARNING; + } + + if (!trunk->endpoints) { + vty_out(vty, "%%Trunk %d has no endpoints allocated.%s", + trunk->trunk_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + int endp_no = strtoul(argv[1], NULL, 16); + if (endp_no < 1 || endp_no >= trunk->number_endpoints) { + vty_out(vty, "Endpoint number %s/%d is invalid.%s", + argv[1], endp_no, VTY_NEWLINE); + return CMD_WARNING; + } + + endp = &trunk->endpoints[endp_no]; + mgcp_free_endp(endp); + return CMD_SUCCESS; +} + +int mgcp_vty_init(void) +{ + install_element_ve(&show_mgcp_cmd); + install_element(ENABLE_NODE, &loop_endp_cmd); + install_element(ENABLE_NODE, &tap_call_cmd); + install_element(ENABLE_NODE, &free_endp_cmd); + + install_element(CONFIG_NODE, &cfg_mgcp_cmd); + install_node(&mgcp_node, config_write_mgcp); + + install_default(MGCP_NODE); + install_element(MGCP_NODE, &ournode_exit_cmd); + install_element(MGCP_NODE, &ournode_end_cmd); + install_element(MGCP_NODE, &cfg_mgcp_local_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bts_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bind_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bind_port_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bind_early_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_base_port_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_base_port_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_net_base_port_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_range_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_net_range_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_transcoder_range_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_transcoder_base_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_dscp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_tos_cmd); + install_element(MGCP_NODE, &cfg_mgcp_agent_addr_cmd); + install_element(MGCP_NODE, &cfg_mgcp_transcoder_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_transcoder_cmd); + install_element(MGCP_NODE, &cfg_mgcp_transcoder_remote_base_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_number_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_name_cmd); + install_element(MGCP_NODE, &cfg_mgcp_loop_cmd); + install_element(MGCP_NODE, &cfg_mgcp_number_endp_cmd); + + install_element(MGCP_NODE, &cfg_mgcp_trunk_cmd); + install_node(&trunk_node, config_write_trunk); + install_default(TRUNK_NODE); + install_element(TRUNK_NODE, &ournode_exit_cmd); + install_element(TRUNK_NODE, &ournode_end_cmd); + install_element(TRUNK_NODE, &cfg_trunk_payload_number_cmd); + install_element(TRUNK_NODE, &cfg_trunk_payload_name_cmd); + install_element(TRUNK_NODE, &cfg_trunk_loop_cmd); + + return 0; +} + +static int allocate_trunk(struct mgcp_trunk_config *trunk) +{ + int i; + struct mgcp_config *cfg = trunk->cfg; + + if (mgcp_endpoints_allocate(trunk) != 0) { + LOGP(DMGCP, LOGL_ERROR, + "Failed to allocate %d endpoints on trunk %d.\n", + trunk->number_endpoints, trunk->trunk_nr); + return -1; + } + + /* early bind */ + for (i = 1; i < trunk->number_endpoints; ++i) { + struct mgcp_endpoint *endp = &trunk->endpoints[i]; + + if (cfg->bts_ports.mode == PORT_ALLOC_STATIC) { + cfg->last_bts_port += 2; + if (mgcp_bind_bts_rtp_port(endp, cfg->last_bts_port) != 0) { + LOGP(DMGCP, LOGL_FATAL, + "Failed to bind: %d\n", cfg->last_bts_port); + return -1; + } + endp->bts_end.local_alloc = PORT_ALLOC_STATIC; + } + + if (cfg->net_ports.mode == PORT_ALLOC_STATIC) { + cfg->last_net_port += 2; + if (mgcp_bind_net_rtp_port(endp, cfg->last_net_port) != 0) { + LOGP(DMGCP, LOGL_FATAL, + "Failed to bind: %d\n", cfg->last_net_port); + return -1; + } + endp->net_end.local_alloc = PORT_ALLOC_STATIC; + } + + if (trunk->trunk_type == MGCP_TRUNK_VIRTUAL && + cfg->transcoder_ip && cfg->transcoder_ports.mode == PORT_ALLOC_STATIC) { + int rtp_port; + + /* network side */ + rtp_port = rtp_calculate_port(ENDPOINT_NUMBER(endp), + cfg->transcoder_ports.base_port); + if (mgcp_bind_trans_net_rtp_port(endp, rtp_port) != 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to bind: %d\n", rtp_port); + return -1; + } + endp->trans_net.local_alloc = PORT_ALLOC_STATIC; + + /* bts side */ + rtp_port = rtp_calculate_port(endp_back_channel(ENDPOINT_NUMBER(endp)), + cfg->transcoder_ports.base_port); + if (mgcp_bind_trans_bts_rtp_port(endp, rtp_port) != 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to bind: %d\n", rtp_port); + return -1; + } + endp->trans_bts.local_alloc = PORT_ALLOC_STATIC; + } + } + + return 0; +} + +int mgcp_parse_config(const char *config_file, struct mgcp_config *cfg) +{ + int rc; + struct mgcp_trunk_config *trunk; + + g_cfg = cfg; + rc = vty_read_config_file(config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file); + return rc; + } + + + if (!g_cfg->bts_ip) + fprintf(stderr, "No BTS ip address specified. This will allow everyone to connect.\n"); + + if (!g_cfg->source_addr) { + fprintf(stderr, "You need to specify a bind address.\n"); + return -1; + } + + /* initialize the last ports */ + g_cfg->last_bts_port = rtp_calculate_port(0, g_cfg->bts_ports.base_port); + g_cfg->last_net_port = rtp_calculate_port(0, g_cfg->net_ports.base_port); + + if (allocate_trunk(&g_cfg->trunk) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to initialize the virtual trunk.\n"); + return -1; + } + + llist_for_each_entry(trunk, &g_cfg->trunks, entry) { + if (allocate_trunk(trunk) != 0) { + LOGP(DMGCP, LOGL_ERROR, + "Failed to initialize E1 trunk %d.\n", trunk->trunk_nr); + return -1; + } + } + + return 0; +} + diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am new file mode 100644 index 000000000..7d895c394 --- /dev/null +++ b/src/libmsc/Makefile.am @@ -0,0 +1,19 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) + +noinst_LIBRARIES = libmsc.a + +libmsc_a_SOURCES = auth.c \ + db.c \ + gsm_04_08.c gsm_04_11.c gsm_04_80.c \ + gsm_subscriber.c \ + mncc.c mncc_builtin.c mncc_sock.c \ + rrlp.c \ + silent_call.c \ + sms_queue.c \ + token_auth.c \ + ussd.c \ + vty_interface_layer3.c \ + osmo_msc.c + diff --git a/src/libmsc/Makefile.in b/src/libmsc/Makefile.in new file mode 100644 index 000000000..1ebc03200 --- /dev/null +++ b/src/libmsc/Makefile.in @@ -0,0 +1,482 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = src/libmsc +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LIBRARIES = $(noinst_LIBRARIES) +AR = ar +ARFLAGS = cru +AM_V_AR = $(am__v_AR_$(V)) +am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY)) +am__v_AR_0 = @echo " AR " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +libmsc_a_AR = $(AR) $(ARFLAGS) +libmsc_a_LIBADD = +am_libmsc_a_OBJECTS = auth.$(OBJEXT) db.$(OBJEXT) gsm_04_08.$(OBJEXT) \ + gsm_04_11.$(OBJEXT) gsm_04_80.$(OBJEXT) \ + gsm_subscriber.$(OBJEXT) mncc.$(OBJEXT) mncc_builtin.$(OBJEXT) \ + mncc_sock.$(OBJEXT) rrlp.$(OBJEXT) silent_call.$(OBJEXT) \ + sms_queue.$(OBJEXT) token_auth.$(OBJEXT) ussd.$(OBJEXT) \ + vty_interface_layer3.$(OBJEXT) osmo_msc.$(OBJEXT) +libmsc_a_OBJECTS = $(am_libmsc_a_OBJECTS) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(libmsc_a_SOURCES) +DIST_SOURCES = $(libmsc_a_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) +noinst_LIBRARIES = libmsc.a +libmsc_a_SOURCES = auth.c \ + db.c \ + gsm_04_08.c gsm_04_11.c gsm_04_80.c \ + gsm_subscriber.c \ + mncc.c mncc_builtin.c mncc_sock.c \ + rrlp.c \ + silent_call.c \ + sms_queue.c \ + token_auth.c \ + ussd.c \ + vty_interface_layer3.c \ + osmo_msc.c + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libmsc/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/libmsc/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) +libmsc.a: $(libmsc_a_OBJECTS) $(libmsc_a_DEPENDENCIES) + $(AM_V_at)-rm -f libmsc.a + $(AM_V_AR)$(libmsc_a_AR) libmsc.a $(libmsc_a_OBJECTS) $(libmsc_a_LIBADD) + $(AM_V_at)$(RANLIB) libmsc.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_04_08.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_04_11.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_04_80.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_subscriber.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mncc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mncc_builtin.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mncc_sock.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_msc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rrlp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/silent_call.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sms_queue.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/token_auth.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ussd.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vty_interface_layer3.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LIBRARIES) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-noinstLIBRARIES ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/libmsc/auth.c b/src/libmsc/auth.c new file mode 100644 index 000000000..e09bde5f3 --- /dev/null +++ b/src/libmsc/auth.c @@ -0,0 +1,132 @@ +/* Authentication related functions */ + +/* + * (C) 2010 by Sylvain Munaut + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include + +#include + + +static int +_use_xor(struct gsm_auth_info *ainfo, struct gsm_auth_tuple *atuple) +{ + int i, l = ainfo->a3a8_ki_len; + + if ((l > A38_XOR_MAX_KEY_LEN) || (l < A38_XOR_MIN_KEY_LEN)) { + LOGP(DMM, LOGL_ERROR, "Invalid XOR key (len=%d) %s\n", + ainfo->a3a8_ki_len, + hexdump(ainfo->a3a8_ki, ainfo->a3a8_ki_len)); + return -1; + } + + for (i=0; i<4; i++) + atuple->sres[i] = atuple->rand[i] ^ ainfo->a3a8_ki[i]; + for (i=4; i<12; i++) + atuple->kc[i-4] = atuple->rand[i] ^ ainfo->a3a8_ki[i]; + + return 0; +} + +static int +_use_comp128_v1(struct gsm_auth_info *ainfo, struct gsm_auth_tuple *atuple) +{ + if (ainfo->a3a8_ki_len != A38_COMP128_KEY_LEN) { + LOGP(DMM, LOGL_ERROR, "Invalid COMP128v1 key (len=%d) %s\n", + ainfo->a3a8_ki_len, + hexdump(ainfo->a3a8_ki, ainfo->a3a8_ki_len)); + return -1; + } + + comp128(ainfo->a3a8_ki, atuple->rand, atuple->sres, atuple->kc); + + return 0; +} + +/* Return values + * -1 -> Internal error + * 0 -> Not available + * 1 -> Tuple returned, need to do auth, then enable cipher + * 2 -> Tuple returned, need to enable cipher + */ +int auth_get_tuple_for_subscr(struct gsm_auth_tuple *atuple, + struct gsm_subscriber *subscr, int key_seq) +{ + struct gsm_auth_info ainfo; + int i, rc; + + /* Get subscriber info (if any) */ + rc = db_get_authinfo_for_subscr(&ainfo, subscr); + if (rc < 0) { + LOGP(DMM, LOGL_NOTICE, + "No retrievable Ki for subscriber, skipping auth\n"); + return rc == -ENOENT ? AUTH_NOT_AVAIL : -1; + } + + /* If possible, re-use the last tuple and skip auth */ + rc = db_get_lastauthtuple_for_subscr(atuple, subscr); + if ((rc == 0) && + (key_seq != GSM_KEY_SEQ_INVAL) && + (atuple->use_count < 3)) + { + atuple->use_count++; + db_sync_lastauthtuple_for_subscr(atuple, subscr); + DEBUGP(DMM, "Auth tuple use < 3, just doing ciphering\n"); + return AUTH_DO_CIPH; + } + + /* Generate a new one */ + atuple->use_count = 1; + atuple->key_seq = (atuple->key_seq + 1) % 7; + for (i=0; irand); i++) + atuple->rand[i] = random() & 0xff; + + switch (ainfo.auth_algo) { + case AUTH_ALGO_NONE: + DEBUGP(DMM, "No authentication for subscriber\n"); + return 0; + + case AUTH_ALGO_XOR: + if (_use_xor(&ainfo, atuple)) + return 0; + break; + + case AUTH_ALGO_COMP128v1: + if (_use_comp128_v1(&ainfo, atuple)) + return 0; + break; + + default: + DEBUGP(DMM, "Unsupported auth type algo_id=%d\n", + ainfo.auth_algo); + return 0; + } + + db_sync_lastauthtuple_for_subscr(atuple, subscr); + + DEBUGP(DMM, "Need to do authentication and ciphering\n"); + return AUTH_DO_AUTH_THAN_CIPH; +} + diff --git a/src/libmsc/db.c b/src/libmsc/db.c new file mode 100644 index 000000000..95a7d36fb --- /dev/null +++ b/src/libmsc/db.c @@ -0,0 +1,1303 @@ +/* Simple HLR/VLR database backend using dbi */ +/* (C) 2008 by Jan Luebbe + * (C) 2009 by Holger Hans Peter Freyther + * (C) 2009 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +static char *db_basename = NULL; +static char *db_dirname = NULL; +static dbi_conn conn; + +static char *create_stmts[] = { + "CREATE TABLE IF NOT EXISTS Meta (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "key TEXT UNIQUE NOT NULL, " + "value TEXT NOT NULL" + ")", + "INSERT OR IGNORE INTO Meta " + "(key, value) " + "VALUES " + "('revision', '2')", + "CREATE TABLE IF NOT EXISTS Subscriber (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "created TIMESTAMP NOT NULL, " + "updated TIMESTAMP NOT NULL, " + "imsi NUMERIC UNIQUE NOT NULL, " + "name TEXT, " + "extension TEXT UNIQUE, " + "authorized INTEGER NOT NULL DEFAULT 0, " + "tmsi TEXT UNIQUE, " + "lac INTEGER NOT NULL DEFAULT 0" + ")", + "CREATE TABLE IF NOT EXISTS AuthToken (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "subscriber_id INTEGER UNIQUE NOT NULL, " + "created TIMESTAMP NOT NULL, " + "token TEXT UNIQUE NOT NULL" + ")", + "CREATE TABLE IF NOT EXISTS Equipment (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "created TIMESTAMP NOT NULL, " + "updated TIMESTAMP NOT NULL, " + "name TEXT, " + "classmark1 NUMERIC, " + "classmark2 BLOB, " + "classmark3 BLOB, " + "imei NUMERIC UNIQUE NOT NULL" + ")", + "CREATE TABLE IF NOT EXISTS EquipmentWatch (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "created TIMESTAMP NOT NULL, " + "updated TIMESTAMP NOT NULL, " + "subscriber_id NUMERIC NOT NULL, " + "equipment_id NUMERIC NOT NULL, " + "UNIQUE (subscriber_id, equipment_id) " + ")", + "CREATE TABLE IF NOT EXISTS SMS (" + /* metadata, not part of sms */ + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "created TIMESTAMP NOT NULL, " + "sent TIMESTAMP, " + "sender_id INTEGER NOT NULL, " + "receiver_id INTEGER NOT NULL, " + "deliver_attempts INTEGER NOT NULL DEFAULT 0, " + /* data directly copied/derived from SMS */ + "valid_until TIMESTAMP, " + "reply_path_req INTEGER NOT NULL, " + "status_rep_req INTEGER NOT NULL, " + "protocol_id INTEGER NOT NULL, " + "data_coding_scheme INTEGER NOT NULL, " + "ud_hdr_ind INTEGER NOT NULL, " + "dest_addr TEXT, " + "user_data BLOB, " /* TP-UD */ + /* additional data, interpreted from SMS */ + "header BLOB, " /* UD Header */ + "text TEXT " /* decoded UD after UDH */ + ")", + "CREATE TABLE IF NOT EXISTS VLR (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "created TIMESTAMP NOT NULL, " + "updated TIMESTAMP NOT NULL, " + "subscriber_id NUMERIC UNIQUE NOT NULL, " + "last_bts NUMERIC NOT NULL " + ")", + "CREATE TABLE IF NOT EXISTS ApduBlobs (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "created TIMESTAMP NOT NULL, " + "apdu_id_flags INTEGER NOT NULL, " + "subscriber_id INTEGER NOT NULL, " + "apdu BLOB " + ")", + "CREATE TABLE IF NOT EXISTS Counters (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "timestamp TIMESTAMP NOT NULL, " + "value INTEGER NOT NULL, " + "name TEXT NOT NULL " + ")", + "CREATE TABLE IF NOT EXISTS RateCounters (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "timestamp TIMESTAMP NOT NULL, " + "value INTEGER NOT NULL, " + "name TEXT NOT NULL, " + "idx INTEGER NOT NULL " + ")", + "CREATE TABLE IF NOT EXISTS AuthKeys (" + "subscriber_id INTEGER PRIMARY KEY, " + "algorithm_id INTEGER NOT NULL, " + "a3a8_ki BLOB " + ")", + "CREATE TABLE IF NOT EXISTS AuthLastTuples (" + "subscriber_id INTEGER PRIMARY KEY, " + "issued TIMESTAMP NOT NULL, " + "use_count INTEGER NOT NULL DEFAULT 0, " + "key_seq INTEGER NOT NULL, " + "rand BLOB NOT NULL, " + "sres BLOB NOT NULL, " + "kc BLOB NOT NULL " + ")", +}; + +void db_error_func(dbi_conn conn, void *data) +{ + const char *msg; + dbi_conn_error(conn, &msg); + LOGP(DDB, LOGL_ERROR, "DBI: %s\n", msg); +} + +static int check_db_revision(void) +{ + dbi_result result; + const char *rev; + + result = dbi_conn_query(conn, + "SELECT value FROM Meta WHERE key='revision'"); + if (!result) + return -EINVAL; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return -EINVAL; + } + rev = dbi_result_get_string(result, "value"); + if (!rev || atoi(rev) != 2) { + dbi_result_free(result); + return -EINVAL; + } + + dbi_result_free(result); + return 0; +} + +int db_init(const char *name) +{ + dbi_initialize(NULL); + + conn = dbi_conn_new("sqlite3"); + if (conn == NULL) { + LOGP(DDB, LOGL_FATAL, "Failed to create connection.\n"); + return 1; + } + + dbi_conn_error_handler( conn, db_error_func, NULL ); + + /* MySQL + dbi_conn_set_option(conn, "host", "localhost"); + dbi_conn_set_option(conn, "username", "your_name"); + dbi_conn_set_option(conn, "password", "your_password"); + dbi_conn_set_option(conn, "dbname", "your_dbname"); + dbi_conn_set_option(conn, "encoding", "UTF-8"); + */ + + /* SqLite 3 */ + db_basename = strdup(name); + db_dirname = strdup(name); + dbi_conn_set_option(conn, "sqlite3_dbdir", dirname(db_dirname)); + dbi_conn_set_option(conn, "dbname", basename(db_basename)); + + if (dbi_conn_connect(conn) < 0) + goto out_err; + + return 0; + +out_err: + free(db_dirname); + free(db_basename); + db_dirname = db_basename = NULL; + return -1; +} + + +int db_prepare() +{ + dbi_result result; + int i; + + for (i = 0; i < ARRAY_SIZE(create_stmts); i++) { + result = dbi_conn_query(conn, create_stmts[i]); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to create some table.\n"); + return 1; + } + dbi_result_free(result); + } + + if (check_db_revision() < 0) { + LOGP(DDB, LOGL_FATAL, "Database schema revision invalid, " + "please update your database schema\n"); + return -1; + } + + return 0; +} + +int db_fini() +{ + dbi_conn_close(conn); + dbi_shutdown(); + + if (db_dirname) + free(db_dirname); + if (db_basename) + free(db_basename); + return 0; +} + +struct gsm_subscriber *db_create_subscriber(struct gsm_network *net, char *imsi) +{ + dbi_result result; + struct gsm_subscriber *subscr; + + /* Is this subscriber known in the db? */ + subscr = db_get_subscriber(net, GSM_SUBSCRIBER_IMSI, imsi); + if (subscr) { + result = dbi_conn_queryf(conn, + "UPDATE Subscriber set updated = datetime('now') " + "WHERE imsi = %s " , imsi); + if (!result) + LOGP(DDB, LOGL_ERROR, "failed to update timestamp\n"); + else + dbi_result_free(result); + return subscr; + } + + subscr = subscr_alloc(); + subscr->flags |= GSM_SUBSCRIBER_FIRST_CONTACT; + if (!subscr) + return NULL; + result = dbi_conn_queryf(conn, + "INSERT INTO Subscriber " + "(imsi, created, updated) " + "VALUES " + "(%s, datetime('now'), datetime('now')) ", + imsi + ); + if (!result) + LOGP(DDB, LOGL_ERROR, "Failed to create Subscriber by IMSI.\n"); + subscr->net = net; + subscr->id = dbi_conn_sequence_last(conn, NULL); + strncpy(subscr->imsi, imsi, GSM_IMSI_LENGTH-1); + dbi_result_free(result); + LOGP(DDB, LOGL_INFO, "New Subscriber: ID %llu, IMSI %s\n", subscr->id, subscr->imsi); + db_subscriber_alloc_exten(subscr); + return subscr; +} + +static_assert(sizeof(unsigned char) == sizeof(struct gsm48_classmark1), classmark1_size); + +static int get_equipment_by_subscr(struct gsm_subscriber *subscr) +{ + dbi_result result; + const char *string; + unsigned char cm1; + const unsigned char *cm2, *cm3; + struct gsm_equipment *equip = &subscr->equipment; + + result = dbi_conn_queryf(conn, + "SELECT Equipment.* " + "FROM Equipment JOIN EquipmentWatch ON " + "EquipmentWatch.equipment_id=Equipment.id " + "WHERE EquipmentWatch.subscriber_id = %llu " + "ORDER BY EquipmentWatch.updated DESC", subscr->id); + if (!result) + return -EIO; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return -ENOENT; + } + + equip->id = dbi_result_get_ulonglong(result, "id"); + + string = dbi_result_get_string(result, "imei"); + if (string) + strncpy(equip->imei, string, sizeof(equip->imei)); + + string = dbi_result_get_string(result, "classmark1"); + if (string) { + cm1 = atoi(string) & 0xff; + memcpy(&equip->classmark1, &cm1, sizeof(equip->classmark1)); + } + + equip->classmark2_len = dbi_result_get_field_length(result, "classmark2"); + cm2 = dbi_result_get_binary(result, "classmark2"); + if (equip->classmark2_len > sizeof(equip->classmark2)) + equip->classmark2_len = sizeof(equip->classmark2); + memcpy(equip->classmark2, cm2, equip->classmark2_len); + + equip->classmark3_len = dbi_result_get_field_length(result, "classmark3"); + cm3 = dbi_result_get_binary(result, "classmark3"); + if (equip->classmark3_len > sizeof(equip->classmark3)) + equip->classmark3_len = sizeof(equip->classmark3); + memcpy(equip->classmark3, cm3, equip->classmark3_len); + + dbi_result_free(result); + + return 0; +} + +int db_get_authinfo_for_subscr(struct gsm_auth_info *ainfo, + struct gsm_subscriber *subscr) +{ + dbi_result result; + const unsigned char *a3a8_ki; + + result = dbi_conn_queryf(conn, + "SELECT * FROM AuthKeys WHERE subscriber_id=%llu", + subscr->id); + if (!result) + return -EIO; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return -ENOENT; + } + + ainfo->auth_algo = dbi_result_get_ulonglong(result, "algorithm_id"); + ainfo->a3a8_ki_len = dbi_result_get_field_length(result, "a3a8_ki"); + a3a8_ki = dbi_result_get_binary(result, "a3a8_ki"); + if (ainfo->a3a8_ki_len > sizeof(ainfo->a3a8_ki)) + ainfo->a3a8_ki_len = sizeof(ainfo->a3a8_ki_len); + memcpy(ainfo->a3a8_ki, a3a8_ki, ainfo->a3a8_ki_len); + + dbi_result_free(result); + + return 0; +} + +int db_sync_authinfo_for_subscr(struct gsm_auth_info *ainfo, + struct gsm_subscriber *subscr) +{ + dbi_result result; + struct gsm_auth_info ainfo_old; + int rc, upd; + unsigned char *ki_str; + + /* Deletion ? */ + if (ainfo == NULL) { + result = dbi_conn_queryf(conn, + "DELETE FROM AuthKeys WHERE subscriber_id=%llu", + subscr->id); + + if (!result) + return -EIO; + + dbi_result_free(result); + + return 0; + } + + /* Check if already existing */ + rc = db_get_authinfo_for_subscr(&ainfo_old, subscr); + if (rc && rc != -ENOENT) + return rc; + upd = rc ? 0 : 1; + + /* Update / Insert */ + dbi_conn_quote_binary_copy(conn, + ainfo->a3a8_ki, ainfo->a3a8_ki_len, &ki_str); + + if (!upd) { + result = dbi_conn_queryf(conn, + "INSERT INTO AuthKeys " + "(subscriber_id, algorithm_id, a3a8_ki) " + "VALUES (%llu, %u, %s)", + subscr->id, ainfo->auth_algo, ki_str); + } else { + result = dbi_conn_queryf(conn, + "UPDATE AuthKeys " + "SET algorithm_id=%u, a3a8_ki=%s " + "WHERE subscriber_id=%llu", + ainfo->auth_algo, ki_str, subscr->id); + } + + free(ki_str); + + if (!result) + return -EIO; + + dbi_result_free(result); + + return 0; +} + +int db_get_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple, + struct gsm_subscriber *subscr) +{ + dbi_result result; + int len; + const unsigned char *blob; + + result = dbi_conn_queryf(conn, + "SELECT * FROM AuthLastTuples WHERE subscriber_id=%llu", + subscr->id); + if (!result) + return -EIO; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return -ENOENT; + } + + memset(atuple, 0, sizeof(atuple)); + + atuple->use_count = dbi_result_get_ulonglong(result, "use_count"); + atuple->key_seq = dbi_result_get_ulonglong(result, "key_seq"); + + len = dbi_result_get_field_length(result, "rand"); + if (len != sizeof(atuple->rand)) + goto err_size; + + blob = dbi_result_get_binary(result, "rand"); + memcpy(atuple->rand, blob, len); + + len = dbi_result_get_field_length(result, "sres"); + if (len != sizeof(atuple->sres)) + goto err_size; + + blob = dbi_result_get_binary(result, "sres"); + memcpy(atuple->sres, blob, len); + + len = dbi_result_get_field_length(result, "kc"); + if (len != sizeof(atuple->kc)) + goto err_size; + + blob = dbi_result_get_binary(result, "kc"); + memcpy(atuple->kc, blob, len); + + dbi_result_free(result); + + return 0; + +err_size: + dbi_result_free(result); + return -EIO; +} + +int db_sync_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple, + struct gsm_subscriber *subscr) +{ + dbi_result result; + int rc, upd; + struct gsm_auth_tuple atuple_old; + unsigned char *rand_str, *sres_str, *kc_str; + + /* Deletion ? */ + if (atuple == NULL) { + result = dbi_conn_queryf(conn, + "DELETE FROM AuthLastTuples WHERE subscriber_id=%llu", + subscr->id); + + if (!result) + return -EIO; + + dbi_result_free(result); + + return 0; + } + + /* Check if already existing */ + rc = db_get_lastauthtuple_for_subscr(&atuple_old, subscr); + if (rc && rc != -ENOENT) + return rc; + upd = rc ? 0 : 1; + + /* Update / Insert */ + dbi_conn_quote_binary_copy(conn, + atuple->rand, sizeof(atuple->rand), &rand_str); + dbi_conn_quote_binary_copy(conn, + atuple->sres, sizeof(atuple->sres), &sres_str); + dbi_conn_quote_binary_copy(conn, + atuple->kc, sizeof(atuple->kc), &kc_str); + + if (!upd) { + result = dbi_conn_queryf(conn, + "INSERT INTO AuthLastTuples " + "(subscriber_id, issued, use_count, " + "key_seq, rand, sres, kc) " + "VALUES (%llu, datetime('now'), %u, " + "%u, %s, %s, %s ) ", + subscr->id, atuple->use_count, atuple->key_seq, + rand_str, sres_str, kc_str); + } else { + char *issued = atuple->key_seq == atuple_old.key_seq ? + "issued" : "datetime('now')"; + result = dbi_conn_queryf(conn, + "UPDATE AuthLastTuples " + "SET issued=%s, use_count=%u, " + "key_seq=%u, rand=%s, sres=%s, kc=%s " + "WHERE subscriber_id = %llu", + issued, atuple->use_count, atuple->key_seq, + rand_str, sres_str, kc_str, subscr->id); + } + + free(rand_str); + free(sres_str); + free(kc_str); + + if (!result) + return -EIO; + + dbi_result_free(result); + + return 0; +} + +static void db_set_from_query(struct gsm_subscriber *subscr, dbi_conn result) +{ + const char *string; + string = dbi_result_get_string(result, "imsi"); + if (string) + strncpy(subscr->imsi, string, GSM_IMSI_LENGTH); + + string = dbi_result_get_string(result, "tmsi"); + if (string) + subscr->tmsi = tmsi_from_string(string); + + string = dbi_result_get_string(result, "name"); + if (string) + strncpy(subscr->name, string, GSM_NAME_LENGTH); + + string = dbi_result_get_string(result, "extension"); + if (string) + strncpy(subscr->extension, string, GSM_EXTENSION_LENGTH); + + subscr->lac = dbi_result_get_uint(result, "lac"); + subscr->authorized = dbi_result_get_uint(result, "authorized"); +} + +#define BASE_QUERY "SELECT * FROM Subscriber " +struct gsm_subscriber *db_get_subscriber(struct gsm_network *net, + enum gsm_subscriber_field field, + const char *id) +{ + dbi_result result; + char *quoted; + struct gsm_subscriber *subscr; + + switch (field) { + case GSM_SUBSCRIBER_IMSI: + dbi_conn_quote_string_copy(conn, id, "ed); + result = dbi_conn_queryf(conn, + BASE_QUERY + "WHERE imsi = %s ", + quoted + ); + free(quoted); + break; + case GSM_SUBSCRIBER_TMSI: + dbi_conn_quote_string_copy(conn, id, "ed); + result = dbi_conn_queryf(conn, + BASE_QUERY + "WHERE tmsi = %s ", + quoted + ); + free(quoted); + break; + case GSM_SUBSCRIBER_EXTENSION: + dbi_conn_quote_string_copy(conn, id, "ed); + result = dbi_conn_queryf(conn, + BASE_QUERY + "WHERE extension = %s ", + quoted + ); + free(quoted); + break; + case GSM_SUBSCRIBER_ID: + dbi_conn_quote_string_copy(conn, id, "ed); + result = dbi_conn_queryf(conn, + BASE_QUERY + "WHERE id = %s ", quoted); + free(quoted); + break; + default: + LOGP(DDB, LOGL_NOTICE, "Unknown query selector for Subscriber.\n"); + return NULL; + } + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber.\n"); + return NULL; + } + if (!dbi_result_next_row(result)) { + DEBUGP(DDB, "Failed to find the Subscriber. '%u' '%s'\n", + field, id); + dbi_result_free(result); + return NULL; + } + + subscr = subscr_alloc(); + subscr->net = net; + subscr->id = dbi_result_get_ulonglong(result, "id"); + + db_set_from_query(subscr, result); + DEBUGP(DDB, "Found Subscriber: ID %llu, IMSI %s, NAME '%s', TMSI %u, EXTEN '%s', LAC %hu, AUTH %u\n", + subscr->id, subscr->imsi, subscr->name, subscr->tmsi, subscr->extension, + subscr->lac, subscr->authorized); + dbi_result_free(result); + + get_equipment_by_subscr(subscr); + + return subscr; +} + +int db_subscriber_update(struct gsm_subscriber *subscr) +{ + char buf[32]; + dbi_result result; + + /* Copy the id to a string as queryf with %llu is failing */ + sprintf(buf, "%llu", subscr->id); + result = dbi_conn_queryf(conn, + BASE_QUERY + "WHERE id = %s", buf); + + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber: %llu\n", subscr->id); + return -EIO; + } + if (!dbi_result_next_row(result)) { + DEBUGP(DDB, "Failed to find the Subscriber. %llu\n", + subscr->id); + dbi_result_free(result); + return -EIO; + } + + db_set_from_query(subscr, result); + dbi_result_free(result); + get_equipment_by_subscr(subscr); + + return 0; +} + +int db_sync_subscriber(struct gsm_subscriber *subscriber) +{ + dbi_result result; + char tmsi[14]; + char *q_tmsi, *q_name, *q_extension; + + dbi_conn_quote_string_copy(conn, + subscriber->name, &q_name); + dbi_conn_quote_string_copy(conn, + subscriber->extension, &q_extension); + + if (subscriber->tmsi != GSM_RESERVED_TMSI) { + sprintf(tmsi, "%u", subscriber->tmsi); + dbi_conn_quote_string_copy(conn, + tmsi, + &q_tmsi); + } else + q_tmsi = strdup("NULL"); + + result = dbi_conn_queryf(conn, + "UPDATE Subscriber " + "SET updated = datetime('now'), " + "name = %s, " + "extension = %s, " + "authorized = %i, " + "tmsi = %s, " + "lac = %i " + "WHERE imsi = %s ", + q_name, + q_extension, + subscriber->authorized, + q_tmsi, + subscriber->lac, + subscriber->imsi); + + free(q_tmsi); + free(q_name); + free(q_extension); + + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to update Subscriber (by IMSI).\n"); + return 1; + } + + dbi_result_free(result); + + return 0; +} + +int db_sync_equipment(struct gsm_equipment *equip) +{ + dbi_result result; + unsigned char *cm2, *cm3; + char *q_imei; + u_int8_t classmark1; + + memcpy(&classmark1, &equip->classmark1, sizeof(classmark1)); + DEBUGP(DDB, "Sync Equipment IMEI=%s, classmark1=%02x", + equip->imei, classmark1); + if (equip->classmark2_len) + DEBUGPC(DDB, ", classmark2=%s", + hexdump(equip->classmark2, equip->classmark2_len)); + if (equip->classmark3_len) + DEBUGPC(DDB, ", classmark3=%s", + hexdump(equip->classmark3, equip->classmark3_len)); + DEBUGPC(DDB, "\n"); + + dbi_conn_quote_binary_copy(conn, equip->classmark2, + equip->classmark2_len, &cm2); + dbi_conn_quote_binary_copy(conn, equip->classmark3, + equip->classmark3_len, &cm3); + dbi_conn_quote_string_copy(conn, equip->imei, &q_imei); + + result = dbi_conn_queryf(conn, + "UPDATE Equipment SET " + "updated = datetime('now'), " + "classmark1 = %u, " + "classmark2 = %s, " + "classmark3 = %s " + "WHERE imei = %s ", + classmark1, cm2, cm3, q_imei); + + free(cm2); + free(cm3); + free(q_imei); + + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to update Equipment\n"); + return -EIO; + } + + dbi_result_free(result); + return 0; +} + +int db_subscriber_alloc_tmsi(struct gsm_subscriber *subscriber) +{ + dbi_result result = NULL; + char tmsi[14]; + char *tmsi_quoted; + + for (;;) { + subscriber->tmsi = rand(); + if (subscriber->tmsi == GSM_RESERVED_TMSI) + continue; + + sprintf(tmsi, "%u", subscriber->tmsi); + dbi_conn_quote_string_copy(conn, tmsi, &tmsi_quoted); + result = dbi_conn_queryf(conn, + "SELECT * FROM Subscriber " + "WHERE tmsi = %s ", + tmsi_quoted); + + free(tmsi_quoted); + + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber " + "while allocating new TMSI.\n"); + return 1; + } + if (dbi_result_get_numrows(result)) { + dbi_result_free(result); + continue; + } + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + DEBUGP(DDB, "Allocated TMSI %u for IMSI %s.\n", + subscriber->tmsi, subscriber->imsi); + return db_sync_subscriber(subscriber); + } + dbi_result_free(result); + } + return 0; +} + +int db_subscriber_alloc_exten(struct gsm_subscriber *subscriber) +{ + dbi_result result = NULL; + u_int32_t try; + + for (;;) { + try = (rand()%(GSM_MAX_EXTEN-GSM_MIN_EXTEN+1)+GSM_MIN_EXTEN); + result = dbi_conn_queryf(conn, + "SELECT * FROM Subscriber " + "WHERE extension = %i", + try + ); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber " + "while allocating new extension.\n"); + return 1; + } + if (dbi_result_get_numrows(result)){ + dbi_result_free(result); + continue; + } + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + break; + } + dbi_result_free(result); + } + sprintf(subscriber->extension, "%i", try); + DEBUGP(DDB, "Allocated extension %i for IMSI %s.\n", try, subscriber->imsi); + return db_sync_subscriber(subscriber); +} +/* + * try to allocate a new unique token for this subscriber and return it + * via a parameter. if the subscriber already has a token, return + * an error. + */ + +int db_subscriber_alloc_token(struct gsm_subscriber *subscriber, u_int32_t *token) +{ + dbi_result result; + u_int32_t try; + + for (;;) { + try = rand(); + if (!try) /* 0 is an invalid token */ + continue; + result = dbi_conn_queryf(conn, + "SELECT * FROM AuthToken " + "WHERE subscriber_id = %llu OR token = \"%08X\" ", + subscriber->id, try); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to query AuthToken " + "while allocating new token.\n"); + return 1; + } + if (dbi_result_get_numrows(result)) { + dbi_result_free(result); + continue; + } + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + break; + } + dbi_result_free(result); + } + result = dbi_conn_queryf(conn, + "INSERT INTO AuthToken " + "(subscriber_id, created, token) " + "VALUES " + "(%llu, datetime('now'), \"%08X\") ", + subscriber->id, try); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to create token %08X for " + "IMSI %s.\n", try, subscriber->imsi); + return 1; + } + dbi_result_free(result); + *token = try; + DEBUGP(DDB, "Allocated token %08X for IMSI %s.\n", try, subscriber->imsi); + + return 0; +} + +int db_subscriber_assoc_imei(struct gsm_subscriber *subscriber, char imei[GSM_IMEI_LENGTH]) +{ + unsigned long long equipment_id, watch_id; + dbi_result result; + + strncpy(subscriber->equipment.imei, imei, + sizeof(subscriber->equipment.imei)-1), + + result = dbi_conn_queryf(conn, + "INSERT OR IGNORE INTO Equipment " + "(imei, created, updated) " + "VALUES " + "(%s, datetime('now'), datetime('now')) ", + imei); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to create Equipment by IMEI.\n"); + return 1; + } + + equipment_id = 0; + if (dbi_result_get_numrows_affected(result)) { + equipment_id = dbi_conn_sequence_last(conn, NULL); + } + dbi_result_free(result); + + if (equipment_id) + DEBUGP(DDB, "New Equipment: ID %llu, IMEI %s\n", equipment_id, imei); + else { + result = dbi_conn_queryf(conn, + "SELECT id FROM Equipment " + "WHERE imei = %s ", + imei + ); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to query Equipment by IMEI.\n"); + return 1; + } + if (!dbi_result_next_row(result)) { + LOGP(DDB, LOGL_ERROR, "Failed to find the Equipment.\n"); + dbi_result_free(result); + return 1; + } + equipment_id = dbi_result_get_ulonglong(result, "id"); + dbi_result_free(result); + } + + result = dbi_conn_queryf(conn, + "INSERT OR IGNORE INTO EquipmentWatch " + "(subscriber_id, equipment_id, created, updated) " + "VALUES " + "(%llu, %llu, datetime('now'), datetime('now')) ", + subscriber->id, equipment_id); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to create EquipmentWatch.\n"); + return 1; + } + + watch_id = 0; + if (dbi_result_get_numrows_affected(result)) + watch_id = dbi_conn_sequence_last(conn, NULL); + + dbi_result_free(result); + if (watch_id) + DEBUGP(DDB, "New EquipmentWatch: ID %llu, IMSI %s, IMEI %s\n", + equipment_id, subscriber->imsi, imei); + else { + result = dbi_conn_queryf(conn, + "UPDATE EquipmentWatch " + "SET updated = datetime('now') " + "WHERE subscriber_id = %llu AND equipment_id = %llu ", + subscriber->id, equipment_id); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to update EquipmentWatch.\n"); + return 1; + } + dbi_result_free(result); + DEBUGP(DDB, "Updated EquipmentWatch: ID %llu, IMSI %s, IMEI %s\n", + equipment_id, subscriber->imsi, imei); + } + + return 0; +} + +/* store an [unsent] SMS to the database */ +int db_sms_store(struct gsm_sms *sms) +{ + dbi_result result; + char *q_text, *q_daddr; + unsigned char *q_udata; + char *validity_timestamp = "2222-2-2"; + + /* FIXME: generate validity timestamp based on validity_minutes */ + + dbi_conn_quote_string_copy(conn, (char *)sms->text, &q_text); + dbi_conn_quote_string_copy(conn, (char *)sms->dest_addr, &q_daddr); + dbi_conn_quote_binary_copy(conn, sms->user_data, sms->user_data_len, + &q_udata); + /* FIXME: correct validity period */ + result = dbi_conn_queryf(conn, + "INSERT INTO SMS " + "(created, sender_id, receiver_id, valid_until, " + "reply_path_req, status_rep_req, protocol_id, " + "data_coding_scheme, ud_hdr_ind, dest_addr, " + "user_data, text) VALUES " + "(datetime('now'), %llu, %llu, %u, " + "%u, %u, %u, %u, %u, %s, %s, %s)", + sms->sender->id, + sms->receiver ? sms->receiver->id : 0, validity_timestamp, + sms->reply_path_req, sms->status_rep_req, sms->protocol_id, + sms->data_coding_scheme, sms->ud_hdr_ind, + q_daddr, q_udata, q_text); + free(q_text); + free(q_daddr); + free(q_udata); + + if (!result) + return -EIO; + + dbi_result_free(result); + return 0; +} + +static struct gsm_sms *sms_from_result(struct gsm_network *net, dbi_result result) +{ + struct gsm_sms *sms = sms_alloc(); + long long unsigned int sender_id, receiver_id; + const char *text, *daddr; + const unsigned char *user_data; + + if (!sms) + return NULL; + + sms->id = dbi_result_get_ulonglong(result, "id"); + + sender_id = dbi_result_get_ulonglong(result, "sender_id"); + sms->sender = subscr_get_by_id(net, sender_id); + + receiver_id = dbi_result_get_ulonglong(result, "receiver_id"); + sms->receiver = subscr_get_by_id(net, receiver_id); + + /* FIXME: validity */ + /* FIXME: those should all be get_uchar, but sqlite3 is braindead */ + sms->reply_path_req = dbi_result_get_uint(result, "reply_path_req"); + sms->status_rep_req = dbi_result_get_uint(result, "status_rep_req"); + sms->ud_hdr_ind = dbi_result_get_uint(result, "ud_hdr_ind"); + sms->protocol_id = dbi_result_get_uint(result, "protocol_id"); + sms->data_coding_scheme = dbi_result_get_uint(result, + "data_coding_scheme"); + /* sms->msg_ref is temporary and not stored in DB */ + + daddr = dbi_result_get_string(result, "dest_addr"); + if (daddr) { + strncpy(sms->dest_addr, daddr, sizeof(sms->dest_addr)); + sms->dest_addr[sizeof(sms->dest_addr)-1] = '\0'; + } + + sms->user_data_len = dbi_result_get_field_length(result, "user_data"); + user_data = dbi_result_get_binary(result, "user_data"); + if (sms->user_data_len > sizeof(sms->user_data)) + sms->user_data_len = (u_int8_t) sizeof(sms->user_data); + memcpy(sms->user_data, user_data, sms->user_data_len); + + text = dbi_result_get_string(result, "text"); + if (text) { + strncpy(sms->text, text, sizeof(sms->text)); + sms->text[sizeof(sms->text)-1] = '\0'; + } + return sms; +} + +struct gsm_sms *db_sms_get(struct gsm_network *net, unsigned long long id) +{ + dbi_result result; + struct gsm_sms *sms; + + result = dbi_conn_queryf(conn, + "SELECT * FROM SMS WHERE SMS.id = %llu", id); + if (!result) + return NULL; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return NULL; + } + + sms = sms_from_result(net, result); + + dbi_result_free(result); + + return sms; +} + +/* retrieve the next unsent SMS with ID >= min_id */ +struct gsm_sms *db_sms_get_unsent(struct gsm_network *net, unsigned long long min_id) +{ + dbi_result result; + struct gsm_sms *sms; + + result = dbi_conn_queryf(conn, + "SELECT SMS.* " + "FROM SMS JOIN Subscriber ON " + "SMS.receiver_id = Subscriber.id " + "WHERE SMS.id >= %llu AND SMS.sent IS NULL " + "AND Subscriber.lac > 0 " + "ORDER BY SMS.id LIMIT 1", + min_id); + if (!result) + return NULL; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return NULL; + } + + sms = sms_from_result(net, result); + + dbi_result_free(result); + + return sms; +} + +struct gsm_sms *db_sms_get_unsent_by_subscr(struct gsm_network *net, + unsigned long long min_subscr_id, + unsigned int failed) +{ + dbi_result result; + struct gsm_sms *sms; + + result = dbi_conn_queryf(conn, + "SELECT SMS.* " + "FROM SMS JOIN Subscriber ON " + "SMS.receiver_id = Subscriber.id " + "WHERE SMS.receiver_id >= %llu AND SMS.sent IS NULL " + "AND Subscriber.lac > 0 AND SMS.deliver_attempts < %u " + "ORDER BY SMS.receiver_id, SMS.id LIMIT 1", + min_subscr_id, failed); + if (!result) + return NULL; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return NULL; + } + + sms = sms_from_result(net, result); + + dbi_result_free(result); + + return sms; +} + +/* retrieve the next unsent SMS for a given subscriber */ +struct gsm_sms *db_sms_get_unsent_for_subscr(struct gsm_subscriber *subscr) +{ + dbi_result result; + struct gsm_sms *sms; + + result = dbi_conn_queryf(conn, + "SELECT SMS.* " + "FROM SMS JOIN Subscriber ON " + "SMS.receiver_id = Subscriber.id " + "WHERE SMS.receiver_id = %llu AND SMS.sent IS NULL " + "AND Subscriber.lac > 0 " + "ORDER BY SMS.id LIMIT 1", + subscr->id); + if (!result) + return NULL; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return NULL; + } + + sms = sms_from_result(subscr->net, result); + + dbi_result_free(result); + + return sms; +} + +/* mark a given SMS as read */ +int db_sms_mark_sent(struct gsm_sms *sms) +{ + dbi_result result; + + result = dbi_conn_queryf(conn, + "UPDATE SMS " + "SET sent = datetime('now') " + "WHERE id = %llu", sms->id); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to mark SMS %llu as sent.\n", sms->id); + return 1; + } + + dbi_result_free(result); + return 0; +} + +/* increase the number of attempted deliveries */ +int db_sms_inc_deliver_attempts(struct gsm_sms *sms) +{ + dbi_result result; + + result = dbi_conn_queryf(conn, + "UPDATE SMS " + "SET deliver_attempts = deliver_attempts + 1 " + "WHERE id = %llu", sms->id); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to inc deliver attempts for " + "SMS %llu.\n", sms->id); + return 1; + } + + dbi_result_free(result); + return 0; +} + +int db_apdu_blob_store(struct gsm_subscriber *subscr, + u_int8_t apdu_id_flags, u_int8_t len, + u_int8_t *apdu) +{ + dbi_result result; + unsigned char *q_apdu; + + dbi_conn_quote_binary_copy(conn, apdu, len, &q_apdu); + + result = dbi_conn_queryf(conn, + "INSERT INTO ApduBlobs " + "(created,subscriber_id,apdu_id_flags,apdu) VALUES " + "(datetime('now'),%llu,%u,%s)", + subscr->id, apdu_id_flags, q_apdu); + + free(q_apdu); + + if (!result) + return -EIO; + + dbi_result_free(result); + return 0; +} + +int db_store_counter(struct counter *ctr) +{ + dbi_result result; + char *q_name; + + dbi_conn_quote_string_copy(conn, ctr->name, &q_name); + + result = dbi_conn_queryf(conn, + "INSERT INTO Counters " + "(timestamp,name,value) VALUES " + "(datetime('now'),%s,%lu)", q_name, ctr->value); + + free(q_name); + + if (!result) + return -EIO; + + dbi_result_free(result); + return 0; +} + +static int db_store_rate_ctr(struct rate_ctr_group *ctrg, unsigned int num, + char *q_prefix) +{ + dbi_result result; + char *q_name; + + dbi_conn_quote_string_copy(conn, ctrg->desc->ctr_desc[num].name, + &q_name); + + result = dbi_conn_queryf(conn, + "Insert INTO RateCounters " + "(timestamp,name,idx,value) VALUES " + "(datetime('now'),%s.%s,%u,%"PRIu64")", + q_prefix, q_name, ctrg->idx, ctrg->ctr[num].current); + + free(q_name); + + if (!result) + return -EIO; + + dbi_result_free(result); + return 0; +} + +int db_store_rate_ctr_group(struct rate_ctr_group *ctrg) +{ + unsigned int i; + char *q_prefix; + + dbi_conn_quote_string_copy(conn, ctrg->desc->group_name_prefix, &q_prefix); + + for (i = 0; i < ctrg->desc->num_ctr; i++) + db_store_rate_ctr(ctrg, i, q_prefix); + + free(q_prefix); + + return 0; +} diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c new file mode 100644 index 000000000..2b61aa9b0 --- /dev/null +++ b/src/libmsc/gsm_04_08.c @@ -0,0 +1,3345 @@ +/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface + * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */ + +/* (C) 2008-2009 by Harald Welte + * (C) 2008-2010 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +void *tall_locop_ctx; +void *tall_authciphop_ctx; + +int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn, u_int32_t tmsi); +static int gsm48_tx_simple(struct gsm_subscriber_connection *conn, + u_int8_t pdisc, u_int8_t msg_type); +static void schedule_reject(struct gsm_subscriber_connection *conn); +static void release_anchor(struct gsm_subscriber_connection *conn); + +struct gsm_lai { + u_int16_t mcc; + u_int16_t mnc; + u_int16_t lac; +}; + +static u_int32_t new_callref = 0x80000001; + +void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg) +{ + net->mncc_recv(net, msg); +} + +static int gsm48_conn_sendmsg(struct msgb *msg, struct gsm_subscriber_connection *conn, + struct gsm_trans *trans) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msg->data; + + /* if we get passed a transaction reference, do some common + * work that the caller no longer has to do */ + if (trans) { + gh->proto_discr = trans->protocol | (trans->transaction_id << 4); + msg->lchan = trans->conn->lchan; + } + + + if (msg->lchan) { + msg->trx = msg->lchan->ts->trx; + if ((gh->proto_discr & GSM48_PDISC_MASK) == GSM48_PDISC_CC) + DEBUGP(DCC, "(bts %d trx %d ts %d ti %02x) " + "Sending '%s' to MS.\n", msg->trx->bts->nr, + msg->trx->nr, msg->lchan->ts->nr, + gh->proto_discr & 0xf0, + gsm48_cc_msg_name(gh->msg_type)); + else + DEBUGP(DCC, "(bts %d trx %d ts %d pd %02x) " + "Sending 0x%02x to MS.\n", msg->trx->bts->nr, + msg->trx->nr, msg->lchan->ts->nr, + gh->proto_discr, gh->msg_type); + } + + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +int gsm48_cc_tx_notify_ss(struct gsm_trans *trans, const char *message) +{ + struct gsm48_hdr *gh; + struct msgb *ss_notify; + + ss_notify = gsm0480_create_notifySS(message); + if (!ss_notify) + return -1; + + gsm0480_wrap_invoke(ss_notify, GSM0480_OP_CODE_NOTIFY_SS, 0); + uint8_t *data = msgb_push(ss_notify, 1); + data[0] = ss_notify->len - 1; + gh = (struct gsm48_hdr *) msgb_push(ss_notify, sizeof(*gh)); + gh->msg_type = GSM48_MT_CC_FACILITY; + return gsm48_conn_sendmsg(ss_notify, trans->conn, trans); +} + +static void release_security_operation(struct gsm_subscriber_connection *conn) +{ + if (!conn->sec_operation) + return; + + talloc_free(conn->sec_operation); + conn->sec_operation = NULL; + msc_release_connection(conn); +} + +static void allocate_security_operation(struct gsm_subscriber_connection *conn) +{ + conn->sec_operation = talloc_zero(tall_authciphop_ctx, + struct gsm_security_operation); +} + +int gsm48_secure_channel(struct gsm_subscriber_connection *conn, int key_seq, + gsm_cbfn *cb, void *cb_data) +{ + struct gsm_network *net = conn->bts->network; + struct gsm_subscriber *subscr = conn->subscr; + struct gsm_security_operation *op; + struct gsm_auth_tuple atuple; + int status = -1, rc; + + /* Check if we _can_ enable encryption. Cases where we can't: + * - Encryption disabled in config + * - Channel already secured (nothing to do) + * - Subscriber equipment doesn't support configured encryption + */ + if (!net->a5_encryption) { + status = GSM_SECURITY_NOAVAIL; + } else if (conn->lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) { + DEBUGP(DMM, "Requesting to secure an already secure channel"); + status = GSM_SECURITY_SUCCEEDED; + } else if (!ms_cm2_a5n_support(subscr->equipment.classmark2, + net->a5_encryption)) { + DEBUGP(DMM, "Subscriber equipment doesn't support requested encryption"); + status = GSM_SECURITY_NOAVAIL; + } + + /* If not done yet, try to get info for this user */ + if (status < 0) { + rc = auth_get_tuple_for_subscr(&atuple, subscr, key_seq); + if (rc <= 0) + status = GSM_SECURITY_NOAVAIL; + } + + /* Are we done yet ? */ + if (status >= 0) + return cb ? + cb(GSM_HOOK_RR_SECURITY, status, NULL, conn, cb_data) : + 0; + + /* Start an operation (can't have more than one pending !!!) */ + if (conn->sec_operation) + return -EBUSY; + + allocate_security_operation(conn); + op = conn->sec_operation; + op->cb = cb; + op->cb_data = cb_data; + memcpy(&op->atuple, &atuple, sizeof(struct gsm_auth_tuple)); + + /* FIXME: Should start a timer for completion ... */ + + /* Then do whatever is needed ... */ + if (rc == AUTH_DO_AUTH_THAN_CIPH) { + /* Start authentication */ + return gsm48_tx_mm_auth_req(conn, op->atuple.rand, op->atuple.key_seq); + } else if (rc == AUTH_DO_CIPH) { + /* Start ciphering directly */ + return gsm0808_cipher_mode(conn, net->a5_encryption, + op->atuple.kc, 8, 0); + } + + return -EINVAL; /* not reached */ +} + +static int authorize_subscriber(struct gsm_loc_updating_operation *loc, + struct gsm_subscriber *subscriber) +{ + if (!subscriber) + return 0; + + /* + * Do not send accept yet as more information should arrive. Some + * phones will not send us the information and we will have to check + * what we want to do with that. + */ + if (loc && (loc->waiting_for_imsi || loc->waiting_for_imei)) + return 0; + + switch (subscriber->net->auth_policy) { + case GSM_AUTH_POLICY_CLOSED: + return subscriber->authorized; + case GSM_AUTH_POLICY_TOKEN: + if (subscriber->authorized) + return subscriber->authorized; + return (subscriber->flags & GSM_SUBSCRIBER_FIRST_CONTACT); + case GSM_AUTH_POLICY_ACCEPT_ALL: + return 1; + default: + return 0; + } +} + +static void release_loc_updating_req(struct gsm_subscriber_connection *conn) +{ + if (!conn->loc_operation) + return; + + /* No need to keep the connection up */ + release_anchor(conn); + + bsc_del_timer(&conn->loc_operation->updating_timer); + talloc_free(conn->loc_operation); + conn->loc_operation = NULL; + msc_release_connection(conn); +} + +static void allocate_loc_updating_req(struct gsm_subscriber_connection *conn) +{ + if (conn->loc_operation) + LOGP(DMM, LOGL_ERROR, "Connection already had operation.\n"); + release_loc_updating_req(conn); + + conn->loc_operation = talloc_zero(tall_locop_ctx, + struct gsm_loc_updating_operation); +} + +static int _gsm0408_authorize_sec_cb(unsigned int hooknum, unsigned int event, + struct msgb *msg, void *data, void *param) +{ + struct gsm_subscriber_connection *conn = data; + int rc = 0; + + switch (event) { + case GSM_SECURITY_AUTH_FAILED: + release_loc_updating_req(conn); + break; + + case GSM_SECURITY_NOAVAIL: + case GSM_SECURITY_SUCCEEDED: + /* We're all good */ + db_subscriber_alloc_tmsi(conn->subscr); + rc = gsm0408_loc_upd_acc(conn, conn->subscr->tmsi); + if (conn->bts->network->send_mm_info) { + /* send MM INFO with network name */ + rc = gsm48_tx_mm_info(conn); + } + + /* call subscr_update after putting the loc_upd_acc + * in the transmit queue, since S_SUBSCR_ATTACHED might + * trigger further action like SMS delivery */ + subscr_update(conn->subscr, conn->bts, + GSM_SUBSCRIBER_UPDATE_ATTACHED); + + /* + * The gsm0408_loc_upd_acc sends a MI with the TMSI. The + * MS needs to respond with a TMSI REALLOCATION COMPLETE + * (even if the TMSI is the same). + */ + break; + + default: + rc = -EINVAL; + }; + + return rc; +} + +static int gsm0408_authorize(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + if (authorize_subscriber(conn->loc_operation, conn->subscr)) + return gsm48_secure_channel(conn, + conn->loc_operation->key_seq, + _gsm0408_authorize_sec_cb, NULL); + return 0; +} + +void gsm0408_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause) +{ + struct gsm_trans *trans, *temp; + + /* avoid someone issuing a clear */ + conn->in_release = 1; + + /* + * Cancel any outstanding location updating request + * operation taking place on the subscriber connection. + */ + release_loc_updating_req(conn); + + /* We might need to cancel the paging response or such. */ + if (conn->sec_operation && conn->sec_operation->cb) { + conn->sec_operation->cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_AUTH_FAILED, + NULL, conn, conn->sec_operation->cb_data); + } + + release_security_operation(conn); + release_anchor(conn); + + /* Free all transactions that are associated with the released lchan */ + /* FIXME: this is not neccessarily the right thing to do, we should + * only set trans->lchan to NULL and wait for another lchan to be + * established to the same MM entity (phone/subscriber) */ + llist_for_each_entry_safe(trans, temp, &conn->bts->network->trans_list, entry) { + if (trans->conn == conn) + trans_free(trans); + } +} + +void gsm0408_clear_all_trans(struct gsm_network *net, int protocol) +{ + struct gsm_trans *trans, *temp; + + LOGP(DCC, LOGL_NOTICE, "Clearing all currently active transactions!!!\n"); + + llist_for_each_entry_safe(trans, temp, &net->trans_list, entry) { + if (trans->protocol == protocol) { + trans->callref = 0; + trans_free(trans); + } + } +} + +/* Chapter 9.2.14 : Send LOCATION UPDATING REJECT */ +int gsm0408_loc_upd_rej(struct gsm_subscriber_connection *conn, u_int8_t cause) +{ + struct gsm_bts *bts = conn->bts; + struct msgb *msg; + + counter_inc(bts->network->stats.loc_upd_resp.reject); + + msg = gsm48_create_loc_upd_rej(cause); + if (!msg) { + LOGP(DMM, LOGL_ERROR, "Failed to create msg for LOCATION UPDATING REJECT.\n"); + return -1; + } + + msg->lchan = conn->lchan; + + LOGP(DMM, LOGL_INFO, "Subscriber %s: LOCATION UPDATING REJECT " + "LAC=%u BTS=%u\n", conn->subscr ? + subscr_name(conn->subscr) : "unknown", + bts->location_area_code, bts->nr); + + return gsm48_conn_sendmsg(msg, conn, NULL); +} + +/* Chapter 9.2.13 : Send LOCATION UPDATE ACCEPT */ +int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn, u_int32_t tmsi) +{ + struct gsm_bts *bts = conn->bts; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + struct gsm48_loc_area_id *lai; + u_int8_t *mid; + + msg->lchan = conn->lchan; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_MM; + gh->msg_type = GSM48_MT_MM_LOC_UPD_ACCEPT; + + lai = (struct gsm48_loc_area_id *) msgb_put(msg, sizeof(*lai)); + gsm48_generate_lai(lai, bts->network->country_code, + bts->network->network_code, bts->location_area_code); + + mid = msgb_put(msg, GSM48_MID_TMSI_LEN); + gsm48_generate_mid_from_tmsi(mid, tmsi); + + DEBUGP(DMM, "-> LOCATION UPDATE ACCEPT\n"); + + counter_inc(bts->network->stats.loc_upd_resp.accept); + + return gsm48_conn_sendmsg(msg, conn, NULL); +} + +/* Transmit Chapter 9.2.10 Identity Request */ +static int mm_tx_identity_req(struct gsm_subscriber_connection *conn, u_int8_t id_type) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + + msg->lchan = conn->lchan; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_MM; + gh->msg_type = GSM48_MT_MM_ID_REQ; + gh->data[0] = id_type; + + return gsm48_conn_sendmsg(msg, conn, NULL); +} + + +/* Parse Chapter 9.2.11 Identity Response */ +static int mm_rx_id_resp(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm_lchan *lchan = msg->lchan; + struct gsm_bts *bts = lchan->ts->trx->bts; + struct gsm_network *net = bts->network; + u_int8_t mi_type = gh->data[1] & GSM_MI_TYPE_MASK; + char mi_string[GSM48_MI_SIZE]; + + gsm48_mi_to_string(mi_string, sizeof(mi_string), &gh->data[1], gh->data[0]); + DEBUGP(DMM, "IDENTITY RESPONSE: mi_type=0x%02x MI(%s)\n", + mi_type, mi_string); + + dispatch_signal(SS_SUBSCR, S_SUBSCR_IDENTITY, gh->data); + + switch (mi_type) { + case GSM_MI_TYPE_IMSI: + /* look up subscriber based on IMSI, create if not found */ + if (!conn->subscr) { + conn->subscr = subscr_get_by_imsi(net, mi_string); + if (!conn->subscr) + conn->subscr = db_create_subscriber(net, mi_string); + } + if (conn->loc_operation) + conn->loc_operation->waiting_for_imsi = 0; + break; + case GSM_MI_TYPE_IMEI: + case GSM_MI_TYPE_IMEISV: + /* update subscribe <-> IMEI mapping */ + if (conn->subscr) { + db_subscriber_assoc_imei(conn->subscr, mi_string); + db_sync_equipment(&conn->subscr->equipment); + } + if (conn->loc_operation) + conn->loc_operation->waiting_for_imei = 0; + break; + } + + /* Check if we can let the mobile station enter */ + return gsm0408_authorize(conn, msg); +} + + +static void loc_upd_rej_cb(void *data) +{ + struct gsm_subscriber_connection *conn = data; + struct gsm_lchan *lchan = conn->lchan; + struct gsm_bts *bts = lchan->ts->trx->bts; + + gsm0408_loc_upd_rej(conn, bts->network->reject_cause); + release_loc_updating_req(conn); +} + +static void schedule_reject(struct gsm_subscriber_connection *conn) +{ + conn->loc_operation->updating_timer.cb = loc_upd_rej_cb; + conn->loc_operation->updating_timer.data = conn; + bsc_schedule_timer(&conn->loc_operation->updating_timer, 5, 0); +} + +static const char *lupd_name(u_int8_t type) +{ + switch (type) { + case GSM48_LUPD_NORMAL: + return "NORMAL"; + case GSM48_LUPD_PERIODIC: + return "PEROIDOC"; + case GSM48_LUPD_IMSI_ATT: + return "IMSI ATTACH"; + default: + return "UNKNOWN"; + } +} + +/* Chapter 9.2.15: Receive Location Updating Request */ +static int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_loc_upd_req *lu; + struct gsm_subscriber *subscr = NULL; + struct gsm_bts *bts = conn->bts; + u_int8_t mi_type; + char mi_string[GSM48_MI_SIZE]; + int rc; + + lu = (struct gsm48_loc_upd_req *) gh->data; + + mi_type = lu->mi[0] & GSM_MI_TYPE_MASK; + + gsm48_mi_to_string(mi_string, sizeof(mi_string), lu->mi, lu->mi_len); + + DEBUGPC(DMM, "mi_type=0x%02x MI(%s) type=%s ", mi_type, mi_string, + lupd_name(lu->type)); + + dispatch_signal(SS_SUBSCR, S_SUBSCR_IDENTITY, &lu->mi_len); + + switch (lu->type) { + case GSM48_LUPD_NORMAL: + counter_inc(bts->network->stats.loc_upd_type.normal); + break; + case GSM48_LUPD_IMSI_ATT: + counter_inc(bts->network->stats.loc_upd_type.attach); + break; + case GSM48_LUPD_PERIODIC: + counter_inc(bts->network->stats.loc_upd_type.periodic); + break; + } + + /* + * Pseudo Spoof detection: Just drop a second/concurrent + * location updating request. + */ + if (conn->loc_operation) { + DEBUGPC(DMM, "ignoring request due an existing one: %p.\n", + conn->loc_operation); + gsm0408_loc_upd_rej(conn, GSM48_REJECT_PROTOCOL_ERROR); + return 0; + } + + allocate_loc_updating_req(conn); + + conn->loc_operation->key_seq = lu->key_seq; + + switch (mi_type) { + case GSM_MI_TYPE_IMSI: + DEBUGPC(DMM, "\n"); + /* we always want the IMEI, too */ + rc = mm_tx_identity_req(conn, GSM_MI_TYPE_IMEI); + conn->loc_operation->waiting_for_imei = 1; + + /* look up subscriber based on IMSI, create if not found */ + subscr = subscr_get_by_imsi(bts->network, mi_string); + if (!subscr) { + subscr = db_create_subscriber(bts->network, mi_string); + } + break; + case GSM_MI_TYPE_TMSI: + DEBUGPC(DMM, "\n"); + /* look up the subscriber based on TMSI, request IMSI if it fails */ + subscr = subscr_get_by_tmsi(bts->network, + tmsi_from_string(mi_string)); + if (!subscr) { + /* send IDENTITY REQUEST message to get IMSI */ + rc = mm_tx_identity_req(conn, GSM_MI_TYPE_IMSI); + conn->loc_operation->waiting_for_imsi = 1; + } + /* we always want the IMEI, too */ + rc = mm_tx_identity_req(conn, GSM_MI_TYPE_IMEI); + conn->loc_operation->waiting_for_imei = 1; + break; + case GSM_MI_TYPE_IMEI: + case GSM_MI_TYPE_IMEISV: + /* no sim card... FIXME: what to do ? */ + DEBUGPC(DMM, "unimplemented mobile identity type\n"); + break; + default: + DEBUGPC(DMM, "unknown mobile identity type\n"); + break; + } + + /* schedule the reject timer */ + schedule_reject(conn); + + if (!subscr) { + DEBUGPC(DRR, "<- Can't find any subscriber for this ID\n"); + /* FIXME: request id? close channel? */ + return -EINVAL; + } + + conn->subscr = subscr; + conn->subscr->equipment.classmark1 = lu->classmark1; + + /* check if we can let the subscriber into our network immediately + * or if we need to wait for identity responses. */ + return gsm0408_authorize(conn, msg); +} + +#if 0 +static u_int8_t to_bcd8(u_int8_t val) +{ + return ((val / 10) << 4) | (val % 10); +} +#endif + +/* Section 9.2.15a */ +int gsm48_tx_mm_info(struct gsm_subscriber_connection *conn) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + struct gsm_network *net = conn->bts->network; + u_int8_t *ptr8; + int name_len, name_pad; +#if 0 + time_t cur_t; + struct tm* cur_time; + int tz15min; +#endif + + msg->lchan = conn->lchan; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_MM; + gh->msg_type = GSM48_MT_MM_INFO; + + if (net->name_long) { +#if 0 + name_len = strlen(net->name_long); + /* 10.5.3.5a */ + ptr8 = msgb_put(msg, 3); + ptr8[0] = GSM48_IE_NAME_LONG; + ptr8[1] = name_len*2 +1; + ptr8[2] = 0x90; /* UCS2, no spare bits, no CI */ + + ptr16 = (u_int16_t *) msgb_put(msg, name_len*2); + for (i = 0; i < name_len; i++) + ptr16[i] = htons(net->name_long[i]); + + /* FIXME: Use Cell Broadcast, not UCS-2, since + * UCS-2 is only supported by later revisions of the spec */ +#endif + name_len = (strlen(net->name_long)*7)/8; + name_pad = (8 - strlen(net->name_long)*7)%8; + if (name_pad > 0) + name_len++; + /* 10.5.3.5a */ + ptr8 = msgb_put(msg, 3); + ptr8[0] = GSM48_IE_NAME_LONG; + ptr8[1] = name_len +1; + ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */ + + ptr8 = msgb_put(msg, name_len); + gsm_7bit_encode(ptr8, net->name_long); + + } + + if (net->name_short) { +#if 0 + name_len = strlen(net->name_short); + /* 10.5.3.5a */ + ptr8 = (u_int8_t *) msgb_put(msg, 3); + ptr8[0] = GSM48_IE_NAME_SHORT; + ptr8[1] = name_len*2 + 1; + ptr8[2] = 0x90; /* UCS2, no spare bits, no CI */ + + ptr16 = (u_int16_t *) msgb_put(msg, name_len*2); + for (i = 0; i < name_len; i++) + ptr16[i] = htons(net->name_short[i]); +#endif + name_len = (strlen(net->name_short)*7)/8; + name_pad = (8 - strlen(net->name_short)*7)%8; + if (name_pad > 0) + name_len++; + /* 10.5.3.5a */ + ptr8 = (u_int8_t *) msgb_put(msg, 3); + ptr8[0] = GSM48_IE_NAME_SHORT; + ptr8[1] = name_len +1; + ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */ + + ptr8 = msgb_put(msg, name_len); + gsm_7bit_encode(ptr8, net->name_short); + + } + +#if 0 + /* Section 10.5.3.9 */ + cur_t = time(NULL); + cur_time = gmtime(&cur_t); + ptr8 = msgb_put(msg, 8); + ptr8[0] = GSM48_IE_NET_TIME_TZ; + ptr8[1] = to_bcd8(cur_time->tm_year % 100); + ptr8[2] = to_bcd8(cur_time->tm_mon); + ptr8[3] = to_bcd8(cur_time->tm_mday); + ptr8[4] = to_bcd8(cur_time->tm_hour); + ptr8[5] = to_bcd8(cur_time->tm_min); + ptr8[6] = to_bcd8(cur_time->tm_sec); + /* 02.42: coded as BCD encoded signed value in units of 15 minutes */ + tz15min = (cur_time->tm_gmtoff)/(60*15); + ptr8[7] = to_bcd8(tz15min); + if (tz15min < 0) + ptr8[7] |= 0x80; +#endif + + DEBUGP(DMM, "-> MM INFO\n"); + + return gsm48_conn_sendmsg(msg, conn, NULL); +} + +/* Section 9.2.2 */ +int gsm48_tx_mm_auth_req(struct gsm_subscriber_connection *conn, u_int8_t *rand, int key_seq) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + struct gsm48_auth_req *ar = (struct gsm48_auth_req *) msgb_put(msg, sizeof(*ar)); + + DEBUGP(DMM, "-> AUTH REQ (rand = %s)\n", hexdump(rand, 16)); + + msg->lchan = conn->lchan; + gh->proto_discr = GSM48_PDISC_MM; + gh->msg_type = GSM48_MT_MM_AUTH_REQ; + + ar->key_seq = key_seq; + + /* 16 bytes RAND parameters */ + if (rand) + memcpy(ar->rand, rand, 16); + + return gsm48_conn_sendmsg(msg, conn, NULL); +} + +/* Section 9.2.1 */ +int gsm48_tx_mm_auth_rej(struct gsm_subscriber_connection *conn) +{ + DEBUGP(DMM, "-> AUTH REJECT\n"); + return gsm48_tx_simple(conn, GSM48_PDISC_MM, GSM48_MT_MM_AUTH_REJ); +} + +static int gsm48_tx_mm_serv_ack(struct gsm_subscriber_connection *conn) +{ + DEBUGP(DMM, "-> CM SERVICE ACK\n"); + return gsm48_tx_simple(conn, GSM48_PDISC_MM, GSM48_MT_MM_CM_SERV_ACC); +} + +/* 9.2.6 CM service reject */ +static int gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn, + enum gsm48_reject_value value) +{ + struct msgb *msg; + + msg = gsm48_create_mm_serv_rej(value); + if (!msg) { + LOGP(DMM, LOGL_ERROR, "Failed to allocate CM Service Reject.\n"); + return -1; + } + + DEBUGP(DMM, "-> CM SERVICE Reject cause: %d\n", value); + msg->lchan = conn->lchan; + return gsm48_conn_sendmsg(msg, conn, NULL); +} + +static int _gsm48_rx_mm_serv_req_sec_cb( + unsigned int hooknum, unsigned int event, + struct msgb *msg, void *data, void *param) +{ + struct gsm_subscriber_connection *conn = data; + int rc = 0; + + switch (event) { + case GSM_SECURITY_AUTH_FAILED: + /* Nothing to do */ + break; + + case GSM_SECURITY_NOAVAIL: + rc = gsm48_tx_mm_serv_ack(conn); + break; + + case GSM_SECURITY_SUCCEEDED: + /* nothing to do. CIPHER MODE COMMAND is + * implicit CM SERV ACK */ + break; + + default: + rc = -EINVAL; + }; + + return rc; +} + +/* + * Handle CM Service Requests + * a) Verify that the packet is long enough to contain the information + * we require otherwsie reject with INCORRECT_MESSAGE + * b) Try to parse the TMSI. If we do not have one reject + * c) Check that we know the subscriber with the TMSI otherwise reject + * with a HLR cause + * d) Set the subscriber on the gsm_lchan and accept + */ +static int gsm48_rx_mm_serv_req(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + u_int8_t mi_type; + char mi_string[GSM48_MI_SIZE]; + + struct gsm_bts *bts = conn->bts; + struct gsm_subscriber *subscr; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_service_request *req = + (struct gsm48_service_request *)gh->data; + /* unfortunately in Phase1 the classmark2 length is variable */ + u_int8_t classmark2_len = gh->data[1]; + u_int8_t *classmark2 = gh->data+2; + u_int8_t mi_len = *(classmark2 + classmark2_len); + u_int8_t *mi = (classmark2 + classmark2_len + 1); + + DEBUGP(DMM, "<- CM SERVICE REQUEST "); + if (msg->data_len < sizeof(struct gsm48_service_request*)) { + DEBUGPC(DMM, "wrong sized message\n"); + return gsm48_tx_mm_serv_rej(conn, + GSM48_REJECT_INCORRECT_MESSAGE); + } + + if (msg->data_len < req->mi_len + 6) { + DEBUGPC(DMM, "does not fit in packet\n"); + return gsm48_tx_mm_serv_rej(conn, + GSM48_REJECT_INCORRECT_MESSAGE); + } + + mi_type = mi[0] & GSM_MI_TYPE_MASK; + if (mi_type != GSM_MI_TYPE_TMSI) { + DEBUGPC(DMM, "mi_type is not TMSI: %d\n", mi_type); + return gsm48_tx_mm_serv_rej(conn, + GSM48_REJECT_INCORRECT_MESSAGE); + } + + gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len); + DEBUGPC(DMM, "serv_type=0x%02x mi_type=0x%02x M(%s)\n", + req->cm_service_type, mi_type, mi_string); + + dispatch_signal(SS_SUBSCR, S_SUBSCR_IDENTITY, (classmark2 + classmark2_len)); + + if (is_siemens_bts(bts)) + send_siemens_mrpci(msg->lchan, classmark2-1); + + subscr = subscr_get_by_tmsi(bts->network, + tmsi_from_string(mi_string)); + + /* FIXME: if we don't know the TMSI, inquire abit IMSI and allocate new TMSI */ + if (!subscr) + return gsm48_tx_mm_serv_rej(conn, + GSM48_REJECT_IMSI_UNKNOWN_IN_HLR); + + if (!conn->subscr) + conn->subscr = subscr; + else if (conn->subscr == subscr) + subscr_put(subscr); /* lchan already has a ref, don't need another one */ + else { + DEBUGP(DMM, "<- CM Channel already owned by someone else?\n"); + subscr_put(subscr); + } + + subscr->equipment.classmark2_len = classmark2_len; + memcpy(subscr->equipment.classmark2, classmark2, classmark2_len); + db_sync_equipment(&subscr->equipment); + + return gsm48_secure_channel(conn, req->cipher_key_seq, + _gsm48_rx_mm_serv_req_sec_cb, NULL); +} + +static int gsm48_rx_mm_imsi_detach_ind(struct msgb *msg) +{ + struct gsm_bts *bts = msg->lchan->ts->trx->bts; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_imsi_detach_ind *idi = + (struct gsm48_imsi_detach_ind *) gh->data; + u_int8_t mi_type = idi->mi[0] & GSM_MI_TYPE_MASK; + char mi_string[GSM48_MI_SIZE]; + struct gsm_subscriber *subscr = NULL; + + gsm48_mi_to_string(mi_string, sizeof(mi_string), idi->mi, idi->mi_len); + DEBUGP(DMM, "IMSI DETACH INDICATION: mi_type=0x%02x MI(%s): ", + mi_type, mi_string); + + counter_inc(bts->network->stats.loc_upd_type.detach); + + switch (mi_type) { + case GSM_MI_TYPE_TMSI: + subscr = subscr_get_by_tmsi(bts->network, + tmsi_from_string(mi_string)); + break; + case GSM_MI_TYPE_IMSI: + subscr = subscr_get_by_imsi(bts->network, mi_string); + break; + case GSM_MI_TYPE_IMEI: + case GSM_MI_TYPE_IMEISV: + /* no sim card... FIXME: what to do ? */ + DEBUGPC(DMM, "unimplemented mobile identity type\n"); + break; + default: + DEBUGPC(DMM, "unknown mobile identity type\n"); + break; + } + + if (subscr) { + subscr_update(subscr, msg->trx->bts, + GSM_SUBSCRIBER_UPDATE_DETACHED); + DEBUGP(DMM, "Subscriber: %s\n", subscr_name(subscr)); + + subscr->equipment.classmark1 = idi->classmark1; + db_sync_equipment(&subscr->equipment); + + subscr_put(subscr); + } else + DEBUGP(DMM, "Unknown Subscriber ?!?\n"); + + /* FIXME: iterate over all transactions and release them, + * imagine an IMSI DETACH happening during an active call! */ + + /* subscriber is detached: should we release lchan? */ + return 0; +} + +static int gsm48_rx_mm_status(struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + + DEBUGP(DMM, "MM STATUS (reject cause 0x%02x)\n", gh->data[0]); + + return 0; +} + +/* Chapter 9.2.3: Authentication Response */ +static int gsm48_rx_mm_auth_resp(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_auth_resp *ar = (struct gsm48_auth_resp*) gh->data; + struct gsm_network *net = conn->bts->network; + + DEBUGP(DMM, "MM AUTHENTICATION RESPONSE (sres = %s): ", + hexdump(ar->sres, 4)); + + /* Safety check */ + if (!conn->sec_operation) { + DEBUGP(DMM, "No authentication/cipher operation in progress !!!\n"); + return -EIO; + } + + /* Validate SRES */ + if (memcmp(conn->sec_operation->atuple.sres, ar->sres,4)) { + int rc; + gsm_cbfn *cb = conn->sec_operation->cb; + + DEBUGPC(DMM, "Invalid (expected %s)\n", + hexdump(conn->sec_operation->atuple.sres, 4)); + + if (cb) + cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_AUTH_FAILED, + NULL, conn, conn->sec_operation->cb_data); + + rc = gsm48_tx_mm_auth_rej(conn); + release_security_operation(conn); + return rc; + } + + DEBUGPC(DMM, "OK\n"); + + /* Start ciphering */ + return gsm0808_cipher_mode(conn, net->a5_encryption, + conn->sec_operation->atuple.kc, 8, 0); +} + +/* Receive a GSM 04.08 Mobility Management (MM) message */ +static int gsm0408_rcv_mm(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + int rc = 0; + + switch (gh->msg_type & 0xbf) { + case GSM48_MT_MM_LOC_UPD_REQUEST: + DEBUGP(DMM, "LOCATION UPDATING REQUEST: "); + rc = mm_rx_loc_upd_req(conn, msg); + break; + case GSM48_MT_MM_ID_RESP: + rc = mm_rx_id_resp(conn, msg); + break; + case GSM48_MT_MM_CM_SERV_REQ: + rc = gsm48_rx_mm_serv_req(conn, msg); + break; + case GSM48_MT_MM_STATUS: + rc = gsm48_rx_mm_status(msg); + break; + case GSM48_MT_MM_TMSI_REALL_COMPL: + DEBUGP(DMM, "TMSI Reallocation Completed. Subscriber: %s\n", + conn->subscr ? + subscr_name(conn->subscr) : + "unknown subscriber"); + release_loc_updating_req(conn); + break; + case GSM48_MT_MM_IMSI_DETACH_IND: + rc = gsm48_rx_mm_imsi_detach_ind(msg); + break; + case GSM48_MT_MM_CM_REEST_REQ: + DEBUGP(DMM, "CM REESTABLISH REQUEST: Not implemented\n"); + break; + case GSM48_MT_MM_AUTH_RESP: + rc = gsm48_rx_mm_auth_resp(conn, msg); + break; + default: + LOGP(DMM, LOGL_NOTICE, "Unknown GSM 04.08 MM msg type 0x%02x\n", + gh->msg_type); + break; + } + + return rc; +} + +/* Receive a PAGING RESPONSE message from the MS */ +static int gsm48_rx_rr_pag_resp(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm_bts *bts = conn->bts; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_pag_resp *resp; + u_int8_t *classmark2_lv = gh->data + 1; + u_int8_t mi_type; + char mi_string[GSM48_MI_SIZE]; + struct gsm_subscriber *subscr = NULL; + int rc = 0; + + resp = (struct gsm48_pag_resp *) &gh->data[0]; + gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh), + mi_string, &mi_type); + DEBUGP(DRR, "PAGING RESPONSE: mi_type=0x%02x MI(%s)\n", + mi_type, mi_string); + + switch (mi_type) { + case GSM_MI_TYPE_TMSI: + subscr = subscr_get_by_tmsi(bts->network, + tmsi_from_string(mi_string)); + break; + case GSM_MI_TYPE_IMSI: + subscr = subscr_get_by_imsi(bts->network, mi_string); + break; + } + + if (!subscr) { + DEBUGP(DRR, "<- Can't find any subscriber for this ID\n"); + /* FIXME: request id? close channel? */ + return -EINVAL; + } + DEBUGP(DRR, "<- Channel was requested by %s\n", + subscr->name && strlen(subscr->name) ? subscr->name : subscr->imsi); + + subscr->equipment.classmark2_len = *classmark2_lv; + memcpy(subscr->equipment.classmark2, classmark2_lv+1, *classmark2_lv); + db_sync_equipment(&subscr->equipment); + + rc = gsm48_handle_paging_resp(conn, msg, subscr); + return rc; +} + +static int gsm48_rx_rr_classmark(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm_subscriber *subscr = conn->subscr; + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + u_int8_t cm2_len, cm3_len = 0; + u_int8_t *cm2, *cm3 = NULL; + + DEBUGP(DRR, "CLASSMARK CHANGE "); + + /* classmark 2 */ + cm2_len = gh->data[0]; + cm2 = &gh->data[1]; + DEBUGPC(DRR, "CM2(len=%u) ", cm2_len); + + if (payload_len > cm2_len + 1) { + /* we must have a classmark3 */ + if (gh->data[cm2_len+1] != 0x20) { + DEBUGPC(DRR, "ERR CM3 TAG\n"); + return -EINVAL; + } + if (cm2_len > 3) { + DEBUGPC(DRR, "CM2 too long!\n"); + return -EINVAL; + } + + cm3_len = gh->data[cm2_len+2]; + cm3 = &gh->data[cm2_len+3]; + if (cm3_len > 14) { + DEBUGPC(DRR, "CM3 len %u too long!\n", cm3_len); + return -EINVAL; + } + DEBUGPC(DRR, "CM3(len=%u)\n", cm3_len); + } + if (subscr) { + subscr->equipment.classmark2_len = cm2_len; + memcpy(subscr->equipment.classmark2, cm2, cm2_len); + if (cm3) { + subscr->equipment.classmark3_len = cm3_len; + memcpy(subscr->equipment.classmark3, cm3, cm3_len); + } + db_sync_equipment(&subscr->equipment); + } + + return 0; +} + +static int gsm48_rx_rr_status(struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + + DEBUGP(DRR, "STATUS rr_cause = %s\n", + rr_cause_name(gh->data[0])); + + return 0; +} + +static int gsm48_rx_rr_meas_rep(struct msgb *msg) +{ + struct gsm_meas_rep *meas_rep = lchan_next_meas_rep(msg->lchan); + + /* This shouldn't actually end up here, as RSL treats + * L3 Info of 08.58 MEASUREMENT REPORT different by calling + * directly into gsm48_parse_meas_rep */ + DEBUGP(DMEAS, "DIRECT GSM48 MEASUREMENT REPORT ?!? "); + gsm48_parse_meas_rep(meas_rep, msg); + + return 0; +} + +static int gsm48_rx_rr_app_info(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + u_int8_t apdu_id_flags; + u_int8_t apdu_len; + u_int8_t *apdu_data; + + apdu_id_flags = gh->data[0]; + apdu_len = gh->data[1]; + apdu_data = gh->data+2; + + DEBUGP(DNM, "RX APPLICATION INFO id/flags=0x%02x apdu_len=%u apdu=%s", + apdu_id_flags, apdu_len, hexdump(apdu_data, apdu_len)); + + return db_apdu_blob_store(conn->subscr, apdu_id_flags, apdu_len, apdu_data); +} + +/* Chapter 9.1.10 Ciphering Mode Complete */ +static int gsm48_rx_rr_ciph_m_compl(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + gsm_cbfn *cb; + int rc = 0; + + DEBUGP(DRR, "CIPHERING MODE COMPLETE\n"); + + /* Safety check */ + if (!conn->sec_operation) { + DEBUGP(DRR, "No authentication/cipher operation in progress !!!\n"); + return -EIO; + } + + /* FIXME: check for MI (if any) */ + + /* Call back whatever was in progress (if anything) ... */ + cb = conn->sec_operation->cb; + if (cb) { + rc = cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_SUCCEEDED, + NULL, conn, conn->sec_operation->cb_data); + } + + /* Complete the operation */ + release_security_operation(conn); + + return rc; +} + +/* Chapter 9.1.16 Handover complete */ +static int gsm48_rx_rr_ho_compl(struct msgb *msg) +{ + struct lchan_signal_data sig; + struct gsm48_hdr *gh = msgb_l3(msg); + + DEBUGP(DRR, "HANDOVER COMPLETE cause = %s\n", + rr_cause_name(gh->data[0])); + + sig.lchan = msg->lchan; + sig.mr = NULL; + dispatch_signal(SS_LCHAN, S_LCHAN_HANDOVER_COMPL, &sig); + /* FIXME: release old channel */ + + return 0; +} + +/* Chapter 9.1.17 Handover Failure */ +static int gsm48_rx_rr_ho_fail(struct msgb *msg) +{ + struct lchan_signal_data sig; + struct gsm48_hdr *gh = msgb_l3(msg); + + DEBUGP(DRR, "HANDOVER FAILED cause = %s\n", + rr_cause_name(gh->data[0])); + + sig.lchan = msg->lchan; + sig.mr = NULL; + dispatch_signal(SS_LCHAN, S_LCHAN_HANDOVER_FAIL, &sig); + /* FIXME: release allocated new channel */ + + return 0; +} + +/* Receive a GSM 04.08 Radio Resource (RR) message */ +static int gsm0408_rcv_rr(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + int rc = 0; + + switch (gh->msg_type) { + case GSM48_MT_RR_CLSM_CHG: + rc = gsm48_rx_rr_classmark(conn, msg); + break; + case GSM48_MT_RR_GPRS_SUSP_REQ: + DEBUGP(DRR, "GRPS SUSPEND REQUEST\n"); + break; + case GSM48_MT_RR_PAG_RESP: + rc = gsm48_rx_rr_pag_resp(conn, msg); + break; + case GSM48_MT_RR_STATUS: + rc = gsm48_rx_rr_status(msg); + break; + case GSM48_MT_RR_MEAS_REP: + rc = gsm48_rx_rr_meas_rep(msg); + break; + case GSM48_MT_RR_APP_INFO: + rc = gsm48_rx_rr_app_info(conn, msg); + break; + case GSM48_MT_RR_CIPH_M_COMPL: + rc = gsm48_rx_rr_ciph_m_compl(conn, msg); + break; + case GSM48_MT_RR_HANDO_COMPL: + rc = gsm48_rx_rr_ho_compl(msg); + break; + case GSM48_MT_RR_HANDO_FAIL: + rc = gsm48_rx_rr_ho_fail(msg); + break; + default: + LOGP(DRR, LOGL_NOTICE, "Unimplemented " + "GSM 04.08 RR msg type 0x%02x\n", gh->msg_type); + break; + } + + return rc; +} + +int gsm48_send_rr_app_info(struct gsm_subscriber_connection *conn, u_int8_t apdu_id, + u_int8_t apdu_len, const u_int8_t *apdu) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + + msg->lchan = conn->lchan; + + DEBUGP(DRR, "TX APPLICATION INFO id=0x%02x, len=%u\n", + apdu_id, apdu_len); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 2 + apdu_len); + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_APP_INFO; + gh->data[0] = apdu_id; + gh->data[1] = apdu_len; + memcpy(gh->data+2, apdu, apdu_len); + + return gsm48_conn_sendmsg(msg, conn, NULL); +} + +/* Call Control */ + +/* The entire call control code is written in accordance with Figure 7.10c + * for 'very early assignment', i.e. we allocate a TCH/F during IMMEDIATE + * ASSIGN, then first use that TCH/F for signalling and later MODE MODIFY + * it for voice */ + +static void new_cc_state(struct gsm_trans *trans, int state) +{ + if (state > 31 || state < 0) + return; + + DEBUGP(DCC, "new state %s -> %s\n", + gsm48_cc_state_name(trans->cc.state), + gsm48_cc_state_name(state)); + + trans->cc.state = state; +} + +static int gsm48_cc_tx_status(struct gsm_trans *trans, void *arg) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + u_int8_t *cause, *call_state; + + gh->msg_type = GSM48_MT_CC_STATUS; + + cause = msgb_put(msg, 3); + cause[0] = 2; + cause[1] = GSM48_CAUSE_CS_GSM | GSM48_CAUSE_LOC_USER; + cause[2] = 0x80 | 30; /* response to status inquiry */ + + call_state = msgb_put(msg, 1); + call_state[0] = 0xc0 | 0x00; + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_tx_simple(struct gsm_subscriber_connection *conn, + u_int8_t pdisc, u_int8_t msg_type) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + msg->lchan = conn->lchan; + + gh->proto_discr = pdisc; + gh->msg_type = msg_type; + + return gsm48_conn_sendmsg(msg, conn, NULL); +} + +static void gsm48_stop_cc_timer(struct gsm_trans *trans) +{ + if (bsc_timer_pending(&trans->cc.timer)) { + DEBUGP(DCC, "stopping pending timer T%x\n", trans->cc.Tcurrent); + bsc_del_timer(&trans->cc.timer); + trans->cc.Tcurrent = 0; + } +} + +static int mncc_recvmsg(struct gsm_network *net, struct gsm_trans *trans, + int msg_type, struct gsm_mncc *mncc) +{ + struct msgb *msg; + unsigned char *data; + + if (trans) + if (trans->conn && trans->conn->lchan) + DEBUGP(DCC, "(bts %d trx %d ts %d ti %x sub %s) " + "Sending '%s' to MNCC.\n", + trans->conn->lchan->ts->trx->bts->nr, + trans->conn->lchan->ts->trx->nr, + trans->conn->lchan->ts->nr, trans->transaction_id, + (trans->subscr)?(trans->subscr->extension):"-", + get_mncc_name(msg_type)); + else + DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) " + "Sending '%s' to MNCC.\n", + (trans->subscr)?(trans->subscr->extension):"-", + get_mncc_name(msg_type)); + else + DEBUGP(DCC, "(bts - trx - ts - ti -- sub -) " + "Sending '%s' to MNCC.\n", get_mncc_name(msg_type)); + + mncc->msg_type = msg_type; + + msg = msgb_alloc(sizeof(struct gsm_mncc), "MNCC"); + if (!msg) + return -ENOMEM; + + data = msgb_put(msg, sizeof(struct gsm_mncc)); + memcpy(data, mncc, sizeof(struct gsm_mncc)); + + cc_tx_to_mncc(net, msg); + + return 0; +} + +int mncc_release_ind(struct gsm_network *net, struct gsm_trans *trans, + u_int32_t callref, int location, int value) +{ + struct gsm_mncc rel; + + memset(&rel, 0, sizeof(rel)); + rel.callref = callref; + mncc_set_cause(&rel, location, value); + return mncc_recvmsg(net, trans, MNCC_REL_IND, &rel); +} + +/* Call Control Specific transaction release. + * gets called by trans_free, DO NOT CALL YOURSELF! */ +void _gsm48_cc_trans_free(struct gsm_trans *trans) +{ + gsm48_stop_cc_timer(trans); + + /* send release to L4, if callref still exists */ + if (trans->callref) { + /* Ressource unavailable */ + mncc_release_ind(trans->subscr->net, trans, trans->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + } + if (trans->cc.state != GSM_CSTATE_NULL) + new_cc_state(trans, GSM_CSTATE_NULL); + if (trans->conn) + trau_mux_unmap(&trans->conn->lchan->ts->e1_link, trans->callref); +} + +static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg); + +/* call-back from paging the B-end of the connection */ +static int setup_trig_pag_evt(unsigned int hooknum, unsigned int event, + struct msgb *msg, void *_conn, void *param) +{ + int found = 0; + struct gsm_subscriber_connection *conn = _conn; + struct gsm_network **paging_request = param, *net; + struct gsm_trans *transt, *tmp; + + if (hooknum != GSM_HOOK_RR_PAGING) + return -EINVAL; + + net = *paging_request; + if (!net) { + DEBUGP(DCC, "Error Network not set!\n"); + return -EINVAL; + } + + /* check all tranactions (without lchan) for subscriber */ + llist_for_each_entry_safe(transt, tmp, &net->trans_list, entry) { + if (transt->paging_request != paging_request || transt->conn) + continue; + switch (event) { + case GSM_PAGING_SUCCEEDED: + if (!conn) // paranoid + break; + DEBUGP(DCC, "Paging subscr %s succeeded!\n", + transt->subscr->extension); + found = 1; + /* Assign lchan */ + if (!transt->conn) { + transt->paging_request = NULL; + transt->conn = conn; + conn->put_channel = 1; + } + /* send SETUP request to called party */ + gsm48_cc_tx_setup(transt, &transt->cc.msg); + break; + case GSM_PAGING_EXPIRED: + case GSM_PAGING_BUSY: + DEBUGP(DCC, "Paging subscr %s expired!\n", + transt->subscr->extension); + /* Temporarily out of order */ + found = 1; + mncc_release_ind(transt->subscr->net, transt, + transt->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_DEST_OOO); + transt->callref = 0; + transt->paging_request = NULL; + trans_free(transt); + break; + } + } + + talloc_free(paging_request); + + /* + * FIXME: The queue needs to be kicked. This is likely to go through a RF + * failure and then the subscr will be poke again. This needs a lot of fixing + * in the subscriber queue code. + */ + if (!found && conn) + conn->put_channel = 1; + return 0; +} + +static int tch_recv_mncc(struct gsm_network *net, u_int32_t callref, int enable); + +/* handle audio path for handover */ +static int handle_ho_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct rtp_socket *old_rs, *new_rs, *other_rs; + struct ho_signal_data *sig = signal_data; + + if (subsys != SS_HO || signal != S_HANDOVER_ACK) + return 0; + + if (ipacc_rtp_direct) { + LOGP(DHO, LOGL_ERROR, "unable to handover in direct RTP mode\n"); + return 0; + } + + /* RTP Proxy mode */ + new_rs = sig->new_lchan->abis_ip.rtp_socket; + old_rs = sig->old_lchan->abis_ip.rtp_socket; + + if (!new_rs) { + LOGP(DHO, LOGL_ERROR, "no RTP socket for new_lchan\n"); + return -EIO; + } + + rsl_ipacc_mdcx_to_rtpsock(sig->new_lchan); + + if (!old_rs) { + LOGP(DHO, LOGL_ERROR, "no RTP socket for old_lchan\n"); + return -EIO; + } + + /* copy rx_action and reference to other sock */ + new_rs->rx_action = old_rs->rx_action; + new_rs->tx_action = old_rs->tx_action; + new_rs->transmit = old_rs->transmit; + + switch (sig->old_lchan->abis_ip.rtp_socket->rx_action) { + case RTP_PROXY: + other_rs = old_rs->proxy.other_sock; + rtp_socket_proxy(new_rs, other_rs); + /* delete reference to other end socket to prevent + * rtp_socket_free() from removing the inverse reference */ + old_rs->proxy.other_sock = NULL; + break; + case RTP_RECV_UPSTREAM: + new_rs->receive = old_rs->receive; + break; + case RTP_NONE: + break; + } + + return 0; +} + +/* some other part of the code sends us a signal */ +static int handle_abisip_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_lchan *lchan = signal_data; + int rc; + struct gsm_network *net; + struct gsm_trans *trans; + + if (subsys != SS_ABISIP) + return 0; + + /* in case we use direct BTS-to-BTS RTP */ + if (ipacc_rtp_direct) + return 0; + + switch (signal) { + case S_ABISIP_CRCX_ACK: + /* in case we don't use direct BTS-to-BTS RTP */ + /* the BTS has successfully bound a TCH to a local ip/port, + * which means we can connect our UDP socket to it */ + if (lchan->abis_ip.rtp_socket) { + rtp_socket_free(lchan->abis_ip.rtp_socket); + lchan->abis_ip.rtp_socket = NULL; + } + + lchan->abis_ip.rtp_socket = rtp_socket_create(); + if (!lchan->abis_ip.rtp_socket) + return -EIO; + + rc = rtp_socket_connect(lchan->abis_ip.rtp_socket, + lchan->abis_ip.bound_ip, + lchan->abis_ip.bound_port); + if (rc < 0) + return -EIO; + + /* check if any transactions on this lchan still have + * a tch_recv_mncc request pending */ + net = lchan->ts->trx->bts->network; + llist_for_each_entry(trans, &net->trans_list, entry) { + if (trans->conn && trans->conn->lchan == lchan && trans->tch_recv) { + DEBUGP(DCC, "pending tch_recv_mncc request\n"); + tch_recv_mncc(net, trans->callref, 1); + } + } + break; + case S_ABISIP_DLCX_IND: + /* the BTS tells us a RTP stream has been disconnected */ + if (lchan->abis_ip.rtp_socket) { + rtp_socket_free(lchan->abis_ip.rtp_socket); + lchan->abis_ip.rtp_socket = NULL; + } + + break; + } + + return 0; +} + +/* map two ipaccess RTP streams onto each other */ +static int tch_map(struct gsm_lchan *lchan, struct gsm_lchan *remote_lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + struct gsm_bts *remote_bts = remote_lchan->ts->trx->bts; + int rc; + + DEBUGP(DCC, "Setting up TCH map between (bts=%u,trx=%u,ts=%u) and (bts=%u,trx=%u,ts=%u)\n", + bts->nr, lchan->ts->trx->nr, lchan->ts->nr, + remote_bts->nr, remote_lchan->ts->trx->nr, remote_lchan->ts->nr); + + if (bts->type != remote_bts->type) { + DEBUGP(DCC, "Cannot switch calls between different BTS types yet\n"); + return -EINVAL; + } + + // todo: map between different bts types + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + if (!ipacc_rtp_direct) { + /* connect the TCH's to our RTP proxy */ + rc = rsl_ipacc_mdcx_to_rtpsock(lchan); + if (rc < 0) + return rc; + rc = rsl_ipacc_mdcx_to_rtpsock(remote_lchan); + if (rc < 0) + return rc; + /* connect them with each other */ + rtp_socket_proxy(lchan->abis_ip.rtp_socket, + remote_lchan->abis_ip.rtp_socket); + } else { + /* directly connect TCH RTP streams to each other */ + rc = rsl_ipacc_mdcx(lchan, remote_lchan->abis_ip.bound_ip, + remote_lchan->abis_ip.bound_port, + remote_lchan->abis_ip.rtp_payload2); + if (rc < 0) + return rc; + rc = rsl_ipacc_mdcx(remote_lchan, lchan->abis_ip.bound_ip, + lchan->abis_ip.bound_port, + lchan->abis_ip.rtp_payload2); + } + break; + case GSM_BTS_TYPE_BS11: + trau_mux_map_lchan(lchan, remote_lchan); + break; + default: + DEBUGP(DCC, "Unknown BTS type %u\n", bts->type); + return -EINVAL; + } + + return 0; +} + +/* bridge channels of two transactions */ +static int tch_bridge(struct gsm_network *net, u_int32_t *refs) +{ + struct gsm_trans *trans1 = trans_find_by_callref(net, refs[0]); + struct gsm_trans *trans2 = trans_find_by_callref(net, refs[1]); + + if (!trans1 || !trans2) + return -EIO; + + if (!trans1->conn || !trans2->conn) + return -EIO; + + /* through-connect channel */ + return tch_map(trans1->conn->lchan, trans2->conn->lchan); +} + +/* enable receive of channels to MNCC upqueue */ +static int tch_recv_mncc(struct gsm_network *net, u_int32_t callref, int enable) +{ + struct gsm_trans *trans; + struct gsm_lchan *lchan; + struct gsm_bts *bts; + int rc; + + /* Find callref */ + trans = trans_find_by_callref(net, callref); + if (!trans) + return -EIO; + if (!trans->conn) + return 0; + lchan = trans->conn->lchan; + bts = lchan->ts->trx->bts; + + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + if (ipacc_rtp_direct) { + DEBUGP(DCC, "Error: RTP proxy is disabled\n"); + return -EINVAL; + } + /* in case, we don't have a RTP socket yet, we note this + * in the transaction and try later */ + if (!lchan->abis_ip.rtp_socket) { + trans->tch_recv = enable; + DEBUGP(DCC, "queue tch_recv_mncc request (%d)\n", enable); + return 0; + } + if (enable) { + /* connect the TCH's to our RTP proxy */ + rc = rsl_ipacc_mdcx_to_rtpsock(lchan); + if (rc < 0) + return rc; + /* assign socket to application interface */ + rtp_socket_upstream(lchan->abis_ip.rtp_socket, + net, callref); + } else + rtp_socket_upstream(lchan->abis_ip.rtp_socket, + net, 0); + break; + case GSM_BTS_TYPE_BS11: + if (enable) + return trau_recv_lchan(lchan, callref); + return trau_mux_unmap(NULL, callref); + break; + default: + DEBUGP(DCC, "Unknown BTS type %u\n", bts->type); + return -EINVAL; + } + + return 0; +} + +static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg) +{ + DEBUGP(DCC, "-> STATUS ENQ\n"); + return gsm48_cc_tx_status(trans, msg); +} + +static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg); +static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg); + +static void gsm48_cc_timeout(void *arg) +{ + struct gsm_trans *trans = arg; + int disconnect = 0, release = 0; + int mo_cause = GSM48_CC_CAUSE_RECOVERY_TIMER; + int mo_location = GSM48_CAUSE_LOC_USER; + int l4_cause = GSM48_CC_CAUSE_NORMAL_UNSPEC; + int l4_location = GSM48_CAUSE_LOC_PRN_S_LU; + struct gsm_mncc mo_rel, l4_rel; + + memset(&mo_rel, 0, sizeof(struct gsm_mncc)); + mo_rel.callref = trans->callref; + memset(&l4_rel, 0, sizeof(struct gsm_mncc)); + l4_rel.callref = trans->callref; + + switch(trans->cc.Tcurrent) { + case 0x303: + release = 1; + l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND; + break; + case 0x310: + disconnect = 1; + l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND; + break; + case 0x313: + disconnect = 1; + /* unknown, did not find it in the specs */ + break; + case 0x301: + disconnect = 1; + l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND; + break; + case 0x308: + if (!trans->cc.T308_second) { + /* restart T308 a second time */ + gsm48_cc_tx_release(trans, &trans->cc.msg); + trans->cc.T308_second = 1; + break; /* stay in release state */ + } + trans_free(trans); + return; +// release = 1; +// l4_cause = 14; +// break; + case 0x306: + release = 1; + mo_cause = trans->cc.msg.cause.value; + mo_location = trans->cc.msg.cause.location; + break; + case 0x323: + disconnect = 1; + break; + default: + release = 1; + } + + if (release && trans->callref) { + /* process release towards layer 4 */ + mncc_release_ind(trans->subscr->net, trans, trans->callref, + l4_location, l4_cause); + trans->callref = 0; + } + + if (disconnect && trans->callref) { + /* process disconnect towards layer 4 */ + mncc_set_cause(&l4_rel, l4_location, l4_cause); + mncc_recvmsg(trans->subscr->net, trans, MNCC_DISC_IND, &l4_rel); + } + + /* process disconnect towards mobile station */ + if (disconnect || release) { + mncc_set_cause(&mo_rel, mo_location, mo_cause); + mo_rel.cause.diag[0] = ((trans->cc.Tcurrent & 0xf00) >> 8) + '0'; + mo_rel.cause.diag[1] = ((trans->cc.Tcurrent & 0x0f0) >> 4) + '0'; + mo_rel.cause.diag[2] = (trans->cc.Tcurrent & 0x00f) + '0'; + mo_rel.cause.diag_len = 3; + + if (disconnect) + gsm48_cc_tx_disconnect(trans, &mo_rel); + if (release) + gsm48_cc_tx_release(trans, &mo_rel); + } + +} + +static void gsm48_start_cc_timer(struct gsm_trans *trans, int current, + int sec, int micro) +{ + DEBUGP(DCC, "starting timer T%x with %d seconds\n", current, sec); + trans->cc.timer.cb = gsm48_cc_timeout; + trans->cc.timer.data = trans; + bsc_schedule_timer(&trans->cc.timer, sec, micro); + trans->cc.Tcurrent = current; +} + +static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + u_int8_t msg_type = gh->msg_type & 0xbf; + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc setup; + + memset(&setup, 0, sizeof(struct gsm_mncc)); + setup.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* emergency setup is identified by msg_type */ + if (msg_type == GSM48_MT_CC_EMERG_SETUP) + setup.emergency = 1; + + /* use subscriber as calling party number */ + if (trans->subscr) { + setup.fields |= MNCC_F_CALLING; + strncpy(setup.calling.number, trans->subscr->extension, + sizeof(setup.calling.number)-1); + strncpy(setup.imsi, trans->subscr->imsi, + sizeof(setup.imsi)-1); + } + /* bearer capability */ + if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) { + setup.fields |= MNCC_F_BEARER_CAP; + gsm48_decode_bearer_cap(&setup.bearer_cap, + TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1); + } + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + setup.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&setup.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* called party bcd number */ + if (TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) { + setup.fields |= MNCC_F_CALLED; + gsm48_decode_called(&setup.called, + TLVP_VAL(&tp, GSM48_IE_CALLED_BCD)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + setup.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&setup.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + /* ss-version */ + if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) { + setup.fields |= MNCC_F_SSVERSION; + gsm48_decode_ssversion(&setup.ssversion, + TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1); + } + /* CLIR suppression */ + if (TLVP_PRESENT(&tp, GSM48_IE_CLIR_SUPP)) + setup.clir.sup = 1; + /* CLIR invocation */ + if (TLVP_PRESENT(&tp, GSM48_IE_CLIR_INVOC)) + setup.clir.inv = 1; + /* cc cap */ + if (TLVP_PRESENT(&tp, GSM48_IE_CC_CAP)) { + setup.fields |= MNCC_F_CCCAP; + gsm48_decode_cccap(&setup.cccap, + TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1); + } + + new_cc_state(trans, GSM_CSTATE_INITIATED); + + LOGP(DCC, LOGL_INFO, "Subscriber %s (%s) sends SETUP to %s\n", + subscr_name(trans->subscr), trans->subscr->extension, + setup.called.number); + + counter_inc(trans->subscr->net->stats.call.mo_setup); + + /* indicate setup to MNCC */ + mncc_recvmsg(trans->subscr->net, trans, MNCC_SETUP_IND, &setup); + + /* MNCC code will modify the channel asynchronously, we should + * ipaccess-bind only after the modification has been made to the + * lchan->tch_mode */ + return 0; +} + +static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + struct gsm_mncc *setup = arg; + int rc, trans_id; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + /* transaction id must not be assigned */ + if (trans->transaction_id != 0xff) { /* unasssigned */ + DEBUGP(DCC, "TX Setup with assigned transaction. " + "This is not allowed!\n"); + /* Temporarily out of order */ + rc = mncc_release_ind(trans->subscr->net, trans, trans->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + trans->callref = 0; + trans_free(trans); + return rc; + } + + /* Get free transaction_id */ + trans_id = trans_assign_trans_id(trans->subscr, GSM48_PDISC_CC, 0); + if (trans_id < 0) { + /* no free transaction ID */ + rc = mncc_release_ind(trans->subscr->net, trans, trans->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + trans->callref = 0; + trans_free(trans); + return rc; + } + trans->transaction_id = trans_id; + + gh->msg_type = GSM48_MT_CC_SETUP; + + gsm48_start_cc_timer(trans, 0x303, GSM48_T303); + + /* bearer capability */ + if (setup->fields & MNCC_F_BEARER_CAP) + gsm48_encode_bearer_cap(msg, 0, &setup->bearer_cap); + /* facility */ + if (setup->fields & MNCC_F_FACILITY) + gsm48_encode_facility(msg, 0, &setup->facility); + /* progress */ + if (setup->fields & MNCC_F_PROGRESS) + gsm48_encode_progress(msg, 0, &setup->progress); + /* calling party BCD number */ + if (setup->fields & MNCC_F_CALLING) + gsm48_encode_calling(msg, &setup->calling); + /* called party BCD number */ + if (setup->fields & MNCC_F_CALLED) + gsm48_encode_called(msg, &setup->called); + /* user-user */ + if (setup->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 0, &setup->useruser); + /* redirecting party BCD number */ + if (setup->fields & MNCC_F_REDIRECTING) + gsm48_encode_redirecting(msg, &setup->redirecting); + /* signal */ + if (setup->fields & MNCC_F_SIGNAL) + gsm48_encode_signal(msg, setup->signal); + + new_cc_state(trans, GSM_CSTATE_CALL_PRESENT); + + counter_inc(trans->subscr->net->stats.call.mt_setup); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc call_conf; + + gsm48_stop_cc_timer(trans); + gsm48_start_cc_timer(trans, 0x310, GSM48_T310); + + memset(&call_conf, 0, sizeof(struct gsm_mncc)); + call_conf.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); +#if 0 + /* repeat */ + if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_CIR)) + call_conf.repeat = 1; + if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_SEQ)) + call_conf.repeat = 2; +#endif + /* bearer capability */ + if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) { + call_conf.fields |= MNCC_F_BEARER_CAP; + gsm48_decode_bearer_cap(&call_conf.bearer_cap, + TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1); + } + /* cause */ + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + call_conf.fields |= MNCC_F_CAUSE; + gsm48_decode_cause(&call_conf.cause, + TLVP_VAL(&tp, GSM48_IE_CAUSE)-1); + } + /* cc cap */ + if (TLVP_PRESENT(&tp, GSM48_IE_CC_CAP)) { + call_conf.fields |= MNCC_F_CCCAP; + gsm48_decode_cccap(&call_conf.cccap, + TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1); + } + + new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF); + + return mncc_recvmsg(trans->subscr->net, trans, MNCC_CALL_CONF_IND, + &call_conf); +} + +static int gsm48_cc_tx_call_proc(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *proceeding = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_CALL_PROC; + + new_cc_state(trans, GSM_CSTATE_MO_CALL_PROC); + + /* bearer capability */ + if (proceeding->fields & MNCC_F_BEARER_CAP) + gsm48_encode_bearer_cap(msg, 0, &proceeding->bearer_cap); + /* facility */ + if (proceeding->fields & MNCC_F_FACILITY) + gsm48_encode_facility(msg, 0, &proceeding->facility); + /* progress */ + if (proceeding->fields & MNCC_F_PROGRESS) + gsm48_encode_progress(msg, 0, &proceeding->progress); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc alerting; + + gsm48_stop_cc_timer(trans); + gsm48_start_cc_timer(trans, 0x301, GSM48_T301); + + memset(&alerting, 0, sizeof(struct gsm_mncc)); + alerting.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + alerting.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&alerting.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + + /* progress */ + if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) { + alerting.fields |= MNCC_F_PROGRESS; + gsm48_decode_progress(&alerting.progress, + TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1); + } + /* ss-version */ + if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) { + alerting.fields |= MNCC_F_SSVERSION; + gsm48_decode_ssversion(&alerting.ssversion, + TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1); + } + + new_cc_state(trans, GSM_CSTATE_CALL_RECEIVED); + + return mncc_recvmsg(trans->subscr->net, trans, MNCC_ALERT_IND, + &alerting); +} + +static int gsm48_cc_tx_alerting(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *alerting = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_ALERTING; + + /* facility */ + if (alerting->fields & MNCC_F_FACILITY) + gsm48_encode_facility(msg, 0, &alerting->facility); + /* progress */ + if (alerting->fields & MNCC_F_PROGRESS) + gsm48_encode_progress(msg, 0, &alerting->progress); + /* user-user */ + if (alerting->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 0, &alerting->useruser); + + new_cc_state(trans, GSM_CSTATE_CALL_DELIVERED); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_tx_progress(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *progress = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_PROGRESS; + + /* progress */ + gsm48_encode_progress(msg, 1, &progress->progress); + /* user-user */ + if (progress->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 0, &progress->useruser); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_tx_connect(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *connect = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_CONNECT; + + gsm48_stop_cc_timer(trans); + gsm48_start_cc_timer(trans, 0x313, GSM48_T313); + + /* facility */ + if (connect->fields & MNCC_F_FACILITY) + gsm48_encode_facility(msg, 0, &connect->facility); + /* progress */ + if (connect->fields & MNCC_F_PROGRESS) + gsm48_encode_progress(msg, 0, &connect->progress); + /* connected number */ + if (connect->fields & MNCC_F_CONNECTED) + gsm48_encode_connected(msg, &connect->connected); + /* user-user */ + if (connect->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 0, &connect->useruser); + + new_cc_state(trans, GSM_CSTATE_CONNECT_IND); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_connect(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc connect; + + gsm48_stop_cc_timer(trans); + + memset(&connect, 0, sizeof(struct gsm_mncc)); + connect.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* use subscriber as connected party number */ + if (trans->subscr) { + connect.fields |= MNCC_F_CONNECTED; + strncpy(connect.connected.number, trans->subscr->extension, + sizeof(connect.connected.number)-1); + strncpy(connect.imsi, trans->subscr->imsi, + sizeof(connect.imsi)-1); + } + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + connect.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&connect.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + connect.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&connect.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + /* ss-version */ + if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) { + connect.fields |= MNCC_F_SSVERSION; + gsm48_decode_ssversion(&connect.ssversion, + TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1); + } + + new_cc_state(trans, GSM_CSTATE_CONNECT_REQUEST); + counter_inc(trans->subscr->net->stats.call.mt_connect); + + return mncc_recvmsg(trans->subscr->net, trans, MNCC_SETUP_CNF, &connect); +} + + +static int gsm48_cc_rx_connect_ack(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm_mncc connect_ack; + + gsm48_stop_cc_timer(trans); + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + counter_inc(trans->subscr->net->stats.call.mo_connect_ack); + + memset(&connect_ack, 0, sizeof(struct gsm_mncc)); + connect_ack.callref = trans->callref; + + return mncc_recvmsg(trans->subscr->net, trans, MNCC_SETUP_COMPL_IND, + &connect_ack); +} + +static int gsm48_cc_tx_connect_ack(struct gsm_trans *trans, void *arg) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_CONNECT_ACK; + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_disconnect(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc disc; + + gsm48_stop_cc_timer(trans); + + new_cc_state(trans, GSM_CSTATE_DISCONNECT_REQ); + + memset(&disc, 0, sizeof(struct gsm_mncc)); + disc.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_CAUSE, 0); + /* cause */ + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + disc.fields |= MNCC_F_CAUSE; + gsm48_decode_cause(&disc.cause, + TLVP_VAL(&tp, GSM48_IE_CAUSE)-1); + } + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + disc.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&disc.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + disc.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&disc.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + /* ss-version */ + if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) { + disc.fields |= MNCC_F_SSVERSION; + gsm48_decode_ssversion(&disc.ssversion, + TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1); + } + + return mncc_recvmsg(trans->subscr->net, trans, MNCC_DISC_IND, &disc); + +} + +static struct gsm_mncc_cause default_cause = { + .location = GSM48_CAUSE_LOC_PRN_S_LU, + .coding = 0, + .rec = 0, + .rec_val = 0, + .value = GSM48_CC_CAUSE_NORMAL_UNSPEC, + .diag_len = 0, + .diag = { 0 }, +}; + +static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *disc = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_DISCONNECT; + + gsm48_stop_cc_timer(trans); + gsm48_start_cc_timer(trans, 0x306, GSM48_T306); + + /* cause */ + if (disc->fields & MNCC_F_CAUSE) + gsm48_encode_cause(msg, 1, &disc->cause); + else + gsm48_encode_cause(msg, 1, &default_cause); + + /* facility */ + if (disc->fields & MNCC_F_FACILITY) + gsm48_encode_facility(msg, 0, &disc->facility); + /* progress */ + if (disc->fields & MNCC_F_PROGRESS) + gsm48_encode_progress(msg, 0, &disc->progress); + /* user-user */ + if (disc->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 0, &disc->useruser); + + /* store disconnect cause for T306 expiry */ + memcpy(&trans->cc.msg, disc, sizeof(struct gsm_mncc)); + + new_cc_state(trans, GSM_CSTATE_DISCONNECT_IND); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_release(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc rel; + int rc; + + gsm48_stop_cc_timer(trans); + + memset(&rel, 0, sizeof(struct gsm_mncc)); + rel.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* cause */ + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + rel.fields |= MNCC_F_CAUSE; + gsm48_decode_cause(&rel.cause, + TLVP_VAL(&tp, GSM48_IE_CAUSE)-1); + } + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + rel.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&rel.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + rel.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&rel.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + /* ss-version */ + if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) { + rel.fields |= MNCC_F_SSVERSION; + gsm48_decode_ssversion(&rel.ssversion, + TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1); + } + + if (trans->cc.state == GSM_CSTATE_RELEASE_REQ) { + /* release collision 5.4.5 */ + rc = mncc_recvmsg(trans->subscr->net, trans, MNCC_REL_CNF, &rel); + } else { + rc = gsm48_tx_simple(trans->conn, + GSM48_PDISC_CC | (trans->transaction_id << 4), + GSM48_MT_CC_RELEASE_COMPL); + rc = mncc_recvmsg(trans->subscr->net, trans, MNCC_REL_IND, &rel); + } + + new_cc_state(trans, GSM_CSTATE_NULL); + + trans->callref = 0; + trans_free(trans); + + return rc; +} + +static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *rel = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_RELEASE; + + trans->callref = 0; + + gsm48_stop_cc_timer(trans); + gsm48_start_cc_timer(trans, 0x308, GSM48_T308); + + /* cause */ + if (rel->fields & MNCC_F_CAUSE) + gsm48_encode_cause(msg, 0, &rel->cause); + /* facility */ + if (rel->fields & MNCC_F_FACILITY) + gsm48_encode_facility(msg, 0, &rel->facility); + /* user-user */ + if (rel->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 0, &rel->useruser); + + trans->cc.T308_second = 0; + memcpy(&trans->cc.msg, rel, sizeof(struct gsm_mncc)); + + if (trans->cc.state != GSM_CSTATE_RELEASE_REQ) + new_cc_state(trans, GSM_CSTATE_RELEASE_REQ); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_release_compl(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc rel; + int rc = 0; + + gsm48_stop_cc_timer(trans); + + memset(&rel, 0, sizeof(struct gsm_mncc)); + rel.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* cause */ + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + rel.fields |= MNCC_F_CAUSE; + gsm48_decode_cause(&rel.cause, + TLVP_VAL(&tp, GSM48_IE_CAUSE)-1); + } + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + rel.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&rel.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + rel.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&rel.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + /* ss-version */ + if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) { + rel.fields |= MNCC_F_SSVERSION; + gsm48_decode_ssversion(&rel.ssversion, + TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1); + } + + if (trans->callref) { + switch (trans->cc.state) { + case GSM_CSTATE_CALL_PRESENT: + rc = mncc_recvmsg(trans->subscr->net, trans, + MNCC_REJ_IND, &rel); + break; + case GSM_CSTATE_RELEASE_REQ: + rc = mncc_recvmsg(trans->subscr->net, trans, + MNCC_REL_CNF, &rel); + break; + default: + rc = mncc_recvmsg(trans->subscr->net, trans, + MNCC_REL_IND, &rel); + } + } + + trans->callref = 0; + trans_free(trans); + + return rc; +} + +static int gsm48_cc_tx_release_compl(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *rel = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + int ret; + + gh->msg_type = GSM48_MT_CC_RELEASE_COMPL; + + trans->callref = 0; + + gsm48_stop_cc_timer(trans); + + /* cause */ + if (rel->fields & MNCC_F_CAUSE) + gsm48_encode_cause(msg, 0, &rel->cause); + /* facility */ + if (rel->fields & MNCC_F_FACILITY) + gsm48_encode_facility(msg, 0, &rel->facility); + /* user-user */ + if (rel->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 0, &rel->useruser); + + ret = gsm48_conn_sendmsg(msg, trans->conn, trans); + + trans_free(trans); + + return ret; +} + +static int gsm48_cc_rx_facility(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc fac; + + memset(&fac, 0, sizeof(struct gsm_mncc)); + fac.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_FACILITY, 0); + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + fac.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&fac.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* ss-version */ + if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) { + fac.fields |= MNCC_F_SSVERSION; + gsm48_decode_ssversion(&fac.ssversion, + TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1); + } + + return mncc_recvmsg(trans->subscr->net, trans, MNCC_FACILITY_IND, &fac); +} + +static int gsm48_cc_tx_facility(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *fac = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_FACILITY; + + /* facility */ + gsm48_encode_facility(msg, 1, &fac->facility); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_hold(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm_mncc hold; + + memset(&hold, 0, sizeof(struct gsm_mncc)); + hold.callref = trans->callref; + return mncc_recvmsg(trans->subscr->net, trans, MNCC_HOLD_IND, &hold); +} + +static int gsm48_cc_tx_hold_ack(struct gsm_trans *trans, void *arg) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_HOLD_ACK; + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_tx_hold_rej(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *hold_rej = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_HOLD_REJ; + + /* cause */ + if (hold_rej->fields & MNCC_F_CAUSE) + gsm48_encode_cause(msg, 1, &hold_rej->cause); + else + gsm48_encode_cause(msg, 1, &default_cause); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_retrieve(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm_mncc retrieve; + + memset(&retrieve, 0, sizeof(struct gsm_mncc)); + retrieve.callref = trans->callref; + return mncc_recvmsg(trans->subscr->net, trans, MNCC_RETRIEVE_IND, + &retrieve); +} + +static int gsm48_cc_tx_retrieve_ack(struct gsm_trans *trans, void *arg) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_RETR_ACK; + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_tx_retrieve_rej(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *retrieve_rej = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_RETR_REJ; + + /* cause */ + if (retrieve_rej->fields & MNCC_F_CAUSE) + gsm48_encode_cause(msg, 1, &retrieve_rej->cause); + else + gsm48_encode_cause(msg, 1, &default_cause); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_start_dtmf(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc dtmf; + + memset(&dtmf, 0, sizeof(struct gsm_mncc)); + dtmf.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* keypad facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_KPD_FACILITY)) { + dtmf.fields |= MNCC_F_KEYPAD; + gsm48_decode_keypad(&dtmf.keypad, + TLVP_VAL(&tp, GSM48_IE_KPD_FACILITY)-1); + } + + return mncc_recvmsg(trans->subscr->net, trans, MNCC_START_DTMF_IND, &dtmf); +} + +static int gsm48_cc_tx_start_dtmf_ack(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *dtmf = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_START_DTMF_ACK; + + /* keypad */ + if (dtmf->fields & MNCC_F_KEYPAD) + gsm48_encode_keypad(msg, dtmf->keypad); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_tx_start_dtmf_rej(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *dtmf = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_START_DTMF_REJ; + + /* cause */ + if (dtmf->fields & MNCC_F_CAUSE) + gsm48_encode_cause(msg, 1, &dtmf->cause); + else + gsm48_encode_cause(msg, 1, &default_cause); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_tx_stop_dtmf_ack(struct gsm_trans *trans, void *arg) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_STOP_DTMF_ACK; + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_stop_dtmf(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm_mncc dtmf; + + memset(&dtmf, 0, sizeof(struct gsm_mncc)); + dtmf.callref = trans->callref; + + return mncc_recvmsg(trans->subscr->net, trans, MNCC_STOP_DTMF_IND, &dtmf); +} + +static int gsm48_cc_rx_modify(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc modify; + + memset(&modify, 0, sizeof(struct gsm_mncc)); + modify.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, 0); + /* bearer capability */ + if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) { + modify.fields |= MNCC_F_BEARER_CAP; + gsm48_decode_bearer_cap(&modify.bearer_cap, + TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1); + } + + new_cc_state(trans, GSM_CSTATE_MO_ORIG_MODIFY); + + return mncc_recvmsg(trans->subscr->net, trans, MNCC_MODIFY_IND, &modify); +} + +static int gsm48_cc_tx_modify(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *modify = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_MODIFY; + + gsm48_start_cc_timer(trans, 0x323, GSM48_T323); + + /* bearer capability */ + gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap); + + new_cc_state(trans, GSM_CSTATE_MO_TERM_MODIFY); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_modify_complete(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc modify; + + gsm48_stop_cc_timer(trans); + + memset(&modify, 0, sizeof(struct gsm_mncc)); + modify.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, 0); + /* bearer capability */ + if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) { + modify.fields |= MNCC_F_BEARER_CAP; + gsm48_decode_bearer_cap(&modify.bearer_cap, + TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1); + } + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + return mncc_recvmsg(trans->subscr->net, trans, MNCC_MODIFY_CNF, &modify); +} + +static int gsm48_cc_tx_modify_complete(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *modify = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_MODIFY_COMPL; + + /* bearer capability */ + gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap); + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_modify_reject(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc modify; + + gsm48_stop_cc_timer(trans); + + memset(&modify, 0, sizeof(struct gsm_mncc)); + modify.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, GSM48_IE_CAUSE); + /* bearer capability */ + if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) { + modify.fields |= GSM48_IE_BEARER_CAP; + gsm48_decode_bearer_cap(&modify.bearer_cap, + TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1); + } + /* cause */ + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + modify.fields |= MNCC_F_CAUSE; + gsm48_decode_cause(&modify.cause, + TLVP_VAL(&tp, GSM48_IE_CAUSE)-1); + } + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + return mncc_recvmsg(trans->subscr->net, trans, MNCC_MODIFY_REJ, &modify); +} + +static int gsm48_cc_tx_modify_reject(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *modify = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_MODIFY_REJECT; + + /* bearer capability */ + gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap); + /* cause */ + gsm48_encode_cause(msg, 1, &modify->cause); + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_tx_notify(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *notify = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_NOTIFY; + + /* notify */ + gsm48_encode_notify(msg, notify->notify); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_notify(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); +// struct tlv_parsed tp; + struct gsm_mncc notify; + + memset(¬ify, 0, sizeof(struct gsm_mncc)); + notify.callref = trans->callref; +// tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len); + if (payload_len >= 1) + gsm48_decode_notify(¬ify.notify, gh->data); + + return mncc_recvmsg(trans->subscr->net, trans, MNCC_NOTIFY_IND, ¬ify); +} + +static int gsm48_cc_tx_userinfo(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *user = arg; + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_USER_INFO; + + /* user-user */ + if (user->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 1, &user->useruser); + /* more data */ + if (user->more) + gsm48_encode_more(msg); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_userinfo(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc user; + + memset(&user, 0, sizeof(struct gsm_mncc)); + user.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_USER_USER, 0); + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + user.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&user.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + /* more data */ + if (TLVP_PRESENT(&tp, GSM48_IE_MORE_DATA)) + user.more = 1; + + return mncc_recvmsg(trans->subscr->net, trans, MNCC_USERINFO_IND, &user); +} + +static int _gsm48_lchan_modify(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *mode = arg; + + return gsm0808_assign_req(trans->conn, mode->lchan_mode, 1); +} + +static struct downstate { + u_int32_t states; + int type; + int (*rout) (struct gsm_trans *trans, void *arg); +} downstatelist[] = { + /* mobile originating call establishment */ + {SBIT(GSM_CSTATE_INITIATED), /* 5.2.1.2 */ + MNCC_CALL_PROC_REQ, gsm48_cc_tx_call_proc}, + {SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.2 | 5.2.1.5 */ + MNCC_ALERT_REQ, gsm48_cc_tx_alerting}, + {SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC) | SBIT(GSM_CSTATE_CALL_DELIVERED), /* 5.2.1.2 | 5.2.1.6 | 5.2.1.6 */ + MNCC_SETUP_RSP, gsm48_cc_tx_connect}, + {SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.4.2 */ + MNCC_PROGRESS_REQ, gsm48_cc_tx_progress}, + /* mobile terminating call establishment */ + {SBIT(GSM_CSTATE_NULL), /* 5.2.2.1 */ + MNCC_SETUP_REQ, gsm48_cc_tx_setup}, + {SBIT(GSM_CSTATE_CONNECT_REQUEST), + MNCC_SETUP_COMPL_REQ, gsm48_cc_tx_connect_ack}, + /* signalling during call */ + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_NOTIFY_REQ, gsm48_cc_tx_notify}, + {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), + MNCC_FACILITY_REQ, gsm48_cc_tx_facility}, + {ALL_STATES, + MNCC_START_DTMF_RSP, gsm48_cc_tx_start_dtmf_ack}, + {ALL_STATES, + MNCC_START_DTMF_REJ, gsm48_cc_tx_start_dtmf_rej}, + {ALL_STATES, + MNCC_STOP_DTMF_RSP, gsm48_cc_tx_stop_dtmf_ack}, + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_HOLD_CNF, gsm48_cc_tx_hold_ack}, + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_HOLD_REJ, gsm48_cc_tx_hold_rej}, + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_RETRIEVE_CNF, gsm48_cc_tx_retrieve_ack}, + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_RETRIEVE_REJ, gsm48_cc_tx_retrieve_rej}, + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_MODIFY_REQ, gsm48_cc_tx_modify}, + {SBIT(GSM_CSTATE_MO_ORIG_MODIFY), + MNCC_MODIFY_RSP, gsm48_cc_tx_modify_complete}, + {SBIT(GSM_CSTATE_MO_ORIG_MODIFY), + MNCC_MODIFY_REJ, gsm48_cc_tx_modify_reject}, + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_USERINFO_REQ, gsm48_cc_tx_userinfo}, + /* clearing */ + {SBIT(GSM_CSTATE_INITIATED), + MNCC_REJ_REQ, gsm48_cc_tx_release_compl}, + {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_DISCONNECT_IND) - SBIT(GSM_CSTATE_RELEASE_REQ) - SBIT(GSM_CSTATE_DISCONNECT_REQ), /* 5.4.4 */ + MNCC_DISC_REQ, gsm48_cc_tx_disconnect}, + {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), /* 5.4.3.2 */ + MNCC_REL_REQ, gsm48_cc_tx_release}, + /* special */ + {ALL_STATES, + MNCC_LCHAN_MODIFY, _gsm48_lchan_modify}, +}; + +#define DOWNSLLEN \ + (sizeof(downstatelist) / sizeof(struct downstate)) + + +int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg) +{ + int i, rc = 0; + struct gsm_trans *trans = NULL, *transt; + struct gsm_subscriber_connection *conn = NULL; + struct gsm_bts *bts = NULL; + struct gsm_mncc *data = arg, rel; + + DEBUGP(DMNCC, "receive message %s\n", get_mncc_name(msg_type)); + + /* handle special messages */ + switch(msg_type) { + case MNCC_BRIDGE: + return tch_bridge(net, arg); + case MNCC_FRAME_DROP: + return tch_recv_mncc(net, data->callref, 0); + case MNCC_FRAME_RECV: + return tch_recv_mncc(net, data->callref, 1); + case GSM_TCHF_FRAME: + /* Find callref */ + trans = trans_find_by_callref(net, data->callref); + if (!trans) { + LOGP(DMNCC, LOGL_ERROR, "TCH frame for non-existing trans\n"); + return -EIO; + } + if (!trans->conn) { + LOGP(DMNCC, LOGL_NOTICE, "TCH frame for trans without conn\n"); + return 0; + } + if (trans->conn->lchan->type != GSM_LCHAN_TCH_F) { + /* This should be LOGL_ERROR or NOTICE, but + * unfortuantely it happens for a couple of frames at + * the beginning of every RTP connection */ + LOGP(DMNCC, LOGL_DEBUG, "TCH frame for lchan != TCH_F\n"); + return 0; + } + bts = trans->conn->lchan->ts->trx->bts; + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + if (!trans->conn->lchan->abis_ip.rtp_socket) { + DEBUGP(DMNCC, "TCH frame to lchan without RTP connection\n"); + return 0; + } + return rtp_send_frame(trans->conn->lchan->abis_ip.rtp_socket, arg); + case GSM_BTS_TYPE_BS11: + return trau_send_frame(trans->conn->lchan, arg); + default: + DEBUGP(DCC, "Unknown BTS type %u\n", bts->type); + } + return -EINVAL; + } + + memset(&rel, 0, sizeof(struct gsm_mncc)); + rel.callref = data->callref; + + /* Find callref */ + trans = trans_find_by_callref(net, data->callref); + + /* Callref unknown */ + if (!trans) { + struct gsm_subscriber *subscr; + + if (msg_type != MNCC_SETUP_REQ) { + DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) " + "Received '%s' from MNCC with " + "unknown callref %d\n", data->called.number, + get_mncc_name(msg_type), data->callref); + /* Invalid call reference */ + return mncc_release_ind(net, NULL, data->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_INVAL_TRANS_ID); + } + if (!data->called.number[0] && !data->imsi[0]) { + DEBUGP(DCC, "(bts - trx - ts - ti) " + "Received '%s' from MNCC with " + "no number or IMSI\n", get_mncc_name(msg_type)); + /* Invalid number */ + return mncc_release_ind(net, NULL, data->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_INV_NR_FORMAT); + } + /* New transaction due to setup, find subscriber */ + if (data->called.number[0]) + subscr = subscr_get_by_extension(net, + data->called.number); + else + subscr = subscr_get_by_imsi(net, data->imsi); + /* If subscriber is not found */ + if (!subscr) { + DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) " + "Received '%s' from MNCC with " + "unknown subscriber %s\n", data->called.number, + get_mncc_name(msg_type), data->called.number); + /* Unknown subscriber */ + return mncc_release_ind(net, NULL, data->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_UNASSIGNED_NR); + } + /* If subscriber is not "attached" */ + if (!subscr->lac) { + DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) " + "Received '%s' from MNCC with " + "detached subscriber %s\n", data->called.number, + get_mncc_name(msg_type), data->called.number); + subscr_put(subscr); + /* Temporarily out of order */ + return mncc_release_ind(net, NULL, data->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_DEST_OOO); + } + /* Create transaction */ + trans = trans_alloc(subscr, GSM48_PDISC_CC, 0xff, data->callref); + if (!trans) { + DEBUGP(DCC, "No memory for trans.\n"); + subscr_put(subscr); + /* Ressource unavailable */ + mncc_release_ind(net, NULL, data->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + return -ENOMEM; + } + /* Find lchan */ + conn = connection_for_subscr(subscr); + + /* If subscriber has no lchan */ + if (!conn) { + /* find transaction with this subscriber already paging */ + llist_for_each_entry(transt, &net->trans_list, entry) { + /* Transaction of our lchan? */ + if (transt == trans || + transt->subscr != subscr) + continue; + DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) " + "Received '%s' from MNCC with " + "unallocated channel, paging already " + "started for lac %d.\n", + data->called.number, + get_mncc_name(msg_type), subscr->lac); + subscr_put(subscr); + trans_free(trans); + return 0; + } + /* store setup informations until paging was successfull */ + memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc)); + + /* Get a channel */ + trans->paging_request = talloc_zero(subscr->net, struct gsm_network*); + if (!trans->paging_request) { + LOGP(DCC, LOGL_ERROR, "Failed to allocate paging token.\n"); + subscr_put(subscr); + trans_free(trans); + return 0; + } + + *trans->paging_request = subscr->net; + subscr_get_channel(subscr, RSL_CHANNEED_TCH_F, setup_trig_pag_evt, trans->paging_request); + + subscr_put(subscr); + return 0; + } + /* Assign lchan */ + trans->conn = conn; + subscr_put(subscr); + } + + if (trans->conn) + conn = trans->conn; + + /* if paging did not respond yet */ + if (!conn) { + DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) " + "Received '%s' from MNCC in paging state\n", + (trans->subscr)?(trans->subscr->extension):"-", + get_mncc_name(msg_type)); + mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_NORM_CALL_CLEAR); + if (msg_type == MNCC_REL_REQ) + rc = mncc_recvmsg(net, trans, MNCC_REL_CNF, &rel); + else + rc = mncc_recvmsg(net, trans, MNCC_REL_IND, &rel); + trans->callref = 0; + trans_free(trans); + return rc; + } + + DEBUGP(DCC, "(bts %d trx %d ts %d ti %02x sub %s) " + "Received '%s' from MNCC in state %d (%s)\n", + conn->bts->nr, conn->lchan->ts->trx->nr, conn->lchan->ts->nr, + trans->transaction_id, + (trans->conn->subscr)?(trans->conn->subscr->extension):"-", + get_mncc_name(msg_type), trans->cc.state, + gsm48_cc_state_name(trans->cc.state)); + + /* Find function for current state and message */ + for (i = 0; i < DOWNSLLEN; i++) + if ((msg_type == downstatelist[i].type) + && ((1 << trans->cc.state) & downstatelist[i].states)) + break; + if (i == DOWNSLLEN) { + DEBUGP(DCC, "Message unhandled at this state.\n"); + return 0; + } + + rc = downstatelist[i].rout(trans, arg); + + return rc; +} + + +static struct datastate { + u_int32_t states; + int type; + int (*rout) (struct gsm_trans *trans, struct msgb *msg); +} datastatelist[] = { + /* mobile originating call establishment */ + {SBIT(GSM_CSTATE_NULL), /* 5.2.1.2 */ + GSM48_MT_CC_SETUP, gsm48_cc_rx_setup}, + {SBIT(GSM_CSTATE_NULL), /* 5.2.1.2 */ + GSM48_MT_CC_EMERG_SETUP, gsm48_cc_rx_setup}, + {SBIT(GSM_CSTATE_CONNECT_IND), /* 5.2.1.2 */ + GSM48_MT_CC_CONNECT_ACK, gsm48_cc_rx_connect_ack}, + /* mobile terminating call establishment */ + {SBIT(GSM_CSTATE_CALL_PRESENT), /* 5.2.2.3.2 */ + GSM48_MT_CC_CALL_CONF, gsm48_cc_rx_call_conf}, + {SBIT(GSM_CSTATE_CALL_PRESENT) | SBIT(GSM_CSTATE_MO_TERM_CALL_CONF), /* ???? | 5.2.2.3.2 */ + GSM48_MT_CC_ALERTING, gsm48_cc_rx_alerting}, + {SBIT(GSM_CSTATE_CALL_PRESENT) | SBIT(GSM_CSTATE_MO_TERM_CALL_CONF) | SBIT(GSM_CSTATE_CALL_RECEIVED), /* (5.2.2.6) | 5.2.2.6 | 5.2.2.6 */ + GSM48_MT_CC_CONNECT, gsm48_cc_rx_connect}, + /* signalling during call */ + {ALL_STATES - SBIT(GSM_CSTATE_NULL), + GSM48_MT_CC_FACILITY, gsm48_cc_rx_facility}, + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_NOTIFY, gsm48_cc_rx_notify}, + {ALL_STATES, + GSM48_MT_CC_START_DTMF, gsm48_cc_rx_start_dtmf}, + {ALL_STATES, + GSM48_MT_CC_STOP_DTMF, gsm48_cc_rx_stop_dtmf}, + {ALL_STATES, + GSM48_MT_CC_STATUS_ENQ, gsm48_cc_rx_status_enq}, + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_HOLD, gsm48_cc_rx_hold}, + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_RETR, gsm48_cc_rx_retrieve}, + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_MODIFY, gsm48_cc_rx_modify}, + {SBIT(GSM_CSTATE_MO_TERM_MODIFY), + GSM48_MT_CC_MODIFY_COMPL, gsm48_cc_rx_modify_complete}, + {SBIT(GSM_CSTATE_MO_TERM_MODIFY), + GSM48_MT_CC_MODIFY_REJECT, gsm48_cc_rx_modify_reject}, + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_USER_INFO, gsm48_cc_rx_userinfo}, + /* clearing */ + {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), /* 5.4.3.2 */ + GSM48_MT_CC_DISCONNECT, gsm48_cc_rx_disconnect}, + {ALL_STATES - SBIT(GSM_CSTATE_NULL), /* 5.4.4.1.2.2 */ + GSM48_MT_CC_RELEASE, gsm48_cc_rx_release}, + {ALL_STATES, /* 5.4.3.4 */ + GSM48_MT_CC_RELEASE_COMPL, gsm48_cc_rx_release_compl}, +}; + +#define DATASLLEN \ + (sizeof(datastatelist) / sizeof(struct datastate)) + +static int gsm0408_rcv_cc(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + u_int8_t msg_type = gh->msg_type & 0xbf; + u_int8_t transaction_id = ((gh->proto_discr & 0xf0) ^ 0x80) >> 4; /* flip */ + struct gsm_trans *trans = NULL; + int i, rc = 0; + + if (msg_type & 0x80) { + DEBUGP(DCC, "MSG 0x%2x not defined for PD error\n", msg_type); + return -EINVAL; + } + + /* Find transaction */ + trans = trans_find_by_id(conn->subscr, GSM48_PDISC_CC, transaction_id); + + DEBUGP(DCC, "(bts %d trx %d ts %d ti %x sub %s) " + "Received '%s' from MS in state %d (%s)\n", + conn->bts->nr, conn->lchan->ts->trx->nr, conn->lchan->ts->nr, + transaction_id, (conn->subscr)?(conn->subscr->extension):"-", + gsm48_cc_msg_name(msg_type), trans?(trans->cc.state):0, + gsm48_cc_state_name(trans?(trans->cc.state):0)); + + /* Create transaction */ + if (!trans) { + DEBUGP(DCC, "Unknown transaction ID %x, " + "creating new trans.\n", transaction_id); + /* Create transaction */ + trans = trans_alloc(conn->subscr, GSM48_PDISC_CC, + transaction_id, new_callref++); + if (!trans) { + DEBUGP(DCC, "No memory for trans.\n"); + rc = gsm48_tx_simple(conn, + GSM48_PDISC_CC | (transaction_id << 4), + GSM48_MT_CC_RELEASE_COMPL); + return -ENOMEM; + } + /* Assign transaction */ + trans->conn = conn; + } + + /* find function for current state and message */ + for (i = 0; i < DATASLLEN; i++) + if ((msg_type == datastatelist[i].type) + && ((1 << trans->cc.state) & datastatelist[i].states)) + break; + if (i == DATASLLEN) { + DEBUGP(DCC, "Message unhandled at this state.\n"); + return 0; + } + + rc = datastatelist[i].rout(trans, msg); + + return rc; +} + +/* Create a dummy to wait five seconds */ +static void release_anchor(struct gsm_subscriber_connection *conn) +{ + if (!conn->anch_operation) + return; + + bsc_del_timer(&conn->anch_operation->timeout); + talloc_free(conn->anch_operation); + conn->anch_operation = NULL; +} + +static void anchor_timeout(void *_data) +{ + struct gsm_subscriber_connection *con = _data; + + release_anchor(con); + msc_release_connection(con); +} + +int gsm0408_new_conn(struct gsm_subscriber_connection *conn) +{ + conn->anch_operation = talloc_zero(conn, struct gsm_anchor_operation); + if (!conn->anch_operation) + return -1; + + conn->anch_operation->timeout.data = conn; + conn->anch_operation->timeout.cb = anchor_timeout; + bsc_schedule_timer(&conn->anch_operation->timeout, 5, 0); + return 0; +} + +/* here we get data from the BSC level... */ +int gsm0408_dispatch(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + u_int8_t pdisc = gh->proto_discr & 0x0f; + int rc = 0; + + if (silent_call_reroute(conn, msg)) + return silent_call_rx(conn, msg); + + switch (pdisc) { + case GSM48_PDISC_CC: + release_anchor(conn); + rc = gsm0408_rcv_cc(conn, msg); + break; + case GSM48_PDISC_MM: + rc = gsm0408_rcv_mm(conn, msg); + break; + case GSM48_PDISC_RR: + rc = gsm0408_rcv_rr(conn, msg); + break; + case GSM48_PDISC_SMS: + release_anchor(conn); + rc = gsm0411_rcv_sms(conn, msg); + break; + case GSM48_PDISC_MM_GPRS: + case GSM48_PDISC_SM_GPRS: + LOGP(DRLL, LOGL_NOTICE, "Unimplemented " + "GSM 04.08 discriminator 0x%02x\n", pdisc); + break; + case GSM48_PDISC_NC_SS: + release_anchor(conn); + rc = handle_rcv_ussd(conn, msg); + break; + default: + LOGP(DRLL, LOGL_NOTICE, "Unknown " + "GSM 04.08 discriminator 0x%02x\n", pdisc); + break; + } + + return rc; +} + +/* + * This will be ran by the linker when loading the DSO. We use it to + * do system initialization, e.g. registration of signal handlers. + */ +static __attribute__((constructor)) void on_dso_load_0408(void) +{ + register_signal_handler(SS_HO, handle_ho_signal, NULL); + register_signal_handler(SS_ABISIP, handle_abisip_signal, NULL); +} diff --git a/src/libmsc/gsm_04_11.c b/src/libmsc/gsm_04_11.c new file mode 100644 index 000000000..812e758cd --- /dev/null +++ b/src/libmsc/gsm_04_11.c @@ -0,0 +1,1240 @@ +/* Point-to-Point (PP) Short Message Service (SMS) + * Support on Mobile Radio Interface + * 3GPP TS 04.11 version 7.1.0 Release 1998 / ETSI TS 100 942 V7.1.0 */ + +/* (C) 2008 by Daniel Willmann + * (C) 2009 by Harald Welte + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GSM411_ALLOC_SIZE 1024 +#define GSM411_ALLOC_HEADROOM 128 + +void *tall_gsms_ctx; +static u_int32_t new_callref = 0x40000001; + +static const struct value_string cp_cause_strs[] = { + { GSM411_CP_CAUSE_NET_FAIL, "Network Failure" }, + { GSM411_CP_CAUSE_CONGESTION, "Congestion" }, + { GSM411_CP_CAUSE_INV_TRANS_ID, "Invalid Transaction ID" }, + { GSM411_CP_CAUSE_SEMANT_INC_MSG, "Semantically Incorrect Message" }, + { GSM411_CP_CAUSE_INV_MAND_INF, "Invalid Mandatory Information" }, + { GSM411_CP_CAUSE_MSGTYPE_NOTEXIST, "Message Type doesn't exist" }, + { GSM411_CP_CAUSE_MSG_INCOMP_STATE, + "Message incompatible with protocol state" }, + { GSM411_CP_CAUSE_IE_NOTEXIST, "IE does not exist" }, + { GSM411_CP_CAUSE_PROTOCOL_ERR, "Protocol Error" }, + { 0, 0 } +}; + +static const struct value_string rp_cause_strs[] = { + { GSM411_RP_CAUSE_MO_NUM_UNASSIGNED, "(MO) Number not assigned" }, + { GSM411_RP_CAUSE_MO_OP_DET_BARR, "(MO) Operator determined barring" }, + { GSM411_RP_CAUSE_MO_CALL_BARRED, "(MO) Call barred" }, + { GSM411_RP_CAUSE_MO_SMS_REJECTED, "(MO) SMS rejected" }, + { GSM411_RP_CAUSE_MO_DEST_OUT_OF_ORDER, "(MO) Destination out of order" }, + { GSM411_RP_CAUSE_MO_UNIDENTIFIED_SUBSCR, "(MO) Unidentified subscriber" }, + { GSM411_RP_CAUSE_MO_FACILITY_REJ, "(MO) Facility reject" }, + { GSM411_RP_CAUSE_MO_UNKNOWN_SUBSCR, "(MO) Unknown subscriber" }, + { GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER, "(MO) Network out of order" }, + { GSM411_RP_CAUSE_MO_TEMP_FAIL, "(MO) Temporary failure" }, + { GSM411_RP_CAUSE_MO_CONGESTION, "(MO) Congestion" }, + { GSM411_RP_CAUSE_MO_RES_UNAVAIL, "(MO) Resource unavailable" }, + { GSM411_RP_CAUSE_MO_REQ_FAC_NOTSUBSCR, "(MO) Requested facility not subscribed" }, + { GSM411_RP_CAUSE_MO_REQ_FAC_NOTIMPL, "(MO) Requested facility not implemented" }, + { GSM411_RP_CAUSE_MO_INTERWORKING, "(MO) Interworking" }, + /* valid only for MT */ + { GSM411_RP_CAUSE_MT_MEM_EXCEEDED, "(MT) Memory Exceeded" }, + /* valid for both directions */ + { GSM411_RP_CAUSE_INV_TRANS_REF, "Invalid Transaction Reference" }, + { GSM411_RP_CAUSE_SEMANT_INC_MSG, "Semantically Incorrect Message" }, + { GSM411_RP_CAUSE_INV_MAND_INF, "Invalid Mandatory Information" }, + { GSM411_RP_CAUSE_MSGTYPE_NOTEXIST, "Message Type non-existant" }, + { GSM411_RP_CAUSE_MSG_INCOMP_STATE, "Message incompatible with protocol state" }, + { GSM411_RP_CAUSE_IE_NOTEXIST, "Information Element not existing" }, + { GSM411_RP_CAUSE_PROTOCOL_ERR, "Protocol Error" }, + { 0, NULL } +}; + + +struct gsm_sms *sms_alloc(void) +{ + return talloc_zero(tall_gsms_ctx, struct gsm_sms); +} + +void sms_free(struct gsm_sms *sms) +{ + /* drop references to subscriber structure */ + if (sms->sender) + subscr_put(sms->sender); + if (sms->receiver) + subscr_put(sms->receiver); + + talloc_free(sms); +} + +struct gsm_sms *sms_from_text(struct gsm_subscriber *receiver, int dcs, const char *text) +{ + struct gsm_sms *sms = sms_alloc(); + + if (!sms) + return NULL; + + sms->receiver = subscr_get(receiver); + strncpy(sms->text, text, sizeof(sms->text)-1); + + /* FIXME: don't use ID 1 static */ + sms->sender = subscr_get_by_id(receiver->net, 1); + sms->reply_path_req = 0; + sms->status_rep_req = 0; + sms->ud_hdr_ind = 0; + sms->protocol_id = 0; /* implicit */ + sms->data_coding_scheme = dcs; + strncpy(sms->dest_addr, receiver->extension, sizeof(sms->dest_addr)-1); + /* Generate user_data */ + sms->user_data_len = gsm_7bit_encode(sms->user_data, sms->text); + + return sms; +} + + +static void send_signal(int sig_no, + struct gsm_trans *trans, + struct gsm_sms *sms, + int paging_result) +{ + struct sms_signal_data sig; + sig.trans = trans; + sig.sms = sms; + sig.paging_result = paging_result; + dispatch_signal(SS_SMS, sig_no, &sig); +} + +/* + * This should be called whenever all SMS to a given subscriber + * on a given connection has been sent. This will inform the higher + * layers that a channel can be given up. + */ +static void gsm411_release_conn(struct gsm_subscriber_connection *conn) +{ + if (!conn) + return; + + subscr_put_channel(conn->subscr); +} + +struct msgb *gsm411_msgb_alloc(void) +{ + return msgb_alloc_headroom(GSM411_ALLOC_SIZE, GSM411_ALLOC_HEADROOM, + "GSM 04.11"); +} + +static int gsm411_sendmsg(struct gsm_subscriber_connection *conn, struct msgb *msg, u_int8_t link_id) +{ + DEBUGP(DSMS, "GSM4.11 TX %s\n", hexdump(msg->data, msg->len)); + msg->l3h = msg->data; + return gsm0808_submit_dtap(conn, msg, link_id, 1); +} + +/* SMC TC1* is expired */ +static void cp_timer_expired(void *data) +{ + struct gsm_trans *trans = data; + + DEBUGP(DSMS, "SMC Timer TC1* is expired, calling trans_free()\n"); + /* FIXME: we need to re-transmit the last CP-DATA 1..3 times */ + trans_free(trans); +} + +/* Prefix msg with a 04.08/04.11 CP header */ +static int gsm411_cp_sendmsg(struct msgb *msg, struct gsm_trans *trans, + u_int8_t msg_type) +{ + struct gsm48_hdr *gh; + + gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh)); + /* Outgoing needs the highest bit set */ + gh->proto_discr = trans->protocol | (trans->transaction_id<<4); + gh->msg_type = msg_type; + + /* mobile originating */ + switch (gh->msg_type) { + case GSM411_MT_CP_DATA: + /* 5.2.3.1.2: enter MO-wait for CP-ack */ + /* 5.2.3.2.3: enter MT-wait for CP-ACK */ + trans->sms.cp_state = GSM411_CPS_WAIT_CP_ACK; + trans->sms.cp_timer.data = trans; + trans->sms.cp_timer.cb = cp_timer_expired; + /* 5.3.2.1: Set Timer TC1A */ + bsc_schedule_timer(&trans->sms.cp_timer, GSM411_TMR_TC1A); + DEBUGP(DSMS, "TX: CP-DATA "); + break; + case GSM411_MT_CP_ACK: + DEBUGP(DSMS, "TX: CP-ACK "); + break; + case GSM411_MT_CP_ERROR: + DEBUGP(DSMS, "TX: CP-ERROR "); + break; + } + + DEBUGPC(DSMS, "trans=%x\n", trans->transaction_id); + + return gsm411_sendmsg(trans->conn, msg, trans->sms.link_id); +} + +/* Prefix msg with a RP-DATA header and send as CP-DATA */ +static int gsm411_rp_sendmsg(struct msgb *msg, struct gsm_trans *trans, + u_int8_t rp_msg_type, u_int8_t rp_msg_ref) +{ + struct gsm411_rp_hdr *rp; + u_int8_t len = msg->len; + + /* GSM 04.11 RP-DATA header */ + rp = (struct gsm411_rp_hdr *)msgb_push(msg, sizeof(*rp)); + rp->len = len + 2; + rp->msg_type = rp_msg_type; + rp->msg_ref = rp_msg_ref; /* FIXME: Choose randomly */ + + return gsm411_cp_sendmsg(msg, trans, GSM411_MT_CP_DATA); +} + +/* Turn int into semi-octet representation: 98 => 0x89 */ +static u_int8_t bcdify(u_int8_t value) +{ + u_int8_t ret; + + ret = value / 10; + ret |= (value % 10) << 4; + + return ret; +} + +/* Turn semi-octet representation into int: 0x89 => 98 */ +static u_int8_t unbcdify(u_int8_t value) +{ + u_int8_t ret; + + if ((value & 0x0F) > 9 || (value >> 4) > 9) + LOGP(DSMS, LOGL_ERROR, + "unbcdify got too big nibble: 0x%02X\n", value); + + ret = (value&0x0F)*10; + ret += value>>4; + + return ret; +} + +/* Generate 03.40 TP-SCTS */ +static void gsm340_gen_scts(u_int8_t *scts, time_t time) +{ + struct tm *tm = localtime(&time); + + *scts++ = bcdify(tm->tm_year % 100); + *scts++ = bcdify(tm->tm_mon + 1); + *scts++ = bcdify(tm->tm_mday); + *scts++ = bcdify(tm->tm_hour); + *scts++ = bcdify(tm->tm_min); + *scts++ = bcdify(tm->tm_sec); + *scts++ = bcdify(tm->tm_gmtoff/(60*15)); +} + +/* Decode 03.40 TP-SCTS (into utc/gmt timestamp) */ +static time_t gsm340_scts(u_int8_t *scts) +{ + struct tm tm; + + u_int8_t yr = unbcdify(*scts++); + + if (yr <= 80) + tm.tm_year = 100 + yr; + else + tm.tm_year = yr; + tm.tm_mon = unbcdify(*scts++) - 1; + tm.tm_mday = unbcdify(*scts++); + tm.tm_hour = unbcdify(*scts++); + tm.tm_min = unbcdify(*scts++); + tm.tm_sec = unbcdify(*scts++); + /* according to gsm 03.40 time zone is + "expressed in quarters of an hour" */ + tm.tm_gmtoff = unbcdify(*scts++) * 15*60; + + return mktime(&tm); +} + +/* Return the default validity period in minutes */ +static unsigned long gsm340_vp_default(void) +{ + unsigned long minutes; + /* Default validity: two days */ + minutes = 24 * 60 * 2; + return minutes; +} + +/* Decode validity period format 'relative' */ +static unsigned long gsm340_vp_relative(u_int8_t *sms_vp) +{ + /* Chapter 9.2.3.12.1 */ + u_int8_t vp; + unsigned long minutes; + + vp = *(sms_vp); + if (vp <= 143) + minutes = vp + 1 * 5; + else if (vp <= 167) + minutes = 12*60 + (vp-143) * 30; + else if (vp <= 196) + minutes = vp-166 * 60 * 24; + else + minutes = vp-192 * 60 * 24 * 7; + return minutes; +} + +/* Decode validity period format 'absolute' */ +static unsigned long gsm340_vp_absolute(u_int8_t *sms_vp) +{ + /* Chapter 9.2.3.12.2 */ + time_t expires, now; + unsigned long minutes; + + expires = gsm340_scts(sms_vp); + now = time(NULL); + if (expires <= now) + minutes = 0; + else + minutes = (expires-now)/60; + return minutes; +} + +/* Decode validity period format 'relative in integer representation' */ +static unsigned long gsm340_vp_relative_integer(u_int8_t *sms_vp) +{ + u_int8_t vp; + unsigned long minutes; + vp = *(sms_vp); + if (vp == 0) { + LOGP(DSMS, LOGL_ERROR, + "reserved relative_integer validity period\n"); + return gsm340_vp_default(); + } + minutes = vp/60; + return minutes; +} + +/* Decode validity period format 'relative in semi-octet representation' */ +static unsigned long gsm340_vp_relative_semioctet(u_int8_t *sms_vp) +{ + unsigned long minutes; + minutes = unbcdify(*sms_vp++)*60; /* hours */ + minutes += unbcdify(*sms_vp++); /* minutes */ + minutes += unbcdify(*sms_vp++)/60; /* seconds */ + return minutes; +} + +/* decode validity period. return minutes */ +static unsigned long gsm340_validity_period(u_int8_t sms_vpf, u_int8_t *sms_vp) +{ + u_int8_t fi; /* functionality indicator */ + + switch (sms_vpf) { + case GSM340_TP_VPF_RELATIVE: + return gsm340_vp_relative(sms_vp); + case GSM340_TP_VPF_ABSOLUTE: + return gsm340_vp_absolute(sms_vp); + case GSM340_TP_VPF_ENHANCED: + /* Chapter 9.2.3.12.3 */ + fi = *sms_vp++; + /* ignore additional fi */ + if (fi & (1<<7)) sms_vp++; + /* read validity period format */ + switch (fi & 0x7) { + case 0x0: + return gsm340_vp_default(); /* no vpf specified */ + case 0x1: + return gsm340_vp_relative(sms_vp); + case 0x2: + return gsm340_vp_relative_integer(sms_vp); + case 0x3: + return gsm340_vp_relative_semioctet(sms_vp); + default: + /* The GSM spec says that the SC should reject any + unsupported and/or undefined values. FIXME */ + LOGP(DSMS, LOGL_ERROR, + "Reserved enhanced validity period format\n"); + return gsm340_vp_default(); + } + case GSM340_TP_VPF_NONE: + default: + return gsm340_vp_default(); + } +} + +/* determine coding alphabet dependent on GSM 03.38 Section 4 DCS */ +enum sms_alphabet gsm338_get_sms_alphabet(u_int8_t dcs) +{ + u_int8_t cgbits = dcs >> 4; + enum sms_alphabet alpha = DCS_NONE; + + if ((cgbits & 0xc) == 0) { + if (cgbits & 2) { + LOGP(DSMS, LOGL_NOTICE, + "Compressed SMS not supported yet\n"); + return 0xffffffff; + } + + switch ((dcs >> 2)&0x03) { + case 0: + alpha = DCS_7BIT_DEFAULT; + break; + case 1: + alpha = DCS_8BIT_DATA; + break; + case 2: + alpha = DCS_UCS2; + break; + } + } else if (cgbits == 0xc || cgbits == 0xd) + alpha = DCS_7BIT_DEFAULT; + else if (cgbits == 0xe) + alpha = DCS_UCS2; + else if (cgbits == 0xf) { + if (dcs & 4) + alpha = DCS_8BIT_DATA; + else + alpha = DCS_7BIT_DEFAULT; + } + + return alpha; +} + +static int gsm340_rx_sms_submit(struct msgb *msg, struct gsm_sms *gsms) +{ + if (db_sms_store(gsms) != 0) { + LOGP(DSMS, LOGL_ERROR, "Failed to store SMS in Database\n"); + return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; + } + /* dispatch a signal to tell higher level about it */ + send_signal(S_SMS_SUBMITTED, NULL, gsms, 0); + + return 0; +} + +/* generate a TPDU address field compliant with 03.40 sec. 9.1.2.5 */ +static int gsm340_gen_oa(u_int8_t *oa, unsigned int oa_len, + struct gsm_subscriber *subscr) +{ + int len_in_bytes; + + oa[1] = 0xb9; /* networks-specific number, private numbering plan */ + + len_in_bytes = gsm48_encode_bcd_number(oa, oa_len, 1, subscr->extension); + + /* GSM 03.40 tells us the length is in 'useful semi-octets' */ + oa[0] = strlen(subscr->extension) & 0xff; + + return len_in_bytes; +} + +/* generate a msgb containing a TPDU derived from struct gsm_sms, + * returns total size of TPDU */ +static int gsm340_gen_tpdu(struct msgb *msg, struct gsm_sms *sms) +{ + u_int8_t *smsp; + u_int8_t oa[12]; /* max len per 03.40 */ + u_int8_t oa_len = 0; + u_int8_t octet_len; + unsigned int old_msg_len = msg->len; + + /* generate first octet with masked bits */ + smsp = msgb_put(msg, 1); + /* TP-MTI (message type indicator) */ + *smsp = GSM340_SMS_DELIVER_SC2MS; + /* TP-MMS (more messages to send) */ + if (0 /* FIXME */) + *smsp |= 0x04; + /* TP-SRI(deliver)/SRR(submit) */ + if (sms->status_rep_req) + *smsp |= 0x20; + /* TP-UDHI (indicating TP-UD contains a header) */ + if (sms->ud_hdr_ind) + *smsp |= 0x40; + + /* generate originator address */ + oa_len = gsm340_gen_oa(oa, sizeof(oa), sms->sender); + smsp = msgb_put(msg, oa_len); + memcpy(smsp, oa, oa_len); + + /* generate TP-PID */ + smsp = msgb_put(msg, 1); + *smsp = sms->protocol_id; + + /* generate TP-DCS */ + smsp = msgb_put(msg, 1); + *smsp = sms->data_coding_scheme; + + /* generate TP-SCTS */ + smsp = msgb_put(msg, 7); + gsm340_gen_scts(smsp, time(NULL)); + + /* generate TP-UDL */ + smsp = msgb_put(msg, 1); + *smsp = sms->user_data_len; + + /* generate TP-UD */ + switch (gsm338_get_sms_alphabet(sms->data_coding_scheme)) { + case DCS_7BIT_DEFAULT: + octet_len = sms->user_data_len*7/8; + if (sms->user_data_len*7%8 != 0) + octet_len++; + /* Warning, user_data_len indicates the amount of septets + * (characters), we need amount of octets occupied */ + smsp = msgb_put(msg, octet_len); + memcpy(smsp, sms->user_data, octet_len); + break; + case DCS_UCS2: + case DCS_8BIT_DATA: + smsp = msgb_put(msg, sms->user_data_len); + memcpy(smsp, sms->user_data, sms->user_data_len); + break; + default: + LOGP(DSMS, LOGL_NOTICE, "Unhandled Data Coding Scheme: 0x%02X\n", + sms->data_coding_scheme); + break; + } + + return msg->len - old_msg_len; +} + +/* process an incoming TPDU (called from RP-DATA) + * return value > 0: RP CAUSE for ERROR; < 0: silent error; 0 = success */ +static int gsm340_rx_tpdu(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + u_int8_t *smsp = msgb_sms(msg); + struct gsm_sms *gsms; + u_int8_t sms_mti, sms_mms, sms_vpf, sms_alphabet, sms_rp; + u_int8_t *sms_vp; + u_int8_t da_len_bytes; + u_int8_t address_lv[12]; /* according to 03.40 / 9.1.2.5 */ + int rc = 0; + + counter_inc(conn->bts->network->stats.sms.submitted); + + gsms = sms_alloc(); + if (!gsms) + return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; + + /* invert those fields where 0 means active/present */ + sms_mti = *smsp & 0x03; + sms_mms = !!(*smsp & 0x04); + sms_vpf = (*smsp & 0x18) >> 3; + gsms->status_rep_req = (*smsp & 0x20); + gsms->ud_hdr_ind = (*smsp & 0x40); + sms_rp = (*smsp & 0x80); + + smsp++; + gsms->msg_ref = *smsp++; + + /* length in bytes of the destination address */ + da_len_bytes = 2 + *smsp/2 + *smsp%2; + if (da_len_bytes > 12) { + LOGP(DSMS, LOGL_ERROR, "Destination Address > 12 bytes ?!?\n"); + rc = GSM411_RP_CAUSE_SEMANT_INC_MSG; + goto out; + } + memset(address_lv, 0, sizeof(address_lv)); + memcpy(address_lv, smsp, da_len_bytes); + /* mangle first byte to reflect length in bytes, not digits */ + address_lv[0] = da_len_bytes - 1; + /* convert to real number */ + gsm48_decode_bcd_number(gsms->dest_addr, sizeof(gsms->dest_addr), address_lv, 1); + smsp += da_len_bytes; + + gsms->protocol_id = *smsp++; + gsms->data_coding_scheme = *smsp++; + + sms_alphabet = gsm338_get_sms_alphabet(gsms->data_coding_scheme); + if (sms_alphabet == 0xffffffff) { + sms_free(gsms); + return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; + } + + switch (sms_vpf) { + case GSM340_TP_VPF_RELATIVE: + sms_vp = smsp++; + break; + case GSM340_TP_VPF_ABSOLUTE: + case GSM340_TP_VPF_ENHANCED: + sms_vp = smsp; + /* the additional functionality indicator... */ + if (sms_vpf == GSM340_TP_VPF_ENHANCED && *smsp & (1<<7)) + smsp++; + smsp += 7; + break; + case GSM340_TP_VPF_NONE: + sms_vp = 0; + break; + default: + LOGP(DSMS, LOGL_NOTICE, + "SMS Validity period not implemented: 0x%02x\n", sms_vpf); + return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; + } + gsms->user_data_len = *smsp++; + if (gsms->user_data_len) { + memcpy(gsms->user_data, smsp, gsms->user_data_len); + + switch (sms_alphabet) { + case DCS_7BIT_DEFAULT: + gsm_7bit_decode(gsms->text, smsp, gsms->user_data_len); + break; + case DCS_8BIT_DATA: + case DCS_UCS2: + case DCS_NONE: + break; + } + } + + gsms->sender = subscr_get(conn->subscr); + + LOGP(DSMS, LOGL_INFO, "RX SMS: Sender: %s, MTI: 0x%02x, VPF: 0x%02x, " + "MR: 0x%02x PID: 0x%02x, DCS: 0x%02x, DA: %s, " + "UserDataLength: 0x%02x, UserData: \"%s\"\n", + subscr_name(gsms->sender), sms_mti, sms_vpf, gsms->msg_ref, + gsms->protocol_id, gsms->data_coding_scheme, gsms->dest_addr, + gsms->user_data_len, + sms_alphabet == DCS_7BIT_DEFAULT ? gsms->text : + hexdump(gsms->user_data, gsms->user_data_len)); + + gsms->validity_minutes = gsm340_validity_period(sms_vpf, sms_vp); + + /* FIXME: This looks very wrong */ + send_signal(0, NULL, gsms, 0); + + /* determine gsms->receiver based on dialled number */ + gsms->receiver = subscr_get_by_extension(conn->bts->network, gsms->dest_addr); + if (!gsms->receiver) { + rc = 1; /* cause 1: unknown subscriber */ + counter_inc(conn->bts->network->stats.sms.no_receiver); + goto out; + } + + switch (sms_mti) { + case GSM340_SMS_SUBMIT_MS2SC: + /* MS is submitting a SMS */ + rc = gsm340_rx_sms_submit(msg, gsms); + break; + case GSM340_SMS_COMMAND_MS2SC: + case GSM340_SMS_DELIVER_REP_MS2SC: + LOGP(DSMS, LOGL_NOTICE, "Unimplemented MTI 0x%02x\n", sms_mti); + rc = GSM411_RP_CAUSE_IE_NOTEXIST; + break; + default: + LOGP(DSMS, LOGL_NOTICE, "Undefined MTI 0x%02x\n", sms_mti); + rc = GSM411_RP_CAUSE_IE_NOTEXIST; + break; + } + + if (!rc && !gsms->receiver) + rc = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED; + +out: + sms_free(gsms); + + return rc; +} + +static int gsm411_send_rp_ack(struct gsm_trans *trans, u_int8_t msg_ref) +{ + struct msgb *msg = gsm411_msgb_alloc(); + + DEBUGP(DSMS, "TX: SMS RP ACK\n"); + + return gsm411_rp_sendmsg(msg, trans, GSM411_MT_RP_ACK_MT, msg_ref); +} + +static int gsm411_send_rp_error(struct gsm_trans *trans, + u_int8_t msg_ref, u_int8_t cause) +{ + struct msgb *msg = gsm411_msgb_alloc(); + + msgb_tv_put(msg, 1, cause); + + LOGP(DSMS, LOGL_NOTICE, "TX: SMS RP ERROR, cause %d (%s)\n", cause, + get_value_string(rp_cause_strs, cause)); + + return gsm411_rp_sendmsg(msg, trans, GSM411_MT_RP_ERROR_MT, msg_ref); +} + +/* Receive a 04.11 TPDU inside RP-DATA / user data */ +static int gsm411_rx_rp_ud(struct msgb *msg, struct gsm_trans *trans, + struct gsm411_rp_hdr *rph, + u_int8_t src_len, u_int8_t *src, + u_int8_t dst_len, u_int8_t *dst, + u_int8_t tpdu_len, u_int8_t *tpdu) +{ + int rc = 0; + + if (src_len && src) + LOGP(DSMS, LOGL_ERROR, "RP-DATA (MO) with SRC ?!?\n"); + + if (!dst_len || !dst || !tpdu_len || !tpdu) { + LOGP(DSMS, LOGL_ERROR, + "RP-DATA (MO) without DST or TPDU ?!?\n"); + gsm411_send_rp_error(trans, rph->msg_ref, + GSM411_RP_CAUSE_INV_MAND_INF); + return -EIO; + } + msg->l4h = tpdu; + + DEBUGP(DSMS, "DST(%u,%s)\n", dst_len, hexdump(dst, dst_len)); + + rc = gsm340_rx_tpdu(trans->conn, msg); + if (rc == 0) + return gsm411_send_rp_ack(trans, rph->msg_ref); + else if (rc > 0) + return gsm411_send_rp_error(trans, rph->msg_ref, rc); + else + return rc; +} + +/* Receive a 04.11 RP-DATA message in accordance with Section 7.3.1.2 */ +static int gsm411_rx_rp_data(struct msgb *msg, struct gsm_trans *trans, + struct gsm411_rp_hdr *rph) +{ + u_int8_t src_len, dst_len, rpud_len; + u_int8_t *src = NULL, *dst = NULL , *rp_ud = NULL; + + /* in the MO case, this should always be zero length */ + src_len = rph->data[0]; + if (src_len) + src = &rph->data[1]; + + dst_len = rph->data[1+src_len]; + if (dst_len) + dst = &rph->data[1+src_len+1]; + + rpud_len = rph->data[1+src_len+1+dst_len]; + if (rpud_len) + rp_ud = &rph->data[1+src_len+1+dst_len+1]; + + DEBUGP(DSMS, "RX_RP-DATA: src_len=%u, dst_len=%u ud_len=%u\n", + src_len, dst_len, rpud_len); + return gsm411_rx_rp_ud(msg, trans, rph, src_len, src, dst_len, dst, + rpud_len, rp_ud); +} + +/* Receive a 04.11 RP-ACK message (response to RP-DATA from us) */ +static int gsm411_rx_rp_ack(struct msgb *msg, struct gsm_trans *trans, + struct gsm411_rp_hdr *rph) +{ + struct gsm_sms *sms = trans->sms.sms; + + /* Acnkowledgement to MT RP_DATA, i.e. the MS confirms it + * successfully received a SMS. We can now safely mark it as + * transmitted */ + + if (!trans->sms.is_mt) { + LOGP(DSMS, LOGL_ERROR, "RX RP-ACK on a MO transfer ?\n"); + return gsm411_send_rp_error(trans, rph->msg_ref, + GSM411_RP_CAUSE_MSG_INCOMP_STATE); + } + + if (!sms) { + LOGP(DSMS, LOGL_ERROR, "RX RP-ACK but no sms in transaction?!?\n"); + return gsm411_send_rp_error(trans, rph->msg_ref, + GSM411_RP_CAUSE_PROTOCOL_ERR); + } + + /* mark this SMS as sent in database */ + db_sms_mark_sent(sms); + + send_signal(S_SMS_DELIVERED, trans, sms, 0); + + sms_free(sms); + trans->sms.sms = NULL; + + /* check for more messages for this subscriber */ + sms = db_sms_get_unsent_for_subscr(trans->subscr); + if (sms) + gsm411_send_sms(trans->conn, sms); + else + gsm411_release_conn(trans->conn); + + /* free the transaction here */ + trans_free(trans); + return 0; +} + +static int gsm411_rx_rp_error(struct msgb *msg, struct gsm_trans *trans, + struct gsm411_rp_hdr *rph) +{ + struct gsm_network *net = trans->conn->bts->network; + struct gsm_sms *sms = trans->sms.sms; + u_int8_t cause_len = rph->data[0]; + u_int8_t cause = rph->data[1]; + + /* Error in response to MT RP_DATA, i.e. the MS did not + * successfully receive the SMS. We need to investigate + * the cause and take action depending on it */ + + LOGP(DSMS, LOGL_NOTICE, "%s: RX SMS RP-ERROR, cause %d:%d (%s)\n", + subscr_name(trans->conn->subscr), cause_len, cause, + get_value_string(rp_cause_strs, cause)); + + if (!trans->sms.is_mt) { + LOGP(DSMS, LOGL_ERROR, "RX RP-ERR on a MO transfer ?\n"); +#if 0 + return gsm411_send_rp_error(trans, rph->msg_ref, + GSM411_RP_CAUSE_MSG_INCOMP_STATE); +#endif + } + + if (!sms) { + LOGP(DSMS, LOGL_ERROR, + "RX RP-ERR, but no sms in transaction?!?\n"); + return -EINVAL; +#if 0 + return gsm411_send_rp_error(trans, rph->msg_ref, + GSM411_RP_CAUSE_PROTOCOL_ERR); +#endif + } + + if (cause == GSM411_RP_CAUSE_MT_MEM_EXCEEDED) { + /* MS has not enough memory to store the message. We need + * to store this in our database and wait for a SMMA message */ + /* FIXME */ + send_signal(S_SMS_MEM_EXCEEDED, trans, sms, 0); + counter_inc(net->stats.sms.rp_err_mem); + } else { + send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0); + counter_inc(net->stats.sms.rp_err_other); + } + + sms_free(sms); + trans->sms.sms = NULL; + + return 0; +} + +static int gsm411_rx_rp_smma(struct msgb *msg, struct gsm_trans *trans, + struct gsm411_rp_hdr *rph) +{ + struct gsm_sms *sms; + int rc; + + rc = gsm411_send_rp_ack(trans, rph->msg_ref); + trans->sms.rp_state = GSM411_RPS_IDLE; + + /* MS tells us that it has memory for more SMS, we need + * to check if we have any pending messages for it and then + * transfer those */ + send_signal(S_SMS_SMMA, trans, NULL, 0); + + /* check for more messages for this subscriber */ + sms = db_sms_get_unsent_for_subscr(trans->subscr); + if (sms) + gsm411_send_sms(trans->conn, sms); + else + gsm411_release_conn(trans->conn); + + return rc; +} + +static int gsm411_rx_cp_data(struct msgb *msg, struct gsm48_hdr *gh, + struct gsm_trans *trans) +{ + struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data; + u_int8_t msg_type = rp_data->msg_type & 0x07; + int rc = 0; + + switch (msg_type) { + case GSM411_MT_RP_DATA_MO: + DEBUGP(DSMS, "RX SMS RP-DATA (MO)\n"); + /* start TR2N and enter 'wait to send RP-ACK state' */ + trans->sms.rp_state = GSM411_RPS_WAIT_TO_TX_RP_ACK; + rc = gsm411_rx_rp_data(msg, trans, rp_data); + break; + case GSM411_MT_RP_ACK_MO: + DEBUGP(DSMS,"RX SMS RP-ACK (MO)\n"); + rc = gsm411_rx_rp_ack(msg, trans, rp_data); + break; + case GSM411_MT_RP_SMMA_MO: + DEBUGP(DSMS, "RX SMS RP-SMMA\n"); + /* start TR2N and enter 'wait to send RP-ACK state' */ + trans->sms.rp_state = GSM411_RPS_WAIT_TO_TX_RP_ACK; + rc = gsm411_rx_rp_smma(msg, trans, rp_data); + break; + case GSM411_MT_RP_ERROR_MO: + rc = gsm411_rx_rp_error(msg, trans, rp_data); + break; + default: + LOGP(DSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type); + rc = gsm411_send_rp_error(trans, rp_data->msg_ref, + GSM411_RP_CAUSE_MSGTYPE_NOTEXIST); + break; + } + + return rc; +} + +/* send CP-ACK to given transaction */ +static int gsm411_tx_cp_ack(struct gsm_trans *trans) +{ + struct msgb *msg = gsm411_msgb_alloc(); + int rc; + + rc = gsm411_cp_sendmsg(msg, trans, GSM411_MT_CP_ACK); + + if (trans->sms.is_mt) { + /* If this is a MT SMS DELIVER, we can clear transaction here */ + trans->sms.cp_state = GSM411_CPS_IDLE; + //trans_free(trans); + } + + return rc; +} + +static int gsm411_tx_cp_error(struct gsm_trans *trans, u_int8_t cause) +{ + struct msgb *msg = gsm411_msgb_alloc(); + u_int8_t *causep; + + LOGP(DSMS, LOGL_NOTICE, "TX CP-ERROR, cause %d (%s)\n", cause, + get_value_string(cp_cause_strs, cause)); + + causep = msgb_put(msg, 1); + *causep = cause; + + return gsm411_cp_sendmsg(msg, trans, GSM411_MT_CP_ERROR); +} + +/* Entry point for incoming GSM48_PDISC_SMS from abis_rsl.c */ +int gsm0411_rcv_sms(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + u_int8_t msg_type = gh->msg_type; + u_int8_t transaction_id = ((gh->proto_discr >> 4) ^ 0x8); /* flip */ + struct gsm_trans *trans; + int rc = 0; + + if (!conn->subscr) + return -EIO; + /* FIXME: send some error message */ + + DEBUGP(DSMS, "trans_id=%x ", transaction_id); + trans = trans_find_by_id(conn->subscr, GSM48_PDISC_SMS, + transaction_id); + if (!trans) { + DEBUGPC(DSMS, "(new) "); + trans = trans_alloc(conn->subscr, GSM48_PDISC_SMS, + transaction_id, new_callref++); + if (!trans) { + DEBUGPC(DSMS, "No memory for trans\n"); + /* FIXME: send some error message */ + return -ENOMEM; + } + trans->sms.cp_state = GSM411_CPS_IDLE; + trans->sms.rp_state = GSM411_RPS_IDLE; + trans->sms.is_mt = 0; + trans->sms.link_id = UM_SAPI_SMS; + + trans->conn = conn; + } + + switch(msg_type) { + case GSM411_MT_CP_DATA: + DEBUGPC(DSMS, "RX SMS CP-DATA\n"); + + /* 5.4: For MO, if a CP-DATA is received for a new + * transaction, equals reception of an implicit + * last CP-ACK for previous transaction */ + if (trans->sms.cp_state == GSM411_CPS_IDLE) { + int i; + struct gsm_trans *ptrans; + + /* Scan through all remote initiated transactions */ + for (i=8; i<15; i++) { + if (i == transaction_id) + continue; + + ptrans = trans_find_by_id(conn->subscr, + GSM48_PDISC_SMS, i); + if (!ptrans) + continue; + + DEBUGP(DSMS, "Implicit CP-ACK for trans_id=%x\n", i); + + /* Finish it for good */ + bsc_del_timer(&ptrans->sms.cp_timer); + ptrans->sms.cp_state = GSM411_CPS_IDLE; + trans_free(ptrans); + } + } + + /* 5.2.3.1.3: MO state exists when SMC has received + * CP-DATA, including sending of the assoc. CP-ACK */ + /* 5.2.3.2.4: MT state exists when SMC has received + * CP-DATA, including sending of the assoc. CP-ACK */ + trans->sms.cp_state = GSM411_CPS_MM_ESTABLISHED; + + /* SMC instance acknowledges the CP-DATA frame */ + gsm411_tx_cp_ack(trans); + + rc = gsm411_rx_cp_data(msg, gh, trans); +#if 0 + /* Send CP-ACK or CP-ERORR in response */ + if (rc < 0) { + rc = gsm411_tx_cp_error(trans, GSM411_CP_CAUSE_NET_FAIL); + } else + rc = gsm411_tx_cp_ack(trans); +#endif + break; + case GSM411_MT_CP_ACK: + /* previous CP-DATA in this transaction was confirmed */ + DEBUGPC(DSMS, "RX SMS CP-ACK\n"); + /* 5.2.3.1.3: MO state exists when SMC has received CP-ACK */ + /* 5.2.3.2.4: MT state exists when SMC has received CP-ACK */ + trans->sms.cp_state = GSM411_CPS_MM_ESTABLISHED; + /* Stop TC1* after CP-ACK has been received */ + bsc_del_timer(&trans->sms.cp_timer); + + if (!trans->sms.is_mt) { + /* FIXME: we have sent one CP-DATA, which was now + * acknowledged. Check if we want to transfer more, + * i.e. multi-part message */ + trans->sms.cp_state = GSM411_CPS_IDLE; + trans_free(trans); + } + break; + case GSM411_MT_CP_ERROR: + DEBUGPC(DSMS, "RX SMS CP-ERROR, cause %d (%s)\n", gh->data[0], + get_value_string(cp_cause_strs, gh->data[0])); + bsc_del_timer(&trans->sms.cp_timer); + trans->sms.cp_state = GSM411_CPS_IDLE; + trans_free(trans); + break; + default: + DEBUGPC(DSMS, "RX Unimplemented CP msg_type: 0x%02x\n", msg_type); + rc = gsm411_tx_cp_error(trans, GSM411_CP_CAUSE_MSGTYPE_NOTEXIST); + trans->sms.cp_state = GSM411_CPS_IDLE; + trans_free(trans); + break; + } + + return rc; +} + +/* Take a SMS in gsm_sms structure and send it through an already + * existing lchan. We also assume that the caller ensured this lchan already + * has a SAPI3 RLL connection! */ +int gsm411_send_sms(struct gsm_subscriber_connection *conn, struct gsm_sms *sms) +{ + struct msgb *msg = gsm411_msgb_alloc(); + struct gsm_trans *trans; + u_int8_t *data, *rp_ud_len; + u_int8_t msg_ref = 42; + int transaction_id; + int rc; + + transaction_id = trans_assign_trans_id(conn->subscr, GSM48_PDISC_SMS, 0); + if (transaction_id == -1) { + LOGP(DSMS, LOGL_ERROR, "No available transaction ids\n"); + send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, 0); + sms_free(sms); + return -EBUSY; + } + + DEBUGP(DSMS, "send_sms_lchan()\n"); + + /* FIXME: allocate transaction with message reference */ + trans = trans_alloc(conn->subscr, GSM48_PDISC_SMS, + transaction_id, new_callref++); + if (!trans) { + LOGP(DSMS, LOGL_ERROR, "No memory for trans\n"); + send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, 0); + sms_free(sms); + /* FIXME: send some error message */ + return -ENOMEM; + } + trans->sms.cp_state = GSM411_CPS_IDLE; + trans->sms.rp_state = GSM411_RPS_IDLE; + trans->sms.is_mt = 1; + trans->sms.sms = sms; + trans->sms.link_id = UM_SAPI_SMS; /* FIXME: main or SACCH ? */ + + trans->conn = conn; + + /* Hardcode SMSC Originating Address for now */ + data = (u_int8_t *)msgb_put(msg, 8); + data[0] = 0x07; /* originator length == 7 */ + data[1] = 0x91; /* type of number: international, ISDN */ + data[2] = 0x44; /* 447785016005 */ + data[3] = 0x77; + data[4] = 0x58; + data[5] = 0x10; + data[6] = 0x06; + data[7] = 0x50; + + /* Hardcoded Destination Address */ + data = (u_int8_t *)msgb_put(msg, 1); + data[0] = 0; /* destination length == 0 */ + + /* obtain a pointer for the rp_ud_len, so we can fill it later */ + rp_ud_len = (u_int8_t *)msgb_put(msg, 1); + + /* generate the 03.40 TPDU */ + rc = gsm340_gen_tpdu(msg, sms); + if (rc < 0) { + send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0); + trans_free(trans); + sms_free(sms); + msgb_free(msg); + return rc; + } + + *rp_ud_len = rc; + + DEBUGP(DSMS, "TX: SMS DELIVER\n"); + + counter_inc(conn->bts->network->stats.sms.delivered); + db_sms_inc_deliver_attempts(trans->sms.sms); + + return gsm411_rp_sendmsg(msg, trans, GSM411_MT_RP_DATA_MT, msg_ref); + /* FIXME: enter 'wait for RP-ACK' state, start TR1N */ +} + +/* paging callback. Here we get called if paging a subscriber has + * succeeded or failed. */ +static int paging_cb_send_sms(unsigned int hooknum, unsigned int event, + struct msgb *msg, void *_conn, void *_sms) +{ + struct gsm_subscriber_connection *conn = _conn; + struct gsm_sms *sms = _sms; + int rc = 0; + + DEBUGP(DSMS, "paging_cb_send_sms(hooknum=%u, event=%u, msg=%p," + "conn=%p, sms=%p/id: %llu)\n", hooknum, event, msg, conn, sms, sms->id); + + if (hooknum != GSM_HOOK_RR_PAGING) + return -EINVAL; + + switch (event) { + case GSM_PAGING_SUCCEEDED: + gsm411_send_sms(conn, sms); + break; + case GSM_PAGING_EXPIRED: + case GSM_PAGING_OOM: + case GSM_PAGING_BUSY: + send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, event); + sms_free(sms); + rc = -ETIMEDOUT; + break; + default: + LOGP(DSMS, LOGL_ERROR, "Unhandled paging event: %d\n", event); + } + + return rc; +} + +/* high-level function to send a SMS to a given subscriber. The function + * will take care of paging the subscriber, establishing the RLL SAPI3 + * connection, etc. */ +int gsm411_send_sms_subscr(struct gsm_subscriber *subscr, + struct gsm_sms *sms) +{ + struct gsm_subscriber_connection *conn; + + /* check if we already have an open lchan to the subscriber. + * if yes, send the SMS this way */ + conn = connection_for_subscr(subscr); + if (conn) { + return gsm411_send_sms(conn, sms); + } + + /* if not, we have to start paging */ + subscr_get_channel(subscr, RSL_CHANNEED_SDCCH, paging_cb_send_sms, sms); + return 0; +} + +void _gsm411_sms_trans_free(struct gsm_trans *trans) +{ + if (trans->sms.sms) { + LOGP(DSMS, LOGL_ERROR, "Transaction contains SMS.\n"); + send_signal(S_SMS_UNKNOWN_ERROR, trans, trans->sms.sms, 0); + sms_free(trans->sms.sms); + trans->sms.sms = NULL; + } + + bsc_del_timer(&trans->sms.cp_timer); +} + +void gsm411_sapi_n_reject(struct gsm_subscriber_connection *conn) +{ + struct gsm_subscriber *subscr; + struct gsm_network *net; + struct gsm_trans *trans, *tmp; + + subscr = subscr_get(conn->subscr); + net = conn->bts->network; + + llist_for_each_entry_safe(trans, tmp, &net->trans_list, entry) + if (trans->conn == conn) { + struct gsm_sms *sms = trans->sms.sms; + if (!sms) { + LOGP(DSMS, LOGL_ERROR, "SAPI Reject but no SMS.\n"); + continue; + } + + send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0); + sms_free(sms); + trans->sms.sms = NULL; + trans_free(trans); + } + + subscr_put_channel(subscr); + subscr_put(subscr); +} + diff --git a/src/libmsc/gsm_04_80.c b/src/libmsc/gsm_04_80.c new file mode 100644 index 000000000..494c319fe --- /dev/null +++ b/src/libmsc/gsm_04_80.c @@ -0,0 +1,175 @@ +/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface + * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */ + +/* (C) 2008-2009 by Harald Welte + * (C) 2008, 2009, 2010 by Holger Hans Peter Freyther + * (C) 2009 by Mike Haben + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static inline unsigned char *msgb_wrap_with_TL(struct msgb *msgb, u_int8_t tag) +{ + uint8_t *data = msgb_push(msgb, 2); + + data[0] = tag; + data[1] = msgb->len - 2; + return data; +} + +static inline unsigned char *msgb_push_TLV1(struct msgb *msgb, u_int8_t tag, + u_int8_t value) +{ + uint8_t *data = msgb_push(msgb, 3); + + data[0] = tag; + data[1] = 1; + data[2] = value; + return data; +} + + +/* Send response to a mobile-originated ProcessUnstructuredSS-Request */ +int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn, + const struct msgb *in_msg, const char *response_text, + const struct ussd_request *req) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + u_int8_t *ptr8; + int response_len; + + /* First put the payload text into the message */ + ptr8 = msgb_put(msg, 0); + response_len = gsm_7bit_encode(ptr8, response_text); + msgb_put(msg, response_len); + + /* Then wrap it as an Octet String */ + msgb_wrap_with_TL(msg, ASN1_OCTET_STRING_TAG); + + /* Pre-pend the DCS octet string */ + msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, 0x0F); + + /* Then wrap these as a Sequence */ + msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG); + + /* Pre-pend the operation code */ + msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, + GSM0480_OP_CODE_PROCESS_USS_REQ); + + /* Wrap the operation code and IA5 string as a sequence */ + msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG); + + /* Pre-pend the invoke ID */ + msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id); + + /* Wrap this up as a Return Result component */ + msgb_wrap_with_TL(msg, GSM0480_CTYPE_RETURN_RESULT); + + /* Wrap the component in a Facility message */ + msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY); + + /* And finally pre-pend the L3 header */ + gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_NC_SS | req->transaction_id + | (1<<7); /* TI direction = 1 */ + gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE; + + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn, + const struct msgb *in_msg, + const struct ussd_request *req) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + + /* First insert the problem code */ + msgb_push_TLV1(msg, GSM_0480_PROBLEM_CODE_TAG_GENERAL, + GSM_0480_GEN_PROB_CODE_UNRECOGNISED); + + /* Before it insert the invoke ID */ + msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id); + + /* Wrap this up as a Reject component */ + msgb_wrap_with_TL(msg, GSM0480_CTYPE_REJECT); + + /* Wrap the component in a Facility message */ + msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY); + + /* And finally pre-pend the L3 header */ + gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_NC_SS; + gh->proto_discr |= req->transaction_id | (1<<7); /* TI direction = 1 */ + gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE; + + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +int gsm0480_send_ussdNotify(struct gsm_subscriber_connection *conn, int level, const char *text) +{ + struct gsm48_hdr *gh; + struct msgb *msg; + + msg = gsm0480_create_unstructuredSS_Notify(level, text); + if (!msg) + return -1; + + gsm0480_wrap_invoke(msg, GSM0480_OP_CODE_USS_NOTIFY, 0); + gsm0480_wrap_facility(msg); + + /* And finally pre-pend the L3 header */ + gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_NC_SS; + gh->msg_type = GSM0480_MTYPE_REGISTER; + + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +int gsm0480_send_releaseComplete(struct gsm_subscriber_connection *conn) +{ + struct gsm48_hdr *gh; + struct msgb *msg; + + msg = gsm48_msgb_alloc(); + if (!msg) + return -1; + + gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_NC_SS; + gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE; + + return gsm0808_submit_dtap(conn, msg, 0, 0); +} diff --git a/src/libmsc/gsm_subscriber.c b/src/libmsc/gsm_subscriber.c new file mode 100644 index 000000000..db61f25aa --- /dev/null +++ b/src/libmsc/gsm_subscriber.c @@ -0,0 +1,410 @@ +/* The concept of a subscriber for the MSC, roughly HLR/VLR functionality */ + +/* (C) 2008 by Harald Welte + * (C) 2009 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +void *tall_sub_req_ctx; + +extern struct llist_head *subscr_bsc_active_subscriber(void); + +int gsm48_secure_channel(struct gsm_subscriber_connection *conn, int key_seq, + gsm_cbfn *cb, void *cb_data); + + +/* + * Struct for pending channel requests. This is managed in the + * llist_head requests of each subscriber. The reference counting + * should work in such a way that a subscriber with a pending request + * remains in memory. + */ +struct subscr_request { + struct llist_head entry; + + /* back reference */ + struct gsm_subscriber *subscr; + + /* the requested channel type */ + int channel_type; + + /* what did we do */ + int state; + + /* the callback data */ + gsm_cbfn *cbfn; + void *param; +}; + +enum { + REQ_STATE_INITIAL, + REQ_STATE_QUEUED, + REQ_STATE_PAGED, + REQ_STATE_FAILED_START, + REQ_STATE_DISPATCHED, +}; + +/* + * We got the channel assigned and can now hand this channel + * over to one of our callbacks. + */ +static int subscr_paging_dispatch(unsigned int hooknum, unsigned int event, + struct msgb *msg, void *data, void *param) +{ + struct subscr_request *request; + struct gsm_subscriber_connection *conn = data; + struct gsm_subscriber *subscr = param; + struct paging_signal_data sig_data; + + /* There is no request anymore... */ + if (llist_empty(&subscr->requests)) + return -1; + + /* Dispatch signal */ + sig_data.subscr = subscr; + sig_data.bts = conn ? conn->bts : NULL; + sig_data.conn = conn; + sig_data.paging_result = event; + dispatch_signal( + SS_PAGING, + event == GSM_PAGING_SUCCEEDED ? + S_PAGING_SUCCEEDED : S_PAGING_EXPIRED, + &sig_data + ); + + /* + * FIXME: What to do with paging requests coming during + * this callback? We must be sure to not start paging when + * we have an active connection to a subscriber and to make + * the subscr_put_channel work as required... + */ + request = (struct subscr_request *)subscr->requests.next; + request->state = REQ_STATE_DISPATCHED; + llist_del(&request->entry); + subscr->in_callback = 1; + request->cbfn(hooknum, event, msg, data, request->param); + subscr->in_callback = 0; + + if (event != GSM_PAGING_SUCCEEDED) { + /* + * This is a workaround for a bigger issue. We have + * issued paging that might involve multiple BTSes + * and one of them have failed now. We will stop the + * other paging requests as well as the next timeout + * would work on the next paging request and the queue + * will do bad things. This should be fixed by counting + * the outstanding results. + */ + paging_request_stop(NULL, subscr, NULL, NULL); + subscr_put_channel(subscr); + } + + subscr_put(subscr); + talloc_free(request); + return 0; +} + +static int subscr_paging_sec_cb(unsigned int hooknum, unsigned int event, + struct msgb *msg, void *data, void *param) +{ + int rc; + + switch (event) { + case GSM_SECURITY_AUTH_FAILED: + /* Dispatch as paging failure */ + rc = subscr_paging_dispatch( + GSM_HOOK_RR_PAGING, GSM_PAGING_EXPIRED, + msg, data, param); + break; + + case GSM_SECURITY_NOAVAIL: + case GSM_SECURITY_SUCCEEDED: + /* Dispatch as paging failure */ + rc = subscr_paging_dispatch( + GSM_HOOK_RR_PAGING, GSM_PAGING_SUCCEEDED, + msg, data, param); + break; + + default: + rc = -EINVAL; + } + + return rc; +} + +static int subscr_paging_cb(unsigned int hooknum, unsigned int event, + struct msgb *msg, void *data, void *param) +{ + struct gsm_subscriber_connection *conn = data; + struct gsm48_hdr *gh; + struct gsm48_pag_resp *pr; + + /* Other cases mean problem, dispatch direclty */ + if (event != GSM_PAGING_SUCCEEDED) + return subscr_paging_dispatch(hooknum, event, msg, data, param); + + /* Get paging response */ + gh = msgb_l3(msg); + pr = (struct gsm48_pag_resp *)gh->data; + + /* We _really_ have a channel, secure it now ! */ + return gsm48_secure_channel(conn, pr->key_seq, subscr_paging_sec_cb, param); +} + + +static void subscr_send_paging_request(struct gsm_subscriber *subscr) +{ + struct subscr_request *request; + int rc; + + assert(!llist_empty(&subscr->requests)); + + request = (struct subscr_request *)subscr->requests.next; + request->state = REQ_STATE_PAGED; + rc = paging_request(subscr->net, subscr, request->channel_type, + subscr_paging_cb, subscr); + + /* paging failed, quit now */ + if (rc <= 0) { + request->state = REQ_STATE_FAILED_START; + subscr_paging_cb(GSM_HOOK_RR_PAGING, GSM_PAGING_BUSY, + NULL, NULL, subscr); + } +} + +void subscr_get_channel(struct gsm_subscriber *subscr, + int type, gsm_cbfn *cbfn, void *param) +{ + struct subscr_request *request; + + request = talloc(tall_sub_req_ctx, struct subscr_request); + if (!request) { + if (cbfn) + cbfn(GSM_HOOK_RR_PAGING, GSM_PAGING_OOM, + NULL, NULL, param); + return; + } + + memset(request, 0, sizeof(*request)); + request->subscr = subscr_get(subscr); + request->channel_type = type; + request->cbfn = cbfn; + request->param = param; + request->state = REQ_STATE_INITIAL; + + /* + * FIXME: We might be able to assign more than one + * channel, e.g. voice and SMS submit at the same + * time. + */ + if (!subscr->in_callback && llist_empty(&subscr->requests)) { + /* add to the list, send a request */ + llist_add_tail(&request->entry, &subscr->requests); + subscr_send_paging_request(subscr); + } else { + /* this will be picked up later, from subscr_put_channel */ + llist_add_tail(&request->entry, &subscr->requests); + request->state = REQ_STATE_QUEUED; + } +} + +void subscr_put_channel(struct gsm_subscriber *subscr) +{ + /* + * FIXME: Continue with other requests now... by checking + * the gsm_subscriber inside the gsm_lchan. Drop the ref count + * of the lchan after having asked the next requestee to handle + * the channel. + */ + /* + * FIXME: is the lchan is of a different type we could still + * issue an immediate assignment for another channel and then + * close this one. + */ + /* + * Currently we will drop the last ref of the lchan which + * will result in a channel release on RSL and we will start + * the paging. This should work most of the time as the MS + * will listen to the paging requests before we timeout + */ + + if (subscr && !llist_empty(&subscr->requests)) + subscr_send_paging_request(subscr); +} + + +struct gsm_subscriber *subscr_get_by_tmsi(struct gsm_network *net, + u_int32_t tmsi) +{ + char tmsi_string[14]; + struct gsm_subscriber *subscr; + + /* we might have a record in memory already */ + llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) { + if (tmsi == subscr->tmsi) + return subscr_get(subscr); + } + + sprintf(tmsi_string, "%u", tmsi); + return db_get_subscriber(net, GSM_SUBSCRIBER_TMSI, tmsi_string); +} + +struct gsm_subscriber *subscr_get_by_imsi(struct gsm_network *net, + const char *imsi) +{ + struct gsm_subscriber *subscr; + + llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) { + if (strcmp(subscr->imsi, imsi) == 0) + return subscr_get(subscr); + } + + return db_get_subscriber(net, GSM_SUBSCRIBER_IMSI, imsi); +} + +struct gsm_subscriber *subscr_get_by_extension(struct gsm_network *net, + const char *ext) +{ + struct gsm_subscriber *subscr; + + llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) { + if (strcmp(subscr->extension, ext) == 0) + return subscr_get(subscr); + } + + return db_get_subscriber(net, GSM_SUBSCRIBER_EXTENSION, ext); +} + +struct gsm_subscriber *subscr_get_by_id(struct gsm_network *net, + unsigned long long id) +{ + struct gsm_subscriber *subscr; + char buf[32]; + sprintf(buf, "%llu", id); + + llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) { + if (subscr->id == id) + return subscr_get(subscr); + } + + return db_get_subscriber(net, GSM_SUBSCRIBER_ID, buf); +} + + +int subscr_update(struct gsm_subscriber *s, struct gsm_bts *bts, int reason) +{ + int rc; + + /* FIXME: Migrate pending requests from one BSC to another */ + switch (reason) { + case GSM_SUBSCRIBER_UPDATE_ATTACHED: + s->net = bts->network; + /* Indicate "attached to LAC" */ + s->lac = bts->location_area_code; + LOGP(DMM, LOGL_INFO, "Subscriber %s ATTACHED LAC=%u\n", + subscr_name(s), s->lac); + rc = db_sync_subscriber(s); + db_subscriber_update(s); + dispatch_signal(SS_SUBSCR, S_SUBSCR_ATTACHED, s); + break; + case GSM_SUBSCRIBER_UPDATE_DETACHED: + /* Only detach if we are currently in this area */ + if (bts->location_area_code == s->lac) + s->lac = GSM_LAC_RESERVED_DETACHED; + LOGP(DMM, LOGL_INFO, "Subscriber %s DETACHED\n", subscr_name(s)); + rc = db_sync_subscriber(s); + db_subscriber_update(s); + dispatch_signal(SS_SUBSCR, S_SUBSCR_DETACHED, s); + break; + default: + fprintf(stderr, "subscr_update with unknown reason: %d\n", + reason); + rc = db_sync_subscriber(s); + db_subscriber_update(s); + break; + }; + + return rc; +} + +void subscr_update_from_db(struct gsm_subscriber *sub) +{ + db_subscriber_update(sub); +} + +int subscr_pending_requests(struct gsm_subscriber *sub) +{ + struct subscr_request *req; + int pending = 0; + + llist_for_each_entry(req, &sub->requests, entry) + pending += 1; + + return pending; +} + +int subscr_pending_clear(struct gsm_subscriber *sub) +{ + int deleted = 0; + struct subscr_request *req, *tmp; + + llist_for_each_entry_safe(req, tmp, &sub->requests, entry) { + subscr_put(req->subscr); + llist_del(&req->entry); + talloc_free(req); + deleted += 1; + } + + return deleted; +} + +int subscr_pending_dump(struct gsm_subscriber *sub, struct vty *vty) +{ + struct subscr_request *req; + + vty_out(vty, "Pending Requests for Subscriber %llu.%s", sub->id, VTY_NEWLINE); + llist_for_each_entry(req, &sub->requests, entry) { + vty_out(vty, "Channel type: %d State: %d Sub: %llu.%s", + req->channel_type, req->state, req->subscr->id, VTY_NEWLINE); + } + + return 0; +} + +int subscr_pending_kick(struct gsm_subscriber *sub) +{ + subscr_put_channel(sub); + return 0; +} diff --git a/src/libmsc/mncc.c b/src/libmsc/mncc.c new file mode 100644 index 000000000..3630b91b4 --- /dev/null +++ b/src/libmsc/mncc.c @@ -0,0 +1,110 @@ +/* mncc.c - utility routines for the MNCC API between the 04.08 + * message parsing and the actual Call Control logic */ + +/* (C) 2008-2009 by Harald Welte + * (C) 2009 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static struct mncc_names { + char *name; + int value; +} mncc_names[] = { + {"MNCC_SETUP_REQ", 0x0101}, + {"MNCC_SETUP_IND", 0x0102}, + {"MNCC_SETUP_RSP", 0x0103}, + {"MNCC_SETUP_CNF", 0x0104}, + {"MNCC_SETUP_COMPL_REQ",0x0105}, + {"MNCC_SETUP_COMPL_IND",0x0106}, + {"MNCC_CALL_CONF_IND", 0x0107}, + {"MNCC_CALL_PROC_REQ", 0x0108}, + {"MNCC_PROGRESS_REQ", 0x0109}, + {"MNCC_ALERT_REQ", 0x010a}, + {"MNCC_ALERT_IND", 0x010b}, + {"MNCC_NOTIFY_REQ", 0x010c}, + {"MNCC_NOTIFY_IND", 0x010d}, + {"MNCC_DISC_REQ", 0x010e}, + {"MNCC_DISC_IND", 0x010f}, + {"MNCC_REL_REQ", 0x0110}, + {"MNCC_REL_IND", 0x0111}, + {"MNCC_REL_CNF", 0x0112}, + {"MNCC_FACILITY_REQ", 0x0113}, + {"MNCC_FACILITY_IND", 0x0114}, + {"MNCC_START_DTMF_IND", 0x0115}, + {"MNCC_START_DTMF_RSP", 0x0116}, + {"MNCC_START_DTMF_REJ", 0x0117}, + {"MNCC_STOP_DTMF_IND", 0x0118}, + {"MNCC_STOP_DTMF_RSP", 0x0119}, + {"MNCC_MODIFY_REQ", 0x011a}, + {"MNCC_MODIFY_IND", 0x011b}, + {"MNCC_MODIFY_RSP", 0x011c}, + {"MNCC_MODIFY_CNF", 0x011d}, + {"MNCC_MODIFY_REJ", 0x011e}, + {"MNCC_HOLD_IND", 0x011f}, + {"MNCC_HOLD_CNF", 0x0120}, + {"MNCC_HOLD_REJ", 0x0121}, + {"MNCC_RETRIEVE_IND", 0x0122}, + {"MNCC_RETRIEVE_CNF", 0x0123}, + {"MNCC_RETRIEVE_REJ", 0x0124}, + {"MNCC_USERINFO_REQ", 0x0125}, + {"MNCC_USERINFO_IND", 0x0126}, + {"MNCC_REJ_REQ", 0x0127}, + {"MNCC_REJ_IND", 0x0128}, + + {"MNCC_BRIDGE", 0x0200}, + {"MNCC_FRAME_RECV", 0x0201}, + {"MNCC_FRAME_DROP", 0x0202}, + {"MNCC_LCHAN_MODIFY", 0x0203}, + + {"GSM_TCH_FRAME", 0x0300}, + + {NULL, 0} }; + +char *get_mncc_name(int value) +{ + int i; + + for (i = 0; mncc_names[i].name; i++) { + if (mncc_names[i].value == value) + return mncc_names[i].name; + } + + return "MNCC_Unknown"; +} + +void mncc_set_cause(struct gsm_mncc *data, int loc, int val) +{ + data->fields |= MNCC_F_CAUSE; + data->cause.location = loc; + data->cause.value = val; +} + diff --git a/src/libmsc/mncc_builtin.c b/src/libmsc/mncc_builtin.c new file mode 100644 index 000000000..0226b2748 --- /dev/null +++ b/src/libmsc/mncc_builtin.c @@ -0,0 +1,411 @@ +/* mncc_builtin.c - default, minimal built-in MNCC Application for + * standalone bsc_hack (netowrk-in-the-box mode) */ + +/* (C) 2008-2010 by Harald Welte + * (C) 2009 by Andreas Eversberg + * All Rights Reserved + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +void *tall_call_ctx; + +static LLIST_HEAD(call_list); + +static u_int32_t new_callref = 0x00000001; + +static void free_call(struct gsm_call *call) +{ + llist_del(&call->entry); + DEBUGP(DMNCC, "(call %x) Call removed.\n", call->callref); + talloc_free(call); +} + + +static struct gsm_call *get_call_ref(u_int32_t callref) +{ + struct gsm_call *callt; + + llist_for_each_entry(callt, &call_list, entry) { + if (callt->callref == callref) + return callt; + } + return NULL; +} + + +/* on incoming call, look up database and send setup to remote subscr. */ +static int mncc_setup_ind(struct gsm_call *call, int msg_type, + struct gsm_mncc *setup) +{ + struct gsm_mncc mncc; + struct gsm_call *remote; + + memset(&mncc, 0, sizeof(struct gsm_mncc)); + mncc.callref = call->callref; + + /* already have remote call */ + if (call->remote_ref) + return 0; + + /* transfer mode 1 would be packet mode, which was never specified */ + if (setup->bearer_cap.mode != 0) { + LOGP(DMNCC, LOGL_NOTICE, "(call %x) We don't support " + "packet mode\n", call->callref); + mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_BEARER_CA_UNAVAIL); + goto out_reject; + } + + /* we currently only do speech */ + if (setup->bearer_cap.transfer != GSM_MNCC_BCAP_SPEECH) { + LOGP(DMNCC, LOGL_NOTICE, "(call %x) We only support " + "voice calls\n", call->callref); + mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_BEARER_CA_UNAVAIL); + goto out_reject; + } + + /* create remote call */ + if (!(remote = talloc(tall_call_ctx, struct gsm_call))) { + mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + goto out_reject; + } + llist_add_tail(&remote->entry, &call_list); + remote->net = call->net; + remote->callref = new_callref++; + DEBUGP(DMNCC, "(call %x) Creating new remote instance %x.\n", + call->callref, remote->callref); + + /* link remote call */ + call->remote_ref = remote->callref; + remote->remote_ref = call->callref; + + /* modify mode */ + memset(&mncc, 0, sizeof(struct gsm_mncc)); + mncc.callref = call->callref; + mncc.lchan_mode = GSM48_CMODE_SPEECH_EFR; + DEBUGP(DMNCC, "(call %x) Modify channel mode.\n", call->callref); + mncc_tx_to_cc(call->net, MNCC_LCHAN_MODIFY, &mncc); + + /* send call proceeding */ + memset(&mncc, 0, sizeof(struct gsm_mncc)); + mncc.callref = call->callref; + DEBUGP(DMNCC, "(call %x) Accepting call.\n", call->callref); + mncc_tx_to_cc(call->net, MNCC_CALL_PROC_REQ, &mncc); + + /* send setup to remote */ +// setup->fields |= MNCC_F_SIGNAL; +// setup->signal = GSM48_SIGNAL_DIALTONE; + setup->callref = remote->callref; + DEBUGP(DMNCC, "(call %x) Forwarding SETUP to remote.\n", call->callref); + return mncc_tx_to_cc(remote->net, MNCC_SETUP_REQ, setup); + +out_reject: + mncc_tx_to_cc(call->net, MNCC_REJ_REQ, &mncc); + free_call(call); + return 0; +} + +static int mncc_alert_ind(struct gsm_call *call, int msg_type, + struct gsm_mncc *alert) +{ + struct gsm_call *remote; + + /* send alerting to remote */ + if (!(remote = get_call_ref(call->remote_ref))) + return 0; + alert->callref = remote->callref; + DEBUGP(DMNCC, "(call %x) Forwarding ALERT to remote.\n", call->callref); + return mncc_tx_to_cc(remote->net, MNCC_ALERT_REQ, alert); +} + +static int mncc_notify_ind(struct gsm_call *call, int msg_type, + struct gsm_mncc *notify) +{ + struct gsm_call *remote; + + /* send notify to remote */ + if (!(remote = get_call_ref(call->remote_ref))) + return 0; + notify->callref = remote->callref; + DEBUGP(DMNCC, "(call %x) Forwarding NOTIF to remote.\n", call->callref); + return mncc_tx_to_cc(remote->net, MNCC_NOTIFY_REQ, notify); +} + +static int mncc_setup_cnf(struct gsm_call *call, int msg_type, + struct gsm_mncc *connect) +{ + struct gsm_mncc connect_ack, frame_recv; + struct gsm_network *net = call->net; + struct gsm_call *remote; + u_int32_t refs[2]; + + /* acknowledge connect */ + memset(&connect_ack, 0, sizeof(struct gsm_mncc)); + connect_ack.callref = call->callref; + DEBUGP(DMNCC, "(call %x) Acknowledge SETUP.\n", call->callref); + mncc_tx_to_cc(call->net, MNCC_SETUP_COMPL_REQ, &connect_ack); + + /* send connect message to remote */ + if (!(remote = get_call_ref(call->remote_ref))) + return 0; + connect->callref = remote->callref; + DEBUGP(DMNCC, "(call %x) Sending CONNECT to remote.\n", call->callref); + mncc_tx_to_cc(remote->net, MNCC_SETUP_RSP, connect); + + /* bridge tch */ + refs[0] = call->callref; + refs[1] = call->remote_ref; + DEBUGP(DMNCC, "(call %x) Bridging with remote.\n", call->callref); + + /* in direct mode, we always have to bridge the channels */ + if (ipacc_rtp_direct) + return mncc_tx_to_cc(call->net, MNCC_BRIDGE, refs); + + /* proxy mode */ + if (!net->handover.active) { + /* in the no-handover case, we can bridge, i.e. use + * the old RTP proxy code */ + return mncc_tx_to_cc(call->net, MNCC_BRIDGE, refs); + } else { + /* in case of handover, we need to re-write the RTP + * SSRC, sequence and timestamp values and thus + * need to enable RTP receive for both directions */ + memset(&frame_recv, 0, sizeof(struct gsm_mncc)); + frame_recv.callref = call->callref; + mncc_tx_to_cc(call->net, MNCC_FRAME_RECV, &frame_recv); + frame_recv.callref = call->remote_ref; + return mncc_tx_to_cc(call->net, MNCC_FRAME_RECV, &frame_recv); + } +} + +static int mncc_disc_ind(struct gsm_call *call, int msg_type, + struct gsm_mncc *disc) +{ + struct gsm_call *remote; + + /* send release */ + DEBUGP(DMNCC, "(call %x) Releasing call with cause %d\n", + call->callref, disc->cause.value); + mncc_tx_to_cc(call->net, MNCC_REL_REQ, disc); + + /* send disc to remote */ + if (!(remote = get_call_ref(call->remote_ref))) { + return 0; + } + disc->callref = remote->callref; + DEBUGP(DMNCC, "(call %x) Disconnecting remote with cause %d\n", + remote->callref, disc->cause.value); + return mncc_tx_to_cc(remote->net, MNCC_DISC_REQ, disc); +} + +static int mncc_rel_ind(struct gsm_call *call, int msg_type, struct gsm_mncc *rel) +{ + struct gsm_call *remote; + + /* send release to remote */ + if (!(remote = get_call_ref(call->remote_ref))) { + free_call(call); + return 0; + } + + rel->callref = remote->callref; + DEBUGP(DMNCC, "(call %x) Releasing remote with cause %d\n", + call->callref, rel->cause.value); + + /* + * Release this side of the call right now. Otherwise we end up + * in this method for the other call and will also try to release + * it and then we will end up with a double free and a crash + */ + free_call(call); + mncc_tx_to_cc(remote->net, MNCC_REL_REQ, rel); + + return 0; +} + +static int mncc_rel_cnf(struct gsm_call *call, int msg_type, struct gsm_mncc *rel) +{ + free_call(call); + return 0; +} + +/* receiving a TCH/F frame from the BSC code */ +static int mncc_rcv_tchf(struct gsm_call *call, int msg_type, + struct gsm_data_frame *dfr) +{ + struct gsm_trans *remote_trans; + + remote_trans = trans_find_by_callref(call->net, call->remote_ref); + + /* this shouldn't really happen */ + if (!remote_trans || !remote_trans->conn) { + LOGP(DMNCC, LOGL_ERROR, "No transaction or transaction without lchan?!?\n"); + return -EIO; + } + + /* RTP socket of remote end has meanwhile died */ + if (!remote_trans->conn->lchan->abis_ip.rtp_socket) + return -EIO; + + return rtp_send_frame(remote_trans->conn->lchan->abis_ip.rtp_socket, dfr); +} + + +/* Internal MNCC handler input function (from CC -> MNCC -> here) */ +int int_mncc_recv(struct gsm_network *net, struct msgb *msg) +{ + void *arg = msgb_data(msg); + struct gsm_mncc *data = arg; + int msg_type = data->msg_type; + int callref; + struct gsm_call *call = NULL, *callt; + int rc = 0; + + /* Special messages */ + switch(msg_type) { + } + + /* find callref */ + callref = data->callref; + llist_for_each_entry(callt, &call_list, entry) { + if (callt->callref == callref) { + call = callt; + break; + } + } + + /* create callref, if setup is received */ + if (!call) { + if (msg_type != MNCC_SETUP_IND) + goto out_free; /* drop */ + /* create call */ + if (!(call = talloc_zero(tall_call_ctx, struct gsm_call))) { + struct gsm_mncc rel; + + memset(&rel, 0, sizeof(struct gsm_mncc)); + rel.callref = callref; + mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + mncc_tx_to_cc(net, MNCC_REL_REQ, &rel); + goto out_free; + } + llist_add_tail(&call->entry, &call_list); + call->net = net; + call->callref = callref; + DEBUGP(DMNCC, "(call %x) Call created.\n", call->callref); + } + + switch (msg_type) { + case GSM_TCHF_FRAME: + case GSM_TCHF_FRAME_EFR: + break; + default: + DEBUGP(DMNCC, "(call %x) Received message %s\n", call->callref, + get_mncc_name(msg_type)); + break; + } + + switch(msg_type) { + case MNCC_SETUP_IND: + rc = mncc_setup_ind(call, msg_type, arg); + break; + case MNCC_SETUP_CNF: + rc = mncc_setup_cnf(call, msg_type, arg); + break; + case MNCC_SETUP_COMPL_IND: + break; + case MNCC_CALL_CONF_IND: + /* we now need to MODIFY the channel */ + data->lchan_mode = GSM48_CMODE_SPEECH_EFR; + mncc_tx_to_cc(call->net, MNCC_LCHAN_MODIFY, data); + break; + case MNCC_ALERT_IND: + rc = mncc_alert_ind(call, msg_type, arg); + break; + case MNCC_NOTIFY_IND: + rc = mncc_notify_ind(call, msg_type, arg); + break; + case MNCC_DISC_IND: + rc = mncc_disc_ind(call, msg_type, arg); + break; + case MNCC_REL_IND: + case MNCC_REJ_IND: + rc = mncc_rel_ind(call, msg_type, arg); + break; + case MNCC_REL_CNF: + rc = mncc_rel_cnf(call, msg_type, arg); + break; + case MNCC_FACILITY_IND: + break; + case MNCC_START_DTMF_IND: + break; + case MNCC_STOP_DTMF_IND: + break; + case MNCC_MODIFY_IND: + mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_SERV_OPT_UNIMPL); + DEBUGP(DMNCC, "(call %x) Rejecting MODIFY with cause %d\n", + call->callref, data->cause.value); + rc = mncc_tx_to_cc(net, MNCC_MODIFY_REJ, data); + break; + case MNCC_MODIFY_CNF: + break; + case MNCC_HOLD_IND: + mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_SERV_OPT_UNIMPL); + DEBUGP(DMNCC, "(call %x) Rejecting HOLD with cause %d\n", + call->callref, data->cause.value); + rc = mncc_tx_to_cc(net, MNCC_HOLD_REJ, data); + break; + case MNCC_RETRIEVE_IND: + mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_SERV_OPT_UNIMPL); + DEBUGP(DMNCC, "(call %x) Rejecting RETRIEVE with cause %d\n", + call->callref, data->cause.value); + rc = mncc_tx_to_cc(net, MNCC_RETRIEVE_REJ, data); + break; + case GSM_TCHF_FRAME: + case GSM_TCHF_FRAME_EFR: + rc = mncc_rcv_tchf(call, msg_type, arg); + break; + default: + LOGP(DMNCC, LOGL_NOTICE, "(call %x) Message unhandled\n", callref); + break; + } + +out_free: + talloc_free(msg); + + return rc; +} diff --git a/src/libmsc/mncc_sock.c b/src/libmsc/mncc_sock.c new file mode 100644 index 000000000..2eef7c86e --- /dev/null +++ b/src/libmsc/mncc_sock.c @@ -0,0 +1,337 @@ +/* mncc_sock.c: Tie the MNCC interface to a unix domain socket */ + +/* (C) 2008-2010 by Harald Welte + * (C) 2009 by Andreas Eversberg + * All Rights Reserved + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +struct mncc_sock_state { + struct gsm_network *net; + struct bsc_fd listen_bfd; /* fd for listen socket */ + struct bsc_fd conn_bfd; /* fd for connection to lcr */ +}; + +/* FIXME: avoid this */ +static struct mncc_sock_state *g_state; + +/* input from CC code into mncc_sock */ +int mncc_sock_from_cc(struct gsm_network *net, struct msgb *msg) +{ + struct gsm_mncc *mncc_in = (struct gsm_mncc *) msgb_data(msg); + int msg_type = mncc_in->msg_type; + + /* Check if we currently have a MNCC handler connected */ + if (g_state->conn_bfd.fd < 0) { + LOGP(DMNCC, LOGL_ERROR, "mncc_sock receives %s for external CC app " + "but socket is gone\n", get_mncc_name(msg_type)); + if (msg_type != GSM_TCHF_FRAME && + msg_type != GSM_TCHF_FRAME_EFR) { + /* release the request */ + struct gsm_mncc mncc_out; + memset(&mncc_out, 0, sizeof(mncc_out)); + mncc_out.callref = mncc_in->callref; + mncc_set_cause(&mncc_out, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_TEMP_FAILURE); + mncc_tx_to_cc(net, MNCC_REL_REQ, &mncc_out); + } + /* free the original message */ + msgb_free(msg); + return -1; + } + + /* FIXME: check for some maximum queue depth? */ + + /* Actually enqueue the message and mark socket write need */ + msgb_enqueue(&net->upqueue, msg); + g_state->conn_bfd.when |= BSC_FD_WRITE; + return 0; +} + +void mncc_sock_write_pending(void) +{ + g_state->conn_bfd.when |= BSC_FD_WRITE; +} + +/* FIXME: move this to libosmocore */ +int osmo_unixsock_listen(struct bsc_fd *bfd, int type, const char *path); + +static void mncc_sock_close(struct mncc_sock_state *state) +{ + struct bsc_fd *bfd = &state->conn_bfd; + + LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has LOST connection\n"); + + close(bfd->fd); + bfd->fd = -1; + bsc_unregister_fd(bfd); + + /* re-enable the generation of ACCEPT for new connections */ + state->listen_bfd.when |= BSC_FD_READ; + + /* FIXME: make sure we don't enqueue anymore */ + + /* release all exisitng calls */ + gsm0408_clear_all_trans(state->net, GSM48_PDISC_CC); + + /* flush the queue */ + while (!llist_empty(&state->net->upqueue)) { + struct msgb *msg = msgb_dequeue(&state->net->upqueue); + msgb_free(msg); + } +} + +static int mncc_sock_read(struct bsc_fd *bfd) +{ + struct mncc_sock_state *state = (struct mncc_sock_state *)bfd->data; + struct gsm_mncc *mncc_prim; + struct msgb *msg; + int rc; + + msg = msgb_alloc(sizeof(*mncc_prim)+256, "mncc_sock_rx"); + if (!msg) + return -ENOMEM; + + mncc_prim = (struct gsm_mncc *) msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) + return 0; + goto close; + } + + rc = mncc_tx_to_cc(state->net, mncc_prim->msg_type, mncc_prim); + + /* as we always synchronously process the message in mncc_send() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + mncc_sock_close(state); + return -1; +} + +static int mncc_sock_write(struct bsc_fd *bfd) +{ + struct mncc_sock_state *state = bfd->data; + struct gsm_network *net = state->net; + int rc; + + while (!llist_empty(&net->upqueue)) { + struct msgb *msg, *msg2; + struct gsm_mncc *mncc_prim; + + /* peek at the beginning of the queue */ + msg = llist_entry(net->upqueue.next, struct msgb, list); + mncc_prim = (struct gsm_mncc *)msg->data; + + bfd->when &= ~BSC_FD_WRITE; + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + bfd->when |= BSC_FD_WRITE; + break; + } + goto close; + } + /* _after_ we send it, we can deueue */ + msg2 = msgb_dequeue(&net->upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + mncc_sock_close(state); + + return -1; +} + +static int mncc_sock_cb(struct bsc_fd *bfd, unsigned int flags) +{ + int rc = 0; + + if (flags & BSC_FD_READ) + rc = mncc_sock_read(bfd); + if (rc < 0) + return rc; + + if (flags & BSC_FD_WRITE) + rc = mncc_sock_write(bfd); + + return rc; +} + +/* accept a new connection */ +static int mncc_sock_accept(struct bsc_fd *bfd, unsigned int flags) +{ + struct mncc_sock_state *state = (struct mncc_sock_state *)bfd->data; + struct bsc_fd *conn_bfd = &state->conn_bfd; + struct sockaddr_un un_addr; + socklen_t len; + int rc; + + len = sizeof(un_addr); + rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len); + if (rc < 0) { + LOGP(DMNCC, LOGL_ERROR, "Failed to accept a new connection\n"); + return -1; + } + + if (conn_bfd->fd >= 0) { + LOGP(DMNCC, LOGL_NOTICE, "MNCC app connects but we already have " + "another active connection ?!?\n"); + /* We already have one MNCC app connected, this is all we support */ + state->listen_bfd.when &= ~BSC_FD_READ; + close(rc); + return 0; + } + + conn_bfd->fd = rc; + conn_bfd->when = BSC_FD_READ; + conn_bfd->cb = mncc_sock_cb; + conn_bfd->data = state; + + if (bsc_register_fd(conn_bfd) != 0) { + LOGP(DMNCC, LOGL_ERROR, "Failed to register new connection fd\n"); + close(conn_bfd->fd); + conn_bfd->fd = -1; + state->listen_bfd.when |= ~BSC_FD_READ; + return -1; + } + + LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has connection with external " + "call control application\n"); + + return 0; +} + + +int mncc_sock_init(struct gsm_network *net) +{ + struct mncc_sock_state *state; + struct bsc_fd *bfd; + int rc; + + state = talloc_zero(tall_bsc_ctx, struct mncc_sock_state); + if (!state) + return -ENOMEM; + + state->net = net; + state->conn_bfd.fd = -1; + + bfd = &state->listen_bfd; + + rc = osmo_unixsock_listen(bfd, SOCK_SEQPACKET, "/tmp/bsc_mncc"); + if (rc < 0) { + LOGP(DMNCC, LOGL_ERROR, "Could not create unix socket: %s\n", + strerror(errno)); + talloc_free(state); + return rc; + } + + bfd->when = BSC_FD_READ; + bfd->cb = mncc_sock_accept; + bfd->data = state; + + rc = bsc_register_fd(bfd); + if (rc < 0) { + LOGP(DMNCC, LOGL_ERROR, "Could not register listen fd: %d\n", rc); + close(bfd->fd); + talloc_free(state); + return rc; + } + + g_state = state; + + return 0; +} + +/* FIXME: move this to libosmocore */ +int osmo_unixsock_listen(struct bsc_fd *bfd, int type, const char *path) +{ + struct sockaddr_un local; + unsigned int namelen; + int rc; + + bfd->fd = socket(AF_UNIX, type, 0); + + if (bfd->fd < 0) { + fprintf(stderr, "Failed to create Unix Domain Socket.\n"); + return -1; + } + + local.sun_family = AF_UNIX; + strncpy(local.sun_path, path, sizeof(local.sun_path)); + local.sun_path[sizeof(local.sun_path) - 1] = '\0'; + unlink(local.sun_path); + + /* we use the same magic that X11 uses in Xtranssock.c for + * calculating the proper length of the sockaddr */ +#if defined(BSD44SOCKETS) || defined(__UNIXWARE__) + local.sun_len = strlen(local.sun_path); +#endif +#if defined(BSD44SOCKETS) || defined(SUN_LEN) + namelen = SUN_LEN(&local); +#else + namelen = strlen(local.sun_path) + + offsetof(struct sockaddr_un, sun_path); +#endif + + rc = bind(bfd->fd, (struct sockaddr *) &local, namelen); + if (rc != 0) { + fprintf(stderr, "Failed to bind the unix domain socket. '%s'\n", + local.sun_path); + return -1; + } + + if (listen(bfd->fd, 0) != 0) { + fprintf(stderr, "Failed to listen.\n"); + return -1; + } + + return 0; +} diff --git a/src/libmsc/osmo_msc.c b/src/libmsc/osmo_msc.c new file mode 100644 index 000000000..8c86dcc8e --- /dev/null +++ b/src/libmsc/osmo_msc.c @@ -0,0 +1,104 @@ +/* main MSC management code... */ + +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include + +static void msc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci) +{ + int sapi = dlci & 0x7; + + if (sapi == UM_SAPI_SMS) + gsm411_sapi_n_reject(conn); +} + +static int msc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause) +{ + gsm0408_clear_request(conn, cause); + if (conn->put_channel) { + conn->put_channel = 0; + subscr_put_channel(conn->subscr); + } + return 1; +} + +static int msc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg, + uint16_t chosen_channel) +{ + gsm0408_new_conn(conn); + gsm0408_dispatch(conn, msg); + + /* TODO: do better */ + return BSC_API_CONN_POL_ACCEPT; +} + +static void msc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg) +{ + gsm0408_dispatch(conn, msg); +} + +static struct bsc_api msc_handler = { + .sapi_n_reject = msc_sapi_n_reject, + .clear_request = msc_clear_request, + .compl_l3 = msc_compl_l3, + .dtap = msc_dtap, +}; + +struct bsc_api *msc_bsc_api() { + return &msc_handler; +} + +/* lchan release handling */ +void msc_release_connection(struct gsm_subscriber_connection *conn) +{ + struct gsm_trans *trans; + + /* skip when we are in release, e.g. due an error */ + if (conn->in_release) + return; + + /* skip releasing of silent calls as they have no transaction */ + if (conn->silent_call) + return; + + /* check if there is a pending operation */ + if (conn->loc_operation || conn->sec_operation || conn->anch_operation) + return; + + llist_for_each_entry(trans, &conn->bts->network->trans_list, entry) { + if (trans->conn == conn) + return; + } + + /* no more connections, asking to release the channel */ + conn->in_release = 1; + gsm0808_clear(conn); + if (conn->put_channel) { + conn->put_channel = 0; + subscr_put_channel(conn->subscr); + } + subscr_con_free(conn); +} diff --git a/src/libmsc/rrlp.c b/src/libmsc/rrlp.c new file mode 100644 index 000000000..ae5ca478e --- /dev/null +++ b/src/libmsc/rrlp.c @@ -0,0 +1,105 @@ +/* Radio Resource LCS (Location) Protocol, GMS TS 04.31 */ + +/* (C) 2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include + +#include +#include +#include +#include + +/* RRLP msPositionReq, nsBased, + * Accuracy=60, Method=gps, ResponseTime=2, oneSet */ +static const u_int8_t ms_based_pos_req[] = { 0x40, 0x01, 0x78, 0xa8 }; + +/* RRLP msPositionReq, msBasedPref, + Accuracy=60, Method=gpsOrEOTD, ResponseTime=5, multipleSets */ +static const u_int8_t ms_pref_pos_req[] = { 0x40, 0x02, 0x79, 0x50 }; + +/* RRLP msPositionReq, msAssistedPref, + Accuracy=60, Method=gpsOrEOTD, ResponseTime=5, multipleSets */ +static const u_int8_t ass_pref_pos_req[] = { 0x40, 0x03, 0x79, 0x50 }; + +static int send_rrlp_req(struct gsm_subscriber_connection *conn) +{ + struct gsm_network *net = conn->bts->network; + const u_int8_t *req; + + switch (net->rrlp.mode) { + case RRLP_MODE_MS_BASED: + req = ms_based_pos_req; + break; + case RRLP_MODE_MS_PREF: + req = ms_pref_pos_req; + break; + case RRLP_MODE_ASS_PREF: + req = ass_pref_pos_req; + break; + case RRLP_MODE_NONE: + default: + return 0; + } + + return gsm48_send_rr_app_info(conn, 0x00, + sizeof(ms_based_pos_req), req); +} + +static int subscr_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_subscriber *subscr; + struct gsm_subscriber_connection *conn; + + switch (signal) { + case S_SUBSCR_ATTACHED: + /* A subscriber has attached. */ + subscr = signal_data; + conn = connection_for_subscr(subscr); + if (!conn) + break; + send_rrlp_req(conn); + break; + } + return 0; +} + +static int paging_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct paging_signal_data *psig_data = signal_data; + + switch (signal) { + case S_PAGING_SUCCEEDED: + /* A subscriber has attached. */ + send_rrlp_req(psig_data->conn); + break; + case S_PAGING_EXPIRED: + break; + } + return 0; +} + +void on_dso_load_rrlp(void) +{ + register_signal_handler(SS_SUBSCR, subscr_sig_cb, NULL); + register_signal_handler(SS_PAGING, paging_sig_cb, NULL); +} diff --git a/src/libmsc/silent_call.c b/src/libmsc/silent_call.c new file mode 100644 index 000000000..64ebdfdb9 --- /dev/null +++ b/src/libmsc/silent_call.c @@ -0,0 +1,143 @@ +/* GSM silent call feature */ + +/* + * (C) 2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* paging of the requested subscriber has completed */ +static int paging_cb_silent(unsigned int hooknum, unsigned int event, + struct msgb *msg, void *_conn, void *_data) +{ + struct gsm_subscriber_connection *conn = _conn; + struct scall_signal_data sigdata; + int rc = 0; + + if (hooknum != GSM_HOOK_RR_PAGING) + return -EINVAL; + + DEBUGP(DSMS, "paging_cb_silent: "); + + sigdata.conn = conn; + sigdata.data = _data; + + switch (event) { + case GSM_PAGING_SUCCEEDED: + DEBUGPC(DSMS, "success, using Timeslot %u on ARFCN %u\n", + conn->lchan->ts->nr, conn->lchan->ts->trx->arfcn); + conn->silent_call = 1; + /* increment lchan reference count */ + dispatch_signal(SS_SCALL, S_SCALL_SUCCESS, &sigdata); + break; + case GSM_PAGING_EXPIRED: + case GSM_PAGING_BUSY: + case GSM_PAGING_OOM: + DEBUGP(DSMS, "expired\n"); + dispatch_signal(SS_SCALL, S_SCALL_EXPIRED, &sigdata); + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +/* receive a layer 3 message from a silent call */ +int silent_call_rx(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + /* FIXME: do something like sending it through a UDP port */ + return 0; +} + +struct msg_match { + u_int8_t pdisc; + u_int8_t msg_type; +}; + +/* list of messages that are handled inside OpenBSC, even in a silent call */ +static const struct msg_match silent_call_accept[] = { + { GSM48_PDISC_MM, GSM48_MT_MM_LOC_UPD_REQUEST }, + { GSM48_PDISC_MM, GSM48_MT_MM_CM_SERV_REQ }, +}; + +/* decide if we need to reroute a message as part of a silent call */ +int silent_call_reroute(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + u_int8_t pdisc = gh->proto_discr & 0x0f; + int i; + + /* if we're not part of a silent call, never reroute */ + if (!conn->silent_call) + return 0; + + /* check if we are a special message that is handled in openbsc */ + for (i = 0; i < ARRAY_SIZE(silent_call_accept); i++) { + if (silent_call_accept[i].pdisc == pdisc && + silent_call_accept[i].msg_type == gh->msg_type) + return 0; + } + + /* otherwise, reroute */ + return 1; +} + + +/* initiate a silent call with a given subscriber */ +int gsm_silent_call_start(struct gsm_subscriber *subscr, void *data, int type) +{ + int rc; + + rc = paging_request(subscr->net, subscr, type, + paging_cb_silent, data); + return rc; +} + +/* end a silent call with a given subscriber */ +int gsm_silent_call_stop(struct gsm_subscriber *subscr) +{ + struct gsm_subscriber_connection *conn; + + conn = connection_for_subscr(subscr); + if (!conn) + return -EINVAL; + + /* did we actually establish a silent call for this guy? */ + if (!conn->silent_call) + return -EINVAL; + + conn->silent_call = 0; + msc_release_connection(conn); + + return 0; +} diff --git a/src/libmsc/sms_queue.c b/src/libmsc/sms_queue.c new file mode 100644 index 000000000..079755d22 --- /dev/null +++ b/src/libmsc/sms_queue.c @@ -0,0 +1,479 @@ +/* SMS queue to continously attempt to deliver SMS */ +/* + * (C) 2010 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +/** + * The difficulty of such a queue is to send a lot of SMS without + * overloading the paging subsystem and the database and other users + * of the MSC. To make the best use we would need to know the number + * of pending paging requests, then throttle the number of SMS we + * want to send and such. + * We will start with a very simple SMS Queue and then try to speed + * things up by collecting data from other parts of the system. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +/* + * One pending SMS that we wait for. + */ +struct gsm_sms_pending { + struct llist_head entry; + + struct gsm_subscriber *subscr; + unsigned long long sms_id; + int failed_attempts; + int resend; +}; + +struct gsm_sms_queue { + struct timer_list resend_pending; + struct timer_list push_queue; + struct gsm_network *network; + int max_fail; + int max_pending; + int pending; + + struct llist_head pending_sms; + unsigned long long last_subscr_id; +}; + +static int sms_subscr_cb(unsigned int, unsigned int, void *, void *); +static int sms_sms_cb(unsigned int, unsigned int, void *, void *); + +static struct gsm_sms_pending *sms_find_pending(struct gsm_sms_queue *smsq, + struct gsm_sms *sms) +{ + struct gsm_sms_pending *pending; + + llist_for_each_entry(pending, &smsq->pending_sms, entry) { + if (pending->sms_id == sms->id) + return pending; + } + + return NULL; +} + +static int sms_is_in_pending(struct gsm_sms_queue *smsq, struct gsm_sms *sms) +{ + return sms_find_pending(smsq, sms) != NULL; +} + +static int sms_subscriber_is_pending(struct gsm_sms_queue *smsq, + struct gsm_subscriber *subscr) +{ + struct gsm_sms_pending *pending; + + llist_for_each_entry(pending, &smsq->pending_sms, entry) { + if (pending->subscr == subscr) + return 1; + } + + return 0; +} + +static struct gsm_sms_pending *sms_pending_from(struct gsm_sms_queue *smsq, + struct gsm_sms *sms) +{ + struct gsm_sms_pending *pending; + + pending = talloc_zero(smsq, struct gsm_sms_pending); + if (!pending) + return NULL; + + pending->subscr = subscr_get(sms->receiver); + pending->sms_id = sms->id; + return pending; +} + +static void sms_pending_free(struct gsm_sms_pending *pending) +{ + subscr_put(pending->subscr); + llist_del(&pending->entry); + talloc_free(pending); +} + +static void sms_pending_resend(struct gsm_sms_pending *pending) +{ + struct gsm_sms_queue *smsq; + LOGP(DSMS, LOGL_DEBUG, + "Scheduling resend of SMS %llu.\n", pending->sms_id); + + pending->resend = 1; + + smsq = pending->subscr->net->sms_queue; + if (bsc_timer_pending(&smsq->resend_pending)) + return; + + bsc_schedule_timer(&smsq->resend_pending, 1, 0); +} + +static void sms_pending_failed(struct gsm_sms_pending *pending, int paging_error) +{ + struct gsm_sms_queue *smsq; + + LOGP(DSMS, LOGL_NOTICE, "Sending SMS %llu failed %d times.\n", + pending->sms_id, pending->failed_attempts); + + smsq = pending->subscr->net->sms_queue; + if (++pending->failed_attempts < smsq->max_fail) + return sms_pending_resend(pending); + + if (paging_error) { + LOGP(DSMS, LOGL_NOTICE, + "Subscriber %llu is not reachable. Setting LAC=0.\n", pending->subscr->id); + pending->subscr->lac = GSM_LAC_RESERVED_DETACHED; + db_sync_subscriber(pending->subscr); + + /* Workaround a failing sync */ + db_subscriber_update(pending->subscr); + } + + sms_pending_free(pending); + smsq->pending -= 1; + sms_queue_trigger(smsq); +} + +/* + * Resend all SMS that are scheduled for a resend. This is done to + * avoid an immediate failure. + */ +static void sms_resend_pending(void *_data) +{ + struct gsm_sms_pending *pending, *tmp; + struct gsm_sms_queue *smsq = _data; + + llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) { + struct gsm_sms *sms; + if (!pending->resend) + continue; + + sms = db_sms_get(smsq->network, pending->sms_id); + + /* the sms is gone? Move to the next */ + if (!sms) { + sms_pending_free(pending); + smsq->pending -= 1; + sms_queue_trigger(smsq); + } else { + pending->resend = 0; + gsm411_send_sms_subscr(sms->receiver, sms); + } + } +} + +static struct gsm_sms *take_next_sms(struct gsm_sms_queue *smsq) +{ + struct gsm_sms *sms; + + sms = db_sms_get_unsent_by_subscr(smsq->network, smsq->last_subscr_id, 10); + if (sms) { + smsq->last_subscr_id = sms->receiver->id + 1; + return sms; + } + + /* need to wrap around */ + smsq->last_subscr_id = 0; + sms = db_sms_get_unsent_by_subscr(smsq->network, + smsq->last_subscr_id, 10); + if (sms) + smsq->last_subscr_id = sms->receiver->id + 1; + return sms; +} + +/** + * I will submit up to max_pending - pending SMS to the + * subsystem. + */ +static void sms_submit_pending(void *_data) +{ + struct gsm_sms_queue *smsq = _data; + int attempts = smsq->max_pending - smsq->pending; + int initialized = 0; + unsigned long long first_sub = 0; + int attempted = 0, rounds = 0; + + LOGP(DSMS, LOGL_NOTICE, "Attempting to send %d SMS\n", attempts); + + do { + struct gsm_sms_pending *pending; + struct gsm_sms *sms; + + + sms = take_next_sms(smsq); + if (!sms) + break; + + rounds += 1; + + /* + * This code needs to detect a loop. It assumes that no SMS + * will vanish during the time this is executed. We will remember + * the id of the first GSM subscriber we see and then will + * compare this. The Database code should make sure that we will + * see all other subscribers first before seeing this one again. + * + * It is always scary to have an infinite loop like this. + */ + if (!initialized) { + first_sub = sms->receiver->id; + initialized = 1; + } else if (first_sub == sms->receiver->id) { + sms_free(sms); + break; + } + + /* no need to send a pending sms */ + if (sms_is_in_pending(smsq, sms)) { + LOGP(DSMS, LOGL_DEBUG, + "SMSqueue with pending sms: %llu. Skipping\n", sms->id); + sms_free(sms); + continue; + } + + /* no need to send a SMS with the same receiver */ + if (sms_subscriber_is_pending(smsq, sms->receiver)) { + LOGP(DSMS, LOGL_DEBUG, + "SMSqueue with pending sub: %llu. Skipping\n", sms->receiver->id); + sms_free(sms); + continue; + } + + pending = sms_pending_from(smsq, sms); + if (!pending) { + LOGP(DSMS, LOGL_ERROR, + "Failed to create pending SMS entry.\n"); + sms_free(sms); + continue; + } + + attempted += 1; + smsq->pending += 1; + llist_add_tail(&pending->entry, &smsq->pending_sms); + gsm411_send_sms_subscr(sms->receiver, sms); + } while (attempted < attempts && rounds < 1000); + + LOGP(DSMS, LOGL_DEBUG, "SMSqueue added %d messages in %d rounds\n", attempted, rounds); +} + +/* + * Kick off the queue again. + */ +int sms_queue_trigger(struct gsm_sms_queue *smsq) +{ + if (bsc_timer_pending(&smsq->push_queue)) + return 0; + + bsc_schedule_timer(&smsq->push_queue, 1, 0); + return 0; +} + +int sms_queue_start(struct gsm_network *network, int max_pending) +{ + struct gsm_sms_queue *sms = talloc_zero(network, struct gsm_sms_queue); + if (!sms) { + LOGP(DMSC, LOGL_ERROR, "Failed to create the SMS queue.\n"); + return -1; + } + + register_signal_handler(SS_SUBSCR, sms_subscr_cb, network); + register_signal_handler(SS_SMS, sms_sms_cb, network); + + network->sms_queue = sms; + INIT_LLIST_HEAD(&sms->pending_sms); + sms->max_fail = 1; + sms->network = network; + sms->max_pending = max_pending; + sms->push_queue.data = sms; + sms->push_queue.cb = sms_submit_pending; + sms->resend_pending.data = sms; + sms->resend_pending.cb = sms_resend_pending; + + sms_submit_pending(sms); + + return 0; +} + +static int sub_ready_for_sm(struct gsm_subscriber *subscr) +{ + struct gsm_subscriber_connection *conn; + struct gsm_sms *sms; + + /* A subscriber has attached. Check if there are + * any pending SMS for him to be delivered */ + conn = connection_for_subscr(subscr); + if (!conn) + return -1; + sms = db_sms_get_unsent_for_subscr(subscr); + if (!sms) + return -1; + gsm411_send_sms(conn, sms); + return 0; +} + +static int sms_subscr_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_subscriber *subscr = signal_data; + + if (signal != S_SUBSCR_ATTACHED) + return 0; + + /* this is readyForSM */ + return sub_ready_for_sm(subscr); +} + +static int sms_sms_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_network *network = handler_data; + struct sms_signal_data *sig_sms = signal_data; + struct gsm_sms_pending *pending; + + /* We got a new SMS and maybe should launch the queue again. */ + if (signal == S_SMS_SUBMITTED || signal == S_SMS_SMMA) { + sms_queue_trigger(network->sms_queue); + return 0; + } + + if (!sig_sms->sms) + return -1; + + + /* + * Find the entry of our queue. The SMS subsystem will submit + * sms that are not in our control as we just have a channel + * open anyway. + */ + pending = sms_find_pending(network->sms_queue, sig_sms->sms); + if (!pending) + return 0; + + switch (signal) { + case S_SMS_DELIVERED: + /* + * Create place for a new SMS but keep the pending data + * so we will not attempt to send the SMS for this subscriber + * as we still have an open channel and will attempt to submit + * SMS to it anyway. + */ + network->sms_queue->pending -= 1; + sms_submit_pending(network->sms_queue); + sms_pending_free(pending); + break; + case S_SMS_MEM_EXCEEDED: + network->sms_queue->pending -= 1; + sms_pending_free(pending); + sms_queue_trigger(network->sms_queue); + break; + case S_SMS_UNKNOWN_ERROR: + /* + * There can be many reasons for this failure. E.g. the paging + * timed out, the subscriber was not paged at all, or there was + * a protocol error. The current strategy is to try sending the + * next SMS for busy/oom and to retransmit when we have paged. + * + * When the paging expires three times we will disable the + * subscriber. If we have some kind of other transmit error we + * should flag the SMS as bad. + */ + switch (sig_sms->paging_result) { + case 0: + /* BAD SMS? */ + db_sms_inc_deliver_attempts(sig_sms->sms); + sms_pending_failed(pending, 0); + break; + case GSM_PAGING_EXPIRED: + sms_pending_failed(pending, 1); + break; + + case GSM_PAGING_OOM: + case GSM_PAGING_BUSY: + network->sms_queue->pending -= 1; + sms_pending_free(pending); + sms_queue_trigger(network->sms_queue); + break; + default: + LOGP(DSMS, LOGL_ERROR, "Unhandled result: %d\n", + sig_sms->paging_result); + } + break; + default: + LOGP(DSMS, LOGL_ERROR, "Unhandled result: %d\n", + sig_sms->paging_result); + } + + return 0; +} + +/* VTY helper functions */ +int sms_queue_stats(struct gsm_sms_queue *smsq, struct vty *vty) +{ + struct gsm_sms_pending *pending; + + vty_out(vty, "SMSqueue with max_pending: %d pending: %d%s", + smsq->max_pending, smsq->pending, VTY_NEWLINE); + + llist_for_each_entry(pending, &smsq->pending_sms, entry) + vty_out(vty, " SMS Pending for Subscriber: %llu SMS: %llu Failed: %d.%s", + pending->subscr->id, pending->sms_id, + pending->failed_attempts, VTY_NEWLINE); + return 0; +} + +int sms_queue_set_max_pending(struct gsm_sms_queue *smsq, int max_pending) +{ + LOGP(DSMS, LOGL_NOTICE, "SMSqueue old max: %d new: %d\n", + smsq->max_pending, max_pending); + smsq->max_pending = max_pending; + return 0; +} + +int sms_queue_set_max_failure(struct gsm_sms_queue *smsq, int max_fail) +{ + LOGP(DSMS, LOGL_NOTICE, "SMSqueue max failure old: %d new: %d\n", + smsq->max_fail, max_fail); + smsq->max_fail = max_fail; + return 0; +} + +int sms_queue_clear(struct gsm_sms_queue *smsq) +{ + struct gsm_sms_pending *pending, *tmp; + + llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) { + LOGP(DSMS, LOGL_NOTICE, + "SMSqueue clearing for sub %llu\n", pending->subscr->id); + sms_pending_free(pending); + } + + smsq->pending = 0; + return 0; +} diff --git a/src/libmsc/token_auth.c b/src/libmsc/token_auth.c new file mode 100644 index 000000000..3404dd4ee --- /dev/null +++ b/src/libmsc/token_auth.c @@ -0,0 +1,153 @@ +/* SMS based token authentication for ad-hoc GSM networks */ + +/* (C) 2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TOKEN_SMS_TEXT "HAR 2009 GSM. Register at http://har2009.gnumonks.org/ " \ + "Your IMSI is %s, auth token is %08X, phone no is %s." + +static char *build_sms_string(struct gsm_subscriber *subscr, u_int32_t token) +{ + char *sms_str; + unsigned int len; + + len = strlen(subscr->imsi) + 8 + strlen(TOKEN_SMS_TEXT); + sms_str = talloc_size(tall_bsc_ctx, len); + if (!sms_str) + return NULL; + + snprintf(sms_str, len, TOKEN_SMS_TEXT, subscr->imsi, token, + subscr->extension); + sms_str[len-1] = '\0'; + + return sms_str; +} + +static int token_subscr_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_subscriber *subscr = signal_data; + struct gsm_sms *sms; + int rc = 0; + + if (signal != S_SUBSCR_ATTACHED) + return 0; + + if (subscr->net->auth_policy != GSM_AUTH_POLICY_TOKEN) + return 0; + + if (subscr->flags & GSM_SUBSCRIBER_FIRST_CONTACT) { + u_int32_t token; + char *sms_str; + + /* we've seen this subscriber for the first time. */ + rc = db_subscriber_alloc_token(subscr, &token); + if (rc != 0) { + rc = -EIO; + goto unauth; + } + + sms_str = build_sms_string(subscr, token); + if (!sms_str) { + rc = -ENOMEM; + goto unauth; + } + + sms = sms_from_text(subscr, 0, sms_str); + talloc_free(sms_str); + if (!sms) { + rc = -ENOMEM; + goto unauth; + } + + rc = gsm411_send_sms_subscr(subscr, sms); + + /* FIXME: else, delete the subscirber from database */ +unauth: + + /* make sure we don't allow him in again unless he clicks the web UI */ + subscr->authorized = 0; + db_sync_subscriber(subscr); + if (rc) { + struct gsm_subscriber_connection *conn = connection_for_subscr(subscr); + if (conn) { + u_int8_t auth_rand[16]; + /* kick the subscriber off the network */ + gsm48_tx_mm_auth_req(conn, auth_rand, 0); + gsm48_tx_mm_auth_rej(conn); + /* FIXME: close the channel early ?*/ + //gsm48_send_rr_Release(lchan); + } + } + } + + return rc; +} + +static int token_sms_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct sms_signal_data *sig = signal_data; + struct gsm_sms *sms = sig->sms;; + struct gsm_subscriber_connection *conn; + u_int8_t auth_rand[16]; + + + if (signal != S_SMS_DELIVERED) + return 0; + + + /* these are not the droids we've been looking for */ + if (!sms->receiver || + !(sms->receiver->flags & GSM_SUBSCRIBER_FIRST_CONTACT)) + return 0; + + + if (sms->receiver->net->auth_policy != GSM_AUTH_POLICY_TOKEN) + return 0; + + + conn = connection_for_subscr(sms->receiver); + if (conn) { + /* kick the subscriber off the network */ + gsm48_tx_mm_auth_req(conn, auth_rand, 0); + gsm48_tx_mm_auth_rej(conn); + /* FIXME: close the channel early ?*/ + //gsm48_send_rr_Release(lchan); + } + + return 0; +} + +//static __attribute__((constructor)) void on_dso_load_token(void) +void on_dso_load_token(void) +{ + register_signal_handler(SS_SUBSCR, token_subscr_cb, NULL); + register_signal_handler(SS_SMS, token_sms_cb, NULL); +} diff --git a/src/libmsc/ussd.c b/src/libmsc/ussd.c new file mode 100644 index 000000000..72f26bd36 --- /dev/null +++ b/src/libmsc/ussd.c @@ -0,0 +1,79 @@ +/* Network-specific handling of mobile-originated USSDs. */ + +/* (C) 2008-2009 by Harald Welte + * (C) 2008, 2009 by Holger Hans Peter Freyther + * (C) 2009 by Mike Haben + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +/* This module defines the network-specific handling of mobile-originated + USSD messages. */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Declarations of USSD strings to be recognised */ +const char USSD_TEXT_OWN_NUMBER[] = "*#100#"; + +/* Forward declarations of network-specific handler functions */ +static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ussd_request *req); + + +/* Entrypoint - handler function common to all mobile-originated USSDs */ +int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + int rc; + struct ussd_request req; + struct gsm48_hdr *gh; + + memset(&req, 0, sizeof(req)); + gh = msgb_l3(msg); + rc = gsm0480_decode_ussd_request(gh, msgb_l3len(msg), &req); + if (req.text[0] == 0xFF) /* Release-Complete */ + return 0; + + if (strstr(USSD_TEXT_OWN_NUMBER, req.text) != NULL) { + DEBUGP(DMM, "USSD: Own number requested\n"); + rc = send_own_number(conn, msg, &req); + } else { + DEBUGP(DMM, "Unhandled USSD %s\n", req.text); + rc = gsm0480_send_ussd_reject(conn, msg, &req); + } + + /* check if we can release it */ + msc_release_connection(conn); + return rc; +} + +/* A network-specific handler function */ +static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ussd_request *req) +{ + char *own_number = conn->subscr->extension; + char response_string[GSM_EXTENSION_LENGTH + 20]; + + /* Need trailing CR as EOT character */ + snprintf(response_string, sizeof(response_string), "Your extension is %s\r", own_number); + return gsm0480_send_ussd_response(conn, msg, response_string, req); +} diff --git a/src/libmsc/vty_interface_layer3.c b/src/libmsc/vty_interface_layer3.c new file mode 100644 index 000000000..a38d15bbb --- /dev/null +++ b/src/libmsc/vty_interface_layer3.c @@ -0,0 +1,790 @@ +/* OpenBSC interface to quagga VTY */ +/* (C) 2009 by Harald Welte + * (C) 2009 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern struct gsm_network *gsmnet_from_vty(struct vty *v); + +static void subscr_dump_full_vty(struct vty *vty, struct gsm_subscriber *subscr, int pending) +{ + int rc; + struct gsm_auth_info ainfo; + struct gsm_auth_tuple atuple; + + vty_out(vty, " ID: %llu, Authorized: %d%s", subscr->id, + subscr->authorized, VTY_NEWLINE); + if (subscr->name) + vty_out(vty, " Name: '%s'%s", subscr->name, VTY_NEWLINE); + if (subscr->extension) + vty_out(vty, " Extension: %s%s", subscr->extension, + VTY_NEWLINE); + vty_out(vty, " LAC: %d/0x%x%s", + subscr->lac, subscr->lac, VTY_NEWLINE); + if (subscr->imsi) + vty_out(vty, " IMSI: %s%s", subscr->imsi, VTY_NEWLINE); + if (subscr->tmsi != GSM_RESERVED_TMSI) + vty_out(vty, " TMSI: %08X%s", subscr->tmsi, + VTY_NEWLINE); + + rc = db_get_authinfo_for_subscr(&ainfo, subscr); + if (!rc) { + vty_out(vty, " A3A8 algorithm id: %d%s", + ainfo.auth_algo, VTY_NEWLINE); + vty_out(vty, " A3A8 Ki: %s%s", + hexdump(ainfo.a3a8_ki, ainfo.a3a8_ki_len), + VTY_NEWLINE); + } + + rc = db_get_lastauthtuple_for_subscr(&atuple, subscr); + if (!rc) { + vty_out(vty, " A3A8 last tuple (used %d times):%s", + atuple.use_count, VTY_NEWLINE); + vty_out(vty, " seq # : %d%s", + atuple.key_seq, VTY_NEWLINE); + vty_out(vty, " RAND : %s%s", + hexdump(atuple.rand, sizeof(atuple.rand)), + VTY_NEWLINE); + vty_out(vty, " SRES : %s%s", + hexdump(atuple.sres, sizeof(atuple.sres)), + VTY_NEWLINE); + vty_out(vty, " Kc : %s%s", + hexdump(atuple.kc, sizeof(atuple.kc)), + VTY_NEWLINE); + } + if (pending) + vty_out(vty, " Pending: %d%s", + subscr_pending_requests(subscr), VTY_NEWLINE); + + vty_out(vty, " Use count: %u%s", subscr->use_count, VTY_NEWLINE); +} + + +/* Subscriber */ +DEFUN(show_subscr_cache, + show_subscr_cache_cmd, + "show subscriber cache", + SHOW_STR "Display contents of subscriber cache\n") +{ + struct gsm_subscriber *subscr; + + llist_for_each_entry(subscr, &active_subscribers, entry) { + vty_out(vty, " Subscriber:%s", VTY_NEWLINE); + subscr_dump_full_vty(vty, subscr, 0); + } + + return CMD_SUCCESS; +} + +DEFUN(sms_send_pend, + sms_send_pend_cmd, + "sms send pending", + "Send all pending SMS") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_sms *sms; + int id = 0; + + while (1) { + sms = db_sms_get_unsent_by_subscr(gsmnet, id, UINT_MAX); + if (!sms) + break; + + gsm411_send_sms_subscr(sms->receiver, sms); + + id = sms->receiver->id + 1; + } + + return CMD_SUCCESS; +} + +static int _send_sms_str(struct gsm_subscriber *receiver, char *str, + u_int8_t tp_pid) +{ + struct gsm_sms *sms; + + sms = sms_from_text(receiver, 0, str); + sms->protocol_id = tp_pid; + + /* store in database for the queue */ + if (db_sms_store(sms) != 0) { + LOGP(DSMS, LOGL_ERROR, "Failed to store SMS in Database\n"); + sms_free(sms); + return CMD_WARNING; + } + + sms_free(sms); + sms_queue_trigger(receiver->net->sms_queue); + return CMD_SUCCESS; +} + +static struct gsm_subscriber *get_subscr_by_argv(struct gsm_network *gsmnet, + const char *type, + const char *id) +{ + if (!strcmp(type, "extension")) + return subscr_get_by_extension(gsmnet, id); + else if (!strcmp(type, "imsi")) + return subscr_get_by_imsi(gsmnet, id); + else if (!strcmp(type, "tmsi")) + return subscr_get_by_tmsi(gsmnet, atoi(id)); + else if (!strcmp(type, "id")) + return subscr_get_by_id(gsmnet, atoi(id)); + + return NULL; +} +#define SUBSCR_TYPES "(extension|imsi|tmsi|id)" +#define SUBSCR_HELP "Operations on a Subscriber\n" \ + "Identify subscriber by his extension (phone number)\n" \ + "Identify subscriber by his IMSI\n" \ + "Identify subscriber by his TMSI\n" \ + "Identify subscriber by his database ID\n" \ + "Identifier for the subscriber\n" + +DEFUN(show_subscr, + show_subscr_cmd, + "show subscriber " SUBSCR_TYPES " ID", + SHOW_STR SUBSCR_HELP) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + subscr_dump_full_vty(vty, subscr, 1); + + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(subscriber_send_pending_sms, + subscriber_send_pending_sms_cmd, + "subscriber " SUBSCR_TYPES " ID sms pending send", + SUBSCR_HELP "SMS Operations\n" "Send pending SMS\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]); + struct gsm_sms *sms; + + sms = db_sms_get_unsent_by_subscr(gsmnet, subscr->id, UINT_MAX); + if (sms) + gsm411_send_sms_subscr(sms->receiver, sms); + + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(subscriber_send_sms, + subscriber_send_sms_cmd, + "subscriber " SUBSCR_TYPES " ID sms send .LINE", + SUBSCR_HELP "SMS Operations\n" "Send SMS\n" "Actual SMS Text") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]); + char *str; + int rc; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + str = argv_concat(argv, argc, 2); + rc = _send_sms_str(subscr, str, 0); + talloc_free(str); + + subscr_put(subscr); + + return rc; +} + +DEFUN(subscriber_silent_sms, + subscriber_silent_sms_cmd, + "subscriber " SUBSCR_TYPES " ID silent-sms send .LINE", + SUBSCR_HELP + "Silent SMS Operation\n" "Send Silent SMS\n" "Actual SMS text\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]); + char *str; + int rc; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + str = argv_concat(argv, argc, 2); + rc = _send_sms_str(subscr, str, 64); + talloc_free(str); + + subscr_put(subscr); + + return rc; +} + +#define CHAN_TYPES "(any|tch/f|tch/any|sdcch)" +#define CHAN_TYPE_HELP \ + "Any channel\n" \ + "TCH/F channel\n" \ + "Any TCH channel\n" \ + "SDCCH channel\n" + +DEFUN(subscriber_silent_call_start, + subscriber_silent_call_start_cmd, + "subscriber " SUBSCR_TYPES " ID silent-call start (any|tch/f|tch/any|sdcch)", + SUBSCR_HELP "Silent call operation\n" "Start silent call\n" + CHAN_TYPE_HELP) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]); + int rc, type; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[2], "tch/f")) + type = RSL_CHANNEED_TCH_F; + else if (!strcmp(argv[2], "tch/any")) + type = RSL_CHANNEED_TCH_ForH; + else if (!strcmp(argv[2], "sdcch")) + type = RSL_CHANNEED_SDCCH; + else + type = RSL_CHANNEED_ANY; /* Defaults to ANY */ + + rc = gsm_silent_call_start(subscr, vty, type); + if (rc <= 0) { + vty_out(vty, "%% Subscriber not attached%s", + VTY_NEWLINE); + subscr_put(subscr); + return CMD_WARNING; + } + + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(subscriber_silent_call_stop, + subscriber_silent_call_stop_cmd, + "subscriber " SUBSCR_TYPES " ID silent-call stop", + SUBSCR_HELP "Silent call operation\n" "Stop silent call\n" + CHAN_TYPE_HELP) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]); + int rc; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + rc = gsm_silent_call_stop(subscr); + if (rc < 0) { + subscr_put(subscr); + return CMD_WARNING; + } + + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(subscriber_ussd_notify, + subscriber_ussd_notify_cmd, + "subscriber " SUBSCR_TYPES " ID ussd-notify (0|1|2) .TEXT", + SUBSCR_HELP "USSD Notify\n" + "Subscriber ID\n" + "Alerting Level\n" + "Text Message to send\n") +{ + char *text; + struct gsm_subscriber_connection *conn; + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]); + int level; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + level = atoi(argv[2]); + text = argv_concat(argv, argc, 3); + if (!text) { + subscr_put(subscr); + return CMD_WARNING; + } + + conn = connection_for_subscr(subscr); + if (!conn) { + vty_out(vty, "%% An active connection is required for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + subscr_put(subscr); + talloc_free(text); + return CMD_WARNING; + } + + gsm0480_send_ussdNotify(conn, level, text); + gsm0480_send_releaseComplete(conn); + + subscr_put(subscr); + talloc_free(text); + return CMD_SUCCESS; +} + +DEFUN(ena_subscr_authorizde, + ena_subscr_authorized_cmd, + "subscriber " SUBSCR_TYPES " ID authorized (0|1)", + SUBSCR_HELP "(De-)Authorize subscriber in HLR\n" + "Subscriber should NOT be authorized\n" + "Subscriber should be authorized\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + subscr->authorized = atoi(argv[2]); + db_sync_subscriber(subscr); + + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(ena_subscr_name, + ena_subscr_name_cmd, + "subscriber " SUBSCR_TYPES " ID name .NAME", + SUBSCR_HELP "Set the name of the subscriber\n" + "Name of the Subscriber\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + char *name; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + name = argv_concat(argv, argc, 2); + if (!name) { + subscr_put(subscr); + return CMD_WARNING; + } + + strncpy(subscr->name, name, sizeof(subscr->name)); + talloc_free(name); + db_sync_subscriber(subscr); + + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(ena_subscr_extension, + ena_subscr_extension_cmd, + "subscriber " SUBSCR_TYPES " ID extension EXTENSION", + SUBSCR_HELP "Set the extension (phone number) of the subscriber\n" + "Extension (phone number)\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + const char *name = argv[2]; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + strncpy(subscr->extension, name, sizeof(subscr->name)); + db_sync_subscriber(subscr); + + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(ena_subscr_clear, + ena_subscr_clear_cmd, + "subscriber " SUBSCR_TYPES " ID clear-requests", + SUBSCR_HELP "Clear the paging requests for this subscriber\n") +{ + int del; + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + del = subscr_pending_clear(subscr); + vty_out(vty, "Cleared %d pending requests.%s", del, VTY_NEWLINE); + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(ena_subscr_pend, + ena_subscr_pend_cmd, + "subscriber " SUBSCR_TYPES " ID show-pending", + SUBSCR_HELP "Clear the paging requests for this subscriber\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + subscr_pending_dump(subscr, vty); + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(ena_subscr_kick, + ena_subscr_kick_cmd, + "subscriber " SUBSCR_TYPES " ID kick-pending", + SUBSCR_HELP "Clear the paging requests for this subscriber\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + subscr_pending_kick(subscr); + subscr_put(subscr); + + return CMD_SUCCESS; +} + +#define A3A8_ALG_TYPES "(none|xor|comp128v1)" +#define A3A8_ALG_HELP \ + "Use No A3A8 algorithm\n" \ + "Use XOR algorithm\n" \ + "Use COMP128v1 algorithm\n" + +DEFUN(ena_subscr_a3a8, + ena_subscr_a3a8_cmd, + "subscriber " SUBSCR_TYPES " ID a3a8 " A3A8_ALG_TYPES " [KI]", + SUBSCR_HELP "Set a3a8 parameters for the subscriber\n" + A3A8_ALG_HELP "Encryption Key Ki\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + const char *alg_str = argv[2]; + const char *ki_str = argc == 4 ? argv[3] : NULL; + struct gsm_auth_info ainfo; + int rc, minlen, maxlen; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcasecmp(alg_str, "none")) { + ainfo.auth_algo = AUTH_ALGO_NONE; + minlen = maxlen = 0; + } else if (!strcasecmp(alg_str, "xor")) { + ainfo.auth_algo = AUTH_ALGO_XOR; + minlen = A38_XOR_MIN_KEY_LEN; + maxlen = A38_XOR_MAX_KEY_LEN; + } else if (!strcasecmp(alg_str, "comp128v1")) { + ainfo.auth_algo = AUTH_ALGO_COMP128v1; + minlen = maxlen = A38_COMP128_KEY_LEN; + } else { + /* Unknown method */ + subscr_put(subscr); + return CMD_WARNING; + } + + if (ki_str) { + rc = hexparse(ki_str, ainfo.a3a8_ki, sizeof(ainfo.a3a8_ki)); + if ((rc > maxlen) || (rc < minlen)) { + subscr_put(subscr); + return CMD_WARNING; + } + ainfo.a3a8_ki_len = rc; + } else { + ainfo.a3a8_ki_len = 0; + if (minlen) { + subscr_put(subscr); + return CMD_WARNING; + } + } + + rc = db_sync_authinfo_for_subscr( + ainfo.auth_algo == AUTH_ALGO_NONE ? NULL : &ainfo, + subscr); + + /* the last tuple probably invalid with the new auth settings */ + db_sync_lastauthtuple_for_subscr(NULL, subscr); + subscr_put(subscr); + + return rc ? CMD_WARNING : CMD_SUCCESS; +} + +DEFUN(subscriber_purge, + subscriber_purge_cmd, + "subscriber purge-inactive", + "Operations on a Subscriber\n" "Purge subscribers with a zero use count.\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + int purged; + + purged = subscr_purge_inactive(net); + vty_out(vty, "%d subscriber(s) were purged.%s", purged, VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(subscriber_update, + subscriber_update_cmd, + "subscriber " SUBSCR_TYPES " ID update", + SUBSCR_HELP "Update the subscriber data from the dabase.\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]); + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + subscr_update_from_db(subscr); + subscr_put(subscr); + return CMD_SUCCESS; +} + +static int scall_cbfn(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct scall_signal_data *sigdata = signal_data; + struct vty *vty = sigdata->data; + + switch (signal) { + case S_SCALL_SUCCESS: + vty_out(vty, "%% silent call on ARFCN %u timeslot %u%s", + sigdata->conn->lchan->ts->trx->arfcn, sigdata->conn->lchan->ts->nr, + VTY_NEWLINE); + break; + case S_SCALL_EXPIRED: + vty_out(vty, "%% silent call expired paging%s", VTY_NEWLINE); + break; + } + return 0; +} + +DEFUN(show_stats, + show_stats_cmd, + "show statistics", + SHOW_STR "Display network statistics\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + + openbsc_vty_print_statistics(vty, net); + vty_out(vty, "Channel Requests : %lu total, %lu no channel%s", + counter_get(net->stats.chreq.total), + counter_get(net->stats.chreq.no_channel), VTY_NEWLINE); + vty_out(vty, "Location Update : %lu attach, %lu normal, %lu periodic%s", + counter_get(net->stats.loc_upd_type.attach), + counter_get(net->stats.loc_upd_type.normal), + counter_get(net->stats.loc_upd_type.periodic), VTY_NEWLINE); + vty_out(vty, "IMSI Detach Indications : %lu%s", + counter_get(net->stats.loc_upd_type.detach), VTY_NEWLINE); + vty_out(vty, "Location Update Response: %lu accept, %lu reject%s", + counter_get(net->stats.loc_upd_resp.accept), + counter_get(net->stats.loc_upd_resp.reject), VTY_NEWLINE); + vty_out(vty, "Handover : %lu attempted, %lu no_channel, %lu timeout, " + "%lu completed, %lu failed%s", + counter_get(net->stats.handover.attempted), + counter_get(net->stats.handover.no_channel), + counter_get(net->stats.handover.timeout), + counter_get(net->stats.handover.completed), + counter_get(net->stats.handover.failed), VTY_NEWLINE); + vty_out(vty, "SMS MO : %lu submitted, %lu no receiver%s", + counter_get(net->stats.sms.submitted), + counter_get(net->stats.sms.no_receiver), VTY_NEWLINE); + vty_out(vty, "SMS MT : %lu delivered, %lu no memory, %lu other error%s", + counter_get(net->stats.sms.delivered), + counter_get(net->stats.sms.rp_err_mem), + counter_get(net->stats.sms.rp_err_other), VTY_NEWLINE); + vty_out(vty, "MO Calls : %lu setup, %lu connect ack%s", + counter_get(net->stats.call.mo_setup), + counter_get(net->stats.call.mo_connect_ack), VTY_NEWLINE); + vty_out(vty, "MT Calls : %lu setup, %lu connect%s", + counter_get(net->stats.call.mt_setup), + counter_get(net->stats.call.mt_connect), VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(show_smsqueue, + show_smsqueue_cmd, + "show sms-queue", + SHOW_STR "Display SMSqueue statistics\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + + sms_queue_stats(net->sms_queue, vty); + return CMD_SUCCESS; +} + +DEFUN(smsqueue_trigger, + smsqueue_trigger_cmd, + "sms-queue trigger", + "SMS Queue\n" "Trigger sending messages\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + + sms_queue_trigger(net->sms_queue); + return CMD_SUCCESS; +} + +DEFUN(smsqueue_max, + smsqueue_max_cmd, + "sms-queue max-pending <1-500>", + "SMS Queue\n" "SMS to attempt to deliver at the same time\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + + sms_queue_set_max_pending(net->sms_queue, atoi(argv[0])); + return CMD_SUCCESS; +} + +DEFUN(smsqueue_clear, + smsqueue_clear_cmd, + "sms-queue clear", + "SMS Queue\n" "Clear the queue of pending SMS\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + + sms_queue_clear(net->sms_queue); + return CMD_SUCCESS; +} + +DEFUN(smsqueue_fail, + smsqueue_fail_cmd, + "sms-queue max-failure <1-500>", + "SMS Queue\n" "Set maximum amount of failures\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + + sms_queue_set_max_failure(net->sms_queue, atoi(argv[0])); + return CMD_SUCCESS; +} + +int bsc_vty_init_extra(void) +{ + register_signal_handler(SS_SCALL, scall_cbfn, NULL); + + install_element_ve(&show_subscr_cmd); + install_element_ve(&show_subscr_cache_cmd); + + install_element_ve(&sms_send_pend_cmd); + + install_element_ve(&subscriber_send_sms_cmd); + install_element_ve(&subscriber_silent_sms_cmd); + install_element_ve(&subscriber_silent_call_start_cmd); + install_element_ve(&subscriber_silent_call_stop_cmd); + install_element_ve(&subscriber_ussd_notify_cmd); + install_element_ve(&subscriber_update_cmd); + install_element_ve(&show_stats_cmd); + install_element_ve(&show_smsqueue_cmd); + + install_element(ENABLE_NODE, &ena_subscr_name_cmd); + install_element(ENABLE_NODE, &ena_subscr_extension_cmd); + install_element(ENABLE_NODE, &ena_subscr_authorized_cmd); + install_element(ENABLE_NODE, &ena_subscr_a3a8_cmd); + install_element(ENABLE_NODE, &ena_subscr_clear_cmd); + install_element(ENABLE_NODE, &ena_subscr_pend_cmd); + install_element(ENABLE_NODE, &ena_subscr_kick_cmd); + install_element(ENABLE_NODE, &subscriber_purge_cmd); + install_element(ENABLE_NODE, &smsqueue_trigger_cmd); + install_element(ENABLE_NODE, &smsqueue_max_cmd); + install_element(ENABLE_NODE, &smsqueue_clear_cmd); + install_element(ENABLE_NODE, &smsqueue_fail_cmd); + install_element(ENABLE_NODE, &subscriber_send_pending_sms_cmd); + + return 0; +} diff --git a/src/libtrau/Makefile.am b/src/libtrau/Makefile.am new file mode 100644 index 000000000..01ed251d8 --- /dev/null +++ b/src/libtrau/Makefile.am @@ -0,0 +1,7 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) + +noinst_LIBRARIES = libtrau.a + +libtrau_a_SOURCES = rtp_proxy.c subchan_demux.c trau_frame.c trau_mux.c trau_upqueue.c diff --git a/src/libtrau/Makefile.in b/src/libtrau/Makefile.in new file mode 100644 index 000000000..9da0496be --- /dev/null +++ b/src/libtrau/Makefile.in @@ -0,0 +1,455 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = src/libtrau +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LIBRARIES = $(noinst_LIBRARIES) +AR = ar +ARFLAGS = cru +AM_V_AR = $(am__v_AR_$(V)) +am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY)) +am__v_AR_0 = @echo " AR " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +libtrau_a_AR = $(AR) $(ARFLAGS) +libtrau_a_LIBADD = +am_libtrau_a_OBJECTS = rtp_proxy.$(OBJEXT) subchan_demux.$(OBJEXT) \ + trau_frame.$(OBJEXT) trau_mux.$(OBJEXT) trau_upqueue.$(OBJEXT) +libtrau_a_OBJECTS = $(am_libtrau_a_OBJECTS) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(libtrau_a_SOURCES) +DIST_SOURCES = $(libtrau_a_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) +noinst_LIBRARIES = libtrau.a +libtrau_a_SOURCES = rtp_proxy.c subchan_demux.c trau_frame.c trau_mux.c trau_upqueue.c +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libtrau/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/libtrau/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) +libtrau.a: $(libtrau_a_OBJECTS) $(libtrau_a_DEPENDENCIES) + $(AM_V_at)-rm -f libtrau.a + $(AM_V_AR)$(libtrau_a_AR) libtrau.a $(libtrau_a_OBJECTS) $(libtrau_a_LIBADD) + $(AM_V_at)$(RANLIB) libtrau.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rtp_proxy.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/subchan_demux.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trau_frame.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trau_mux.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trau_upqueue.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LIBRARIES) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-noinstLIBRARIES ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/libtrau/rtp_proxy.c b/src/libtrau/rtp_proxy.c new file mode 100644 index 000000000..eefc0e1d6 --- /dev/null +++ b/src/libtrau/rtp_proxy.c @@ -0,0 +1,728 @@ +/* RTP proxy handling for ip.access nanoBTS */ + +/* (C) 2009 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include /* gettimeofday() */ +#include /* get..() */ +#include /* clock() */ +#include /* uname() */ + +#include +#include +#include +#include +#include +#include + +/* attempt to determine byte order */ +#include +#include +#include + +#ifndef __BYTE_ORDER +#error "__BYTE_ORDER should be defined by someone" +#endif + +static LLIST_HEAD(rtp_sockets); + +/* should we mangle the CNAME inside SDES of RTCP packets? We disable + * this by default, as it seems to be not needed */ +static int mangle_rtcp_cname = 0; + +enum rtp_bfd_priv { + RTP_PRIV_NONE, + RTP_PRIV_RTP, + RTP_PRIV_RTCP +}; + +#define RTP_ALLOC_SIZE 1500 + +/* according to RFC 1889 */ +struct rtcp_hdr { + u_int8_t byte0; + u_int8_t type; + u_int16_t length; +} __attribute__((packed)); + +#define RTCP_TYPE_SDES 202 + +#define RTCP_IE_CNAME 1 + +/* according to RFC 3550 */ +struct rtp_hdr { +#if __BYTE_ORDER == __LITTLE_ENDIAN + u_int8_t csrc_count:4, + extension:1, + padding:1, + version:2; + u_int8_t payload_type:7, + marker:1; +#elif __BYTE_ORDER == __BIG_ENDIAN + u_int8_t version:2, + padding:1, + extension:1, + csrc_count:4; + u_int8_t marker:1, + payload_type:7; +#endif + u_int16_t sequence; + u_int32_t timestamp; + u_int32_t ssrc; +} __attribute__((packed)); + +struct rtp_x_hdr { + u_int16_t by_profile; + u_int16_t length; +} __attribute__((packed)); + +#define RTP_VERSION 2 + +/* decode an rtp frame and create a new buffer with payload */ +static int rtp_decode(struct msgb *msg, u_int32_t callref, struct msgb **data) +{ + struct msgb *new_msg; + struct gsm_data_frame *frame; + struct rtp_hdr *rtph = (struct rtp_hdr *)msg->data; + struct rtp_x_hdr *rtpxh; + u_int8_t *payload; + int payload_len; + int msg_type; + int x_len; + + if (msg->len < 12) { + DEBUGPC(DMUX, "received RTP frame too short (len = %d)\n", + msg->len); + return -EINVAL; + } + if (rtph->version != RTP_VERSION) { + DEBUGPC(DMUX, "received RTP version %d not supported.\n", + rtph->version); + return -EINVAL; + } + payload = msg->data + sizeof(struct rtp_hdr) + (rtph->csrc_count << 2); + payload_len = msg->len - sizeof(struct rtp_hdr) - (rtph->csrc_count << 2); + if (payload_len < 0) { + DEBUGPC(DMUX, "received RTP frame too short (len = %d, " + "csrc count = %d)\n", msg->len, rtph->csrc_count); + return -EINVAL; + } + if (rtph->extension) { + if (payload_len < sizeof(struct rtp_x_hdr)) { + DEBUGPC(DMUX, "received RTP frame too short for " + "extension header\n"); + return -EINVAL; + } + rtpxh = (struct rtp_x_hdr *)payload; + x_len = ntohs(rtpxh->length) * 4 + sizeof(struct rtp_x_hdr); + payload += x_len; + payload_len -= x_len; + if (payload_len < 0) { + DEBUGPC(DMUX, "received RTP frame too short, " + "extension header exceeds frame length\n"); + return -EINVAL; + } + } + if (rtph->padding) { + if (payload_len < 0) { + DEBUGPC(DMUX, "received RTP frame too short for " + "padding length\n"); + return -EINVAL; + } + payload_len -= payload[payload_len - 1]; + if (payload_len < 0) { + DEBUGPC(DMUX, "received RTP frame with padding " + "greater than payload\n"); + return -EINVAL; + } + } + + switch (rtph->payload_type) { + case RTP_PT_GSM_FULL: + msg_type = GSM_TCHF_FRAME; + if (payload_len != 33) { + DEBUGPC(DMUX, "received RTP full rate frame with " + "payload length != 32 (len = %d)\n", + payload_len); + return -EINVAL; + } + break; + case RTP_PT_GSM_EFR: + msg_type = GSM_TCHF_FRAME_EFR; + break; + default: + DEBUGPC(DMUX, "received RTP frame with unknown payload " + "type %d\n", rtph->payload_type); + return -EINVAL; + } + + new_msg = msgb_alloc(sizeof(struct gsm_data_frame) + payload_len, + "GSM-DATA"); + if (!new_msg) + return -ENOMEM; + frame = (struct gsm_data_frame *)(new_msg->data); + frame->msg_type = msg_type; + frame->callref = callref; + memcpy(frame->data, payload, payload_len); + msgb_put(new_msg, sizeof(struct gsm_data_frame) + payload_len); + + *data = new_msg; + return 0; +} + +/* "to - from" */ +static void tv_difference(struct timeval *diff, const struct timeval *from, + const struct timeval *__to) +{ + struct timeval _to = *__to, *to = &_to; + + if (to->tv_usec < from->tv_usec) { + to->tv_sec -= 1; + to->tv_usec += 1000000; + } + + diff->tv_usec = to->tv_usec - from->tv_usec; + diff->tv_sec = to->tv_sec - from->tv_sec; +} + +/* encode and send a rtp frame */ +int rtp_send_frame(struct rtp_socket *rs, struct gsm_data_frame *frame) +{ + struct rtp_sub_socket *rss = &rs->rtp; + struct msgb *msg; + struct rtp_hdr *rtph; + int payload_type; + int payload_len; + int duration; /* in samples */ + + if (rs->tx_action != RTP_SEND_DOWNSTREAM) { + /* initialize sequences */ + rs->tx_action = RTP_SEND_DOWNSTREAM; + rs->transmit.ssrc = rand(); + rs->transmit.sequence = random(); + rs->transmit.timestamp = random(); + } + + switch (frame->msg_type) { + case GSM_TCHF_FRAME: + payload_type = RTP_PT_GSM_FULL; + payload_len = 33; + duration = 160; + break; + case GSM_TCHF_FRAME_EFR: + payload_type = RTP_PT_GSM_EFR; + payload_len = 31; + duration = 160; + break; + default: + DEBUGPC(DMUX, "unsupported message type %d\n", + frame->msg_type); + return -EINVAL; + } + + { + struct timeval tv, tv_diff; + long int usec_diff, frame_diff; + + gettimeofday(&tv, NULL); + tv_difference(&tv_diff, &rs->transmit.last_tv, &tv); + rs->transmit.last_tv = tv; + + usec_diff = tv_diff.tv_sec * 1000000 + tv_diff.tv_usec; + frame_diff = (usec_diff / 20000); + + if (abs(frame_diff) > 1) { + long int frame_diff_excess = frame_diff - 1; + + LOGP(DMUX, LOGL_NOTICE, + "Correcting frame difference of %ld frames\n", frame_diff_excess); + rs->transmit.sequence += frame_diff_excess; + rs->transmit.timestamp += frame_diff_excess * duration; + } + } + + msg = msgb_alloc(sizeof(struct rtp_hdr) + payload_len, "RTP-GSM-FULL"); + if (!msg) + return -ENOMEM; + rtph = (struct rtp_hdr *)msg->data; + rtph->version = RTP_VERSION; + rtph->padding = 0; + rtph->extension = 0; + rtph->csrc_count = 0; + rtph->marker = 0; + rtph->payload_type = payload_type; + rtph->sequence = htons(rs->transmit.sequence++); + rtph->timestamp = htonl(rs->transmit.timestamp); + rs->transmit.timestamp += duration; + rtph->ssrc = htonl(rs->transmit.ssrc); + memcpy(msg->data + sizeof(struct rtp_hdr), frame->data, payload_len); + msgb_put(msg, sizeof(struct rtp_hdr) + payload_len); + msgb_enqueue(&rss->tx_queue, msg); + rss->bfd.when |= BSC_FD_WRITE; + + return 0; +} + +/* iterate over all chunks in one RTCP message, look for CNAME IEs and + * replace all of those with 'new_cname' */ +static int rtcp_sdes_cname_mangle(struct msgb *msg, struct rtcp_hdr *rh, + u_int16_t *rtcp_len, const char *new_cname) +{ + u_int8_t *rtcp_end; + u_int8_t *cur = (u_int8_t *) rh; + u_int8_t tag, len = 0; + + rtcp_end = cur + *rtcp_len; + /* move cur to end of RTP header */ + cur += sizeof(*rh); + + /* iterate over Chunks */ + while (cur+4 < rtcp_end) { + /* skip four bytes SSRC/CSRC */ + cur += 4; + + /* iterate over IE's inside the chunk */ + while (cur+1 < rtcp_end) { + tag = *cur++; + if (tag == 0) { + /* end of chunk, skip additional zero */ + while (*cur++ == 0) { } + break; + } + len = *cur++; + + if (tag == RTCP_IE_CNAME) { + /* we've found the CNAME, lets mangle it */ + if (len < strlen(new_cname)) { + /* we need to make more space */ + int increase = strlen(new_cname) - len; + + msgb_push(msg, increase); + memmove(cur+len+increase, cur+len, + rtcp_end - (cur+len)); + /* FIXME: we have to respect RTCP + * padding/alignment rules! */ + len += increase; + *(cur-1) += increase; + rtcp_end += increase; + *rtcp_len += increase; + } + /* copy new CNAME into message */ + memcpy(cur, new_cname, strlen(new_cname)); + /* FIXME: zero the padding in case new CNAME + * is smaller than old one !!! */ + } + cur += len; + } + } + + return 0; +} + +static int rtcp_mangle(struct msgb *msg, struct rtp_socket *rs) +{ + struct rtp_sub_socket *rss = &rs->rtcp; + struct rtcp_hdr *rtph; + u_int16_t old_len; + int rc; + + if (!mangle_rtcp_cname) + return 0; + + printf("RTCP\n"); + /* iterate over list of RTCP messages */ + rtph = (struct rtcp_hdr *)msg->data; + while ((void *)rtph + sizeof(*rtph) <= (void *)msg->data + msg->len) { + old_len = (ntohs(rtph->length) + 1) * 4; + if ((void *)rtph + old_len > (void *)msg->data + msg->len) { + DEBUGPC(DMUX, "received RTCP packet too short for " + "length element\n"); + return -EINVAL; + } + if (rtph->type == RTCP_TYPE_SDES) { + char new_cname[255]; + strncpy(new_cname, inet_ntoa(rss->sin_local.sin_addr), + sizeof(new_cname)); + new_cname[sizeof(new_cname)-1] = '\0'; + rc = rtcp_sdes_cname_mangle(msg, rtph, &old_len, + new_cname); + if (rc < 0) + return rc; + } + rtph = (void *)rtph + old_len; + } + + return 0; +} + +/* read from incoming RTP/RTCP socket */ +static int rtp_socket_read(struct rtp_socket *rs, struct rtp_sub_socket *rss) +{ + int rc; + struct msgb *msg = msgb_alloc(RTP_ALLOC_SIZE, "RTP/RTCP"); + struct msgb *new_msg; + struct rtp_sub_socket *other_rss; + + if (!msg) + return -ENOMEM; + + rc = read(rss->bfd.fd, msg->data, RTP_ALLOC_SIZE); + if (rc <= 0) { + rss->bfd.when &= ~BSC_FD_READ; + return rc; + } + + msgb_put(msg, rc); + + switch (rs->rx_action) { + case RTP_PROXY: + if (!rs->proxy.other_sock) { + rc = -EIO; + goto out_free; + } + if (rss->bfd.priv_nr == RTP_PRIV_RTP) + other_rss = &rs->proxy.other_sock->rtp; + else if (rss->bfd.priv_nr == RTP_PRIV_RTCP) { + other_rss = &rs->proxy.other_sock->rtcp; + /* modify RTCP SDES CNAME */ + rc = rtcp_mangle(msg, rs); + if (rc < 0) + goto out_free; + } else { + rc = -EINVAL; + goto out_free; + } + msgb_enqueue(&other_rss->tx_queue, msg); + other_rss->bfd.when |= BSC_FD_WRITE; + break; + + case RTP_RECV_UPSTREAM: + if (!rs->receive.callref || !rs->receive.net) { + rc = -EIO; + goto out_free; + } + if (rss->bfd.priv_nr == RTP_PRIV_RTCP) { + if (!mangle_rtcp_cname) { + msgb_free(msg); + break; + } + /* modify RTCP SDES CNAME */ + rc = rtcp_mangle(msg, rs); + if (rc < 0) + goto out_free; + msgb_enqueue(&rss->tx_queue, msg); + rss->bfd.when |= BSC_FD_WRITE; + break; + } + if (rss->bfd.priv_nr != RTP_PRIV_RTP) { + rc = -EINVAL; + goto out_free; + } + rc = rtp_decode(msg, rs->receive.callref, &new_msg); + if (rc < 0) + goto out_free; + msgb_free(msg); + trau_tx_to_mncc(rs->receive.net, new_msg); + break; + + case RTP_NONE: /* if socket exists, but disabled by app */ + msgb_free(msg); + break; + } + + return 0; + +out_free: + msgb_free(msg); + return rc; +} + +/* write from tx_queue to RTP/RTCP socket */ +static int rtp_socket_write(struct rtp_socket *rs, struct rtp_sub_socket *rss) +{ + struct msgb *msg; + int written; + + msg = msgb_dequeue(&rss->tx_queue); + if (!msg) { + rss->bfd.when &= ~BSC_FD_WRITE; + return 0; + } + + written = write(rss->bfd.fd, msg->data, msg->len); + if (written < msg->len) { + LOGP(DMIB, LOGL_ERROR, "short write"); + msgb_free(msg); + return -EIO; + } + + msgb_free(msg); + + return 0; +} + + +/* callback for the select.c:bfd_* layer */ +static int rtp_bfd_cb(struct bsc_fd *bfd, unsigned int flags) +{ + struct rtp_socket *rs = bfd->data; + struct rtp_sub_socket *rss; + + switch (bfd->priv_nr) { + case RTP_PRIV_RTP: + rss = &rs->rtp; + break; + case RTP_PRIV_RTCP: + rss = &rs->rtcp; + break; + default: + return -EINVAL; + } + + if (flags & BSC_FD_READ) + rtp_socket_read(rs, rss); + + if (flags & BSC_FD_WRITE) + rtp_socket_write(rs, rss); + + return 0; +} + +static void init_rss(struct rtp_sub_socket *rss, + struct rtp_socket *rs, int fd, int priv_nr) +{ + /* initialize bfd */ + rss->bfd.fd = fd; + rss->bfd.data = rs; + rss->bfd.priv_nr = priv_nr; + rss->bfd.cb = rtp_bfd_cb; +} + +struct rtp_socket *rtp_socket_create(void) +{ + int rc; + struct rtp_socket *rs; + + DEBUGP(DMUX, "rtp_socket_create(): "); + + rs = talloc_zero(tall_bsc_ctx, struct rtp_socket); + if (!rs) + return NULL; + + INIT_LLIST_HEAD(&rs->rtp.tx_queue); + INIT_LLIST_HEAD(&rs->rtcp.tx_queue); + + rc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (rc < 0) + goto out_free; + + init_rss(&rs->rtp, rs, rc, RTP_PRIV_RTP); + rc = bsc_register_fd(&rs->rtp.bfd); + if (rc < 0) + goto out_rtp_socket; + + rc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (rc < 0) + goto out_rtp_bfd; + + init_rss(&rs->rtcp, rs, rc, RTP_PRIV_RTCP); + rc = bsc_register_fd(&rs->rtcp.bfd); + if (rc < 0) + goto out_rtcp_socket; + + DEBUGPC(DMUX, "success\n"); + + rc = rtp_socket_bind(rs, INADDR_ANY); + if (rc < 0) + goto out_rtcp_bfd; + + return rs; + +out_rtcp_bfd: + bsc_unregister_fd(&rs->rtcp.bfd); +out_rtcp_socket: + close(rs->rtcp.bfd.fd); +out_rtp_bfd: + bsc_unregister_fd(&rs->rtp.bfd); +out_rtp_socket: + close(rs->rtp.bfd.fd); +out_free: + talloc_free(rs); + DEBUGPC(DMUX, "failed\n"); + return NULL; +} + +static int rtp_sub_socket_bind(struct rtp_sub_socket *rss, u_int32_t ip, + u_int16_t port) +{ + int rc; + socklen_t alen = sizeof(rss->sin_local); + + rss->sin_local.sin_family = AF_INET; + rss->sin_local.sin_addr.s_addr = htonl(ip); + rss->sin_local.sin_port = htons(port); + rss->bfd.when |= BSC_FD_READ; + + rc = bind(rss->bfd.fd, (struct sockaddr *)&rss->sin_local, + sizeof(rss->sin_local)); + if (rc < 0) + return rc; + + /* retrieve the address we actually bound to, in case we + * passed INADDR_ANY as IP address */ + return getsockname(rss->bfd.fd, (struct sockaddr *)&rss->sin_local, + &alen); +} + +#define RTP_PORT_BASE 30000 +static unsigned int next_udp_port = RTP_PORT_BASE; + +/* bind a RTP socket to a local address */ +int rtp_socket_bind(struct rtp_socket *rs, u_int32_t ip) +{ + int rc = -EIO; + struct in_addr ia; + + ia.s_addr = htonl(ip); + DEBUGP(DMUX, "rtp_socket_bind(rs=%p, IP=%s): ", rs, + inet_ntoa(ia)); + + /* try to bind to a consecutive pair of ports */ + for (next_udp_port = next_udp_port % 0xffff; + next_udp_port < 0xffff; next_udp_port += 2) { + rc = rtp_sub_socket_bind(&rs->rtp, ip, next_udp_port); + if (rc != 0) + continue; + + rc = rtp_sub_socket_bind(&rs->rtcp, ip, next_udp_port+1); + if (rc == 0) + break; + } + if (rc < 0) { + DEBUGPC(DMUX, "failed\n"); + return rc; + } + + ia.s_addr = rs->rtp.sin_local.sin_addr.s_addr; + DEBUGPC(DMUX, "BOUND_IP=%s, BOUND_PORT=%u\n", + inet_ntoa(ia), ntohs(rs->rtp.sin_local.sin_port)); + return ntohs(rs->rtp.sin_local.sin_port); +} + +static int rtp_sub_socket_connect(struct rtp_sub_socket *rss, + u_int32_t ip, u_int16_t port) +{ + int rc; + socklen_t alen = sizeof(rss->sin_local); + + rss->sin_remote.sin_family = AF_INET; + rss->sin_remote.sin_addr.s_addr = htonl(ip); + rss->sin_remote.sin_port = htons(port); + + rc = connect(rss->bfd.fd, (struct sockaddr *) &rss->sin_remote, + sizeof(rss->sin_remote)); + if (rc < 0) + return rc; + + return getsockname(rss->bfd.fd, (struct sockaddr *)&rss->sin_local, + &alen); +} + +/* 'connect' a RTP socket to a remote peer */ +int rtp_socket_connect(struct rtp_socket *rs, u_int32_t ip, u_int16_t port) +{ + int rc; + struct in_addr ia; + + ia.s_addr = htonl(ip); + DEBUGP(DMUX, "rtp_socket_connect(rs=%p, ip=%s, port=%u)\n", + rs, inet_ntoa(ia), port); + + rc = rtp_sub_socket_connect(&rs->rtp, ip, port); + if (rc < 0) + return rc; + + return rtp_sub_socket_connect(&rs->rtcp, ip, port+1); +} + +/* bind two RTP/RTCP sockets together */ +int rtp_socket_proxy(struct rtp_socket *this, struct rtp_socket *other) +{ + DEBUGP(DMUX, "rtp_socket_proxy(this=%p, other=%p)\n", + this, other); + + this->rx_action = RTP_PROXY; + this->proxy.other_sock = other; + + other->rx_action = RTP_PROXY; + other->proxy.other_sock = this; + + return 0; +} + +/* bind RTP/RTCP socket to application */ +int rtp_socket_upstream(struct rtp_socket *this, struct gsm_network *net, + u_int32_t callref) +{ + DEBUGP(DMUX, "rtp_socket_proxy(this=%p, callref=%u)\n", + this, callref); + + if (callref) { + this->rx_action = RTP_RECV_UPSTREAM; + this->receive.net = net; + this->receive.callref = callref; + } else + this->rx_action = RTP_NONE; + + return 0; +} + +static void free_tx_queue(struct rtp_sub_socket *rss) +{ + struct msgb *msg; + + while ((msg = msgb_dequeue(&rss->tx_queue))) + msgb_free(msg); +} + +int rtp_socket_free(struct rtp_socket *rs) +{ + DEBUGP(DMUX, "rtp_socket_free(rs=%p)\n", rs); + + /* make sure we don't leave references dangling to us */ + if (rs->rx_action == RTP_PROXY && + rs->proxy.other_sock) + rs->proxy.other_sock->proxy.other_sock = NULL; + + bsc_unregister_fd(&rs->rtp.bfd); + close(rs->rtp.bfd.fd); + free_tx_queue(&rs->rtp); + + bsc_unregister_fd(&rs->rtcp.bfd); + close(rs->rtcp.bfd.fd); + free_tx_queue(&rs->rtcp); + + talloc_free(rs); + + return 0; +} diff --git a/src/libtrau/subchan_demux.c b/src/libtrau/subchan_demux.c new file mode 100644 index 000000000..6bcf279fe --- /dev/null +++ b/src/libtrau/subchan_demux.c @@ -0,0 +1,321 @@ +/* A E1 sub-channel (de)multiplexer with TRAU frame sync */ + +/* (C) 2009 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +void *tall_tqe_ctx; + +static inline void append_bit(struct demux_subch *sch, u_int8_t bit) +{ + sch->out_bitbuf[sch->out_idx++] = bit; +} + +#define SYNC_HDR_BITS 16 +static const u_int8_t nullbytes[SYNC_HDR_BITS]; + +/* check if we have just completed the 16 bit zero sync header, + * in accordance with GSM TS 08.60 Chapter 4.8.1 */ +static int sync_hdr_complete(struct demux_subch *sch, u_int8_t bit) +{ + if (bit == 0) + sch->consecutive_zeros++; + else + sch->consecutive_zeros = 0; + + if (sch->consecutive_zeros >= SYNC_HDR_BITS) { + sch->consecutive_zeros = 0; + return 1; + } + + return 0; +} + +/* resynchronize to current location */ +static void resync_to_here(struct demux_subch *sch) +{ + memset(sch->out_bitbuf, 0, SYNC_HDR_BITS); + + /* set index in a way that we can continue receiving bits after + * the end of the SYNC header */ + sch->out_idx = SYNC_HDR_BITS; + sch->in_sync = 1; +} + +int subch_demux_init(struct subch_demux *dmx) +{ + int i; + + dmx->chan_activ = 0; + for (i = 0; i < NR_SUBCH; i++) { + struct demux_subch *sch = &dmx->subch[i]; + sch->out_idx = 0; + memset(sch->out_bitbuf, 0xff, sizeof(sch->out_bitbuf)); + } + return 0; +} + +/* input some arbitrary (modulo 4) number of bytes of a 64k E1 channel, + * split it into the 16k subchannels */ +int subch_demux_in(struct subch_demux *dmx, u_int8_t *data, int len) +{ + int i, c; + + /* we avoid partially filled bytes in outbuf */ + if (len % 4) + return -EINVAL; + + for (i = 0; i < len; i++) { + u_int8_t inbyte = data[i]; + + for (c = 0; c < NR_SUBCH; c++) { + struct demux_subch *sch = &dmx->subch[c]; + u_int8_t inbits; + u_int8_t bit; + + /* ignore inactive subchannels */ + if (!(dmx->chan_activ & (1 << c))) + continue; + + inbits = inbyte >> (c << 1); + + /* two bits for each subchannel */ + if (inbits & 0x01) + bit = 1; + else + bit = 0; + append_bit(sch, bit); + + if (sync_hdr_complete(sch, bit)) + resync_to_here(sch); + + if (inbits & 0x02) + bit = 1; + else + bit = 0; + append_bit(sch, bit); + + if (sync_hdr_complete(sch, bit)) + resync_to_here(sch); + + /* FIXME: verify the first bit in octet 2, 4, 6, ... + * according to TS 08.60 4.8.1 */ + + /* once we have reached TRAU_FRAME_BITS, call + * the TRAU frame handler callback function */ + if (sch->out_idx >= TRAU_FRAME_BITS) { + if (sch->in_sync) { + dmx->out_cb(dmx, c, sch->out_bitbuf, + sch->out_idx, dmx->data); + sch->in_sync = 0; + } + sch->out_idx = 0; + } + } + } + return i; +} + +int subch_demux_activate(struct subch_demux *dmx, int subch) +{ + if (subch >= NR_SUBCH) + return -EINVAL; + + dmx->chan_activ |= (1 << subch); + return 0; +} + +int subch_demux_deactivate(struct subch_demux *dmx, int subch) +{ + if (subch >= NR_SUBCH) + return -EINVAL; + + dmx->chan_activ &= ~(1 << subch); + return 0; +} + +/* MULTIPLEXER */ + +static int alloc_add_idle_frame(struct subch_mux *mx, int sch_nr) +{ + /* allocate and initialize with idle pattern */ + return subchan_mux_enqueue(mx, sch_nr, trau_idle_frame(), + TRAU_FRAME_BITS); +} + +/* return the requested number of bits from the specified subchannel */ +static int get_subch_bits(struct subch_mux *mx, int subch, + u_int8_t *bits, int num_requested) +{ + struct mux_subch *sch = &mx->subch[subch]; + int num_bits = 0; + + while (num_bits < num_requested) { + struct subch_txq_entry *txe; + int num_bits_left; + int num_bits_thistime; + + /* make sure we have a valid entry at top of tx queue. + * if not, add an idle frame */ + if (llist_empty(&sch->tx_queue)) + alloc_add_idle_frame(mx, subch); + + if (llist_empty(&sch->tx_queue)) + return -EIO; + + txe = llist_entry(sch->tx_queue.next, struct subch_txq_entry, list); + num_bits_left = txe->bit_len - txe->next_bit; + + if (num_bits_left < num_requested) + num_bits_thistime = num_bits_left; + else + num_bits_thistime = num_requested; + + /* pull the bits from the txe */ + memcpy(bits + num_bits, txe->bits + txe->next_bit, num_bits_thistime); + txe->next_bit += num_bits_thistime; + + /* free the tx_queue entry if it is fully consumed */ + if (txe->next_bit >= txe->bit_len) { + llist_del(&txe->list); + talloc_free(txe); + } + + /* increment global number of bits dequeued */ + num_bits += num_bits_thistime; + } + + return num_requested; +} + +/* compact an array of 8 single-bit bytes into one byte of 8 bits */ +static u_int8_t compact_bits(const u_int8_t *bits) +{ + u_int8_t ret = 0; + int i; + + for (i = 0; i < 8; i++) + ret |= (bits[i] ? 1 : 0) << i; + + return ret; +} + +/* obtain a single output byte from the subchannel muxer */ +static int mux_output_byte(struct subch_mux *mx, u_int8_t *byte) +{ + u_int8_t bits[8]; + int rc; + + /* combine two bits of every subchan */ + rc = get_subch_bits(mx, 0, &bits[0], 2); + rc = get_subch_bits(mx, 1, &bits[2], 2); + rc = get_subch_bits(mx, 2, &bits[4], 2); + rc = get_subch_bits(mx, 3, &bits[6], 2); + + *byte = compact_bits(bits); + + return rc; +} + +/* Request the output of some muxed bytes from the subchan muxer */ +int subchan_mux_out(struct subch_mux *mx, u_int8_t *data, int len) +{ + int i; + + for (i = 0; i < len; i++) { + int rc; + rc = mux_output_byte(mx, &data[i]); + if (rc < 0) + break; + } + return i; +} + +static int llist_len(struct llist_head *head) +{ + struct llist_head *entry; + int i = 0; + + llist_for_each(entry, head) + i++; + + return i; +} + +/* evict the 'num_evict' number of oldest entries in the queue */ +static void tx_queue_evict(struct mux_subch *sch, int num_evict) +{ + struct subch_txq_entry *tqe; + int i; + + for (i = 0; i < num_evict; i++) { + if (llist_empty(&sch->tx_queue)) + return; + + tqe = llist_entry(sch->tx_queue.next, struct subch_txq_entry, list); + llist_del(&tqe->list); + talloc_free(tqe); + } +} + +/* enqueue some data into the tx_queue of a given subchannel */ +int subchan_mux_enqueue(struct subch_mux *mx, int s_nr, const u_int8_t *data, + int len) +{ + struct mux_subch *sch = &mx->subch[s_nr]; + int list_len = llist_len(&sch->tx_queue); + struct subch_txq_entry *tqe = talloc_zero_size(tall_tqe_ctx, + sizeof(*tqe) + len); + if (!tqe) + return -ENOMEM; + + tqe->bit_len = len; + memcpy(tqe->bits, data, len); + + if (list_len > 2) + tx_queue_evict(sch, list_len-2); + + llist_add_tail(&tqe->list, &sch->tx_queue); + + return 0; +} + +/* initialize one subchannel muxer instance */ +int subchan_mux_init(struct subch_mux *mx) +{ + int i; + + memset(mx, 0, sizeof(*mx)); + for (i = 0; i < NR_SUBCH; i++) { + struct mux_subch *sch = &mx->subch[i]; + INIT_LLIST_HEAD(&sch->tx_queue); + } + + return 0; +} diff --git a/src/libtrau/trau_frame.c b/src/libtrau/trau_frame.c new file mode 100644 index 000000000..d4d6447cc --- /dev/null +++ b/src/libtrau/trau_frame.c @@ -0,0 +1,260 @@ +/* TRAU frame handling according to GSM TS 08.60 */ + +/* (C) 2009 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +static u_int32_t get_bits(const u_int8_t *bitbuf, int offset, int num) +{ + int i; + u_int32_t ret = 0; + + for (i = offset; i < offset + num; i++) { + ret = ret << 1; + if (bitbuf[i]) + ret |= 1; + } + return ret; +} + +/* Decode according to 3.1.1 */ +static void decode_fr(struct decoded_trau_frame *fr, const u_int8_t *trau_bits) +{ + int i; + int d_idx = 0; + + /* C1 .. C15 */ + memcpy(fr->c_bits+0, trau_bits+17, 15); + /* C16 .. C21 */ + memcpy(fr->c_bits+15, trau_bits+310, 6); + /* T1 .. T4 */ + memcpy(fr->t_bits+0, trau_bits+316, 4); + /* D1 .. D255 */ + for (i = 32; i < 304; i+= 16) { + memcpy(fr->d_bits + d_idx, trau_bits+i+1, 15); + d_idx += 15; + } + /* D256 .. D260 */ + memcpy(fr->d_bits + d_idx, trau_bits + 305, 5); +} + +/* Decode according to 3.1.2 */ +static void decode_amr(struct decoded_trau_frame *fr, const u_int8_t *trau_bits) +{ + int i; + int d_idx = 0; + + /* C1 .. C15 */ + memcpy(fr->c_bits+0, trau_bits+17, 15); + /* C16 .. C25 */ + memcpy(fr->c_bits+15, trau_bits+33, 10); + /* T1 .. T4 */ + memcpy(fr->t_bits+0, trau_bits+316, 4); + /* D1 .. D5 */ + memcpy(fr->d_bits, trau_bits+43, 5); + /* D6 .. D245 */ + for (i = 48; i < 304; i += 16) { + memcpy(fr->d_bits + d_idx, trau_bits+i+1, 15); + d_idx += 15; + } + /* D246 .. D256 */ + memcpy(fr->d_bits + d_idx, trau_bits + 305, 11); +} + +int decode_trau_frame(struct decoded_trau_frame *fr, const u_int8_t *trau_bits) +{ + u_int8_t cbits5 = get_bits(trau_bits, 17, 5); + + switch (cbits5) { + case TRAU_FT_FR_UP: + case TRAU_FT_FR_DOWN: + case TRAU_FT_IDLE_UP: + case TRAU_FT_IDLE_DOWN: + case TRAU_FT_EFR: + decode_fr(fr, trau_bits); + break; + case TRAU_FT_AMR: + decode_amr(fr, trau_bits); + break; + case TRAU_FT_OM_UP: + case TRAU_FT_OM_DOWN: + case TRAU_FT_DATA_UP: + case TRAU_FT_DATA_DOWN: + case TRAU_FT_D145_SYNC: + case TRAU_FT_EDATA: + LOGP(DMUX, LOGL_NOTICE, "can't decode unimplemented TRAU " + "Frame Type 0x%02x\n", cbits5); + return -1; + break; + default: + LOGP(DMUX, LOGL_NOTICE, "can't decode unknown TRAU " + "Frame Type 0x%02x\n", cbits5); + return -1; + break; + } + + return 0; +} + +const u_int8_t ft_fr_down_bits[] = { 1, 1, 1, 0, 0 }; +const u_int8_t ft_idle_down_bits[] = { 0, 1, 1, 1, 0 }; + +/* modify an uplink TRAU frame so we can send it downlink */ +int trau_frame_up2down(struct decoded_trau_frame *fr) +{ + u_int8_t cbits5 = get_bits(fr->c_bits, 0, 5); + + switch (cbits5) { + case TRAU_FT_FR_UP: + memcpy(fr->c_bits, ft_fr_down_bits, 5); + /* clear time alignment */ + memset(fr->c_bits+5, 0, 6); + /* FIXME: SP / BFI in case of DTx */ + /* C12 .. C21 are spare and coded as '1' */ + memset(fr->c_bits+11, 0x01, 10); + break; + case TRAU_FT_EFR: + /* clear time alignment */ + memset(fr->c_bits+5, 0, 6); + /* FIXME: set UFE appropriately */ + /* FIXME: SP / BFI in case of DTx */ + break; + case TRAU_FT_IDLE_UP: + memcpy(fr->c_bits, ft_idle_down_bits, 5); + /* clear time alignment */ + memset(fr->c_bits+5, 0, 6); + /* FIXME: SP / BFI in case of DTx */ + /* C12 .. C21 are spare and coded as '1' */ + memset(fr->c_bits+11, 0x01, 10); + break; + case TRAU_FT_FR_DOWN: + case TRAU_FT_IDLE_DOWN: + case TRAU_FT_OM_DOWN: + case TRAU_FT_DATA_DOWN: + /* we cannot convert a downlink to a downlink frame */ + return -EINVAL; + break; + case TRAU_FT_AMR: + case TRAU_FT_OM_UP: + case TRAU_FT_DATA_UP: + case TRAU_FT_D145_SYNC: + case TRAU_FT_EDATA: + LOGP(DMUX, LOGL_NOTICE, "unimplemented TRAU Frame Type " + "0x%02x\n", cbits5); + return -1; + break; + default: + LOGP(DMUX, LOGL_NOTICE, "unknown TRAU Frame Type " + "0x%02x\n", cbits5); + return -1; + break; + } + + return 0; + +} + +static void encode_fr(u_int8_t *trau_bits, const struct decoded_trau_frame *fr) +{ + int i; + int d_idx = 0; + + trau_bits[16] = 1; + /* C1 .. C15 */ + memcpy(trau_bits+17, fr->c_bits+0, 15); + /* D1 .. D255 */ + for (i = 32; i < 304; i+= 16) { + trau_bits[i] = 1; + memcpy(trau_bits+i+1, fr->d_bits + d_idx, 15); + d_idx += 15; + } + /* D256 .. D260 */ + trau_bits[304] = 1; + memcpy(trau_bits + 305, fr->d_bits + d_idx, 5); + /* C16 .. C21 */ + memcpy(trau_bits+310, fr->c_bits+15, 6); + + /* FIXME: handle timing adjustment */ + + /* T1 .. T4 */ + memcpy(trau_bits+316, fr->t_bits+0, 4); +} + + +int encode_trau_frame(u_int8_t *trau_bits, const struct decoded_trau_frame *fr) +{ + u_int8_t cbits5 = get_bits(fr->c_bits, 0, 5); + + /* 16 bits of sync header */ + memset(trau_bits, 0, 16); + + switch (cbits5) { + case TRAU_FT_FR_UP: + case TRAU_FT_FR_DOWN: + case TRAU_FT_IDLE_UP: + case TRAU_FT_IDLE_DOWN: + case TRAU_FT_EFR: + encode_fr(trau_bits, fr); + break; + case TRAU_FT_AMR: + case TRAU_FT_OM_UP: + case TRAU_FT_OM_DOWN: + case TRAU_FT_DATA_UP: + case TRAU_FT_DATA_DOWN: + case TRAU_FT_D145_SYNC: + case TRAU_FT_EDATA: + LOGP(DMUX, LOGL_NOTICE, "unimplemented TRAU Frame Type " + "0x%02x\n", cbits5); + return -1; + break; + default: + LOGP(DMUX, LOGL_NOTICE, "unknown TRAU Frame Type " + "0x%02x\n", cbits5); + return -1; + break; + } + + return 0; +} + +static struct decoded_trau_frame fr_idle_frame = { + .c_bits = { 0, 1, 1, 1, 0 }, /* IDLE DOWNLINK 3.5.5 */ + .t_bits = { 1, 1, 1, 1 }, +}; +static u_int8_t encoded_idle_frame[TRAU_FRAME_BITS]; +static int dbits_initted; + +u_int8_t *trau_idle_frame(void) +{ + /* only initialize during the first call */ + if (!dbits_initted) { + /* set all D-bits to 1 */ + memset(&fr_idle_frame.d_bits, 0x01, 260); + encode_fr(encoded_idle_frame, &fr_idle_frame); + } + return encoded_idle_frame; +} diff --git a/src/libtrau/trau_mux.c b/src/libtrau/trau_mux.c new file mode 100644 index 000000000..712e22d85 --- /dev/null +++ b/src/libtrau/trau_mux.c @@ -0,0 +1,312 @@ +/* Simple TRAU frame reflector to route voice calls */ + +/* (C) 2009 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +u_int8_t gsm_fr_map[] = { + 6, 6, 5, 5, 4, 4, 3, 3, + 7, 2, 2, 6, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 7, 2, 2, 6, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 7, 2, 2, 6, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 7, 2, 2, 6, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3 +}; + +struct map_entry { + struct llist_head list; + struct gsm_e1_subslot src, dst; +}; + +struct upqueue_entry { + struct llist_head list; + struct gsm_network *net; + struct gsm_e1_subslot src; + u_int32_t callref; +}; + +static LLIST_HEAD(ss_map); +static LLIST_HEAD(ss_upqueue); + +void *tall_map_ctx, *tall_upq_ctx; + +/* map one particular subslot to another subslot */ +int trau_mux_map(const struct gsm_e1_subslot *src, + const struct gsm_e1_subslot *dst) +{ + struct map_entry *me; + + me = talloc(tall_map_ctx, struct map_entry); + if (!me) { + LOGP(DMIB, LOGL_FATAL, "Out of memory\n"); + return -ENOMEM; + } + + DEBUGP(DCC, "Setting up TRAU mux map between (e1=%u,ts=%u,ss=%u) " + "and (e1=%u,ts=%u,ss=%u)\n", + src->e1_nr, src->e1_ts, src->e1_ts_ss, + dst->e1_nr, dst->e1_ts, dst->e1_ts_ss); + + /* make sure to get rid of any stale old mappings */ + trau_mux_unmap(src, 0); + trau_mux_unmap(dst, 0); + + memcpy(&me->src, src, sizeof(me->src)); + memcpy(&me->dst, dst, sizeof(me->dst)); + llist_add(&me->list, &ss_map); + + return 0; +} + +int trau_mux_map_lchan(const struct gsm_lchan *src, + const struct gsm_lchan *dst) +{ + struct gsm_e1_subslot *src_ss, *dst_ss; + + src_ss = &src->ts->e1_link; + dst_ss = &dst->ts->e1_link; + + return trau_mux_map(src_ss, dst_ss); +} + + +/* unmap one particular subslot from another subslot */ +int trau_mux_unmap(const struct gsm_e1_subslot *ss, u_int32_t callref) +{ + struct map_entry *me, *me2; + struct upqueue_entry *ue, *ue2; + + if (ss) + llist_for_each_entry_safe(me, me2, &ss_map, list) { + if (!memcmp(&me->src, ss, sizeof(*ss)) || + !memcmp(&me->dst, ss, sizeof(*ss))) { + llist_del(&me->list); + return 0; + } + } + llist_for_each_entry_safe(ue, ue2, &ss_upqueue, list) { + if (ue->callref == callref) { + llist_del(&ue->list); + return 0; + } + if (ss && !memcmp(&ue->src, ss, sizeof(*ss))) { + llist_del(&ue->list); + return 0; + } + } + return -ENOENT; +} + +/* look-up an enty in the TRAU mux map */ +static struct gsm_e1_subslot * +lookup_trau_mux_map(const struct gsm_e1_subslot *src) +{ + struct map_entry *me; + + llist_for_each_entry(me, &ss_map, list) { + if (!memcmp(&me->src, src, sizeof(*src))) + return &me->dst; + if (!memcmp(&me->dst, src, sizeof(*src))) + return &me->src; + } + return NULL; +} + +/* look-up an enty in the TRAU upqueue */ +struct upqueue_entry * +lookup_trau_upqueue(const struct gsm_e1_subslot *src) +{ + struct upqueue_entry *ue; + + llist_for_each_entry(ue, &ss_upqueue, list) { + if (!memcmp(&ue->src, src, sizeof(*src))) + return ue; + } + return NULL; +} + +static const u_int8_t c_bits_check[] = { 0, 0, 0, 1, 0 }; + +/* we get called by subchan_demux */ +int trau_mux_input(struct gsm_e1_subslot *src_e1_ss, + const u_int8_t *trau_bits, int num_bits) +{ + struct decoded_trau_frame tf; + u_int8_t trau_bits_out[TRAU_FRAME_BITS]; + struct gsm_e1_subslot *dst_e1_ss = lookup_trau_mux_map(src_e1_ss); + struct subch_mux *mx; + struct upqueue_entry *ue; + int rc; + + /* decode TRAU, change it to downlink, re-encode */ + rc = decode_trau_frame(&tf, trau_bits); + if (rc) + return rc; + + if (!dst_e1_ss) { + struct msgb *msg; + struct gsm_data_frame *frame; + unsigned char *data; + int i, j, k, l, o; + /* frame shall be sent to upqueue */ + if (!(ue = lookup_trau_upqueue(src_e1_ss))) + return -EINVAL; + if (!ue->callref) + return -EINVAL; + if (memcmp(tf.c_bits, c_bits_check, sizeof(c_bits_check))) + DEBUGPC(DMUX, "illegal trau (C1-C5) %s\n", + hexdump(tf.c_bits, sizeof(c_bits_check))); + msg = msgb_alloc(sizeof(struct gsm_data_frame) + 33, + "GSM-DATA"); + if (!msg) + return -ENOMEM; + + frame = (struct gsm_data_frame *)msg->data; + memset(frame, 0, sizeof(struct gsm_data_frame)); + data = frame->data; + data[0] = 0xd << 4; + /* reassemble d-bits */ + i = 0; /* counts bits */ + j = 4; /* counts output bits */ + k = gsm_fr_map[0]-1; /* current number bit in element */ + l = 0; /* counts element bits */ + o = 0; /* offset input bits */ + while (i < 260) { + data[j/8] |= (tf.d_bits[k+o] << (7-(j%8))); + if (--k < 0) { + o += gsm_fr_map[l]; + k = gsm_fr_map[++l]-1; + } + i++; + j++; + } + frame->msg_type = GSM_TCHF_FRAME; + frame->callref = ue->callref; + trau_tx_to_mncc(ue->net, msg); + + return 0; + } + + mx = e1inp_get_mux(dst_e1_ss->e1_nr, dst_e1_ss->e1_ts); + if (!mx) + return -EINVAL; + + trau_frame_up2down(&tf); + encode_trau_frame(trau_bits_out, &tf); + + /* and send it to the muxer */ + return subchan_mux_enqueue(mx, dst_e1_ss->e1_ts_ss, trau_bits_out, + TRAU_FRAME_BITS); +} + +/* add receiver instance for lchan and callref */ +int trau_recv_lchan(struct gsm_lchan *lchan, u_int32_t callref) +{ + struct gsm_e1_subslot *src_ss; + struct upqueue_entry *ue; + + ue = talloc(tall_upq_ctx, struct upqueue_entry); + if (!ue) + return -ENOMEM; + + src_ss = &lchan->ts->e1_link; + + DEBUGP(DCC, "Setting up TRAU receiver (e1=%u,ts=%u,ss=%u) " + "and (callref 0x%x)\n", + src_ss->e1_nr, src_ss->e1_ts, src_ss->e1_ts_ss, + callref); + + /* make sure to get rid of any stale old mappings */ + trau_mux_unmap(src_ss, callref); + + memcpy(&ue->src, src_ss, sizeof(ue->src)); + ue->net = lchan->ts->trx->bts->network; + ue->callref = callref; + llist_add(&ue->list, &ss_upqueue); + + return 0; +} + +int trau_send_frame(struct gsm_lchan *lchan, struct gsm_data_frame *frame) +{ + u_int8_t trau_bits_out[TRAU_FRAME_BITS]; + struct gsm_e1_subslot *dst_e1_ss = &lchan->ts->e1_link; + struct subch_mux *mx; + int i, j, k, l, o; + unsigned char *data = frame->data; + struct decoded_trau_frame tf; + + mx = e1inp_get_mux(dst_e1_ss->e1_nr, dst_e1_ss->e1_ts); + if (!mx) + return -EINVAL; + + switch (frame->msg_type) { + case GSM_TCHF_FRAME: + /* set c-bits and t-bits */ + tf.c_bits[0] = 1; + tf.c_bits[1] = 1; + tf.c_bits[2] = 1; + tf.c_bits[3] = 0; + tf.c_bits[4] = 0; + memset(&tf.c_bits[5], 0, 6); + memset(&tf.c_bits[11], 1, 10); + memset(&tf.t_bits[0], 1, 4); + /* reassemble d-bits */ + i = 0; /* counts bits */ + j = 4; /* counts input bits */ + k = gsm_fr_map[0]-1; /* current number bit in element */ + l = 0; /* counts element bits */ + o = 0; /* offset output bits */ + while (i < 260) { + tf.d_bits[k+o] = (data[j/8] >> (7-(j%8))) & 1; + if (--k < 0) { + o += gsm_fr_map[l]; + k = gsm_fr_map[++l]-1; + } + i++; + j++; + } + break; + default: + DEBUGPC(DMUX, "unsupported message type %d\n", + frame->msg_type); + return -EINVAL; + } + + encode_trau_frame(trau_bits_out, &tf); + + /* and send it to the muxer */ + return subchan_mux_enqueue(mx, dst_e1_ss->e1_ts_ss, trau_bits_out, + TRAU_FRAME_BITS); +} diff --git a/src/libtrau/trau_upqueue.c b/src/libtrau/trau_upqueue.c new file mode 100644 index 000000000..f8edaf0ff --- /dev/null +++ b/src/libtrau/trau_upqueue.c @@ -0,0 +1,27 @@ +/* trau_upqueue.c - Pass msgb's up the chain */ + +/* (C) 2010 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +void trau_tx_to_mncc(struct gsm_network *net, struct msgb *msg) +{ + net->mncc_recv(net, msg); +} diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am new file mode 100644 index 000000000..95b9ef4f1 --- /dev/null +++ b/src/osmo-bsc/Makefile.am @@ -0,0 +1,18 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS) + +bin_PROGRAMS = osmo-bsc + + +osmo_bsc_SOURCES = osmo_bsc_main.c osmo_bsc_rf.c osmo_bsc_vty.c osmo_bsc_api.c \ + osmo_bsc_grace.c osmo_bsc_msc.c osmo_bsc_sccp.c \ + osmo_bsc_filter.c osmo_bsc_bssap.c osmo_bsc_audio.c +# once again since TRAU uses CC symbol :( +osmo_bsc_LDADD = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(LIBOSMOSCCP_LIBS) diff --git a/src/osmo-bsc/Makefile.in b/src/osmo-bsc/Makefile.in new file mode 100644 index 000000000..d83952c98 --- /dev/null +++ b/src/osmo-bsc/Makefile.in @@ -0,0 +1,513 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +bin_PROGRAMS = osmo-bsc$(EXEEXT) +subdir = src/osmo-bsc +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_osmo_bsc_OBJECTS = osmo_bsc_main.$(OBJEXT) osmo_bsc_rf.$(OBJEXT) \ + osmo_bsc_vty.$(OBJEXT) osmo_bsc_api.$(OBJEXT) \ + osmo_bsc_grace.$(OBJEXT) osmo_bsc_msc.$(OBJEXT) \ + osmo_bsc_sccp.$(OBJEXT) osmo_bsc_filter.$(OBJEXT) \ + osmo_bsc_bssap.$(OBJEXT) osmo_bsc_audio.$(OBJEXT) +osmo_bsc_OBJECTS = $(am_osmo_bsc_OBJECTS) +am__DEPENDENCIES_1 = +osmo_bsc_DEPENDENCIES = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(am__DEPENDENCIES_1) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(osmo_bsc_SOURCES) +DIST_SOURCES = $(osmo_bsc_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS) +osmo_bsc_SOURCES = osmo_bsc_main.c osmo_bsc_rf.c osmo_bsc_vty.c osmo_bsc_api.c \ + osmo_bsc_grace.c osmo_bsc_msc.c osmo_bsc_sccp.c \ + osmo_bsc_filter.c osmo_bsc_bssap.c osmo_bsc_audio.c + +# once again since TRAU uses CC symbol :( +osmo_bsc_LDADD = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(LIBOSMOSCCP_LIBS) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/osmo-bsc/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/osmo-bsc/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +osmo-bsc$(EXEEXT): $(osmo_bsc_OBJECTS) $(osmo_bsc_DEPENDENCIES) + @rm -f osmo-bsc$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(osmo_bsc_OBJECTS) $(osmo_bsc_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_api.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_audio.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_bssap.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_filter.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_grace.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_msc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_rf.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_sccp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_vty.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-binPROGRAMS + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/osmo-bsc/osmo_bsc_api.c b/src/osmo-bsc/osmo_bsc_api.c new file mode 100644 index 000000000..b8cbcf2f3 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_api.c @@ -0,0 +1,174 @@ +/* (C) 2009-2010 by Holger Hans Peter Freyther + * (C) 2009-2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include + +#define return_when_not_connected(conn) \ + if (!conn->sccp_con) {\ + LOGP(DMSC, LOGL_ERROR, "MSC Connection not present.\n"); \ + return; \ + } + +#define return_when_not_connected_val(conn, ret) \ + if (!conn->sccp_con) {\ + LOGP(DMSC, LOGL_ERROR, "MSC Connection not present.\n"); \ + return ret; \ + } + +#define queue_msg_or_return(resp) \ + if (!resp) { \ + LOGP(DMSC, LOGL_ERROR, "Failed to allocate response.\n"); \ + return; \ + } \ + bsc_queue_for_msc(conn->sccp_con, resp); + +static uint16_t get_network_code_for_msc(struct gsm_network *net) +{ + if (net->msc_data->core_ncc != -1) + return net->msc_data->core_ncc; + return net->network_code; +} + +static uint16_t get_country_code_for_msc(struct gsm_network *net) +{ + if (net->msc_data->core_mcc != -1) + return net->msc_data->core_mcc; + return net->country_code; +} + +static void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci) +{ + struct msgb *resp; + return_when_not_connected(conn); + + resp = gsm0808_create_sapi_reject(dlci); + queue_msg_or_return(resp); +} + +static void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn, + struct msgb *msg, uint8_t chosen_encr) +{ + struct msgb *resp; + return_when_not_connected(conn); + + LOGP(DMSC, LOGL_DEBUG, "CIPHER MODE COMPLETE from MS, forwarding to MSC\n"); + resp = gsm0808_create_cipher_complete(msg, chosen_encr); + queue_msg_or_return(resp); +} + +/* + * Instruct to reserve data for a new connectiom, create the complete + * layer three message, send it to open the connection. + */ +static int bsc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg, + uint16_t chosen_channel) +{ + struct msgb *resp; + uint16_t network_code = get_network_code_for_msc(conn->bts->network); + uint16_t country_code = get_country_code_for_msc(conn->bts->network); + + /* allocate resource for a new connection */ + if (bsc_create_new_connection(conn) != 0) + return BSC_API_CONN_POL_REJECT; + + bsc_scan_bts_msg(conn, msg); + resp = gsm0808_create_layer3(msg, network_code, country_code, + conn->bts->location_area_code, + conn->bts->cell_identity); + if (!resp) { + LOGP(DMSC, LOGL_DEBUG, "Failed to create layer3 message.\n"); + bsc_delete_connection(conn->sccp_con); + return BSC_API_CONN_POL_REJECT; + } + + if (bsc_open_connection(conn->sccp_con, resp) != 0) { + bsc_delete_connection(conn->sccp_con); + msgb_free(resp); + return BSC_API_CONN_POL_REJECT; + } + + return BSC_API_CONN_POL_ACCEPT; +} + +static void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg) +{ + struct msgb *resp; + return_when_not_connected(conn); + + bsc_scan_bts_msg(conn, msg); + resp = gsm0808_create_dtap(msg, link_id); + queue_msg_or_return(resp); +} + +static void bsc_assign_compl(struct gsm_subscriber_connection *conn, uint8_t rr_cause, + uint8_t chosen_channel, uint8_t encr_alg_id, + uint8_t speech_model) +{ + struct msgb *resp; + return_when_not_connected(conn); + + resp = gsm0808_create_assignment_completed(rr_cause, chosen_channel, + encr_alg_id, speech_model); + queue_msg_or_return(resp); +} + +static void bsc_assign_fail(struct gsm_subscriber_connection *conn, + uint8_t cause, uint8_t *rr_cause) +{ + struct msgb *resp; + return_when_not_connected(conn); + + resp = gsm0808_create_assignment_failure(cause, rr_cause); + queue_msg_or_return(resp); +} + +static int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause) +{ + struct msgb *resp; + return_when_not_connected_val(conn, 1); + + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE); + if (!resp) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate response.\n"); + return 0; + } + + bsc_queue_for_msc(conn->sccp_con, resp); + return 0; +} + +static struct bsc_api bsc_handler = { + .sapi_n_reject = bsc_sapi_n_reject, + .cipher_mode_compl = bsc_cipher_mode_compl, + .compl_l3 = bsc_compl_l3, + .dtap = bsc_dtap, + .assign_compl = bsc_assign_compl, + .assign_fail = bsc_assign_fail, + .clear_request = bsc_clear_request, +}; + +struct bsc_api *osmo_bsc_api() +{ + return &bsc_handler; +} diff --git a/src/osmo-bsc/osmo_bsc_audio.c b/src/osmo-bsc/osmo_bsc_audio.c new file mode 100644 index 000000000..515cfa7fb --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_audio.c @@ -0,0 +1,70 @@ +/* + * ipaccess audio handling + * + * (C) 2009-2010 by Holger Hans Peter Freyther + * (C) 2009-2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +static int handle_abisip_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_subscriber_connection *con; + struct gsm_lchan *lchan = signal_data; + int rc; + + if (subsys != SS_ABISIP) + return 0; + + con = lchan->conn; + if (!con || !con->sccp_con) + return 0; + + switch (signal) { + case S_ABISIP_CRCX_ACK: + /* we can ask it to connect now */ + LOGP(DMSC, LOGL_DEBUG, "Connecting BTS to port: %d conn: %d\n", + con->sccp_con->rtp_port, lchan->abis_ip.conn_id); + + rc = rsl_ipacc_mdcx(lchan, ntohl(INADDR_ANY), + con->sccp_con->rtp_port, + lchan->abis_ip.rtp_payload2); + if (rc < 0) { + LOGP(DMSC, LOGL_ERROR, "Failed to send MDCX: %d\n", rc); + return rc; + } + break; + } + + return 0; +} + +int osmo_bsc_audio_init(struct gsm_network *net) +{ + net->hardcoded_rtp_payload = 98; + register_signal_handler(SS_ABISIP, handle_abisip_signal, net); + return 0; +} diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c new file mode 100644 index 000000000..f8711314d --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_bssap.c @@ -0,0 +1,548 @@ +/* GSM 08.08 BSSMAP handling */ +/* (C) 2009-2010 by Holger Hans Peter Freyther + * (C) 2009-2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +static uint16_t read_data16(const uint8_t *data) +{ + uint16_t res; + + memcpy(&res, data, sizeof(res)); + return res; +} + +/* + * helpers for the assignment command + */ +enum gsm0808_permitted_speech audio_support_to_gsm88(struct gsm_audio_support *audio) +{ + if (audio->hr) { + switch (audio->ver) { + case 1: + return GSM0808_PERM_HR1; + break; + case 2: + return GSM0808_PERM_HR2; + break; + case 3: + return GSM0808_PERM_HR3; + break; + default: + LOGP(DMSC, LOGL_ERROR, "Wrong speech mode: %d\n", audio->ver); + return GSM0808_PERM_FR1; + } + } else { + switch (audio->ver) { + case 1: + return GSM0808_PERM_FR1; + break; + case 2: + return GSM0808_PERM_FR2; + break; + case 3: + return GSM0808_PERM_FR3; + break; + default: + LOGP(DMSC, LOGL_ERROR, "Wrong speech mode: %d\n", audio->ver); + return GSM0808_PERM_HR1; + } + } +} + +enum gsm48_chan_mode gsm88_to_chan_mode(enum gsm0808_permitted_speech speech) +{ + switch (speech) { + case GSM0808_PERM_HR1: + case GSM0808_PERM_FR1: + return GSM48_CMODE_SPEECH_V1; + break; + case GSM0808_PERM_HR2: + case GSM0808_PERM_FR2: + return GSM48_CMODE_SPEECH_EFR; + break; + case GSM0808_PERM_HR3: + case GSM0808_PERM_FR3: + return GSM48_CMODE_SPEECH_AMR; + break; + } + + LOGP(DMSC, LOGL_FATAL, "Should not be reached.\n"); + return GSM48_CMODE_SPEECH_AMR; +} + +static int bssmap_handle_reset_ack(struct gsm_network *net, + struct msgb *msg, unsigned int length) +{ + LOGP(DMSC, LOGL_NOTICE, "Reset ACK from MSC\n"); + return 0; +} + +/* GSM 08.08 § 3.2.1.19 */ +static int bssmap_handle_paging(struct gsm_network *net, + struct msgb *msg, unsigned int payload_length) +{ + struct gsm_subscriber *subscr; + struct tlv_parsed tp; + char mi_string[GSM48_MI_SIZE]; + uint32_t tmsi = GSM_RESERVED_TMSI; + unsigned int lac = GSM_LAC_RESERVED_ALL_BTS; + uint8_t data_length; + const uint8_t *data; + uint8_t chan_needed = RSL_CHANNEED_ANY; + + tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0); + + if (!TLVP_PRESENT(&tp, GSM0808_IE_IMSI)) { + LOGP(DMSC, LOGL_ERROR, "Mandantory IMSI not present.\n"); + return -1; + } else if ((TLVP_VAL(&tp, GSM0808_IE_IMSI)[0] & GSM_MI_TYPE_MASK) != GSM_MI_TYPE_IMSI) { + LOGP(DMSC, LOGL_ERROR, "Wrong content in the IMSI\n"); + return -1; + } + + if (!TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST)) { + LOGP(DMSC, LOGL_ERROR, "Mandantory CELL IDENTIFIER LIST not present.\n"); + return -1; + } + + if (TLVP_PRESENT(&tp, GSM0808_IE_TMSI)) { + gsm48_mi_to_string(mi_string, sizeof(mi_string), + TLVP_VAL(&tp, GSM0808_IE_TMSI), TLVP_LEN(&tp, GSM0808_IE_TMSI)); + tmsi = strtoul(mi_string, NULL, 10); + } + + + /* + * parse the IMSI + */ + gsm48_mi_to_string(mi_string, sizeof(mi_string), + TLVP_VAL(&tp, GSM0808_IE_IMSI), TLVP_LEN(&tp, GSM0808_IE_IMSI)); + + /* + * parse the cell identifier list + */ + data_length = TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST); + data = TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST); + + /* + * Support paging to all network or one BTS at one LAC + */ + if (data_length == 3 && data[0] == CELL_IDENT_LAC) { + lac = ntohs(read_data16(&data[1])); + } else if (data_length > 1 || (data[0] & 0x0f) != CELL_IDENT_BSS) { + LOGP(DMSC, LOGL_ERROR, "Unsupported Cell Identifier List: %s\n", hexdump(data, data_length)); + return -1; + } + + if (TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_NEEDED) && TLVP_LEN(&tp, GSM0808_IE_CHANNEL_NEEDED) == 1) + chan_needed = TLVP_VAL(&tp, GSM0808_IE_CHANNEL_NEEDED)[0] & 0x03; + + if (TLVP_PRESENT(&tp, GSM0808_IE_EMLPP_PRIORITY)) { + LOGP(DMSC, LOGL_ERROR, "eMLPP is not handled\n"); + } + + subscr = subscr_get_or_create(net, mi_string); + if (!subscr) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate a subscriber for %s\n", mi_string); + return -1; + } + + subscr->lac = lac; + subscr->tmsi = tmsi; + + LOGP(DMSC, LOGL_DEBUG, "Paging request from MSC IMSI: '%s' TMSI: '0x%x/%u' LAC: 0x%x\n", mi_string, tmsi, tmsi, lac); + paging_request(net, subscr, chan_needed, NULL, NULL); + return 0; +} + +/* + * GSM 08.08 § 3.1.9.1 and 3.2.1.21... + * release our gsm_subscriber_connection and send message + */ +static int bssmap_handle_clear_command(struct osmo_bsc_sccp_con *conn, + struct msgb *msg, unsigned int payload_length) +{ + struct msgb *resp; + + /* TODO: handle the cause of this package */ + + if (conn->conn) { + LOGP(DMSC, LOGL_DEBUG, "Releasing all transactions on %p\n", conn); + gsm0808_clear(conn->conn); + subscr_con_free(conn->conn); + conn->conn = NULL; + } + + /* send the clear complete message */ + resp = gsm0808_create_clear_complete(); + if (!resp) { + LOGP(DMSC, LOGL_ERROR, "Sending clear complete failed.\n"); + return -1; + } + + bsc_queue_for_msc(conn, resp); + return 0; +} + +/* + * GSM 08.08 § 3.4.7 cipher mode handling. We will have to pick + * the cipher to be used for this. In case we are already using + * a cipher we will have to send cipher mode reject to the MSC, + * otherwise we will have to pick something that we and the MS + * is supporting. Currently we are doing it in a rather static + * way by picking one ecnryption or no encrytpion. + */ +static int bssmap_handle_cipher_mode(struct osmo_bsc_sccp_con *conn, + struct msgb *msg, unsigned int payload_length) +{ + uint16_t len; + struct gsm_network *network = NULL; + const uint8_t *data; + struct tlv_parsed tp; + struct msgb *resp; + int reject_cause = -1; + int include_imeisv = 1; + + if (!conn->conn) { + LOGP(DMSC, LOGL_ERROR, "No lchan/msc_data in cipher mode command.\n"); + goto reject; + } + + if (conn->ciphering_handled) { + LOGP(DMSC, LOGL_ERROR, "Already seen ciphering command. Protocol Error.\n"); + goto reject; + } + + conn->ciphering_handled = 1; + + tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0); + if (!TLVP_PRESENT(&tp, GSM0808_IE_ENCRYPTION_INFORMATION)) { + LOGP(DMSC, LOGL_ERROR, "IE Encryption Information missing.\n"); + goto reject; + } + + /* + * check if our global setting is allowed + * - Currently we check for A5/0 and A5/1 + * - Copy the key if that is necessary + * - Otherwise reject + */ + len = TLVP_LEN(&tp, GSM0808_IE_ENCRYPTION_INFORMATION); + if (len < 1) { + LOGP(DMSC, LOGL_ERROR, "IE Encryption Information is too short.\n"); + goto reject; + } + + network = conn->conn->bts->network; + data = TLVP_VAL(&tp, GSM0808_IE_ENCRYPTION_INFORMATION); + + if (TLVP_PRESENT(&tp, GSM0808_IE_CIPHER_RESPONSE_MODE)) + include_imeisv = TLVP_VAL(&tp, GSM0808_IE_CIPHER_RESPONSE_MODE)[0] & 0x1; + + if (network->a5_encryption == 0 && (data[0] & 0x1) == 0x1) { + gsm0808_cipher_mode(conn->conn, 0, NULL, 0, include_imeisv); + } else if (network->a5_encryption != 0 && (data[0] & 0x2) == 0x2) { + gsm0808_cipher_mode(conn->conn, 1, &data[1], len - 1, include_imeisv); + } else { + LOGP(DMSC, LOGL_ERROR, "Can not select encryption...\n"); + goto reject; + } + +reject: + resp = gsm0808_create_cipher_reject(reject_cause); + if (!resp) { + LOGP(DMSC, LOGL_ERROR, "Sending the cipher reject failed.\n"); + return -1; + } + + bsc_queue_for_msc(conn, resp); + return -1; +} + +/* + * Handle the assignment request message. + * + * See §3.2.1.1 for the message type + */ +static int bssmap_handle_assignm_req(struct osmo_bsc_sccp_con *conn, + struct msgb *msg, unsigned int length) +{ + struct msgb *resp; + struct gsm_network *network; + struct tlv_parsed tp; + uint8_t *data; + uint16_t cic; + uint8_t timeslot; + uint8_t multiplex; + enum gsm48_chan_mode chan_mode = GSM48_CMODE_SIGN; + int i, supported, port, full_rate = -1; + + if (!conn->conn) { + LOGP(DMSC, LOGL_ERROR, "No lchan/msc_data in cipher mode command.\n"); + return -1; + } + + network = conn->conn->bts->network; + tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0); + + if (!TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_TYPE)) { + LOGP(DMSC, LOGL_ERROR, "Mandantory channel type not present.\n"); + goto reject; + } + + if (!TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) { + LOGP(DMSC, LOGL_ERROR, "Identity code missing. Audio routing will not work.\n"); + goto reject; + } + + cic = ntohs(read_data16(TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE))); + timeslot = cic & 0x1f; + multiplex = (cic & ~0x1f) >> 5; + + /* + * Currently we only support a limited subset of all + * possible channel types. The limitation ends by not using + * multi-slot, limiting the channel coding, speech... + */ + if (TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE) < 3) { + LOGP(DMSC, LOGL_ERROR, "ChannelType len !=3 not supported: %d\n", + TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE)); + goto reject; + } + + /* + * Try to figure out if we support the proposed speech codecs. For + * now we will always pick the full rate codecs. + */ + + data = (uint8_t *) TLVP_VAL(&tp, GSM0808_IE_CHANNEL_TYPE); + if ((data[0] & 0xf) != 0x1) { + LOGP(DMSC, LOGL_ERROR, "ChannelType != speech: %d\n", data[0]); + goto reject; + } + + if (data[1] != GSM0808_SPEECH_FULL_PREF && data[1] != GSM0808_SPEECH_HALF_PREF) { + LOGP(DMSC, LOGL_ERROR, "ChannelType full not allowed: %d\n", data[1]); + goto reject; + } + + /* + * go through the list of preferred codecs of our gsm network + * and try to find it among the permitted codecs. If we found + * it we will send chan_mode to the right mode and break the + * inner loop. The outer loop will exit due chan_mode having + * the correct value. + */ + full_rate = 0; + for (supported = 0; + chan_mode == GSM48_CMODE_SIGN && supported < network->msc_data->audio_length; + ++supported) { + + int perm_val = audio_support_to_gsm88(network->msc_data->audio_support[supported]); + for (i = 2; i < TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE); ++i) { + if ((data[i] & 0x7f) == perm_val) { + chan_mode = gsm88_to_chan_mode(perm_val); + full_rate = (data[i] & 0x4) == 0; + break; + } else if ((data[i] & 0x80) == 0x00) { + break; + } + } + } + + if (chan_mode == GSM48_CMODE_SIGN) { + LOGP(DMSC, LOGL_ERROR, "No supported audio type found.\n"); + goto reject; + } + + /* map it to a MGCP Endpoint and a RTP port */ + port = mgcp_timeslot_to_endpoint(multiplex, timeslot); + conn->rtp_port = rtp_calculate_port(port, + network->msc_data->rtp_base); + + return gsm0808_assign_req(conn->conn, chan_mode, full_rate); + +reject: + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL); + if (!resp) { + LOGP(DMSC, LOGL_ERROR, "Channel allocation failure.\n"); + return -1; + } + + bsc_queue_for_msc(conn, resp); + return -1; +} + +static int bssmap_rcvmsg_udt(struct gsm_network *net, + struct msgb *msg, unsigned int length) +{ + int ret = 0; + + if (length < 1) { + LOGP(DMSC, LOGL_ERROR, "Not enough room: %d\n", length); + return -1; + } + + switch (msg->l4h[0]) { + case BSS_MAP_MSG_RESET_ACKNOWLEDGE: + ret = bssmap_handle_reset_ack(net, msg, length); + break; + case BSS_MAP_MSG_PAGING: + if (bsc_grace_allow_new_connection(net)) + ret = bssmap_handle_paging(net, msg, length); + break; + } + + return ret; +} + +static int bssmap_rcvmsg_dt1(struct osmo_bsc_sccp_con *conn, + struct msgb *msg, unsigned int length) +{ + int ret = 0; + + if (length < 1) { + LOGP(DMSC, LOGL_ERROR, "Not enough room: %d\n", length); + return -1; + } + + switch (msg->l4h[0]) { + case BSS_MAP_MSG_CLEAR_CMD: + ret = bssmap_handle_clear_command(conn, msg, length); + break; + case BSS_MAP_MSG_CIPHER_MODE_CMD: + ret = bssmap_handle_cipher_mode(conn, msg, length); + break; + case BSS_MAP_MSG_ASSIGMENT_RQST: + ret = bssmap_handle_assignm_req(conn, msg, length); + break; + default: + LOGP(DMSC, LOGL_DEBUG, "Unimplemented msg type: %d\n", msg->l4h[0]); + break; + } + + return ret; +} + +static int dtap_rcvmsg(struct osmo_bsc_sccp_con *conn, + struct msgb *msg, unsigned int length) +{ + struct dtap_header *header; + struct msgb *gsm48; + uint8_t *data; + + if (!conn->conn) { + LOGP(DMSC, LOGL_ERROR, "No subscriber connection available\n"); + return -1; + } + + header = (struct dtap_header *) msg->l3h; + if (sizeof(*header) >= length) { + LOGP(DMSC, LOGL_ERROR, "The DTAP header does not fit. Wanted: %u got: %u\n", sizeof(*header), length); + LOGP(DMSC, LOGL_ERROR, "hex: %s\n", hexdump(msg->l3h, length)); + return -1; + } + + if (header->length > length - sizeof(*header)) { + LOGP(DMSC, LOGL_ERROR, "The DTAP l4 information does not fit: header: %u length: %u\n", header->length, length); + LOGP(DMSC, LOGL_ERROR, "hex: %s\n", hexdump(msg->l3h, length)); + return -1; + } + + LOGP(DMSC, LOGL_DEBUG, "DTAP message: SAPI: %u CHAN: %u\n", header->link_id & 0x07, header->link_id & 0xC0); + + /* forward the data */ + gsm48 = gsm48_msgb_alloc(); + if (!gsm48) { + LOGP(DMSC, LOGL_ERROR, "Allocation of the message failed.\n"); + return -1; + } + + gsm48->l3h = gsm48->data; + data = msgb_put(gsm48, length - sizeof(*header)); + memcpy(data, msg->l3h + sizeof(*header), length - sizeof(*header)); + + /* pass it to the filter for extra actions */ + bsc_scan_msc_msg(conn->conn, gsm48); + return gsm0808_submit_dtap(conn->conn, gsm48, header->link_id, 1); +} + +int bsc_handle_udt(struct gsm_network *network, + struct bsc_msc_connection *conn, + struct msgb *msgb, unsigned int length) +{ + struct bssmap_header *bs; + + LOGP(DMSC, LOGL_DEBUG, "Incoming SCCP message ftom MSC: %s\n", + hexdump(msgb->l3h, length)); + + if (length < sizeof(*bs)) { + LOGP(DMSC, LOGL_ERROR, "The header is too short.\n"); + return -1; + } + + bs = (struct bssmap_header *) msgb->l3h; + if (bs->length < length - sizeof(*bs)) + return -1; + + switch (bs->type) { + case BSSAP_MSG_BSS_MANAGEMENT: + msgb->l4h = &msgb->l3h[sizeof(*bs)]; + bssmap_rcvmsg_udt(network, msgb, length - sizeof(*bs)); + break; + default: + LOGP(DMSC, LOGL_ERROR, "Unimplemented msg type: %d\n", bs->type); + } + + return 0; +} + +int bsc_handle_dt1(struct osmo_bsc_sccp_con *conn, + struct msgb *msg, unsigned int len) +{ + if (len < sizeof(struct bssmap_header)) { + LOGP(DMSC, LOGL_ERROR, "The header is too short.\n"); + } + + switch (msg->l3h[0]) { + case BSSAP_MSG_BSS_MANAGEMENT: + msg->l4h = &msg->l3h[sizeof(struct bssmap_header)]; + bssmap_rcvmsg_dt1(conn, msg, len - sizeof(struct bssmap_header)); + break; + case BSSAP_MSG_DTAP: + dtap_rcvmsg(conn, msg, len); + break; + default: + LOGP(DMSC, LOGL_DEBUG, "Unimplemented msg type: %d\n", msg->l3h[0]); + } + + return -1; +} diff --git a/src/osmo-bsc/osmo_bsc_filter.c b/src/osmo-bsc/osmo_bsc_filter.c new file mode 100644 index 000000000..d2735a63a --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_filter.c @@ -0,0 +1,170 @@ +/* (C) 2009-2010 by Holger Hans Peter Freyther + * (C) 2009-2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +static void handle_lu_request(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm48_hdr *gh; + struct gsm48_loc_upd_req *lu; + struct gsm48_loc_area_id lai; + struct gsm_network *net; + + if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu)) { + LOGP(DMSC, LOGL_ERROR, "LU too small to look at: %u\n", msgb_l3len(msg)); + return; + } + + net = conn->bts->network; + + gh = msgb_l3(msg); + lu = (struct gsm48_loc_upd_req *) gh->data; + + gsm48_generate_lai(&lai, net->country_code, net->network_code, + conn->bts->location_area_code); + + if (memcmp(&lai, &lu->lai, sizeof(lai)) != 0) { + LOGP(DMSC, LOGL_DEBUG, "Marking con for welcome USSD.\n"); + conn->sccp_con->new_subscriber = 1; + } +} + +/* we will need to stop the paging request */ +static int handle_page_resp(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + uint8_t mi_type; + char mi_string[GSM48_MI_SIZE]; + struct gsm48_hdr *gh; + struct gsm48_pag_resp *resp; + struct gsm_subscriber *subscr; + + if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*resp)) { + LOGP(DMSC, LOGL_ERROR, "PagingResponse too small: %u\n", msgb_l3len(msg)); + return -1; + } + + gh = msgb_l3(msg); + resp = (struct gsm48_pag_resp *) &gh->data[0]; + + gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh), + mi_string, &mi_type); + DEBUGP(DRR, "PAGING RESPONSE: mi_type=0x%02x MI(%s)\n", + mi_type, mi_string); + + switch (mi_type) { + case GSM_MI_TYPE_TMSI: + subscr = subscr_active_by_tmsi(conn->bts->network, + tmsi_from_string(mi_string)); + break; + case GSM_MI_TYPE_IMSI: + subscr = subscr_active_by_imsi(conn->bts->network, mi_string); + break; + default: + subscr = NULL; + break; + } + + if (!subscr) { + LOGP(DMSC, LOGL_ERROR, "Non active subscriber got paged.\n"); + return -1; + } + + paging_request_stop(conn->bts, subscr, conn, msg); + subscr_put(subscr); + return 0; +} + +/** + * This is used to scan a message for extra functionality of the BSC. This + * includes scanning for location updating requests/acceptd and then send + * a welcome USSD message to the subscriber. + */ +int bsc_scan_bts_msg(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t pdisc = gh->proto_discr & 0x0f; + uint8_t mtype = gh->msg_type & 0xbf; + + if (pdisc == GSM48_PDISC_MM) { + if (mtype == GSM48_MT_MM_LOC_UPD_REQUEST) + handle_lu_request(conn, msg); + } else if (pdisc == GSM48_PDISC_RR) { + if (mtype == GSM48_MT_RR_PAG_RESP) + handle_page_resp(conn, msg); + } + + return 0; +} + +static void send_welcome_ussd(struct gsm_subscriber_connection *conn) +{ + struct gsm_network *net; + net = conn->bts->network; + + if (!net->msc_data->ussd_welcome_txt) + return; + + gsm0480_send_ussdNotify(conn, 1, net->msc_data->ussd_welcome_txt); + gsm0480_send_releaseComplete(conn); +} + +/** + * Messages coming back from the MSC. + */ +int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm_network *net; + struct gsm48_loc_area_id *lai; + struct gsm48_hdr *gh; + uint8_t mtype; + + if (msgb_l3len(msg) < sizeof(*gh)) { + LOGP(DMSC, LOGL_ERROR, "GSM48 header does not fit.\n"); + return -1; + } + + gh = (struct gsm48_hdr *) msgb_l3(msg); + mtype = gh->msg_type & 0xbf; + net = conn->bts->network; + + if (mtype == GSM48_MT_MM_LOC_UPD_ACCEPT) { + if (net->msc_data->core_ncc != -1 || + net->msc_data->core_mcc != -1) { + if (msgb_l3len(msg) >= sizeof(*gh) + sizeof(*lai)) { + lai = (struct gsm48_loc_area_id *) &gh->data[0]; + gsm48_generate_lai(lai, net->country_code, + net->network_code, + conn->bts->location_area_code); + } + } + + if (conn->sccp_con->new_subscriber) + send_welcome_ussd(conn); + } + + return 0; +} diff --git a/src/osmo-bsc/osmo_bsc_grace.c b/src/osmo-bsc/osmo_bsc_grace.c new file mode 100644 index 000000000..f699cf39c --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_grace.c @@ -0,0 +1,107 @@ +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +int bsc_grace_allow_new_connection(struct gsm_network *network) +{ + if (!network->msc_data->rf_ctl) + return 1; + return network->msc_data->rf_ctl->policy == S_RF_ON; +} + +static int handle_sub(struct gsm_lchan *lchan, const char *text) +{ + struct gsm_subscriber_connection *conn; + + /* only send it to TCH */ + if (lchan->type != GSM_LCHAN_TCH_H && lchan->type != GSM_LCHAN_TCH_F) + return -1; + + /* only send on the primary channel */ + conn = lchan->conn; + if (!conn) + return -1; + + if (conn->lchan != lchan) + return -1; + + /* only when active */ + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + gsm0480_send_ussdNotify(conn, 0, text); + gsm0480_send_releaseComplete(conn); + + return 0; +} + +/* + * The place to handle the grace mode. Right now we will send + * USSD messages to the subscriber, in the future we might start + * a timer to have different modes for the grace period. + */ +static int handle_grace(struct gsm_network *network) +{ + int ts_nr, lchan_nr; + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + + if (!network->msc_data->mid_call_txt) + return 0; + + llist_for_each_entry(bts, &network->bts_list, list) { + llist_for_each_entry(trx, &bts->trx_list, list) { + for (ts_nr = 0; ts_nr < TRX_NR_TS; ++ts_nr) { + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; ++lchan_nr) { + handle_sub(&ts->lchan[lchan_nr], + network->msc_data->mid_call_txt); + } + } + } + } + return 0; +} + +static int handle_rf_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct rf_signal_data *sig; + + if (subsys != SS_RF) + return -1; + + sig = signal_data; + + if (signal == S_RF_GRACE) + handle_grace(sig->net); + + return 0; +} + +static __attribute__((constructor)) void on_dso_load_grace(void) +{ + register_signal_handler(SS_RF, handle_rf_signal, NULL); +} diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c new file mode 100644 index 000000000..b5f64ab37 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_main.c @@ -0,0 +1,261 @@ +/* (C) 2008-2009 by Harald Welte + * (C) 2009-2010 by Holger Hans Peter Freyther + * (C) 2009-2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define _GNU_SOURCE +#include + +#include +#include +#include +#include +#include + + +#include "../../bscconfig.h" + +static struct log_target *stderr_target; +struct gsm_network *bsc_gsmnet = 0; +static const char *config_file = "openbsc.cfg"; +static const char *rf_ctl = NULL; +extern const char *openbsc_copyright; +static int daemonize = 0; + +extern int bsc_bootstrap_network(int (*layer4)(struct gsm_network *, struct msgb *), const char *cfg_file); + +static void print_usage() +{ + printf("Usage: bsc_msc_ip\n"); +} + +static void print_help() +{ + printf(" Some useful help...\n"); + printf(" -h --help this text\n"); + printf(" -D --daemonize Fork the process into a background daemon\n"); + printf(" -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM enable debugging\n"); + printf(" -s --disable-color\n"); + printf(" -T --timestamp. Print a timestamp in the debug output.\n"); + printf(" -c --config-file filename The config file to use.\n"); + printf(" -l --local=IP. The local address of the MGCP.\n"); + printf(" -e --log-level number. Set a global loglevel.\n"); + printf(" -r --rf-ctl NAME. A unix domain socket to listen for cmds.\n"); + printf(" -t --testmode. A special mode to provoke failures at the MSC.\n"); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"debug", 1, 0, 'd'}, + {"daemonize", 0, 0, 'D'}, + {"config-file", 1, 0, 'c'}, + {"disable-color", 0, 0, 's'}, + {"timestamp", 0, 0, 'T'}, + {"local", 1, 0, 'l'}, + {"log-level", 1, 0, 'e'}, + {"rf-ctl", 1, 0, 'r'}, + {"testmode", 0, 0, 't'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hd:DsTc:e:r:t", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage(); + print_help(); + exit(0); + case 's': + log_set_use_color(stderr_target, 0); + break; + case 'd': + log_parse_category_mask(stderr_target, optarg); + break; + case 'D': + daemonize = 1; + break; + case 'c': + config_file = strdup(optarg); + break; + case 'T': + log_set_print_timestamp(stderr_target, 1); + break; + case 'P': + ipacc_rtp_direct = 0; + break; + case 'e': + log_set_log_level(stderr_target, atoi(optarg)); + break; + case 'r': + rf_ctl = optarg; + break; + default: + /* ignore */ + break; + } + } +} + +extern int bts_model_unknown_init(void); +extern int bts_model_bs11_init(void); +extern int bts_model_nanobts_init(void); + +extern enum node_type bsc_vty_go_parent(struct vty *vty); + +static struct vty_app_info vty_info = { + .name = "OsmoBSC", + .version = PACKAGE_VERSION, + .go_parent_cb = bsc_vty_go_parent, + .is_config_node = bsc_vty_is_config_node, +}; + +extern int bsc_shutdown_net(struct gsm_network *net); +static void signal_handler(int signal) +{ + fprintf(stdout, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + bsc_shutdown_net(bsc_gsmnet); + dispatch_signal(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL); + sleep(3); + exit(0); + break; + case SIGABRT: + /* in case of abort, we want to obtain a talloc report + * and then return to the caller, who will abort the process */ + case SIGUSR1: + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(tall_bsc_ctx, stderr); + break; + case SIGUSR2: + if (!bsc_gsmnet->msc_data) + return; + if (!bsc_gsmnet->msc_data->msc_con) + return; + if (!bsc_gsmnet->msc_data->msc_con->is_connected) + return; + bsc_msc_lost(bsc_gsmnet->msc_data->msc_con); + break; + default: + break; + } +} + +int main(int argc, char **argv) +{ + int rc; + + log_init(&log_info); + tall_bsc_ctx = talloc_named_const(NULL, 1, "openbsc"); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + + bts_model_unknown_init(); + bts_model_bs11_init(); + bts_model_nanobts_init(); + + /* enable filters */ + log_set_all_filter(stderr_target, 1); + + /* This needs to precede handle_options() */ + vty_info.copyright = openbsc_copyright; + vty_init(&vty_info); + bsc_vty_init(); + + /* parse options */ + handle_options(argc, argv); + + /* seed the PRNG */ + srand(time(NULL)); + + /* initialize SCCP */ + sccp_set_log_area(DSCCP); + + + rc = bsc_bootstrap_network(NULL, config_file); + if (rc < 0) { + fprintf(stderr, "Bootstrapping the network failed. exiting.\n"); + exit(1); + } + bsc_api_init(bsc_gsmnet, osmo_bsc_api()); + + if (rf_ctl) { + struct osmo_msc_data *data = bsc_gsmnet->msc_data; + data->rf_ctl = osmo_bsc_rf_create(rf_ctl, bsc_gsmnet); + if (!data->rf_ctl) { + fprintf(stderr, "Failed to create the RF service.\n"); + exit(1); + } + } + + if (osmo_bsc_msc_init(bsc_gsmnet) != 0) { + LOGP(DNAT, LOGL_ERROR, "Failed to start up. Exiting.\n"); + exit(1); + } + + if (osmo_bsc_sccp_init(bsc_gsmnet) != 0) { + LOGP(DNM, LOGL_ERROR, "Failed to register SCCP.\n"); + exit(1); + } + + if (osmo_bsc_audio_init(bsc_gsmnet) != 0) { + LOGP(DMSC, LOGL_ERROR, "Failed to register audio support.\n"); + exit(1); + } + + signal(SIGINT, &signal_handler); + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + signal(SIGPIPE, SIG_IGN); + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + while (1) { + bsc_select_main(0); + } + + return 0; +} diff --git a/src/osmo-bsc/osmo_bsc_msc.c b/src/osmo-bsc/osmo_bsc_msc.c new file mode 100644 index 000000000..2e8cf0579 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_msc.c @@ -0,0 +1,368 @@ +/* + * Handle the connection to the MSC. This include ping/timeout/reconnect + * (C) 2008-2009 by Harald Welte + * (C) 2009-2010 by Holger Hans Peter Freyther + * (C) 2009-2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + + +static void initialize_if_needed(struct bsc_msc_connection *conn); +static void send_id_get_response(struct osmo_msc_data *data, int fd); +static void send_ping(struct osmo_msc_data *data); + +/* + * MGCP forwarding code + */ +static int mgcp_do_read(struct bsc_fd *fd) +{ + struct osmo_msc_data *data = (struct osmo_msc_data *) fd->data; + struct msgb *mgcp; + int ret; + + mgcp = msgb_alloc_headroom(4096, 128, "mgcp_from_gw"); + if (!mgcp) { + LOGP(DMGCP, LOGL_ERROR, "Failed to allocate MGCP message.\n"); + return -1; + } + + ret = read(fd->fd, mgcp->data, 4096 - 128); + if (ret <= 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to read: %d/%s\n", errno, strerror(errno)); + msgb_free(mgcp); + return -1; + } else if (ret > 4096 - 128) { + LOGP(DMGCP, LOGL_ERROR, "Too much data: %d\n", ret); + msgb_free(mgcp); + return -1; + } + + mgcp->l2h = msgb_put(mgcp, ret); + msc_queue_write(data->msc_con, mgcp, IPAC_PROTO_MGCP_OLD); + return 0; +} + +static int mgcp_do_write(struct bsc_fd *fd, struct msgb *msg) +{ + int ret; + + LOGP(DMGCP, LOGL_DEBUG, "Sending msg to MGCP GW size: %u\n", msg->len); + + ret = write(fd->fd, msg->data, msg->len); + if (ret != msg->len) + LOGP(DMGCP, LOGL_ERROR, "Failed to forward message to MGCP GW (%s).\n", strerror(errno)); + + return ret; +} + +static void mgcp_forward(struct osmo_msc_data *data, struct msgb *msg) +{ + struct msgb *mgcp; + + if (msgb_l2len(msg) > 4096) { + LOGP(DMGCP, LOGL_ERROR, "Can not forward too big message.\n"); + return; + } + + mgcp = msgb_alloc(4096, "mgcp_to_gw"); + if (!mgcp) { + LOGP(DMGCP, LOGL_ERROR, "Failed to send message.\n"); + return; + } + + msgb_put(mgcp, msgb_l2len(msg)); + memcpy(mgcp->data, msg->l2h, mgcp->len); + if (write_queue_enqueue(&data->mgcp_agent, mgcp) != 0) { + LOGP(DMGCP, LOGL_FATAL, "Could not queue message to MGCP GW.\n"); + msgb_free(mgcp); + } +} + +static int mgcp_create_port(struct osmo_msc_data *data) +{ + int on; + struct sockaddr_in addr; + + data->mgcp_agent.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0); + if (data->mgcp_agent.bfd.fd < 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to create UDP socket errno: %d\n", errno); + return -1; + } + + on = 1; + setsockopt(data->mgcp_agent.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + /* try to bind the socket */ + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + + if (bind(data->mgcp_agent.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to bind to any port.\n"); + close(data->mgcp_agent.bfd.fd); + data->mgcp_agent.bfd.fd = -1; + return -1; + } + + /* connect to the remote */ + addr.sin_port = htons(2427); + if (connect(data->mgcp_agent.bfd.fd, (struct sockaddr *) & addr, sizeof(addr)) < 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to connect to local MGCP GW. %s\n", strerror(errno)); + close(data->mgcp_agent.bfd.fd); + data->mgcp_agent.bfd.fd = -1; + return -1; + } + + write_queue_init(&data->mgcp_agent, 10); + data->mgcp_agent.bfd.when = BSC_FD_READ; + data->mgcp_agent.bfd.data = data; + data->mgcp_agent.read_cb = mgcp_do_read; + data->mgcp_agent.write_cb = mgcp_do_write; + + if (bsc_register_fd(&data->mgcp_agent.bfd) != 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to register BFD\n"); + close(data->mgcp_agent.bfd.fd); + data->mgcp_agent.bfd.fd = -1; + return -1; + } + + return 0; +} + +/* + * Send data to the network + */ +int msc_queue_write(struct bsc_msc_connection *conn, struct msgb *msg, int proto) +{ + ipaccess_prepend_header(msg, proto); + if (write_queue_enqueue(&conn->write_queue, msg) != 0) { + LOGP(DMSC, LOGL_FATAL, "Failed to queue IPA/%d\n", proto); + msgb_free(msg); + return -1; + } + + return 0; +} + +static int msc_alink_do_write(struct bsc_fd *fd, struct msgb *msg) +{ + int ret; + + LOGP(DMSC, LOGL_DEBUG, "Sending SCCP to MSC: %u\n", msgb_l2len(msg)); + LOGP(DMI, LOGL_DEBUG, "MSC TX %s\n", hexdump(msg->data, msg->len)); + + ret = write(fd->fd, msg->data, msg->len); + if (ret < msg->len) + perror("MSC: Failed to send SCCP"); + + return ret; +} + +static int ipaccess_a_fd_cb(struct bsc_fd *bfd) +{ + int error; + struct msgb *msg = ipaccess_read_msg(bfd, &error); + struct ipaccess_head *hh; + struct osmo_msc_data *data = (struct osmo_msc_data *) bfd->data; + + if (!msg) { + if (error == 0) { + LOGP(DMSC, LOGL_ERROR, "The connection to the MSC was lost.\n"); + bsc_msc_lost(data->msc_con); + return -1; + } + + LOGP(DMSC, LOGL_ERROR, "Failed to parse ip access message: %d\n", error); + return -1; + } + + LOGP(DMSC, LOGL_DEBUG, "From MSC: %s proto: %d\n", hexdump(msg->data, msg->len), msg->l2h[0]); + + /* handle base message handling */ + hh = (struct ipaccess_head *) msg->data; + ipaccess_rcvmsg_base(msg, bfd); + + /* initialize the networking. This includes sending a GSM08.08 message */ + if (hh->proto == IPAC_PROTO_IPACCESS) { + if (msg->l2h[0] == IPAC_MSGT_ID_ACK) + initialize_if_needed(data->msc_con); + else if (msg->l2h[0] == IPAC_MSGT_ID_GET) { + send_id_get_response(data, bfd->fd); + } else if (msg->l2h[0] == IPAC_MSGT_PONG) { + bsc_del_timer(&data->pong_timer); + } + } else if (hh->proto == IPAC_PROTO_SCCP) { + sccp_system_incoming(msg); + } else if (hh->proto == IPAC_PROTO_MGCP_OLD) { + mgcp_forward(data, msg); + } + + msgb_free(msg); + return 0; +} + +static void send_ping(struct osmo_msc_data *data) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(4096, 128, "ping"); + if (!msg) { + LOGP(DMSC, LOGL_ERROR, "Failed to create PING.\n"); + return; + } + + msg->l2h = msgb_put(msg, 1); + msg->l2h[0] = IPAC_MSGT_PING; + + msc_queue_write(data->msc_con, msg, IPAC_PROTO_IPACCESS); +} + +static void msc_ping_timeout_cb(void *_data) +{ + struct osmo_msc_data *data = (struct osmo_msc_data *) _data; + if (data->ping_timeout < 0) + return; + + send_ping(data); + + /* send another ping in 20 seconds */ + bsc_schedule_timer(&data->ping_timer, data->ping_timeout, 0); + + /* also start a pong timer */ + bsc_schedule_timer(&data->pong_timer, data->pong_timeout, 0); +} + +static void msc_pong_timeout_cb(void *_data) +{ + struct osmo_msc_data *data = (struct osmo_msc_data *) _data; + + LOGP(DMSC, LOGL_ERROR, "MSC didn't answer PING. Closing connection.\n"); + bsc_msc_lost(data->msc_con); +} + +static void msc_connection_connected(struct bsc_msc_connection *con) +{ + struct msc_signal_data sig; + struct osmo_msc_data *data; + int ret, on; + on = 1; + ret = setsockopt(con->write_queue.bfd.fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); + if (ret != 0) + LOGP(DMSC, LOGL_ERROR, "Failed to set TCP_NODELAY: %s\n", strerror(errno)); + + data = (struct osmo_msc_data *) con->write_queue.bfd.data; + msc_ping_timeout_cb(data); + + sig.data = data; + dispatch_signal(SS_MSC, S_MSC_CONNECTED, &sig); +} + +/* + * The connection to the MSC was lost and we will need to free all + * resources and then attempt to reconnect. + */ +static void msc_connection_was_lost(struct bsc_msc_connection *msc) +{ + struct msc_signal_data sig; + struct osmo_msc_data *data; + + LOGP(DMSC, LOGL_ERROR, "Lost MSC connection. Freing stuff.\n"); + + data = (struct osmo_msc_data *) msc->write_queue.bfd.data; + bsc_del_timer(&data->ping_timer); + bsc_del_timer(&data->pong_timer); + + sig.data = data; + dispatch_signal(SS_MSC, S_MSC_LOST, &sig); + + msc->is_authenticated = 0; + bsc_msc_schedule_connect(msc); +} + +static void initialize_if_needed(struct bsc_msc_connection *conn) +{ + struct msgb *msg; + + if (!conn->is_authenticated) { + /* send a gsm 08.08 reset message from here */ + msg = gsm0808_create_reset(); + if (!msg) { + LOGP(DMSC, LOGL_ERROR, "Failed to create the reset message.\n"); + return; + } + + sccp_write(msg, &sccp_ssn_bssap, &sccp_ssn_bssap, 0); + msgb_free(msg); + conn->is_authenticated = 1; + } +} + +static void send_id_get_response(struct osmo_msc_data *data, int fd) +{ + struct msgb *msg; + + msg = bsc_msc_id_get_resp(data->bsc_token); + if (!msg) + return; + msc_queue_write(data->msc_con, msg, IPAC_PROTO_IPACCESS); +} + +int osmo_bsc_msc_init(struct gsm_network *network) +{ + struct osmo_msc_data *data = network->msc_data; + + if (mgcp_create_port(data) != 0) + return -1; + + data->msc_con = bsc_msc_create(data->msc_ip, + data->msc_port, + data->msc_ip_dscp); + if (!data->msc_con) { + LOGP(DMSC, LOGL_ERROR, "Creating the MSC network connection failed.\n"); + return -1; + } + + data->ping_timer.cb = msc_ping_timeout_cb; + data->ping_timer.data = data; + data->pong_timer.cb = msc_pong_timeout_cb; + data->pong_timer.data = data; + + data->msc_con->write_queue.bfd.data = data; + data->msc_con->connection_loss = msc_connection_was_lost; + data->msc_con->connected = msc_connection_connected; + data->msc_con->write_queue.read_cb = ipaccess_a_fd_cb; + data->msc_con->write_queue.write_cb = msc_alink_do_write; + bsc_msc_connect(data->msc_con); + + return 0; +} diff --git a/src/osmo-bsc/osmo_bsc_rf.c b/src/osmo-bsc/osmo_bsc_rf.c new file mode 100644 index 000000000..5652c9df5 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_rf.c @@ -0,0 +1,364 @@ +/* RF Ctl handling socket */ + +/* (C) 2010 by Harald Welte + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#define RF_CMD_QUERY '?' +#define RF_CMD_OFF '0' +#define RF_CMD_ON '1' +#define RF_CMD_D_OFF 'd' +#define RF_CMD_ON_G 'g' + +static int lock_each_trx(struct gsm_network *net, int lock) +{ + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) { + struct gsm_bts_trx *trx; + llist_for_each_entry(trx, &bts->trx_list, list) { + gsm_trx_lock_rf(trx, lock); + } + } + + return 0; +} + +static void send_resp(struct osmo_bsc_rf_conn *conn, char send) +{ + struct msgb *msg; + + msg = msgb_alloc(10, "RF Query"); + if (!msg) { + LOGP(DINP, LOGL_ERROR, "Failed to allocate response msg.\n"); + return; + } + + msg->l2h = msgb_put(msg, 1); + msg->l2h[0] = send; + + if (write_queue_enqueue(&conn->queue, msg) != 0) { + LOGP(DINP, LOGL_ERROR, "Failed to enqueue the answer.\n"); + msgb_free(msg); + return; + } + + return; +} + + +/* + * Send a + * 'g' when we are in grace mode + * '1' when one TRX is online, + * '0' otherwise + */ +static void handle_query(struct osmo_bsc_rf_conn *conn) +{ + struct gsm_bts *bts; + char send = RF_CMD_OFF; + + if (conn->rf->policy == S_RF_GRACE) + return send_resp(conn, RF_CMD_ON_G); + + llist_for_each_entry(bts, &conn->rf->gsm_network->bts_list, list) { + struct gsm_bts_trx *trx; + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->nm_state.availability == NM_AVSTATE_OK && + trx->nm_state.operational != NM_STATE_LOCKED) { + send = RF_CMD_ON; + break; + } + } + } + + send_resp(conn, send); +} + +static void rf_check_cb(void *_data) +{ + struct gsm_bts *bts; + struct osmo_bsc_rf *rf = _data; + + llist_for_each_entry(bts, &rf->gsm_network->bts_list, list) { + struct gsm_bts_trx *trx; + + /* don't bother to check a booting or missing BTS */ + if (!bts->oml_link || !is_ipaccess_bts(bts)) + continue; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->nm_state.availability != NM_AVSTATE_OK || + trx->nm_state.operational != NM_OPSTATE_ENABLED || + trx->nm_state.administrative != NM_STATE_UNLOCKED) { + LOGP(DNM, LOGL_ERROR, "RF activation failed. Starting again.\n"); + ipaccess_drop_oml(bts); + break; + } + } + } +} + +static void send_signal(struct osmo_bsc_rf *rf, int val) +{ + struct rf_signal_data sig; + sig.net = rf->gsm_network; + + rf->policy = val; + dispatch_signal(SS_RF, val, &sig); +} + +static int switch_rf_off(struct osmo_bsc_rf *rf) +{ + lock_each_trx(rf->gsm_network, 1); + send_signal(rf, S_RF_OFF); + + return 0; +} + +static void grace_timeout(void *_data) +{ + struct osmo_bsc_rf *rf = (struct osmo_bsc_rf *) _data; + + LOGP(DINP, LOGL_NOTICE, "Grace timeout. Disabling the TRX.\n"); + switch_rf_off(rf); +} + +static int enter_grace(struct osmo_bsc_rf *rf) +{ + rf->grace_timeout.cb = grace_timeout; + rf->grace_timeout.data = rf; + bsc_schedule_timer(&rf->grace_timeout, rf->gsm_network->msc_data->mid_call_timeout, 0); + LOGP(DINP, LOGL_NOTICE, "Going to switch RF off in %d seconds.\n", + rf->gsm_network->msc_data->mid_call_timeout); + + send_signal(rf, S_RF_GRACE); + return 0; +} + +static void rf_delay_cmd_cb(void *data) +{ + struct osmo_bsc_rf *rf = data; + + switch (rf->last_request) { + case RF_CMD_D_OFF: + rf->last_state_command = "RF Direct Off"; + bsc_del_timer(&rf->rf_check); + bsc_del_timer(&rf->grace_timeout); + switch_rf_off(rf); + break; + case RF_CMD_ON: + rf->last_state_command = "RF Direct On"; + bsc_del_timer(&rf->grace_timeout); + lock_each_trx(rf->gsm_network, 0); + send_signal(rf, S_RF_ON); + bsc_schedule_timer(&rf->rf_check, 3, 0); + break; + case RF_CMD_OFF: + rf->last_state_command = "RF Scheduled Off"; + bsc_del_timer(&rf->rf_check); + enter_grace(rf); + break; + } +} + +static int rf_read_cmd(struct bsc_fd *fd) +{ + struct osmo_bsc_rf_conn *conn = fd->data; + char buf[1]; + int rc; + + rc = read(fd->fd, buf, sizeof(buf)); + if (rc != sizeof(buf)) { + LOGP(DINP, LOGL_ERROR, "Short read %d/%s\n", errno, strerror(errno)); + bsc_unregister_fd(fd); + close(fd->fd); + write_queue_clear(&conn->queue); + talloc_free(conn); + return -1; + } + + switch (buf[0]) { + case RF_CMD_QUERY: + handle_query(conn); + break; + case RF_CMD_D_OFF: + case RF_CMD_ON: + case RF_CMD_OFF: + conn->rf->last_request = buf[0]; + if (!bsc_timer_pending(&conn->rf->delay_cmd)) + bsc_schedule_timer(&conn->rf->delay_cmd, 1, 0); + break; + default: + conn->rf->last_state_command = "Unknown command"; + LOGP(DINP, LOGL_ERROR, "Unknown command %d\n", buf[0]); + break; + } + + return 0; +} + +static int rf_write_cmd(struct bsc_fd *fd, struct msgb *msg) +{ + int rc; + + rc = write(fd->fd, msg->data, msg->len); + if (rc != msg->len) { + LOGP(DINP, LOGL_ERROR, "Short write %d/%s\n", errno, strerror(errno)); + return -1; + } + + return 0; +} + +static int rf_ctl_accept(struct bsc_fd *bfd, unsigned int what) +{ + struct osmo_bsc_rf_conn *conn; + struct osmo_bsc_rf *rf = bfd->data; + struct sockaddr_un addr; + socklen_t len = sizeof(addr); + int fd; + + fd = accept(bfd->fd, (struct sockaddr *) &addr, &len); + if (fd < 0) { + LOGP(DINP, LOGL_ERROR, "Failed to accept. errno: %d/%s\n", + errno, strerror(errno)); + return -1; + } + + conn = talloc_zero(rf, struct osmo_bsc_rf_conn); + if (!conn) { + LOGP(DINP, LOGL_ERROR, "Failed to allocate mem.\n"); + close(fd); + return -1; + } + + write_queue_init(&conn->queue, 10); + conn->queue.bfd.data = conn; + conn->queue.bfd.fd = fd; + conn->queue.bfd.when = BSC_FD_READ | BSC_FD_WRITE; + conn->queue.read_cb = rf_read_cmd; + conn->queue.write_cb = rf_write_cmd; + conn->rf = rf; + + if (bsc_register_fd(&conn->queue.bfd) != 0) { + close(fd); + talloc_free(conn); + return -1; + } + + return 0; +} + +struct osmo_bsc_rf *osmo_bsc_rf_create(const char *path, struct gsm_network *net) +{ + unsigned int namelen; + struct sockaddr_un local; + struct bsc_fd *bfd; + struct osmo_bsc_rf *rf; + int rc; + + rf = talloc_zero(NULL, struct osmo_bsc_rf); + if (!rf) { + LOGP(DINP, LOGL_ERROR, "Failed to create osmo_bsc_rf.\n"); + return NULL; + } + + bfd = &rf->listen; + bfd->fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (bfd->fd < 0) { + LOGP(DINP, LOGL_ERROR, "Can not create socket. %d/%s\n", + errno, strerror(errno)); + return NULL; + } + + local.sun_family = AF_UNIX; + strncpy(local.sun_path, path, sizeof(local.sun_path)); + local.sun_path[sizeof(local.sun_path) - 1] = '\0'; + unlink(local.sun_path); + + /* we use the same magic that X11 uses in Xtranssock.c for + * calculating the proper length of the sockaddr */ +#if defined(BSD44SOCKETS) || defined(__UNIXWARE__) + local.sun_len = strlen(local.sun_path); +#endif +#if defined(BSD44SOCKETS) || defined(SUN_LEN) + namelen = SUN_LEN(&local); +#else + namelen = strlen(local.sun_path) + + offsetof(struct sockaddr_un, sun_path); +#endif + + rc = bind(bfd->fd, (struct sockaddr *) &local, namelen); + if (rc != 0) { + LOGP(DINP, LOGL_ERROR, "Failed to bind '%s' errno: %d/%s\n", + local.sun_path, errno, strerror(errno)); + close(bfd->fd); + talloc_free(rf); + return NULL; + } + + if (listen(bfd->fd, 0) != 0) { + LOGP(DINP, LOGL_ERROR, "Failed to listen: %d/%s\n", errno, strerror(errno)); + close(bfd->fd); + talloc_free(rf); + return NULL; + } + + bfd->when = BSC_FD_READ; + bfd->cb = rf_ctl_accept; + bfd->data = rf; + + if (bsc_register_fd(bfd) != 0) { + LOGP(DINP, LOGL_ERROR, "Failed to register bfd.\n"); + close(bfd->fd); + talloc_free(rf); + return NULL; + } + + rf->gsm_network = net; + rf->policy = S_RF_ON; + rf->last_state_command = ""; + + /* check the rf state */ + rf->rf_check.data = rf; + rf->rf_check.cb = rf_check_cb; + + /* delay cmd handling */ + rf->delay_cmd.data = rf; + rf->delay_cmd.cb = rf_delay_cmd_cb; + + return rf; +} + diff --git a/src/osmo-bsc/osmo_bsc_sccp.c b/src/osmo-bsc/osmo_bsc_sccp.c new file mode 100644 index 000000000..1abb47394 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_sccp.c @@ -0,0 +1,288 @@ +/* Interaction with the SCCP subsystem */ +/* + * (C) 2009-2010 by Holger Hans Peter Freyther + * (C) 2009-2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/* SCCP helper */ +#define SCCP_IT_TIMER 60 + +static LLIST_HEAD(active_connections); + +static void free_queued(struct osmo_bsc_sccp_con *conn) +{ + struct msgb *msg; + + while (!llist_empty(&conn->sccp_queue)) { + /* this is not allowed to fail */ + msg = msgb_dequeue(&conn->sccp_queue); + msgb_free(msg); + } + + conn->sccp_queue_size = 0; +} + +static void send_queued(struct osmo_bsc_sccp_con *conn) +{ + struct msgb *msg; + + while (!llist_empty(&conn->sccp_queue)) { + /* this is not allowed to fail */ + msg = msgb_dequeue(&conn->sccp_queue); + sccp_connection_write(conn->sccp, msg); + msgb_free(msg); + conn->sccp_queue_size -= 1; + } +} + +static void msc_outgoing_sccp_data(struct sccp_connection *conn, + struct msgb *msg, unsigned int len) +{ + struct osmo_bsc_sccp_con *bsc_con = + (struct osmo_bsc_sccp_con *) conn->data_ctx; + + bsc_handle_dt1(bsc_con, msg, len); +} + +static void msc_outgoing_sccp_state(struct sccp_connection *conn, int old_state) +{ + struct osmo_bsc_sccp_con *con_data; + + if (conn->connection_state >= SCCP_CONNECTION_STATE_RELEASE_COMPLETE) { + con_data = (struct osmo_bsc_sccp_con *) conn->data_ctx; + if(con_data->conn) { + LOGP(DMSC, LOGL_ERROR, + "ERROR: The lchan is still associated\n."); + gsm0808_clear(con_data->conn); + subscr_con_free(con_data->conn); + con_data->conn = NULL; + } + + con_data->sccp = NULL; + free_queued(con_data); + sccp_connection_free(conn); + bsc_delete_connection(con_data); + } else if (conn->connection_state == SCCP_CONNECTION_STATE_ESTABLISHED) { + LOGP(DMSC, LOGL_DEBUG, "Connection established: %p\n", conn); + con_data = (struct osmo_bsc_sccp_con *) conn->data_ctx; + + bsc_del_timer(&con_data->sccp_cc_timeout); + bsc_schedule_timer(&con_data->sccp_it_timeout, SCCP_IT_TIMER, 0); + + send_queued(con_data); + } +} + +static void bsc_sccp_force_free(struct osmo_bsc_sccp_con *data) +{ + if (data->conn) { + gsm0808_clear(data->conn); + subscr_con_free(data->conn); + data->conn = NULL; + } + + free_queued(data); + sccp_connection_force_free(data->sccp); + data->sccp = NULL; + bsc_delete_connection(data); +} + +static void sccp_it_timeout(void *_data) +{ + struct osmo_bsc_sccp_con *data = + (struct osmo_bsc_sccp_con *) _data; + + sccp_connection_send_it(data->sccp); + bsc_schedule_timer(&data->sccp_it_timeout, SCCP_IT_TIMER, 0); +} + +static void sccp_cc_timeout(void *_data) +{ + struct osmo_bsc_sccp_con *data = + (struct osmo_bsc_sccp_con *) _data; + + if (data->sccp->connection_state >= SCCP_CONNECTION_STATE_ESTABLISHED) + return; + + LOGP(DMSC, LOGL_ERROR, "The connection was never established.\n"); + bsc_sccp_force_free(data); +} + +static void msc_sccp_write_ipa(struct sccp_connection *conn, struct msgb *msg, void *data) +{ + struct gsm_network *net = (struct gsm_network *) data; + msc_queue_write(net->msc_data->msc_con, msg, IPAC_PROTO_SCCP); +} + +static int msc_sccp_accept(struct sccp_connection *connection, void *data) +{ + LOGP(DMSC, LOGL_DEBUG, "Rejecting incoming SCCP connection.\n"); + return -1; +} + +static int msc_sccp_read(struct msgb *msgb, unsigned int length, void *data) +{ + struct gsm_network *net = (struct gsm_network *) data; + return bsc_handle_udt(net, net->msc_data->msc_con, msgb, length); +} + +int bsc_queue_for_msc(struct osmo_bsc_sccp_con *conn, struct msgb *msg) +{ + struct sccp_connection *sccp = conn->sccp; + + if (sccp->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) { + LOGP(DMSC, LOGL_ERROR, "Connection closing, dropping packet on: %p\n", sccp); + msgb_free(msg); + } else if (sccp->connection_state == SCCP_CONNECTION_STATE_ESTABLISHED + && conn->sccp_queue_size == 0) { + sccp_connection_write(sccp, msg); + msgb_free(msg); + } else if (conn->sccp_queue_size > 10) { + LOGP(DMSC, LOGL_ERROR, "Connection closing, dropping packet on: %p\n", sccp); + msgb_free(msg); + } else { + LOGP(DMSC, LOGL_DEBUG, "Queueing packet on %p. Queue size: %d\n", sccp, conn->sccp_queue_size); + conn->sccp_queue_size += 1; + msgb_enqueue(&conn->sccp_queue, msg); + } + + return 0; +} + +int bsc_create_new_connection(struct gsm_subscriber_connection *conn) +{ + struct gsm_network *net; + struct osmo_bsc_sccp_con *bsc_con; + struct sccp_connection *sccp; + + net = conn->bts->network; + if (!net->msc_data->msc_con->is_authenticated) { + LOGP(DMSC, LOGL_ERROR, "Not connected to a MSC. Not forwarding data.\n"); + return -1; + } + + if (!bsc_grace_allow_new_connection(net)) { + LOGP(DMSC, LOGL_NOTICE, "BSC in grace period. No new connections.\n"); + return -1; + } + + sccp = sccp_connection_socket(); + if (!sccp) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate memory.\n"); + return -ENOMEM; + } + + bsc_con = talloc_zero(conn->bts, struct osmo_bsc_sccp_con); + if (!bsc_con) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate.\n"); + sccp_connection_free(sccp); + return -1; + } + + /* callbacks */ + sccp->state_cb = msc_outgoing_sccp_state; + sccp->data_cb = msc_outgoing_sccp_data; + sccp->data_ctx = bsc_con; + + /* prepare the timers */ + bsc_con->sccp_it_timeout.cb = sccp_it_timeout; + bsc_con->sccp_it_timeout.data = bsc_con; + bsc_con->sccp_cc_timeout.cb = sccp_cc_timeout; + bsc_con->sccp_cc_timeout.data = bsc_con; + + INIT_LLIST_HEAD(&bsc_con->sccp_queue); + + bsc_con->sccp = sccp; + bsc_con->msc_con = net->msc_data->msc_con; + bsc_con->conn = conn; + llist_add(&bsc_con->entry, &active_connections); + conn->sccp_con = bsc_con; + return 0; +} + +int bsc_open_connection(struct osmo_bsc_sccp_con *conn, struct msgb *msg) +{ + bsc_schedule_timer(&conn->sccp_cc_timeout, 10, 0); + sccp_connection_connect(conn->sccp, &sccp_ssn_bssap, msg); + msgb_free(msg); + return 0; +} + +int bsc_delete_connection(struct osmo_bsc_sccp_con *sccp) +{ + if (!sccp) + return 0; + + if (sccp->conn) + LOGP(DMSC, LOGL_ERROR, "Should have been cleared.\n"); + + llist_del(&sccp->entry); + bsc_del_timer(&sccp->sccp_it_timeout); + bsc_del_timer(&sccp->sccp_cc_timeout); + talloc_free(sccp); + return 0; +} + +static void bsc_close_connections(struct bsc_msc_connection *msc_con) +{ + struct osmo_bsc_sccp_con *con, *tmp; + + llist_for_each_entry_safe(con, tmp, &active_connections, entry) + bsc_sccp_force_free(con); +} + +static int handle_msc_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct osmo_msc_data *data; + + if (subsys != SS_MSC) + return 0; + + data = (struct osmo_msc_data *) signal_data; + if (signal == S_MSC_LOST) + bsc_close_connections(data->msc_con); + + return 0; +} + +int osmo_bsc_sccp_init(struct gsm_network *gsmnet) +{ + sccp_set_log_area(DSCCP); + sccp_system_init(msc_sccp_write_ipa, gsmnet); + sccp_connection_set_incoming(&sccp_ssn_bssap, msc_sccp_accept, NULL); + sccp_set_read(&sccp_ssn_bssap, msc_sccp_read, gsmnet); + + register_signal_handler(SS_MSC, handle_msc_signal, gsmnet); + + return 0; +} diff --git a/src/osmo-bsc/osmo_bsc_vty.c b/src/osmo-bsc/osmo_bsc_vty.c new file mode 100644 index 000000000..166774275 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_vty.c @@ -0,0 +1,311 @@ +/* Osmo BSC VTY Configuration */ +/* (C) 2009-2010 by Holger Hans Peter Freyther + * (C) 2009-2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include + + +#define IPA_STR "IP.ACCESS specific\n" + +extern struct gsm_network *bsc_gsmnet; + +static struct osmo_msc_data *osmo_msc_data(struct vty *vty) +{ + return bsc_gsmnet->msc_data; +} + +static struct cmd_node msc_node = { + MSC_NODE, + "%s(msc)#", + 1, +}; + +DEFUN(cfg_net_msc, cfg_net_msc_cmd, + "msc", "Configure MSC details") +{ + vty->index = bsc_gsmnet; + vty->node = MSC_NODE; + + return CMD_SUCCESS; +} + +static int config_write_msc(struct vty *vty) +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + + vty_out(vty, " msc%s", VTY_NEWLINE); + if (data->bsc_token) + vty_out(vty, " token %s%s", data->bsc_token, VTY_NEWLINE); + if (data->core_ncc != -1) + vty_out(vty, " core-mobile-network-code %d%s", + data->core_ncc, VTY_NEWLINE); + if (data->core_mcc != -1) + vty_out(vty, " core-mobile-country-code %d%s", + data->core_mcc, VTY_NEWLINE); + vty_out(vty, " ip.access rtp-base %d%s", data->rtp_base, VTY_NEWLINE); + vty_out(vty, " ip %s%s", data->msc_ip, VTY_NEWLINE); + vty_out(vty, " port %d%s", data->msc_port, VTY_NEWLINE); + vty_out(vty, " ip-dscp %d%s", data->msc_ip_dscp, VTY_NEWLINE); + vty_out(vty, " timeout-ping %d%s", data->ping_timeout, VTY_NEWLINE); + vty_out(vty, " timeout-pong %d%s", data->pong_timeout, VTY_NEWLINE); + if (data->mid_call_txt) + vty_out(vty, "mid-call-text %s%s", data->mid_call_txt, VTY_NEWLINE); + vty_out(vty, " mid-call-timeout %d%s", data->mid_call_timeout, VTY_NEWLINE); + if (data->ussd_welcome_txt) + vty_out(vty, " bsc-welcome-text %s%s", data->ussd_welcome_txt, VTY_NEWLINE); + + if (data->audio_length != 0) { + int i; + + vty_out(vty, " codec_list "); + for (i = 0; i < data->audio_length; ++i) { + if (i != 0) + vty_out(vty, ", "); + + if (data->audio_support[i]->hr) + vty_out(vty, "hr%.1u", data->audio_support[i]->ver); + else + vty_out(vty, "fr%.1u", data->audio_support[i]->ver); + } + vty_out(vty, "%s", VTY_NEWLINE); + + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_token, + cfg_net_bsc_token_cmd, + "token TOKEN", + "A token for the BSC to be sent to the MSC") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + + bsc_replace_string(data, &data->bsc_token, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_ncc, + cfg_net_bsc_ncc_cmd, + "core-mobile-network-code <1-999>", + "Use this network code for the backbone\n" "NCC value\n") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + data->core_ncc = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_mcc, + cfg_net_bsc_mcc_cmd, + "core-mobile-country-code <1-999>", + "Use this country code for the backbone\n" "MCC value\n") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + data->core_mcc = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_rtp_base, + cfg_net_bsc_rtp_base_cmd, + "ip.access rtp-base <1-65000>", + IPA_STR + "Set the rtp-base port for the RTP stream\n" + "Port number\n") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + data->rtp_base = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_codec_list, + cfg_net_bsc_codec_list_cmd, + "codec-list .LIST", + "Set the allowed audio codecs\n" + "List of audio codecs\n") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + int saw_fr, saw_hr; + int i; + + saw_fr = saw_hr = 0; + + /* free the old list... if it exists */ + if (data->audio_support) { + talloc_free(data->audio_support); + data->audio_support = NULL; + data->audio_length = 0; + } + + /* create a new array */ + data->audio_support = + talloc_zero_array(data, struct gsm_audio_support *, argc); + data->audio_length = argc; + + for (i = 0; i < argc; ++i) { + /* check for hrX or frX */ + if (strlen(argv[i]) != 3 + || argv[i][1] != 'r' + || (argv[i][0] != 'h' && argv[i][0] != 'f') + || argv[i][2] < 0x30 + || argv[i][2] > 0x39) + goto error; + + data->audio_support[i] = talloc_zero(data->audio_support, + struct gsm_audio_support); + data->audio_support[i]->ver = atoi(argv[i] + 2); + + if (strncmp("hr", argv[i], 2) == 0) { + data->audio_support[i]->hr = 1; + saw_hr = 1; + } else if (strncmp("fr", argv[i], 2) == 0) { + data->audio_support[i]->hr = 0; + saw_fr = 1; + } + + if (saw_hr && saw_fr) { + vty_out(vty, "Can not have full-rate and half-rate codec.%s", + VTY_NEWLINE); + return CMD_ERR_INCOMPLETE; + } + } + + return CMD_SUCCESS; + +error: + vty_out(vty, "Codec name must be hrX or frX. Was '%s'%s", + argv[i], VTY_NEWLINE); + return CMD_ERR_INCOMPLETE; +} + +DEFUN(cfg_net_msc_ip, + cfg_net_msc_ip_cmd, + "ip A.B.C.D", "Set the MSC/MUX IP address.") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + + bsc_replace_string(data, &data->msc_ip, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_port, + cfg_net_msc_port_cmd, + "port <1-65000>", + "Set the MSC/MUX port.") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + data->msc_port = atoi(argv[0]); + return CMD_SUCCESS; +} + + +DEFUN(cfg_net_msc_prio, + cfg_net_msc_prio_cmd, + "ip-dscp <0-255>", + "Set the IP_TOS socket attribite") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + data->msc_ip_dscp = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_ping_time, + cfg_net_msc_ping_time_cmd, + "timeout-ping NR", + "Set the PING interval, negative for not sending PING") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + data->ping_timeout = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_pong_time, + cfg_net_msc_pong_time_cmd, + "timeout-pong NR", + "Set the time to wait for a PONG.") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + data->pong_timeout = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_mid_call_text, + cfg_net_msc_mid_call_text_cmd, + "mid-call-text .TEXT", + "Set the USSD notifcation to be send.\n" "Text to be sent\n") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + char *txt = argv_concat(argv, argc, 0); + if (!txt) + return CMD_WARNING; + + bsc_replace_string(data, &data->mid_call_txt, txt); + talloc_free(txt); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_mid_call_timeout, + cfg_net_msc_mid_call_timeout_cmd, + "mid-call-timeout NR", + "Switch from Grace to Off in NR seconds.\n" "Timeout in seconds\n") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + data->mid_call_timeout = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_welcome_ussd, + cfg_net_msc_welcome_ussd_cmd, + "bsc-welcome-text .TEXT", + "Set the USSD notification to be sent.\n" "Text to be sent\n") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + char *str = argv_concat(argv, argc, 0); + if (!str) + return CMD_WARNING; + + bsc_replace_string(data, &data->ussd_welcome_txt, str); + talloc_free(str); + return CMD_SUCCESS; +} + +int bsc_vty_init_extra(void) +{ + install_element(GSMNET_NODE, &cfg_net_msc_cmd); + install_node(&msc_node, config_write_msc); + install_default(MSC_NODE); + install_element(MSC_NODE, &cfg_net_bsc_token_cmd); + install_element(MSC_NODE, &cfg_net_bsc_ncc_cmd); + install_element(MSC_NODE, &cfg_net_bsc_mcc_cmd); + install_element(MSC_NODE, &cfg_net_bsc_rtp_base_cmd); + install_element(MSC_NODE, &cfg_net_bsc_codec_list_cmd); + install_element(MSC_NODE, &cfg_net_msc_ip_cmd); + install_element(MSC_NODE, &cfg_net_msc_port_cmd); + install_element(MSC_NODE, &cfg_net_msc_prio_cmd); + install_element(MSC_NODE, &cfg_net_msc_ping_time_cmd); + install_element(MSC_NODE, &cfg_net_msc_pong_time_cmd); + install_element(MSC_NODE, &cfg_net_msc_mid_call_text_cmd); + install_element(MSC_NODE, &cfg_net_msc_mid_call_timeout_cmd); + install_element(MSC_NODE, &cfg_net_msc_welcome_ussd_cmd); + + return 0; +} diff --git a/src/osmo-bsc_mgcp/Makefile.am b/src/osmo-bsc_mgcp/Makefile.am new file mode 100644 index 000000000..32cc81300 --- /dev/null +++ b/src/osmo-bsc_mgcp/Makefile.am @@ -0,0 +1,10 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) + +bin_PROGRAMS = bsc_mgcp + +bsc_mgcp_SOURCES = mgcp_main.c +bsc_mgcp_LDADD = $(top_builddir)/src/libcommon/libcommon.a \ + $(top_builddir)/src/libmgcp/libmgcp.a \ + $(LIBOSMOVTY_LIBS) diff --git a/src/osmo-bsc_mgcp/Makefile.in b/src/osmo-bsc_mgcp/Makefile.in new file mode 100644 index 000000000..714fae3ac --- /dev/null +++ b/src/osmo-bsc_mgcp/Makefile.in @@ -0,0 +1,487 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +bin_PROGRAMS = bsc_mgcp$(EXEEXT) +subdir = src/osmo-bsc_mgcp +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_bsc_mgcp_OBJECTS = mgcp_main.$(OBJEXT) +bsc_mgcp_OBJECTS = $(am_bsc_mgcp_OBJECTS) +am__DEPENDENCIES_1 = +bsc_mgcp_DEPENDENCIES = $(top_builddir)/src/libcommon/libcommon.a \ + $(top_builddir)/src/libmgcp/libmgcp.a $(am__DEPENDENCIES_1) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(bsc_mgcp_SOURCES) +DIST_SOURCES = $(bsc_mgcp_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) +bsc_mgcp_SOURCES = mgcp_main.c +bsc_mgcp_LDADD = $(top_builddir)/src/libcommon/libcommon.a \ + $(top_builddir)/src/libmgcp/libmgcp.a \ + $(LIBOSMOVTY_LIBS) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/osmo-bsc_mgcp/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/osmo-bsc_mgcp/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +bsc_mgcp$(EXEEXT): $(bsc_mgcp_OBJECTS) $(bsc_mgcp_DEPENDENCIES) + @rm -f bsc_mgcp$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(bsc_mgcp_OBJECTS) $(bsc_mgcp_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mgcp_main.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-binPROGRAMS + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/osmo-bsc_mgcp/mgcp_main.c b/src/osmo-bsc_mgcp/mgcp_main.c new file mode 100644 index 000000000..c8d9a625e --- /dev/null +++ b/src/osmo-bsc_mgcp/mgcp_main.c @@ -0,0 +1,283 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The main method to drive it as a standalone process */ + +/* + * (C) 2009 by Holger Hans Peter Freyther + * (C) 2009 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../../bscconfig.h" + +/* this is here for the vty... it will never be called */ +void subscr_put() { abort(); } + +#define _GNU_SOURCE +#include + +#warning "Make use of the rtp proxy code" + +static struct mgcp_config *cfg; +static int reset_endpoints = 0; +static int daemonize = 0; + +const char *openbsc_copyright = + "Copyright (C) 2009-2010 Holger Freyther and On-Waves\r\n" + "Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\r\n" + "Dieter Spaar, Andreas Eversberg, Harald Welte\r\n\r\n" + "License AGPLv3+: GNU AGPL version 3 or later \r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +static char *config_file = "mgcp.cfg"; + +/* used by msgb and mgcp */ +void *tall_bsc_ctx = NULL; + +static void print_help() +{ + printf("Some useful help...\n"); + printf(" -h --help is printing this text.\n"); + printf(" -c --config-file filename The config file to use.\n"); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"config-file", 1, 0, 'c'}, + {"daemonize", 0, 0, 'D'}, + {"version", 0, 0, 'V'}, + {0, 0, 0, 0}, + }; + + c = getopt_long(argc, argv, "hc:VD", long_options, &option_index); + + if (c == -1) + break; + + switch(c) { + case 'h': + print_help(); + exit(0); + break; + case 'c': + config_file = talloc_strdup(tall_bsc_ctx, optarg); + break; + case 'V': + print_version(1); + exit(0); + break; + case 'D': + daemonize = 1; + break; + default: + /* ignore */ + break; + }; + } +} + +/* simply remember this */ +static int mgcp_rsip_cb(struct mgcp_config *cfg) +{ + reset_endpoints = 1; + + return 0; +} + +static int mgcp_change_cb(struct mgcp_trunk_config *cfg, int endpoint, int state) +{ + if (state != MGCP_ENDP_MDCX) + return 0; + + mgcp_send_dummy(&cfg->endpoints[endpoint]); + return 0; +} + +static int read_call_agent(struct bsc_fd *fd, unsigned int what) +{ + struct sockaddr_in addr; + socklen_t slen = sizeof(addr); + struct msgb *msg; + struct msgb *resp; + int i; + + msg = (struct msgb *) fd->data; + + /* read one less so we can use it as a \0 */ + int rc = recvfrom(cfg->gw_fd.bfd.fd, msg->data, msg->data_len - 1, 0, + (struct sockaddr *) &addr, &slen); + if (rc < 0) { + perror("Gateway failed to read"); + return -1; + } else if (slen > sizeof(addr)) { + fprintf(stderr, "Gateway received message from outerspace: %lu %d\n", + slen, sizeof(addr)); + return -1; + } + + /* handle message now */ + msg->l2h = msgb_put(msg, rc); + resp = mgcp_handle_message(cfg, msg); + msgb_reset(msg); + + if (resp) { + sendto(cfg->gw_fd.bfd.fd, resp->l2h, msgb_l2len(resp), 0, (struct sockaddr *) &addr, sizeof(addr)); + msgb_free(resp); + } + + if (reset_endpoints) { + LOGP(DMGCP, LOGL_NOTICE, "Asked to reset endpoints.\n"); + reset_endpoints = 0; + + /* is checking in_addr.s_addr == INADDR_LOOPBACK making it more secure? */ + for (i = 1; i < cfg->trunk.number_endpoints; ++i) + mgcp_free_endp(&cfg->trunk.endpoints[i]); + } + + return 0; +} + +extern enum node_type bsc_vty_go_parent(struct vty *vty); + +static struct vty_app_info vty_info = { + .name = "OpenBSC MGCP", + .version = PACKAGE_VERSION, + .go_parent_cb = bsc_vty_go_parent, + .is_config_node = bsc_vty_is_config_node, +}; + +int main(int argc, char **argv) +{ + struct gsm_network dummy_network; + struct sockaddr_in addr; + int on = 1, rc; + struct log_target *stderr_target; + + tall_bsc_ctx = talloc_named_const(NULL, 1, "mgcp-callagent"); + + log_init(&log_info); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + log_set_all_filter(stderr_target, 1); + + cfg = mgcp_config_alloc(); + if (!cfg) + return -1; + + vty_info.copyright = openbsc_copyright; + vty_init(&vty_info); + logging_vty_add_cmds(); + mgcp_vty_init(); + + handle_options(argc, argv); + + rc = mgcp_parse_config(config_file, cfg); + if (rc < 0) + return rc; + + rc = telnet_init(tall_bsc_ctx, &dummy_network, 4243); + if (rc < 0) + return rc; + + /* set some callbacks */ + cfg->reset_cb = mgcp_rsip_cb; + cfg->change_cb = mgcp_change_cb; + + /* we need to bind a socket */ + if (rc == 0) { + cfg->gw_fd.bfd.when = BSC_FD_READ; + cfg->gw_fd.bfd.cb = read_call_agent; + cfg->gw_fd.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0); + if (cfg->gw_fd.bfd.fd < 0) { + perror("Gateway failed to listen"); + return -1; + } + + setsockopt(cfg->gw_fd.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(cfg->source_port); + inet_aton(cfg->source_addr, &addr.sin_addr); + + if (bind(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Gateway failed to bind"); + return -1; + } + + cfg->gw_fd.bfd.data = msgb_alloc(4096, "mgcp-msg"); + if (!cfg->gw_fd.bfd.data) { + fprintf(stderr, "Gateway memory error.\n"); + return -1; + } + + + if (bsc_register_fd(&cfg->gw_fd.bfd) != 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to register the fd\n"); + return -1; + } + + LOGP(DMGCP, LOGL_NOTICE, "Configured for MGCP.\n"); + } + + /* initialisation */ + srand(time(NULL)); + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + /* main loop */ + while (1) { + bsc_select_main(0); + } + + + return 0; +} diff --git a/src/osmo-bsc_nat/Makefile.am b/src/osmo-bsc_nat/Makefile.am new file mode 100644 index 000000000..98a343108 --- /dev/null +++ b/src/osmo-bsc_nat/Makefile.am @@ -0,0 +1,15 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS) + +bin_PROGRAMS = osmo-bsc_nat + + +osmo_bsc_nat_SOURCES = bsc_filter.c bsc_mgcp_utils.c bsc_nat.c bsc_nat_utils.c \ + bsc_nat_vty.c bsc_sccp.c bsc_ussd.c +osmo_bsc_nat_LDADD = $(top_builddir)/src/libcommon/libcommon.a \ + $(top_builddir)/src/libmgcp/libmgcp.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + -lrt $(LIBOSMOSCCP_LIBS) diff --git a/src/osmo-bsc_nat/Makefile.in b/src/osmo-bsc_nat/Makefile.in new file mode 100644 index 000000000..ba69f7edb --- /dev/null +++ b/src/osmo-bsc_nat/Makefile.in @@ -0,0 +1,504 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +bin_PROGRAMS = osmo-bsc_nat$(EXEEXT) +subdir = src/osmo-bsc_nat +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_osmo_bsc_nat_OBJECTS = bsc_filter.$(OBJEXT) \ + bsc_mgcp_utils.$(OBJEXT) bsc_nat.$(OBJEXT) \ + bsc_nat_utils.$(OBJEXT) bsc_nat_vty.$(OBJEXT) \ + bsc_sccp.$(OBJEXT) bsc_ussd.$(OBJEXT) +osmo_bsc_nat_OBJECTS = $(am_osmo_bsc_nat_OBJECTS) +am__DEPENDENCIES_1 = +osmo_bsc_nat_DEPENDENCIES = $(top_builddir)/src/libcommon/libcommon.a \ + $(top_builddir)/src/libmgcp/libmgcp.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libtrau/libtrau.a $(am__DEPENDENCIES_1) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(osmo_bsc_nat_SOURCES) +DIST_SOURCES = $(osmo_bsc_nat_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS) +osmo_bsc_nat_SOURCES = bsc_filter.c bsc_mgcp_utils.c bsc_nat.c bsc_nat_utils.c \ + bsc_nat_vty.c bsc_sccp.c bsc_ussd.c + +osmo_bsc_nat_LDADD = $(top_builddir)/src/libcommon/libcommon.a \ + $(top_builddir)/src/libmgcp/libmgcp.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + -lrt $(LIBOSMOSCCP_LIBS) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/osmo-bsc_nat/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/osmo-bsc_nat/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +osmo-bsc_nat$(EXEEXT): $(osmo_bsc_nat_OBJECTS) $(osmo_bsc_nat_DEPENDENCIES) + @rm -f osmo-bsc_nat$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(osmo_bsc_nat_OBJECTS) $(osmo_bsc_nat_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_filter.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_mgcp_utils.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_nat.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_nat_utils.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_nat_vty.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_sccp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_ussd.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-binPROGRAMS + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/osmo-bsc_nat/bsc_filter.c b/src/osmo-bsc_nat/bsc_filter.c new file mode 100644 index 000000000..73e987893 --- /dev/null +++ b/src/osmo-bsc_nat/bsc_filter.c @@ -0,0 +1,216 @@ +/* BSC Multiplexer/NAT */ + +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include + +#include + +/* + * The idea is to have a simple struct describing a IPA packet with + * SCCP SSN and the GSM 08.08 payload and decide. We will both have + * a white and a blacklist of packets we want to handle. + * + * TODO: Implement a "NOT" in the filter language. + */ + +#define ALLOW_ANY -1 + +#define FILTER_TO_BSC 1 +#define FILTER_TO_MSC 2 +#define FILTER_TO_BOTH 3 + + +struct bsc_pkt_filter { + int ipa_proto; + int dest_ssn; + int bssap; + int gsm; + int filter_dir; +}; + +static struct bsc_pkt_filter black_list[] = { + /* filter reset messages to the MSC */ + { IPAC_PROTO_SCCP, SCCP_SSN_BSSAP, 0, BSS_MAP_MSG_RESET, FILTER_TO_MSC }, + + /* filter reset ack messages to the BSC */ + { IPAC_PROTO_SCCP, SCCP_SSN_BSSAP, 0, BSS_MAP_MSG_RESET_ACKNOWLEDGE, FILTER_TO_BSC }, + + /* filter ip access */ + { IPAC_PROTO_IPACCESS, ALLOW_ANY, ALLOW_ANY, ALLOW_ANY, FILTER_TO_MSC }, +}; + +static struct bsc_pkt_filter white_list[] = { + /* allow IPAC_PROTO_SCCP messages to both sides */ + { IPAC_PROTO_SCCP, ALLOW_ANY, ALLOW_ANY, ALLOW_ANY, FILTER_TO_BOTH }, + + /* allow MGCP messages to both sides */ + { IPAC_PROTO_MGCP_OLD, ALLOW_ANY, ALLOW_ANY, ALLOW_ANY, FILTER_TO_BOTH }, +}; + +struct bsc_nat_parsed *bsc_nat_parse(struct msgb *msg) +{ + struct sccp_parse_result result; + struct bsc_nat_parsed *parsed; + struct ipaccess_head *hh; + + /* quick fail */ + if (msg->len < 4) + return NULL; + + parsed = talloc_zero(msg, struct bsc_nat_parsed); + if (!parsed) + return NULL; + + /* more init */ + parsed->ipa_proto = parsed->called_ssn = parsed->calling_ssn = -1; + parsed->sccp_type = parsed->bssap = parsed->gsm_type = -1; + + /* start parsing */ + hh = (struct ipaccess_head *) msg->data; + parsed->ipa_proto = hh->proto; + + msg->l2h = &hh->data[0]; + + /* do a size check on the input */ + if (ntohs(hh->len) != msgb_l2len(msg)) { + LOGP(DINP, LOGL_ERROR, "Wrong input length?\n"); + talloc_free(parsed); + return NULL; + } + + /* analyze sccp down here */ + if (parsed->ipa_proto == IPAC_PROTO_SCCP) { + memset(&result, 0, sizeof(result)); + if (sccp_parse_header(msg, &result) != 0) { + talloc_free(parsed); + return 0; + } + + if (msg->l3h && msgb_l3len(msg) < 3) { + LOGP(DNAT, LOGL_ERROR, "Not enough space or GSM payload\n"); + talloc_free(parsed); + return 0; + } + + parsed->sccp_type = sccp_determine_msg_type(msg); + parsed->src_local_ref = result.source_local_reference; + parsed->dest_local_ref = result.destination_local_reference; + parsed->called_ssn = result.called.ssn; + parsed->calling_ssn = result.calling.ssn; + + /* in case of connection confirm we have no payload */ + if (msg->l3h) { + parsed->bssap = msg->l3h[0]; + parsed->gsm_type = msg->l3h[2]; + } + } + + return parsed; +} + +int bsc_nat_filter_ipa(int dir, struct msgb *msg, struct bsc_nat_parsed *parsed) +{ + int i; + + /* go through the blacklist now */ + for (i = 0; i < ARRAY_SIZE(black_list); ++i) { + /* ignore the rule? */ + if (black_list[i].filter_dir != FILTER_TO_BOTH + && black_list[i].filter_dir != dir) + continue; + + /* the proto is not blacklisted */ + if (black_list[i].ipa_proto != ALLOW_ANY + && black_list[i].ipa_proto != parsed->ipa_proto) + continue; + + if (parsed->ipa_proto == IPAC_PROTO_SCCP) { + /* the SSN is not blacklisted */ + if (black_list[i].dest_ssn != ALLOW_ANY + && black_list[i].dest_ssn != parsed->called_ssn) + continue; + + /* bssap */ + if (black_list[i].bssap != ALLOW_ANY + && black_list[i].bssap != parsed->bssap) + continue; + + /* gsm */ + if (black_list[i].gsm != ALLOW_ANY + && black_list[i].gsm != parsed->gsm_type) + continue; + + /* blacklisted */ + LOGP(DNAT, LOGL_INFO, "Blacklisted with rule %d\n", i); + return 1; + } else { + /* blacklisted, we have no content sniffing yet */ + LOGP(DNAT, LOGL_INFO, "Blacklisted with rule %d\n", i); + return 1; + } + } + + /* go through the whitelust now */ + for (i = 0; i < ARRAY_SIZE(white_list); ++i) { + /* ignore the rule? */ + if (white_list[i].filter_dir != FILTER_TO_BOTH + && white_list[i].filter_dir != dir) + continue; + + /* the proto is not whitelisted */ + if (white_list[i].ipa_proto != ALLOW_ANY + && white_list[i].ipa_proto != parsed->ipa_proto) + continue; + + if (parsed->ipa_proto == IPAC_PROTO_SCCP) { + /* the SSN is not whitelisted */ + if (white_list[i].dest_ssn != ALLOW_ANY + && white_list[i].dest_ssn != parsed->called_ssn) + continue; + + /* bssap */ + if (white_list[i].bssap != ALLOW_ANY + && white_list[i].bssap != parsed->bssap) + continue; + + /* gsm */ + if (white_list[i].gsm != ALLOW_ANY + && white_list[i].gsm != parsed->gsm_type) + continue; + + /* whitelisted */ + LOGP(DNAT, LOGL_INFO, "Whitelisted with rule %d\n", i); + return 0; + } else { + /* whitelisted */ + return 0; + } + } + + return 1; +} diff --git a/src/osmo-bsc_nat/bsc_mgcp_utils.c b/src/osmo-bsc_nat/bsc_mgcp_utils.c new file mode 100644 index 000000000..9eac00bf4 --- /dev/null +++ b/src/osmo-bsc_nat/bsc_mgcp_utils.c @@ -0,0 +1,764 @@ +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +int bsc_mgcp_nr_multiplexes(int max_endpoints) +{ + int div = max_endpoints / 32; + + if ((max_endpoints % 32) != 0) + div += 1; + + return div; +} + +static int bsc_init_endps_if_needed(struct bsc_connection *con) +{ + int multiplexes; + + /* we have done that */ + if (con->_endpoint_status) + return 0; + + /* we have no config... */ + if (!con->cfg) + return -1; + + multiplexes = bsc_mgcp_nr_multiplexes(con->cfg->max_endpoints); + con->number_multiplexes = multiplexes; + con->max_endpoints = con->cfg->max_endpoints; + con->_endpoint_status = talloc_zero_array(con, char, 32 * multiplexes + 1); + return con->_endpoint_status == NULL; +} + +static int bsc_assign_endpoint(struct bsc_connection *bsc, struct sccp_connections *con) +{ + int multiplex; + int timeslot; + const int number_endpoints = bsc->max_endpoints; + int i; + + mgcp_endpoint_to_timeslot(bsc->last_endpoint, &multiplex, ×lot); + timeslot += 1; + + for (i = 0; i < number_endpoints; ++i) { + int endpoint; + + /* Wrap around timeslots */ + if (timeslot == 0) + timeslot = 1; + + if (timeslot == 0x1f) { + timeslot = 1; + multiplex += 1; + } + + /* Wrap around the multiplex */ + if (multiplex >= bsc->number_multiplexes) + multiplex = 0; + + endpoint = mgcp_timeslot_to_endpoint(multiplex, timeslot); + + /* Now check if we are allowed to assign this one */ + if (endpoint >= bsc->max_endpoints) { + multiplex = 0; + timeslot = 1; + endpoint = mgcp_timeslot_to_endpoint(multiplex, timeslot); + } + + + if (bsc->_endpoint_status[endpoint] == 0) { + bsc->_endpoint_status[endpoint] = 1; + con->bsc_endp = endpoint; + bsc->last_endpoint = endpoint; + return 0; + } + + timeslot += 1; + } + + return -1; +} + +static uint16_t create_cic(int endpoint) +{ + int timeslot, multiplex; + + mgcp_endpoint_to_timeslot(endpoint, &multiplex, ×lot); + return (multiplex << 5) | (timeslot & 0x1f); +} + +int bsc_mgcp_assign_patch(struct sccp_connections *con, struct msgb *msg) +{ + struct sccp_connections *mcon; + struct tlv_parsed tp; + uint16_t cic; + uint8_t timeslot; + uint8_t multiplex; + unsigned int endp; + + if (!msg->l3h) { + LOGP(DNAT, LOGL_ERROR, "Assignment message should have l3h pointer.\n"); + return -1; + } + + if (msgb_l3len(msg) < 3) { + LOGP(DNAT, LOGL_ERROR, "Assignment message has not enough space for GSM0808.\n"); + return -1; + } + + tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 3, msgb_l3len(msg) - 3, 0, 0); + if (!TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) { + LOGP(DNAT, LOGL_ERROR, "Circuit identity code not found in assignment message.\n"); + return -1; + } + + cic = ntohs(*(uint16_t *)TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)); + timeslot = cic & 0x1f; + multiplex = (cic & ~0x1f) >> 5; + + + endp = mgcp_timeslot_to_endpoint(multiplex, timeslot); + + if (endp >= con->bsc->nat->mgcp_cfg->trunk.number_endpoints) { + LOGP(DNAT, LOGL_ERROR, + "MSC attempted to assign bad endpoint 0x%x\n", + endp); + return -1; + } + + /* find stale connections using that endpoint */ + llist_for_each_entry(mcon, &con->bsc->nat->sccp_connections, list_entry) { + if (mcon->msc_endp == endp) { + LOGP(DNAT, LOGL_ERROR, + "Endpoint %d was assigned to 0x%x and now 0x%x\n", + endp, + sccp_src_ref_to_int(&mcon->patched_ref), + sccp_src_ref_to_int(&con->patched_ref)); + bsc_mgcp_dlcx(mcon); + } + } + + con->msc_endp = endp; + if (bsc_init_endps_if_needed(con->bsc) != 0) + return -1; + if (bsc_assign_endpoint(con->bsc, con) != 0) + return -1; + + /* + * now patch the message for the new CIC... + * still assumed to be one multiplex only + */ + cic = htons(create_cic(con->bsc_endp)); + memcpy((uint8_t *) TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE), + &cic, sizeof(cic)); + + return 0; +} + +static void bsc_mgcp_free_endpoint(struct bsc_nat *nat, int i) +{ + if (nat->bsc_endpoints[i].transaction_id) { + talloc_free(nat->bsc_endpoints[i].transaction_id); + nat->bsc_endpoints[i].transaction_id = NULL; + } + + nat->bsc_endpoints[i].transaction_state = 0; + nat->bsc_endpoints[i].bsc = NULL; +} + +void bsc_mgcp_free_endpoints(struct bsc_nat *nat) +{ + int i; + + for (i = 1; i < nat->mgcp_cfg->trunk.number_endpoints; ++i){ + bsc_mgcp_free_endpoint(nat, i); + mgcp_free_endp(&nat->mgcp_cfg->trunk.endpoints[i]); + } +} + +/* send a MDCX where we do not want a response */ +static void bsc_mgcp_send_mdcx(struct bsc_connection *bsc, int port, struct mgcp_endpoint *endp) +{ + char buf[2096]; + int len; + + len = snprintf(buf, sizeof(buf), + "MDCX 23 %x@mgw MGCP 1.0\r\n" + "Z: noanswer\r\n" + "\r\n" + "c=IN IP4 %s\r\n" + "m=audio %d RTP/AVP 255\r\n", + port, + bsc->nat->mgcp_cfg->source_addr, + endp->bts_end.local_port); + if (len < 0) { + LOGP(DMGCP, LOGL_ERROR, "snprintf for DLCX failed.\n"); + return; + } + + #warning "The MDCX is not send to the BSC. It should" +} + +static void bsc_mgcp_send_dlcx(struct bsc_connection *bsc, int endpoint) +{ + char buf[2096]; + int len; + + len = snprintf(buf, sizeof(buf), + "DLCX 26 %x@mgw MGCP 1.0\r\n" + "Z: noanswer\r\n", endpoint); + if (len < 0) { + LOGP(DMGCP, LOGL_ERROR, "snprintf for DLCX failed.\n"); + return; + } + + bsc_write_mgcp(bsc, (uint8_t *) buf, len); +} + +void bsc_mgcp_init(struct sccp_connections *con) +{ + con->msc_endp = -1; + con->bsc_endp = -1; +} + +void bsc_mgcp_dlcx(struct sccp_connections *con) +{ + /* send a DLCX down the stream */ + if (con->bsc_endp != -1 && con->bsc->_endpoint_status) { + if (con->bsc->_endpoint_status[con->bsc_endp] != 1) + LOGP(DNAT, LOGL_ERROR, "Endpoint 0x%x was not in use\n", con->bsc_endp); + con->bsc->_endpoint_status[con->bsc_endp] = 0; + bsc_mgcp_send_dlcx(con->bsc, con->bsc_endp); + bsc_mgcp_free_endpoint(con->bsc->nat, con->msc_endp); + } + + bsc_mgcp_init(con); +} + + +struct sccp_connections *bsc_mgcp_find_con(struct bsc_nat *nat, int endpoint) +{ + struct sccp_connections *con = NULL; + struct sccp_connections *sccp; + + llist_for_each_entry(sccp, &nat->sccp_connections, list_entry) { + if (sccp->msc_endp == -1) + continue; + if (sccp->msc_endp != endpoint) + continue; + + con = sccp; + } + + if (con) + return con; + + LOGP(DMGCP, LOGL_ERROR, "Failed to find the connection.\n"); + return NULL; +} + +int bsc_mgcp_policy_cb(struct mgcp_trunk_config *tcfg, int endpoint, int state, const char *transaction_id) +{ + struct bsc_nat *nat; + struct bsc_endpoint *bsc_endp; + struct sccp_connections *sccp; + struct mgcp_endpoint *mgcp_endp; + struct msgb *bsc_msg; + + nat = tcfg->cfg->data; + bsc_endp = &nat->bsc_endpoints[endpoint]; + mgcp_endp = &nat->mgcp_cfg->trunk.endpoints[endpoint]; + + if (bsc_endp->transaction_id) { + LOGP(DMGCP, LOGL_ERROR, "Endpoint 0x%x had pending transaction: '%s'\n", + endpoint, bsc_endp->transaction_id); + talloc_free(bsc_endp->transaction_id); + bsc_endp->transaction_id = NULL; + bsc_endp->transaction_state = 0; + } + bsc_endp->bsc = NULL; + + sccp = bsc_mgcp_find_con(nat, endpoint); + + if (!sccp) { + LOGP(DMGCP, LOGL_ERROR, "Did not find BSC for change on endpoint: 0x%x state: %d\n", endpoint, state); + + switch (state) { + case MGCP_ENDP_CRCX: + return MGCP_POLICY_REJECT; + break; + case MGCP_ENDP_DLCX: + return MGCP_POLICY_CONT; + break; + case MGCP_ENDP_MDCX: + return MGCP_POLICY_CONT; + break; + default: + LOGP(DMGCP, LOGL_FATAL, "Unhandled state: %d\n", state); + return MGCP_POLICY_CONT; + break; + } + } + + /* we need to generate a new and patched message */ + bsc_msg = bsc_mgcp_rewrite((char *) nat->mgcp_msg, nat->mgcp_length, sccp->bsc_endp, + nat->mgcp_cfg->source_addr, mgcp_endp->bts_end.local_port); + if (!bsc_msg) { + LOGP(DMGCP, LOGL_ERROR, "Failed to patch the msg.\n"); + return MGCP_POLICY_CONT; + } + + + bsc_endp->transaction_id = talloc_strdup(nat, transaction_id); + bsc_endp->transaction_state = state; + bsc_endp->bsc = sccp->bsc; + + /* we need to update some bits */ + if (state == MGCP_ENDP_CRCX) { + struct sockaddr_in sock; + socklen_t len = sizeof(sock); + if (getpeername(sccp->bsc->write_queue.bfd.fd, (struct sockaddr *) &sock, &len) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Can not get the peername...%d/%s\n", + errno, strerror(errno)); + } else { + mgcp_endp->bts_end.addr = sock.sin_addr; + } + + /* send the message and a fake MDCX to force sending of a dummy packet */ + bsc_write(sccp->bsc, bsc_msg, IPAC_PROTO_MGCP_OLD); + bsc_mgcp_send_mdcx(sccp->bsc, sccp->bsc_endp, mgcp_endp); + return MGCP_POLICY_DEFER; + } else if (state == MGCP_ENDP_DLCX) { + /* we will free the endpoint now and send a DLCX to the BSC */ + msgb_free(bsc_msg); + bsc_mgcp_dlcx(sccp); + return MGCP_POLICY_CONT; + } else { + bsc_write(sccp->bsc, bsc_msg, IPAC_PROTO_MGCP_OLD); + return MGCP_POLICY_DEFER; + } +} + +/* + * We do have a failure, free data downstream.. + */ +static void free_chan_downstream(struct mgcp_endpoint *endp, struct bsc_endpoint *bsc_endp, + struct bsc_connection *bsc) +{ + LOGP(DMGCP, LOGL_ERROR, "No CI, freeing endpoint 0x%x in state %d\n", + ENDPOINT_NUMBER(endp), bsc_endp->transaction_state); + + /* if a CRCX failed... send a DLCX down the stream */ + if (bsc_endp->transaction_state == MGCP_ENDP_CRCX) { + struct sccp_connections *con; + con = bsc_mgcp_find_con(bsc->nat, ENDPOINT_NUMBER(endp)); + if (!con) { + LOGP(DMGCP, LOGL_ERROR, + "No SCCP connection for endp 0x%x\n", + ENDPOINT_NUMBER(endp)); + } else { + if (con->bsc == bsc) { + bsc_mgcp_send_dlcx(bsc, con->bsc_endp); + } else { + LOGP(DMGCP, LOGL_ERROR, + "Endpoint belongs to a different BSC\n"); + } + } + } + + bsc_mgcp_free_endpoint(bsc->nat, ENDPOINT_NUMBER(endp)); + mgcp_free_endp(endp); +} + +/* + * We have received a msg from the BSC. We will see if we know + * this transaction and if it belongs to the BSC. Then we will + * need to patch the content to point to the local network and we + * need to update the I: that was assigned by the BSS. + */ +void bsc_mgcp_forward(struct bsc_connection *bsc, struct msgb *msg) +{ + struct msgb *output; + struct bsc_endpoint *bsc_endp = NULL; + struct mgcp_endpoint *endp = NULL; + int i, code; + char transaction_id[60]; + + /* Some assumption that our buffer is big enough.. and null terminate */ + if (msgb_l2len(msg) > 2000) { + LOGP(DMGCP, LOGL_ERROR, "MGCP message too long.\n"); + return; + } + + msg->l2h[msgb_l2len(msg)] = '\0'; + + if (bsc_mgcp_parse_response((const char *) msg->l2h, &code, transaction_id) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to parse response code.\n"); + return; + } + + for (i = 1; i < bsc->nat->mgcp_cfg->trunk.number_endpoints; ++i) { + if (bsc->nat->bsc_endpoints[i].bsc != bsc) + continue; + /* no one listening? a bug? */ + if (!bsc->nat->bsc_endpoints[i].transaction_id) + continue; + if (strcmp(transaction_id, bsc->nat->bsc_endpoints[i].transaction_id) != 0) + continue; + + endp = &bsc->nat->mgcp_cfg->trunk.endpoints[i]; + bsc_endp = &bsc->nat->bsc_endpoints[i]; + break; + } + + if (!bsc_endp) { + LOGP(DMGCP, LOGL_ERROR, "Could not find active endpoint: %s for msg: '%s'\n", + transaction_id, (const char *) msg->l2h); + return; + } + + endp->ci = bsc_mgcp_extract_ci((const char *) msg->l2h); + if (endp->ci == CI_UNUSED) { + free_chan_downstream(endp, bsc_endp, bsc); + return; + } + + /* free some stuff */ + talloc_free(bsc_endp->transaction_id); + bsc_endp->transaction_id = NULL; + bsc_endp->transaction_state = 0; + + /* + * rewrite the information. In case the endpoint was deleted + * there should be nothing for us to rewrite so putting endp->rtp_port + * with the value of 0 should be no problem. + */ + output = bsc_mgcp_rewrite((char * ) msg->l2h, msgb_l2len(msg), -1, + bsc->nat->mgcp_cfg->source_addr, endp->net_end.local_port); + + if (!output) { + LOGP(DMGCP, LOGL_ERROR, "Failed to rewrite MGCP msg.\n"); + return; + } + + if (write_queue_enqueue(&bsc->nat->mgcp_cfg->gw_fd, output) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to queue MGCP msg.\n"); + msgb_free(output); + } +} + +int bsc_mgcp_parse_response(const char *str, int *code, char transaction[60]) +{ + /* we want to parse two strings */ + return sscanf(str, "%3d %59s\n", code, transaction) != 2; +} + +uint32_t bsc_mgcp_extract_ci(const char *str) +{ + unsigned int ci; + char *res = strstr(str, "I: "); + if (!res) { + LOGP(DMGCP, LOGL_ERROR, "No CI in msg '%s'\n", str); + return CI_UNUSED; + } + + if (sscanf(res, "I: %u", &ci) != 1) { + LOGP(DMGCP, LOGL_ERROR, "Failed to parse CI in msg '%s'\n", str); + return CI_UNUSED; + } + + return ci; +} + +static void patch_mgcp(struct msgb *output, const char *op, const char *tok, + int endp, int len, int cr) +{ + int slen; + int ret; + char buf[40]; + + buf[0] = buf[39] = '\0'; + ret = sscanf(tok, "%*s %s", buf); + + slen = sprintf((char *) output->l3h, "%s %s %x@mgw MGCP 1.0%s", + op, buf, endp, cr ? "\r\n" : "\n"); + output->l3h = msgb_put(output, slen); +} + +/* we need to replace some strings... */ +struct msgb *bsc_mgcp_rewrite(char *input, int length, int endpoint, const char *ip, int port) +{ + static const char *crcx_str = "CRCX "; + static const char *dlcx_str = "DLCX "; + static const char *mdcx_str = "MDCX "; + + static const char *ip_str = "c=IN IP4 "; + static const char *aud_str = "m=audio "; + + char buf[128]; + char *running, *token; + struct msgb *output; + + if (length > 4096 - 128) { + LOGP(DMGCP, LOGL_ERROR, "Input is too long.\n"); + return NULL; + } + + output = msgb_alloc_headroom(4096, 128, "MGCP rewritten"); + if (!output) { + LOGP(DMGCP, LOGL_ERROR, "Failed to allocate new MGCP msg.\n"); + return NULL; + } + + running = input; + output->l2h = output->data; + output->l3h = output->l2h; + for (token = strsep(&running, "\n"); running; token = strsep(&running, "\n")) { + int len = strlen(token); + int cr = len > 0 && token[len - 1] == '\r'; + + if (strncmp(crcx_str, token, (sizeof crcx_str) - 1) == 0) { + patch_mgcp(output, "CRCX", token, endpoint, len, cr); + } else if (strncmp(dlcx_str, token, (sizeof dlcx_str) - 1) == 0) { + patch_mgcp(output, "DLCX", token, endpoint, len, cr); + } else if (strncmp(mdcx_str, token, (sizeof mdcx_str) - 1) == 0) { + patch_mgcp(output, "MDCX", token, endpoint, len, cr); + } else if (strncmp(ip_str, token, (sizeof ip_str) - 1) == 0) { + output->l3h = msgb_put(output, strlen(ip_str)); + memcpy(output->l3h, ip_str, strlen(ip_str)); + output->l3h = msgb_put(output, strlen(ip)); + memcpy(output->l3h, ip, strlen(ip)); + + if (cr) { + output->l3h = msgb_put(output, 2); + output->l3h[0] = '\r'; + output->l3h[1] = '\n'; + } else { + output->l3h = msgb_put(output, 1); + output->l3h[0] = '\n'; + } + } else if (strncmp(aud_str, token, (sizeof aud_str) - 1) == 0) { + int payload; + if (sscanf(token, "m=audio %*d RTP/AVP %d", &payload) != 1) { + LOGP(DMGCP, LOGL_ERROR, "Could not parsed audio line.\n"); + msgb_free(output); + return NULL; + } + + snprintf(buf, sizeof(buf)-1, "m=audio %d RTP/AVP %d%s", + port, payload, cr ? "\r\n" : "\n"); + buf[sizeof(buf)-1] = '\0'; + + output->l3h = msgb_put(output, strlen(buf)); + memcpy(output->l3h, buf, strlen(buf)); + } else { + output->l3h = msgb_put(output, len + 1); + memcpy(output->l3h, token, len); + output->l3h[len] = '\n'; + } + } + + return output; +} + +static int mgcp_do_read(struct bsc_fd *fd) +{ + struct bsc_nat *nat; + struct msgb *msg, *resp; + int rc; + + nat = fd->data; + + rc = read(fd->fd, nat->mgcp_msg, sizeof(nat->mgcp_msg) - 1); + if (rc <= 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to read errno: %d\n", errno); + return -1; + } + + nat->mgcp_msg[rc] = '\0'; + nat->mgcp_length = rc; + + msg = msgb_alloc(sizeof(nat->mgcp_msg), "MGCP GW Read"); + if (!msg) { + LOGP(DMGCP, LOGL_ERROR, "Failed to create buffer.\n"); + return -1; + } + + msg->l2h = msgb_put(msg, rc); + memcpy(msg->l2h, nat->mgcp_msg, msgb_l2len(msg)); + resp = mgcp_handle_message(nat->mgcp_cfg, msg); + msgb_free(msg); + + /* we do have a direct answer... e.g. AUEP */ + if (resp) { + if (write_queue_enqueue(&nat->mgcp_cfg->gw_fd, resp) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to enqueue msg.\n"); + msgb_free(resp); + } + } + + return 0; +} + +static int mgcp_do_write(struct bsc_fd *bfd, struct msgb *msg) +{ + int rc; + + rc = write(bfd->fd, msg->data, msg->len); + + if (rc != msg->len) { + LOGP(DMGCP, LOGL_ERROR, "Failed to write msg to MGCP CallAgent.\n"); + return -1; + } + + return rc; +} + +int bsc_mgcp_nat_init(struct bsc_nat *nat) +{ + int on; + struct sockaddr_in addr; + struct mgcp_config *cfg = nat->mgcp_cfg; + + if (!cfg->call_agent_addr) { + LOGP(DMGCP, LOGL_ERROR, "The BSC nat requires the call agent ip to be set.\n"); + return -1; + } + + if (cfg->bts_ip) { + LOGP(DMGCP, LOGL_ERROR, "Do not set the BTS ip for the nat.\n"); + return -1; + } + + cfg->gw_fd.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0); + if (cfg->gw_fd.bfd.fd < 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to create MGCP socket. errno: %d\n", errno); + return -1; + } + + on = 1; + setsockopt(cfg->gw_fd.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + addr.sin_family = AF_INET; + addr.sin_port = htons(cfg->source_port); + inet_aton(cfg->source_addr, &addr.sin_addr); + + if (bind(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to bind. errno: %d\n", errno); + close(cfg->gw_fd.bfd.fd); + cfg->gw_fd.bfd.fd = -1; + return -1; + } + + addr.sin_port = htons(2727); + inet_aton(cfg->call_agent_addr, &addr.sin_addr); + if (connect(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to connect to: '%s'. errno: %d\n", + cfg->call_agent_addr, errno); + close(cfg->gw_fd.bfd.fd); + cfg->gw_fd.bfd.fd = -1; + return -1; + } + + write_queue_init(&cfg->gw_fd, 10); + cfg->gw_fd.bfd.when = BSC_FD_READ; + cfg->gw_fd.bfd.data = nat; + cfg->gw_fd.read_cb = mgcp_do_read; + cfg->gw_fd.write_cb = mgcp_do_write; + + if (bsc_register_fd(&cfg->gw_fd.bfd) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to register MGCP fd.\n"); + close(cfg->gw_fd.bfd.fd); + cfg->gw_fd.bfd.fd = -1; + return -1; + } + + /* some more MGCP config handling */ + cfg->data = nat; + cfg->policy_cb = bsc_mgcp_policy_cb; + cfg->trunk.force_realloc = 1; + + if (cfg->bts_ip) + talloc_free(cfg->bts_ip); + cfg->bts_ip = ""; + + nat->bsc_endpoints = talloc_zero_array(nat, + struct bsc_endpoint, + cfg->trunk.number_endpoints + 1); + if (!nat->bsc_endpoints) { + LOGP(DMGCP, LOGL_ERROR, "Failed to allocate nat endpoints\n"); + close(cfg->gw_fd.bfd.fd); + cfg->gw_fd.bfd.fd = -1; + return -1; + } + + if (mgcp_reset_transcoder(cfg) < 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to send packet to the transcoder.\n"); + talloc_free(nat->bsc_endpoints); + nat->bsc_endpoints = NULL; + close(cfg->gw_fd.bfd.fd); + cfg->gw_fd.bfd.fd = -1; + return -1; + } + + return 0; +} + +void bsc_mgcp_clear_endpoints_for(struct bsc_connection *bsc) +{ + struct rate_ctr *ctr = NULL; + int i; + + if (bsc->cfg) + ctr = &bsc->cfg->stats.ctrg->ctr[BCFG_CTR_DROPPED_CALLS]; + + for (i = 1; i < bsc->nat->mgcp_cfg->trunk.number_endpoints; ++i) { + struct bsc_endpoint *bsc_endp = &bsc->nat->bsc_endpoints[i]; + + if (bsc_endp->bsc != bsc) + continue; + + if (ctr) + rate_ctr_inc(ctr); + + bsc_mgcp_free_endpoint(bsc->nat, i); + mgcp_free_endp(&bsc->nat->mgcp_cfg->trunk.endpoints[i]); + } +} diff --git a/src/osmo-bsc_nat/bsc_nat.c b/src/osmo-bsc_nat/bsc_nat.c new file mode 100644 index 000000000..643b3c4ba --- /dev/null +++ b/src/osmo-bsc_nat/bsc_nat.c @@ -0,0 +1,1387 @@ +/* BSC Multiplexer/NAT */ + +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * (C) 2009 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define _GNU_SOURCE +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +#include "../../bscconfig.h" + +#define SCCP_CLOSE_TIME 20 +#define SCCP_CLOSE_TIME_TIMEOUT 19 + +struct log_target *stderr_target; +static const char *config_file = "bsc-nat.cfg"; +static struct in_addr local_addr; +static struct bsc_fd bsc_listen; +static const char *msc_ip = NULL; +static struct timer_list sccp_close; +static int daemonize = 0; + +const char *openbsc_copyright = + "Copyright (C) 2010 Holger Hans Peter Freyther and On-Waves\r\n" + "License AGPLv3+: GNU AGPL version 3 or later \r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +static struct bsc_nat *nat; +static void bsc_send_data(struct bsc_connection *bsc, const uint8_t *data, unsigned int length, int); +static void msc_send_reset(struct bsc_msc_connection *con); +static void bsc_stat_reject(int filter, struct bsc_connection *bsc, int normal); + +struct bsc_config *bsc_config_num(struct bsc_nat *nat, int num) +{ + struct bsc_config *conf; + + llist_for_each_entry(conf, &nat->bsc_configs, entry) + if (conf->nr == num) + return conf; + + return NULL; +} + +static void queue_for_msc(struct bsc_msc_connection *con, struct msgb *msg) +{ + if (!con) { + LOGP(DINP, LOGL_ERROR, "No MSC Connection assigned. Check your code.\n"); + msgb_free(msg); + return; + } + + + if (write_queue_enqueue(&con->write_queue, msg) != 0) { + LOGP(DINP, LOGL_ERROR, "Failed to enqueue the write.\n"); + msgb_free(msg); + } +} + +static void send_reset_ack(struct bsc_connection *bsc) +{ + static const uint8_t gsm_reset_ack[] = { + 0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01, + 0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x03, + 0x00, 0x01, 0x31, + }; + + bsc_send_data(bsc, gsm_reset_ack, sizeof(gsm_reset_ack), IPAC_PROTO_SCCP); +} + +static void send_ping(struct bsc_connection *bsc) +{ + static const uint8_t id_ping[] = { + IPAC_MSGT_PING, + }; + + bsc_send_data(bsc, id_ping, sizeof(id_ping), IPAC_PROTO_IPACCESS); +} + +static void send_pong(struct bsc_connection *bsc) +{ + static const uint8_t id_pong[] = { + IPAC_MSGT_PONG, + }; + + bsc_send_data(bsc, id_pong, sizeof(id_pong), IPAC_PROTO_IPACCESS); +} + +static void bsc_pong_timeout(void *_bsc) +{ + struct bsc_connection *bsc = _bsc; + + LOGP(DNAT, LOGL_ERROR, "BSC Nr: %d PONG timeout.\n", bsc->cfg->nr); + bsc_close_connection(bsc); +} + +static void bsc_ping_timeout(void *_bsc) +{ + struct bsc_connection *bsc = _bsc; + + if (bsc->nat->ping_timeout < 0) + return; + + send_ping(bsc); + + /* send another ping in 20 seconds */ + bsc_schedule_timer(&bsc->ping_timeout, bsc->nat->ping_timeout, 0); + + /* also start a pong timer */ + bsc_schedule_timer(&bsc->pong_timeout, bsc->nat->pong_timeout, 0); +} + +static void start_ping_pong(struct bsc_connection *bsc) +{ + bsc->pong_timeout.data = bsc; + bsc->pong_timeout.cb = bsc_pong_timeout; + bsc->ping_timeout.data = bsc; + bsc->ping_timeout.cb = bsc_ping_timeout; + + bsc_ping_timeout(bsc); +} + +static void send_id_ack(struct bsc_connection *bsc) +{ + static const uint8_t id_ack[] = { + IPAC_MSGT_ID_ACK + }; + + bsc_send_data(bsc, id_ack, sizeof(id_ack), IPAC_PROTO_IPACCESS); +} + +static void send_id_req(struct bsc_connection *bsc) +{ + static const uint8_t id_req[] = { + IPAC_MSGT_ID_GET, + 0x01, IPAC_IDTAG_UNIT, + 0x01, IPAC_IDTAG_MACADDR, + 0x01, IPAC_IDTAG_LOCATION1, + 0x01, IPAC_IDTAG_LOCATION2, + 0x01, IPAC_IDTAG_EQUIPVERS, + 0x01, IPAC_IDTAG_SWVERSION, + 0x01, IPAC_IDTAG_UNITNAME, + 0x01, IPAC_IDTAG_SERNR, + }; + + bsc_send_data(bsc, id_req, sizeof(id_req), IPAC_PROTO_IPACCESS); +} + +static void nat_send_rlsd_msc(struct sccp_connections *conn) +{ + struct sccp_connection_released *rel; + struct msgb *msg; + + msg = msgb_alloc_headroom(4096, 128, "rlsd"); + if (!msg) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate clear command.\n"); + return; + } + + msg->l2h = msgb_put(msg, sizeof(*rel)); + rel = (struct sccp_connection_released *) msg->l2h; + rel->type = SCCP_MSG_TYPE_RLSD; + rel->release_cause = SCCP_RELEASE_CAUSE_SCCP_FAILURE; + rel->destination_local_reference = conn->remote_ref; + rel->source_local_reference = conn->patched_ref; + + ipaccess_prepend_header(msg, IPAC_PROTO_SCCP); + + queue_for_msc(conn->msc_con, msg); +} + +static void nat_send_rlsd_bsc(struct sccp_connections *conn) +{ + struct sccp_connection_released *rel; + struct msgb *msg; + + msg = msgb_alloc_headroom(4096, 128, "rlsd"); + if (!msg) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate clear command.\n"); + return; + } + + msg->l2h = msgb_put(msg, sizeof(*rel)); + rel = (struct sccp_connection_released *) msg->l2h; + rel->type = SCCP_MSG_TYPE_RLSD; + rel->release_cause = SCCP_RELEASE_CAUSE_SCCP_FAILURE; + rel->destination_local_reference = conn->real_ref; + rel->source_local_reference = conn->remote_ref; + + bsc_write(conn->bsc, msg, IPAC_PROTO_SCCP); +} + +static struct msgb *nat_creat_clrc(struct sccp_connections *conn, uint8_t cause) +{ + struct msgb *msg; + struct msgb *sccp; + + msg = gsm0808_create_clear_command(cause); + if (!msg) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate clear command.\n"); + return NULL; + } + + sccp = sccp_create_dt1(&conn->real_ref, msg->data, msg->len); + if (!sccp) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate SCCP msg.\n"); + msgb_free(msg); + return NULL; + } + + msgb_free(msg); + return sccp; +} + +static int nat_send_clrc_bsc(struct sccp_connections *conn) +{ + struct msgb *sccp; + + sccp = nat_creat_clrc(conn, 0x20); + if (!sccp) + return -1; + return bsc_write(conn->bsc, sccp, IPAC_PROTO_SCCP); +} + +static void nat_send_rlc(struct bsc_msc_connection *msc_con, + struct sccp_source_reference *src, + struct sccp_source_reference *dst) +{ + struct sccp_connection_release_complete *rlc; + struct msgb *msg; + + msg = msgb_alloc_headroom(4096, 128, "rlc"); + if (!msg) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate clear command.\n"); + return; + } + + msg->l2h = msgb_put(msg, sizeof(*rlc)); + rlc = (struct sccp_connection_release_complete *) msg->l2h; + rlc->type = SCCP_MSG_TYPE_RLC; + rlc->destination_local_reference = *dst; + rlc->source_local_reference = *src; + + ipaccess_prepend_header(msg, IPAC_PROTO_SCCP); + + queue_for_msc(msc_con, msg); +} + +static void send_mgcp_reset(struct bsc_connection *bsc) +{ + static const uint8_t mgcp_reset[] = { + "RSIP 1 13@mgw MGCP 1.0\r\n" + }; + + bsc_write_mgcp(bsc, mgcp_reset, sizeof mgcp_reset - 1); +} + +/* + * Below is the handling of messages coming + * from the MSC and need to be forwarded to + * a real BSC. + */ +static void initialize_msc_if_needed(struct bsc_msc_connection *msc_con) +{ + if (msc_con->first_contact) + return; + + msc_con->first_contact = 1; + msc_send_reset(msc_con); +} + +static void send_id_get_response(struct bsc_msc_connection *msc_con) +{ + struct msgb *msg = bsc_msc_id_get_resp(nat->token); + if (!msg) + return; + + ipaccess_prepend_header(msg, IPAC_PROTO_IPACCESS); + queue_for_msc(msc_con, msg); +} + +/* + * Currently we are lacking refcounting so we need to copy each message. + */ +static void bsc_send_data(struct bsc_connection *bsc, const uint8_t *data, unsigned int length, int proto) +{ + struct msgb *msg; + + if (length > 4096 - 128) { + LOGP(DINP, LOGL_ERROR, "Can not send message of that size.\n"); + return; + } + + msg = msgb_alloc_headroom(4096, 128, "to-bsc"); + if (!msg) { + LOGP(DINP, LOGL_ERROR, "Failed to allocate memory for BSC msg.\n"); + return; + } + + msg->l2h = msgb_put(msg, length); + memcpy(msg->data, data, length); + + bsc_write(bsc, msg, proto); +} + +/* + * Update the release statistics + */ +static void bsc_stat_reject(int filter, struct bsc_connection *bsc, int normal) +{ + if (!bsc->cfg) { + LOGP(DNAT, LOGL_ERROR, "BSC is not authenticated."); + return; + } + + if (filter >= 0) { + LOGP(DNAT, LOGL_ERROR, "Connection was not rejected"); + return; + } + + if (filter == -1) + rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_ILL_PACKET]); + else if (normal) + rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_REJECTED_MSG]); + else + rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_REJECTED_CR]); +} + +/* + * Release an established connection. We will have to release it to the BSC + * and to the network and we do it the following way. + * 1.) Give up on the MSC side + * 1.1) Send a RLSD message, it is a bit non standard but should work, we + * ignore the RLC... we might complain about it. Other options would + * be to send a Release Request, handle the Release Complete.. + * 1.2) Mark the data structure to be con_local and wait for 2nd + * + * 2.) Give up on the BSC side + * 2.1) Depending on the con type reject the service, or just close it + */ +static void bsc_send_con_release(struct bsc_connection *bsc, struct sccp_connections *con) +{ + struct msgb *rlsd; + /* 1. release the network */ + rlsd = sccp_create_rlsd(&con->patched_ref, &con->remote_ref, + SCCP_RELEASE_CAUSE_END_USER_ORIGINATED); + if (!rlsd) + LOGP(DNAT, LOGL_ERROR, "Failed to create RLSD message.\n"); + else { + ipaccess_prepend_header(rlsd, IPAC_PROTO_SCCP); + queue_for_msc(con->msc_con, rlsd); + } + con->con_local = 1; + con->msc_con = NULL; + + /* 2. release the BSC side */ + if (con->con_type == NAT_CON_TYPE_LU) { + struct msgb *payload, *udt; + payload = gsm48_create_loc_upd_rej(GSM48_REJECT_PLMN_NOT_ALLOWED); + + if (payload) { + gsm0808_prepend_dtap_header(payload, 0); + udt = sccp_create_dt1(&con->real_ref, payload->data, payload->len); + if (udt) + bsc_write(bsc, udt, IPAC_PROTO_SCCP); + else + LOGP(DNAT, LOGL_ERROR, "Failed to create DT1\n"); + + msgb_free(payload); + } else { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate LU Reject.\n"); + } + } + + nat_send_clrc_bsc(con); + + rlsd = sccp_create_rlsd(&con->remote_ref, &con->real_ref, + SCCP_RELEASE_CAUSE_END_USER_ORIGINATED); + if (!rlsd) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate RLSD for the BSC.\n"); + sccp_connection_destroy(con); + return; + } + + con->con_type = NAT_CON_TYPE_LOCAL_REJECT; + bsc_write(bsc, rlsd, IPAC_PROTO_SCCP); +} + +static void bsc_send_con_refuse(struct bsc_connection *bsc, + struct bsc_nat_parsed *parsed, int con_type) +{ + struct msgb *payload; + struct msgb *refuse; + + if (con_type == NAT_CON_TYPE_LU) + payload = gsm48_create_loc_upd_rej(GSM48_REJECT_PLMN_NOT_ALLOWED); + else if (con_type == NAT_CON_TYPE_CM_SERV_REQ) + payload = gsm48_create_mm_serv_rej(GSM48_REJECT_PLMN_NOT_ALLOWED); + else { + LOGP(DNAT, LOGL_ERROR, "Unknown connection type: %d\n", con_type); + payload = NULL; + } + + /* + * Some BSCs do not handle the payload inside a SCCP CREF msg + * so we will need to: + * 1.) Allocate a local connection and mark it as local.. + * 2.) queue data for downstream.. and the RLC should delete everything + */ + if (payload) { + struct msgb *cc, *udt, *clear, *rlsd; + struct sccp_connections *con; + con = create_sccp_src_ref(bsc, parsed); + if (!con) + goto send_refuse; + + /* declare it local and assign a unique remote_ref */ + con->con_type = NAT_CON_TYPE_LOCAL_REJECT; + con->con_local = 1; + con->has_remote_ref = 1; + con->remote_ref = con->patched_ref; + + /* 1. create a confirmation */ + cc = sccp_create_cc(&con->remote_ref, &con->real_ref); + if (!cc) + goto send_refuse; + + /* 2. create the DT1 */ + gsm0808_prepend_dtap_header(payload, 0); + udt = sccp_create_dt1(&con->real_ref, payload->data, payload->len); + if (!udt) { + msgb_free(cc); + goto send_refuse; + } + + /* 3. send a Clear Command */ + clear = nat_creat_clrc(con, 0x20); + if (!clear) { + msgb_free(cc); + msgb_free(udt); + goto send_refuse; + } + + /* 4. send a RLSD */ + rlsd = sccp_create_rlsd(&con->remote_ref, &con->real_ref, + SCCP_RELEASE_CAUSE_END_USER_ORIGINATED); + if (!rlsd) { + msgb_free(cc); + msgb_free(udt); + msgb_free(clear); + goto send_refuse; + } + + bsc_write(bsc, cc, IPAC_PROTO_SCCP); + bsc_write(bsc, udt, IPAC_PROTO_SCCP); + bsc_write(bsc, clear, IPAC_PROTO_SCCP); + bsc_write(bsc, rlsd, IPAC_PROTO_SCCP); + msgb_free(payload); + return; + } + + +send_refuse: + if (payload) + msgb_free(payload); + + refuse = sccp_create_refuse(parsed->src_local_ref, + SCCP_REFUSAL_SCCP_FAILURE, NULL, 0); + if (!refuse) { + LOGP(DNAT, LOGL_ERROR, + "Creating refuse msg failed for SCCP 0x%x on BSC Nr: %d.\n", + sccp_src_ref_to_int(parsed->src_local_ref), bsc->cfg->nr); + return; + } + + bsc_write(bsc, refuse, IPAC_PROTO_SCCP); +} + + +static int forward_sccp_to_bts(struct bsc_msc_connection *msc_con, struct msgb *msg) +{ + struct sccp_connections *con = NULL; + struct bsc_connection *bsc; + struct bsc_nat_parsed *parsed; + int proto; + + /* filter, drop, patch the message? */ + parsed = bsc_nat_parse(msg); + if (!parsed) { + LOGP(DNAT, LOGL_ERROR, "Can not parse msg from BSC.\n"); + return -1; + } + + if (bsc_nat_filter_ipa(DIR_BSC, msg, parsed)) + goto exit; + + proto = parsed->ipa_proto; + + /* Route and modify the SCCP packet */ + if (proto == IPAC_PROTO_SCCP) { + switch (parsed->sccp_type) { + case SCCP_MSG_TYPE_UDT: + /* forward UDT messages to every BSC */ + goto send_to_all; + break; + case SCCP_MSG_TYPE_RLSD: + case SCCP_MSG_TYPE_CREF: + case SCCP_MSG_TYPE_DT1: + case SCCP_MSG_TYPE_IT: + con = patch_sccp_src_ref_to_bsc(msg, parsed, nat); + if (parsed->gsm_type == BSS_MAP_MSG_ASSIGMENT_RQST) { + counter_inc(nat->stats.sccp.calls); + + if (con) { + struct rate_ctr_group *ctrg; + ctrg = con->bsc->cfg->stats.ctrg; + rate_ctr_inc(&ctrg->ctr[BCFG_CTR_SCCP_CALLS]); + if (bsc_mgcp_assign_patch(con, msg) != 0) + LOGP(DNAT, LOGL_ERROR, "Failed to assign...\n"); + } else + LOGP(DNAT, LOGL_ERROR, "Assignment command but no BSC.\n"); + } + break; + case SCCP_MSG_TYPE_CC: + con = patch_sccp_src_ref_to_bsc(msg, parsed, nat); + if (!con || update_sccp_src_ref(con, parsed) != 0) + goto exit; + break; + case SCCP_MSG_TYPE_RLC: + LOGP(DNAT, LOGL_ERROR, "Unexpected release complete from MSC.\n"); + goto exit; + break; + case SCCP_MSG_TYPE_CR: + /* MSC never opens a SCCP connection, fall through */ + default: + goto exit; + } + + if (!con && parsed->sccp_type == SCCP_MSG_TYPE_RLSD) { + LOGP(DNAT, LOGL_NOTICE, "Sending fake RLC on RLSD message to network.\n"); + /* Exchange src/dest for the reply */ + nat_send_rlc(msc_con, parsed->dest_local_ref, parsed->src_local_ref); + } else if (!con) + LOGP(DNAT, LOGL_ERROR, "Unknown connection for msg type: 0x%x from the MSC.\n", parsed->sccp_type); + } + + talloc_free(parsed); + if (!con) + return -1; + if (!con->bsc->authenticated) { + LOGP(DNAT, LOGL_ERROR, "Selected BSC not authenticated.\n"); + return -1; + } + + bsc_send_data(con->bsc, msg->l2h, msgb_l2len(msg), proto); + return 0; + +send_to_all: + /* + * Filter Paging from the network. We do not want to send a PAGING + * Command to every BSC in our network. We will analys the PAGING + * message and then send it to the authenticated messages... + */ + if (parsed->ipa_proto == IPAC_PROTO_SCCP && parsed->gsm_type == BSS_MAP_MSG_PAGING) { + int lac; + bsc = bsc_nat_find_bsc(nat, msg, &lac); + if (bsc && bsc->cfg->forbid_paging) + LOGP(DNAT, LOGL_DEBUG, "Paging forbidden for BTS: %d\n", bsc->cfg->nr); + else if (bsc) + bsc_send_data(bsc, msg->l2h, msgb_l2len(msg), parsed->ipa_proto); + else if (lac != -1) + LOGP(DNAT, LOGL_ERROR, "Could not determine BSC for paging on lac: %d/0x%x\n", + lac, lac); + + goto exit; + } + /* currently send this to every BSC connected */ + llist_for_each_entry(bsc, &nat->bsc_connections, list_entry) { + if (!bsc->authenticated) + continue; + + bsc_send_data(bsc, msg->l2h, msgb_l2len(msg), parsed->ipa_proto); + } + +exit: + talloc_free(parsed); + return 0; +} + +static void msc_connection_was_lost(struct bsc_msc_connection *con) +{ + struct bsc_connection *bsc, *tmp; + + LOGP(DMSC, LOGL_ERROR, "Closing all connections downstream.\n"); + llist_for_each_entry_safe(bsc, tmp, &nat->bsc_connections, list_entry) + bsc_close_connection(bsc); + + bsc_mgcp_free_endpoints(nat); + bsc_msc_schedule_connect(con); +} + +static void msc_connection_connected(struct bsc_msc_connection *con) +{ + counter_inc(nat->stats.msc.reconn); +} + +static void msc_send_reset(struct bsc_msc_connection *msc_con) +{ + static const uint8_t reset[] = { + 0x00, 0x12, 0xfd, + 0x09, 0x00, 0x03, 0x05, 0x07, 0x02, 0x42, 0xfe, + 0x02, 0x42, 0xfe, 0x06, 0x00, 0x04, 0x30, 0x04, + 0x01, 0x20 + }; + + struct msgb *msg; + + msg = msgb_alloc_headroom(4096, 128, "08.08 reset"); + if (!msg) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate reset msg.\n"); + return; + } + + msg->l2h = msgb_put(msg, sizeof(reset)); + memcpy(msg->l2h, reset, msgb_l2len(msg)); + + queue_for_msc(msc_con, msg); + + LOGP(DMSC, LOGL_NOTICE, "Scheduled GSM0808 reset msg for the MSC.\n"); +} + +static int ipaccess_msc_read_cb(struct bsc_fd *bfd) +{ + int error; + struct bsc_msc_connection *msc_con; + struct msgb *msg = ipaccess_read_msg(bfd, &error); + struct ipaccess_head *hh; + + msc_con = (struct bsc_msc_connection *) bfd->data; + + if (!msg) { + if (error == 0) + LOGP(DNAT, LOGL_FATAL, "The connection the MSC was lost, exiting\n"); + else + LOGP(DNAT, LOGL_ERROR, "Failed to parse ip access message: %d\n", error); + + bsc_msc_lost(msc_con); + return -1; + } + + LOGP(DNAT, LOGL_DEBUG, "MSG from MSC: %s proto: %d\n", hexdump(msg->data, msg->len), msg->l2h[0]); + + /* handle base message handling */ + hh = (struct ipaccess_head *) msg->data; + ipaccess_rcvmsg_base(msg, bfd); + + /* initialize the networking. This includes sending a GSM08.08 message */ + if (hh->proto == IPAC_PROTO_IPACCESS) { + if (msg->l2h[0] == IPAC_MSGT_ID_ACK) + initialize_msc_if_needed(msc_con); + else if (msg->l2h[0] == IPAC_MSGT_ID_GET) + send_id_get_response(msc_con); + } else if (hh->proto == IPAC_PROTO_SCCP) + forward_sccp_to_bts(msc_con, msg); + + msgb_free(msg); + return 0; +} + +static int ipaccess_msc_write_cb(struct bsc_fd *bfd, struct msgb *msg) +{ + int rc; + rc = write(bfd->fd, msg->data, msg->len); + + if (rc != msg->len) { + LOGP(DNAT, LOGL_ERROR, "Failed to write MSG to MSC.\n"); + return -1; + } + + return rc; +} + +/* + * Below is the handling of messages coming + * from the BSC and need to be forwarded to + * a real BSC. + */ + +/* + * Remove the connection from the connections list, + * remove it from the patching of SCCP header lists + * as well. Maybe in the future even close connection.. + */ +void bsc_close_connection(struct bsc_connection *connection) +{ + struct sccp_connections *sccp_patch, *tmp; + struct rate_ctr *ctr = NULL; + + /* stop the timeout timer */ + bsc_del_timer(&connection->id_timeout); + bsc_del_timer(&connection->ping_timeout); + bsc_del_timer(&connection->pong_timeout); + + if (connection->cfg) + ctr = &connection->cfg->stats.ctrg->ctr[BCFG_CTR_DROPPED_SCCP]; + + /* remove all SCCP connections */ + llist_for_each_entry_safe(sccp_patch, tmp, &nat->sccp_connections, list_entry) { + if (sccp_patch->bsc != connection) + continue; + + if (ctr) + rate_ctr_inc(ctr); + if (sccp_patch->has_remote_ref && !sccp_patch->con_local) + nat_send_rlsd_msc(sccp_patch); + sccp_connection_destroy(sccp_patch); + } + + /* close endpoints allocated by this BSC */ + bsc_mgcp_clear_endpoints_for(connection); + + bsc_unregister_fd(&connection->write_queue.bfd); + close(connection->write_queue.bfd.fd); + write_queue_clear(&connection->write_queue); + llist_del(&connection->list_entry); + + talloc_free(connection); +} + +static void ipaccess_close_bsc(void *data) +{ + struct sockaddr_in sock; + socklen_t len = sizeof(sock); + struct bsc_connection *conn = data; + + + getpeername(conn->write_queue.bfd.fd, (struct sockaddr *) &sock, &len); + LOGP(DNAT, LOGL_ERROR, "BSC on %s didn't respond to identity request. Closing.\n", + inet_ntoa(sock.sin_addr)); + bsc_close_connection(conn); +} + +static void ipaccess_auth_bsc(struct tlv_parsed *tvp, struct bsc_connection *bsc) +{ + struct bsc_config *conf; + const char *token = (const char *) TLVP_VAL(tvp, IPAC_IDTAG_UNITNAME); + const int len = TLVP_LEN(tvp, IPAC_IDTAG_UNITNAME); + + if (bsc->cfg) { + LOGP(DNAT, LOGL_ERROR, "Reauth on fd %d bsc nr %d\n", + bsc->write_queue.bfd.fd, bsc->cfg->nr); + return; + } + + llist_for_each_entry(conf, &bsc->nat->bsc_configs, entry) { + if (strncmp(conf->token, token, len) == 0) { + rate_ctr_inc(&conf->stats.ctrg->ctr[BCFG_CTR_NET_RECONN]); + bsc->authenticated = 1; + bsc->cfg = conf; + bsc_del_timer(&bsc->id_timeout); + LOGP(DNAT, LOGL_NOTICE, "Authenticated bsc nr: %d on fd %d\n", + conf->nr, bsc->write_queue.bfd.fd); + start_ping_pong(bsc); + return; + } + } + + LOGP(DNAT, LOGL_ERROR, "No bsc found for token %s on fd: %d.\n", token, + bsc->write_queue.bfd.fd); +} + +static void handle_con_stats(struct sccp_connections *con) +{ + struct rate_ctr_group *ctrg; + int id = bsc_conn_type_to_ctr(con); + + if (id == -1) + return; + + if (!con->bsc || !con->bsc->cfg) + return; + + ctrg = con->bsc->cfg->stats.ctrg; + rate_ctr_inc(&ctrg->ctr[id]); +} + +static int forward_sccp_to_msc(struct bsc_connection *bsc, struct msgb *msg) +{ + int con_filter = 0; + char *imsi = NULL; + struct bsc_msc_connection *con_msc = NULL; + struct bsc_connection *con_bsc = NULL; + int con_type; + struct bsc_nat_parsed *parsed; + + /* Parse and filter messages */ + parsed = bsc_nat_parse(msg); + if (!parsed) { + LOGP(DNAT, LOGL_ERROR, "Can not parse msg from BSC.\n"); + msgb_free(msg); + return -1; + } + + if (bsc_nat_filter_ipa(DIR_MSC, msg, parsed)) + goto exit; + + /* + * check authentication after filtering to not reject auth + * responses coming from the BSC. We have to make sure that + * nothing from the exit path will forward things to the MSC + */ + if (!bsc->authenticated) { + LOGP(DNAT, LOGL_ERROR, "BSC is not authenticated.\n"); + msgb_free(msg); + return -1; + } + + + /* modify the SCCP entries */ + if (parsed->ipa_proto == IPAC_PROTO_SCCP) { + int filter; + struct sccp_connections *con; + switch (parsed->sccp_type) { + case SCCP_MSG_TYPE_CR: + filter = bsc_nat_filter_sccp_cr(bsc, msg, parsed, &con_type, &imsi); + if (filter < 0) { + bsc_stat_reject(filter, bsc, 0); + goto exit3; + } + + if (!create_sccp_src_ref(bsc, parsed)) + goto exit2; + con = patch_sccp_src_ref_to_msc(msg, parsed, bsc); + con->msc_con = bsc->nat->msc_con; + con_msc = con->msc_con; + con->con_type = con_type; + con->imsi_checked = filter; + if (imsi) + con->imsi = talloc_steal(con, imsi); + imsi = NULL; + con_bsc = con->bsc; + handle_con_stats(con); + break; + case SCCP_MSG_TYPE_RLSD: + case SCCP_MSG_TYPE_CREF: + case SCCP_MSG_TYPE_DT1: + case SCCP_MSG_TYPE_CC: + case SCCP_MSG_TYPE_IT: + con = patch_sccp_src_ref_to_msc(msg, parsed, bsc); + if (con) { + /* only filter non local connections */ + if (!con->con_local) { + filter = bsc_nat_filter_dt(bsc, msg, con, parsed); + if (filter < 0) { + bsc_stat_reject(filter, bsc, 1); + bsc_send_con_release(bsc, con); + con = NULL; + goto exit2; + } + + /* hand data to a side channel */ + if (bsc_check_ussd(con, parsed, msg) == 1) + con->con_local = 2; + + /* + * Optionally rewrite setup message. This can + * replace the msg and the parsed structure becomes + * invalid. + */ + msg = bsc_nat_rewrite_setup(bsc->nat, msg, parsed, con->imsi); + talloc_free(parsed); + parsed = NULL; + } + + con_bsc = con->bsc; + con_msc = con->msc_con; + con_filter = con->con_local; + } + + break; + case SCCP_MSG_TYPE_RLC: + con = patch_sccp_src_ref_to_msc(msg, parsed, bsc); + if (con) { + con_bsc = con->bsc; + con_msc = con->msc_con; + con_filter = con->con_local; + } + remove_sccp_src_ref(bsc, msg, parsed); + break; + case SCCP_MSG_TYPE_UDT: + /* simply forward everything */ + con = NULL; + break; + default: + LOGP(DNAT, LOGL_ERROR, "Not forwarding to msc sccp type: 0x%x\n", parsed->sccp_type); + con = NULL; + goto exit2; + break; + } + } else if (parsed->ipa_proto == IPAC_PROTO_MGCP_OLD) { + bsc_mgcp_forward(bsc, msg); + goto exit2; + } else { + LOGP(DNAT, LOGL_ERROR, "Not forwarding unknown stream id: 0x%x\n", parsed->ipa_proto); + goto exit2; + } + + if (con_msc && con_bsc != bsc) { + LOGP(DNAT, LOGL_ERROR, "The connection belongs to a different BTS: input: %d con: %d\n", + bsc->cfg->nr, con_bsc->cfg->nr); + goto exit2; + } + + /* do not forward messages to the MSC */ + if (con_filter) + goto exit2; + + if (!con_msc) { + LOGP(DNAT, LOGL_ERROR, "Not forwarding data bsc_nr: %d ipa: %d type: 0x%x\n", + bsc->cfg->nr, + parsed ? parsed->ipa_proto : -1, + parsed ? parsed->sccp_type : -1); + goto exit2; + } + + /* send the non-filtered but maybe modified msg */ + queue_for_msc(con_msc, msg); + if (parsed) + talloc_free(parsed); + return 0; + +exit: + /* if we filter out the reset send an ack to the BSC */ + if (parsed->bssap == 0 && parsed->gsm_type == BSS_MAP_MSG_RESET) { + send_reset_ack(bsc); + send_reset_ack(bsc); + } else if (parsed->ipa_proto == IPAC_PROTO_IPACCESS) { + /* do we know who is handling this? */ + if (msg->l2h[0] == IPAC_MSGT_ID_RESP) { + struct tlv_parsed tvp; + ipaccess_idtag_parse(&tvp, + (unsigned char *) msg->l2h + 2, + msgb_l2len(msg) - 2); + if (TLVP_PRESENT(&tvp, IPAC_IDTAG_UNITNAME)) + ipaccess_auth_bsc(&tvp, bsc); + } + + goto exit2; + } + +exit2: + if (imsi) + talloc_free(imsi); + talloc_free(parsed); + msgb_free(msg); + return -1; + +exit3: + /* send a SCCP Connection Refused */ + if (imsi) + talloc_free(imsi); + bsc_send_con_refuse(bsc, parsed, con_type); + talloc_free(parsed); + msgb_free(msg); + return -1; +} + +static int ipaccess_bsc_read_cb(struct bsc_fd *bfd) +{ + int error; + struct bsc_connection *bsc = bfd->data; + struct msgb *msg = ipaccess_read_msg(bfd, &error); + struct ipaccess_head *hh; + + if (!msg) { + if (error == 0) + LOGP(DNAT, LOGL_ERROR, + "The connection to the BSC Nr: %d was lost. Cleaning it\n", + bsc->cfg ? bsc->cfg->nr : -1); + else + LOGP(DNAT, LOGL_ERROR, + "Stream error on BSC Nr: %d. Failed to parse ip access message: %d\n", + bsc->cfg ? bsc->cfg->nr : -1, error); + + bsc_close_connection(bsc); + return -1; + } + + + LOGP(DNAT, LOGL_DEBUG, "MSG from BSC: %s proto: %d\n", hexdump(msg->data, msg->len), msg->l2h[0]); + + /* Handle messages from the BSC */ + hh = (struct ipaccess_head *) msg->data; + + /* stop the pong timeout */ + if (hh->proto == IPAC_PROTO_IPACCESS) { + if (msg->l2h[0] == IPAC_MSGT_PONG) { + bsc_del_timer(&bsc->pong_timeout); + msgb_free(msg); + return 0; + } else if (msg->l2h[0] == IPAC_MSGT_PING) { + send_pong(bsc); + msgb_free(msg); + return 0; + } + } + + /* FIXME: Currently no PONG is sent to the BSC */ + /* FIXME: Currently no ID ACK is sent to the BSC */ + forward_sccp_to_msc(bsc, msg); + + return 0; +} + +static int ipaccess_listen_bsc_cb(struct bsc_fd *bfd, unsigned int what) +{ + struct bsc_connection *bsc; + int fd, rc, on; + struct sockaddr_in sa; + socklen_t sa_len = sizeof(sa); + + if (!(what & BSC_FD_READ)) + return 0; + + fd = accept(bfd->fd, (struct sockaddr *) &sa, &sa_len); + if (fd < 0) { + perror("accept"); + return fd; + } + + /* count the reconnect */ + counter_inc(nat->stats.bsc.reconn); + + /* + * if we are not connected to a msc... just close the socket + */ + if (!bsc_nat_msc_is_connected(nat)) { + LOGP(DNAT, LOGL_NOTICE, "Disconnecting BSC due lack of MSC connection.\n"); + close(fd); + return 0; + } + + on = 1; + rc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); + if (rc != 0) + LOGP(DNAT, LOGL_ERROR, "Failed to set TCP_NODELAY: %s\n", strerror(errno)); + + rc = setsockopt(fd, IPPROTO_IP, IP_TOS, + &nat->bsc_ip_dscp, sizeof(nat->bsc_ip_dscp)); + if (rc != 0) + LOGP(DNAT, LOGL_ERROR, "Failed to set IP_TOS: %s\n", strerror(errno)); + + /* todo... do something with the connection */ + /* todo... use GNUtls to see if we want to trust this as a BTS */ + + /* + * + */ + bsc = bsc_connection_alloc(nat); + if (!bsc) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate BSC struct.\n"); + close(fd); + return -1; + } + + bsc->write_queue.bfd.data = bsc; + bsc->write_queue.bfd.fd = fd; + bsc->write_queue.read_cb = ipaccess_bsc_read_cb; + bsc->write_queue.write_cb = bsc_write_cb; + bsc->write_queue.bfd.when = BSC_FD_READ; + if (bsc_register_fd(&bsc->write_queue.bfd) < 0) { + LOGP(DNAT, LOGL_ERROR, "Failed to register BSC fd.\n"); + close(fd); + talloc_free(bsc); + return -2; + } + + LOGP(DNAT, LOGL_NOTICE, "BSC connection on %d with IP: %s\n", + fd, inet_ntoa(sa.sin_addr)); + llist_add(&bsc->list_entry, &nat->bsc_connections); + send_id_ack(bsc); + send_id_req(bsc); + send_mgcp_reset(bsc); + + /* + * start the hangup timer + */ + bsc->id_timeout.data = bsc; + bsc->id_timeout.cb = ipaccess_close_bsc; + bsc_schedule_timer(&bsc->id_timeout, nat->auth_timeout, 0); + return 0; +} + +static void print_usage() +{ + printf("Usage: bsc_nat\n"); +} + +static void print_help() +{ + printf(" Some useful help...\n"); + printf(" -h --help this text\n"); + printf(" -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM enable debugging\n"); + printf(" -D --daemonize Fork the process into a background daemon\n"); + printf(" -s --disable-color\n"); + printf(" -c --config-file filename The config file to use.\n"); + printf(" -m --msc=IP. The address of the MSC.\n"); + printf(" -l --local=IP. The local address of this BSC.\n"); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"debug", 1, 0, 'd'}, + {"config-file", 1, 0, 'c'}, + {"disable-color", 0, 0, 's'}, + {"timestamp", 0, 0, 'T'}, + {"msc", 1, 0, 'm'}, + {"local", 1, 0, 'l'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hd:sTPc:m:l:", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage(); + print_help(); + exit(0); + case 's': + log_set_use_color(stderr_target, 0); + break; + case 'd': + log_parse_category_mask(stderr_target, optarg); + break; + case 'c': + config_file = strdup(optarg); + break; + case 'T': + log_set_print_timestamp(stderr_target, 1); + break; + case 'm': + msc_ip = optarg; + break; + case 'l': + inet_aton(optarg, &local_addr); + break; + default: + /* ignore */ + break; + } + } +} + +static void signal_handler(int signal) +{ + switch (signal) { + case SIGABRT: + /* in case of abort, we want to obtain a talloc report + * and then return to the caller, who will abort the process */ + case SIGUSR1: + talloc_report_full(tall_bsc_ctx, stderr); + break; + default: + break; + } +} + +static void sccp_close_unconfirmed(void *_data) +{ + struct sccp_connections *conn, *tmp1; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + llist_for_each_entry_safe(conn, tmp1, &nat->sccp_connections, list_entry) { + if (conn->has_remote_ref) + continue; + + int diff = (now.tv_sec - conn->creation_time.tv_sec) / 60; + if (diff < SCCP_CLOSE_TIME_TIMEOUT) + continue; + + LOGP(DNAT, LOGL_ERROR, "SCCP connection 0x%x/0x%x was never confirmed.\n", + sccp_src_ref_to_int(&conn->real_ref), + sccp_src_ref_to_int(&conn->patched_ref)); + sccp_connection_destroy(conn); + } + + bsc_schedule_timer(&sccp_close, SCCP_CLOSE_TIME, 0); +} + +extern void *tall_msgb_ctx; +extern void *tall_ctr_ctx; +static void talloc_init_ctx() +{ + tall_bsc_ctx = talloc_named_const(NULL, 0, "nat"); + tall_msgb_ctx = talloc_named_const(tall_bsc_ctx, 0, "msgb"); + tall_ctr_ctx = talloc_named_const(tall_bsc_ctx, 0, "counter"); +} + +extern enum node_type bsc_vty_go_parent(struct vty *vty); + +static struct vty_app_info vty_info = { + .name = "OsmoBSCNAT", + .version = PACKAGE_VERSION, + .go_parent_cb = bsc_vty_go_parent, + .is_config_node = bsc_vty_is_config_node, +}; + +int main(int argc, char **argv) +{ + int rc; + + talloc_init_ctx(); + + log_init(&log_info); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + log_set_all_filter(stderr_target, 1); + + nat = bsc_nat_alloc(); + if (!nat) { + fprintf(stderr, "Failed to allocate the BSC nat.\n"); + return -4; + } + + nat->mgcp_cfg = mgcp_config_alloc(); + if (!nat->mgcp_cfg) { + fprintf(stderr, "Failed to allocate MGCP cfg.\n"); + return -5; + } + + vty_info.copyright = openbsc_copyright; + vty_init(&vty_info); + logging_vty_add_cmds(); + bsc_nat_vty_init(nat); + + + /* parse options */ + local_addr.s_addr = INADDR_ANY; + handle_options(argc, argv); + + rate_ctr_init(tall_bsc_ctx); + + /* init vty and parse */ + telnet_init(tall_bsc_ctx, NULL, 4244); + if (mgcp_parse_config(config_file, nat->mgcp_cfg) < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file); + return -3; + } + + /* over rule the VTY config */ + if (msc_ip) + bsc_nat_set_msc_ip(nat, msc_ip); + + /* seed the PRNG */ + srand(time(NULL)); + + /* + * Setup the MGCP code.. + */ + if (bsc_mgcp_nat_init(nat) != 0) + return -4; + + /* connect to the MSC */ + nat->msc_con = bsc_msc_create(nat->msc_ip, nat->msc_port, 0); + if (!nat->msc_con) { + fprintf(stderr, "Creating a bsc_msc_connection failed.\n"); + exit(1); + } + + nat->msc_con->connection_loss = msc_connection_was_lost; + nat->msc_con->connected = msc_connection_connected; + nat->msc_con->write_queue.read_cb = ipaccess_msc_read_cb; + nat->msc_con->write_queue.write_cb = ipaccess_msc_write_cb;; + nat->msc_con->write_queue.bfd.data = nat->msc_con; + bsc_msc_connect(nat->msc_con); + + /* wait for the BSC */ + rc = make_sock(&bsc_listen, IPPROTO_TCP, ntohl(local_addr.s_addr), + 5000, ipaccess_listen_bsc_cb); + if (rc != 0) { + fprintf(stderr, "Failed to listen for BSC.\n"); + exit(1); + } + + rc = bsc_ussd_init(nat); + if (rc != 0) { + LOGP(DNAT, LOGL_ERROR, "Failed to bind the USSD socket.\n"); + exit(1); + } + + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGPIPE, SIG_IGN); + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + /* recycle timer */ + sccp_set_log_area(DSCCP); + sccp_close.cb = sccp_close_unconfirmed; + sccp_close.data = NULL; + bsc_schedule_timer(&sccp_close, SCCP_CLOSE_TIME, 0); + + while (1) { + bsc_select_main(0); + } + + return 0; +} + +/* Close all connections handed out to the USSD module */ +int bsc_close_ussd_connections(struct bsc_nat *nat) +{ + struct sccp_connections *con; + llist_for_each_entry(con, &nat->sccp_connections, list_entry) { + if (con->con_local != 2) + continue; + if (!con->bsc) + continue; + + nat_send_clrc_bsc(con); + nat_send_rlsd_bsc(con); + } + + return 0; +} diff --git a/src/osmo-bsc_nat/bsc_nat_utils.c b/src/osmo-bsc_nat/bsc_nat_utils.c new file mode 100644 index 000000000..cd294ccfb --- /dev/null +++ b/src/osmo-bsc_nat/bsc_nat_utils.c @@ -0,0 +1,893 @@ + +/* BSC Multiplexer/NAT Utilities */ + +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include +#include +#include + +static const struct rate_ctr_desc bsc_cfg_ctr_description[] = { + [BCFG_CTR_SCCP_CONN] = { "sccp.conn", "SCCP Connections "}, + [BCFG_CTR_SCCP_CALLS] = { "sccp.calls", "SCCP Assignment Commands "}, + [BCFG_CTR_NET_RECONN] = { "net.reconnects", "Network reconnects "}, + [BCFG_CTR_DROPPED_SCCP] = { "dropped.sccp", "Dropped SCCP connections."}, + [BCFG_CTR_DROPPED_CALLS] = { "dropped.calls", "Dropped active calls. "}, + [BCFG_CTR_REJECTED_CR] = { "rejected.cr", "Rejected CR due filter "}, + [BCFG_CTR_REJECTED_MSG] = { "rejected.msg", "Rejected MSG due filter "}, + [BCFG_CTR_ILL_PACKET] = { "rejected.ill", "Rejected due parse error "}, + [BCFG_CTR_CON_TYPE_LU] = { "conn.lu", "Conn Location Update "}, + [BCFG_CTR_CON_CMSERV_RQ] = { "conn.rq", "Conn CM Service Req "}, + [BCFG_CTR_CON_PAG_RESP] = { "conn.pag", "Conn Paging Response "}, + [BCFG_CTR_CON_SSA] = { "conn.ssa", "Conn USSD "}, + [BCFG_CTR_CON_OTHER] = { "conn.other", "Conn Other "}, +}; + +static const struct rate_ctr_group_desc bsc_cfg_ctrg_desc = { + .group_name_prefix = "nat.bsc", + .group_description = "NAT BSC Statistics", + .num_ctr = ARRAY_SIZE(bsc_cfg_ctr_description), + .ctr_desc = bsc_cfg_ctr_description, +}; + +static const struct rate_ctr_desc acc_list_ctr_description[] = { + [ACC_LIST_BSC_FILTER] = { "access-list.bsc-filter", "Rejected by rule for BSC"}, + [ACC_LIST_NAT_FILTER] = { "access-list.nat-filter", "Rejected by rule for NAT"}, +}; + +static const struct rate_ctr_group_desc bsc_cfg_acc_list_desc = { + .group_name_prefix = "nat.filter", + .group_description = "NAT Access-List Statistics", + .num_ctr = ARRAY_SIZE(acc_list_ctr_description), + .ctr_desc = acc_list_ctr_description, +}; + +struct bsc_nat *bsc_nat_alloc(void) +{ + struct bsc_nat *nat = talloc_zero(tall_bsc_ctx, struct bsc_nat); + if (!nat) + return NULL; + + INIT_LLIST_HEAD(&nat->sccp_connections); + INIT_LLIST_HEAD(&nat->bsc_connections); + INIT_LLIST_HEAD(&nat->bsc_configs); + INIT_LLIST_HEAD(&nat->access_lists); + + nat->stats.sccp.conn = counter_alloc("nat.sccp.conn"); + nat->stats.sccp.calls = counter_alloc("nat.sccp.calls"); + nat->stats.bsc.reconn = counter_alloc("nat.bsc.conn"); + nat->stats.bsc.auth_fail = counter_alloc("nat.bsc.auth_fail"); + nat->stats.msc.reconn = counter_alloc("nat.msc.conn"); + nat->stats.ussd.reconn = counter_alloc("nat.ussd.conn"); + nat->msc_ip = talloc_strdup(nat, "127.0.0.1"); + nat->msc_port = 5000; + nat->auth_timeout = 2; + nat->ping_timeout = 20; + nat->pong_timeout = 5; + return nat; +} + +void bsc_nat_set_msc_ip(struct bsc_nat *nat, const char *ip) +{ + bsc_replace_string(nat, &nat->msc_ip, ip); +} + +struct bsc_connection *bsc_connection_alloc(struct bsc_nat *nat) +{ + struct bsc_connection *con = talloc_zero(nat, struct bsc_connection); + if (!con) + return NULL; + + con->nat = nat; + write_queue_init(&con->write_queue, 100); + return con; +} + +struct bsc_config *bsc_config_alloc(struct bsc_nat *nat, const char *token) +{ + struct bsc_config *conf = talloc_zero(nat, struct bsc_config); + if (!conf) + return NULL; + + conf->token = talloc_strdup(conf, token); + conf->nr = nat->num_bsc; + conf->nat = nat; + conf->max_endpoints = 32; + + INIT_LLIST_HEAD(&conf->lac_list); + + llist_add_tail(&conf->entry, &nat->bsc_configs); + ++nat->num_bsc; + + conf->stats.ctrg = rate_ctr_group_alloc(conf, &bsc_cfg_ctrg_desc, conf->nr); + if (!conf->stats.ctrg) { + talloc_free(conf); + return NULL; + } + + return conf; +} + +void bsc_config_free(struct bsc_config *cfg) +{ + rate_ctr_group_free(cfg->stats.ctrg); +} + +void bsc_config_add_lac(struct bsc_config *cfg, int _lac) +{ + struct bsc_lac_entry *lac; + + llist_for_each_entry(lac, &cfg->lac_list, entry) + if (lac->lac == _lac) + return; + + lac = talloc_zero(cfg, struct bsc_lac_entry); + if (!lac) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate.\n"); + return; + } + + lac->lac = _lac; + llist_add_tail(&lac->entry, &cfg->lac_list); +} + +void bsc_config_del_lac(struct bsc_config *cfg, int _lac) +{ + struct bsc_lac_entry *lac; + + llist_for_each_entry(lac, &cfg->lac_list, entry) + if (lac->lac == _lac) { + llist_del(&lac->entry); + talloc_free(lac); + return; + } +} + +int bsc_config_handles_lac(struct bsc_config *cfg, int lac_nr) +{ + struct bsc_lac_entry *entry; + + llist_for_each_entry(entry, &cfg->lac_list, entry) + if (entry->lac == lac_nr) + return 1; + + return 0; +} + +void sccp_connection_destroy(struct sccp_connections *conn) +{ + LOGP(DNAT, LOGL_DEBUG, "Destroy 0x%x <-> 0x%x mapping for con %p\n", + sccp_src_ref_to_int(&conn->real_ref), + sccp_src_ref_to_int(&conn->patched_ref), conn->bsc); + bsc_mgcp_dlcx(conn); + llist_del(&conn->list_entry); + talloc_free(conn); +} + +struct bsc_connection *bsc_nat_find_bsc(struct bsc_nat *nat, struct msgb *msg, int *lac_out) +{ + struct bsc_connection *bsc; + int data_length; + const uint8_t *data; + struct tlv_parsed tp; + int i = 0; + + *lac_out = -1; + + if (!msg->l3h || msgb_l3len(msg) < 3) { + LOGP(DNAT, LOGL_ERROR, "Paging message is too short.\n"); + return NULL; + } + + tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 3, msgb_l3len(msg) - 3, 0, 0); + if (!TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST)) { + LOGP(DNAT, LOGL_ERROR, "No CellIdentifier List inside paging msg.\n"); + return NULL; + } + + data_length = TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST); + data = TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST); + + /* No need to try a different BSS */ + if (data[0] == CELL_IDENT_BSS) { + return NULL; + } else if (data[0] != CELL_IDENT_LAC) { + LOGP(DNAT, LOGL_ERROR, "Unhandled cell ident discrminator: %d\n", data[0]); + return NULL; + } + + /* Currently we only handle one BSC */ + for (i = 1; i < data_length - 1; i += 2) { + unsigned int _lac = ntohs(*(unsigned int *) &data[i]); + *lac_out = _lac; + llist_for_each_entry(bsc, &nat->bsc_connections, list_entry) { + if (!bsc->cfg) + continue; + if (!bsc->authenticated) + continue; + if (!bsc_config_handles_lac(bsc->cfg, _lac)) + continue; + + return bsc; + } + } + + return NULL; +} + +int bsc_write_mgcp(struct bsc_connection *bsc, const uint8_t *data, unsigned int length) +{ + struct msgb *msg; + + if (length > 4096 - 128) { + LOGP(DINP, LOGL_ERROR, "Can not send message of that size.\n"); + return -1; + } + + msg = msgb_alloc_headroom(4096, 128, "to-bsc"); + if (!msg) { + LOGP(DINP, LOGL_ERROR, "Failed to allocate memory for BSC msg.\n"); + return -1; + } + + /* copy the data */ + msg->l3h = msgb_put(msg, length); + memcpy(msg->l3h, data, length); + + return bsc_write(bsc, msg, IPAC_PROTO_MGCP_OLD); +} + +int bsc_write(struct bsc_connection *bsc, struct msgb *msg, int proto) +{ + return bsc_do_write(&bsc->write_queue, msg, proto); +} + +int bsc_do_write(struct write_queue *queue, struct msgb *msg, int proto) +{ + /* prepend the header */ + ipaccess_prepend_header(msg, proto); + return bsc_write_msg(queue, msg); +} + +int bsc_write_msg(struct write_queue *queue, struct msgb *msg) +{ + if (write_queue_enqueue(queue, msg) != 0) { + LOGP(DINP, LOGL_ERROR, "Failed to enqueue the write.\n"); + msgb_free(msg); + return -1; + } + + return 0; +} + +int bsc_nat_lst_check_allow(struct bsc_nat_acc_lst *lst, const char *mi_string) +{ + struct bsc_nat_acc_lst_entry *entry; + + llist_for_each_entry(entry, &lst->fltr_list, list) { + if (!entry->imsi_allow) + continue; + if (regexec(&entry->imsi_allow_re, mi_string, 0, NULL, 0) == 0) + return 0; + } + + return 1; +} + +static int lst_check_deny(struct bsc_nat_acc_lst *lst, const char *mi_string) +{ + struct bsc_nat_acc_lst_entry *entry; + + llist_for_each_entry(entry, &lst->fltr_list, list) { + if (!entry->imsi_deny) + continue; + if (regexec(&entry->imsi_deny_re, mi_string, 0, NULL, 0) == 0) + return 0; + } + + return 1; +} + +/* apply white/black list */ +static int auth_imsi(struct bsc_connection *bsc, const char *mi_string) +{ + /* + * Now apply blacklist/whitelist of the BSC and the NAT. + * 1.) Allow directly if the IMSI is allowed at the BSC + * 2.) Reject if the IMSI is not allowed at the BSC + * 3.) Reject if the IMSI not allowed at the global level. + * 4.) Allow directly if the IMSI is allowed at the global level + */ + struct bsc_nat_acc_lst *nat_lst = NULL; + struct bsc_nat_acc_lst *bsc_lst = NULL; + + bsc_lst = bsc_nat_acc_lst_find(bsc->nat, bsc->cfg->acc_lst_name); + nat_lst = bsc_nat_acc_lst_find(bsc->nat, bsc->nat->acc_lst_name); + + + if (bsc_lst) { + /* 1. BSC allow */ + if (bsc_nat_lst_check_allow(bsc_lst, mi_string) == 0) + return 1; + + /* 2. BSC deny */ + if (lst_check_deny(bsc_lst, mi_string) == 0) { + LOGP(DNAT, LOGL_ERROR, + "Filtering %s by imsi_deny on bsc nr: %d.\n", mi_string, bsc->cfg->nr); + rate_ctr_inc(&bsc_lst->stats->ctr[ACC_LIST_BSC_FILTER]); + return -2; + } + + } + + /* 3. NAT deny */ + if (nat_lst) { + if (lst_check_deny(nat_lst, mi_string) == 0) { + LOGP(DNAT, LOGL_ERROR, + "Filtering %s by nat imsi_deny on bsc nr: %d.\n", mi_string, bsc->cfg->nr); + rate_ctr_inc(&nat_lst->stats->ctr[ACC_LIST_NAT_FILTER]); + return -3; + } + } + + return 1; +} + +static int _cr_check_loc_upd(struct bsc_connection *bsc, + uint8_t *data, unsigned int length, + char **imsi) +{ + uint8_t mi_type; + struct gsm48_loc_upd_req *lu; + char mi_string[GSM48_MI_SIZE]; + + if (length < sizeof(*lu)) { + LOGP(DNAT, LOGL_ERROR, + "LU does not fit. Length is %d \n", length); + return -1; + } + + lu = (struct gsm48_loc_upd_req *) data; + mi_type = lu->mi[0] & GSM_MI_TYPE_MASK; + + /* + * We can only deal with the IMSI. This will fail for a phone that + * will send the TMSI of a previous network to us. + */ + if (mi_type != GSM_MI_TYPE_IMSI) + return 0; + + gsm48_mi_to_string(mi_string, sizeof(mi_string), lu->mi, lu->mi_len); + *imsi = talloc_strdup(bsc, mi_string); + return auth_imsi(bsc, mi_string); +} + +static int _cr_check_cm_serv_req(struct bsc_connection *bsc, + uint8_t *data, unsigned int length, + int *con_type, char **imsi) +{ + static const uint32_t classmark_offset = + offsetof(struct gsm48_service_request, classmark); + + char mi_string[GSM48_MI_SIZE]; + uint8_t mi_type; + int rc; + struct gsm48_service_request *req; + + /* unfortunately in Phase1 the classmark2 length is variable */ + + if (length < sizeof(*req)) { + LOGP(DNAT, LOGL_ERROR, + "CM Serv Req does not fit. Length is %d\n", length); + return -1; + } + + req = (struct gsm48_service_request *) data; + if (req->cm_service_type == 0x8) + *con_type = NAT_CON_TYPE_SSA; + rc = gsm48_extract_mi((uint8_t *) &req->classmark, + length - classmark_offset, mi_string, &mi_type); + if (rc < 0) { + LOGP(DNAT, LOGL_ERROR, "Failed to parse the classmark2/mi. error: %d\n", rc); + return -1; + } + + /* we have to let the TMSI or such pass */ + if (mi_type != GSM_MI_TYPE_IMSI) + return 0; + + *imsi = talloc_strdup(bsc, mi_string); + return auth_imsi(bsc, mi_string); +} + +static int _cr_check_pag_resp(struct bsc_connection *bsc, + uint8_t *data, unsigned int length, + char **imsi) +{ + struct gsm48_pag_resp *resp; + char mi_string[GSM48_MI_SIZE]; + uint8_t mi_type; + + if (length < sizeof(*resp)) { + LOGP(DNAT, LOGL_ERROR, "PAG RESP does not fit. Length was %d.\n", length); + return -1; + } + + resp = (struct gsm48_pag_resp *) data; + if (gsm48_paging_extract_mi(resp, length, mi_string, &mi_type) < 0) { + LOGP(DNAT, LOGL_ERROR, "Failed to extract the MI.\n"); + return -1; + } + + /* we need to let it pass for now */ + if (mi_type != GSM_MI_TYPE_IMSI) + return 0; + + *imsi = talloc_strdup(bsc, mi_string); + return auth_imsi(bsc, mi_string); +} + +static int _dt_check_id_resp(struct bsc_connection *bsc, + uint8_t *data, unsigned int length, + struct sccp_connections *con) +{ + char mi_string[GSM48_MI_SIZE]; + uint8_t mi_type; + int ret; + + if (length < 2) { + LOGP(DNAT, LOGL_ERROR, "mi does not fit.\n"); + return -1; + } + + if (data[0] < length - 1) { + LOGP(DNAT, LOGL_ERROR, "mi length too big.\n"); + return -2; + } + + mi_type = data[1] & GSM_MI_TYPE_MASK; + gsm48_mi_to_string(mi_string, sizeof(mi_string), &data[1], data[0]); + + if (mi_type != GSM_MI_TYPE_IMSI) + return 0; + + ret = auth_imsi(bsc, mi_string); + con->imsi_checked = 1; + con->imsi = talloc_strdup(con, mi_string); + return ret; +} + +/* Filter out CR data... */ +int bsc_nat_filter_sccp_cr(struct bsc_connection *bsc, struct msgb *msg, + struct bsc_nat_parsed *parsed, int *con_type, + char **imsi) +{ + struct tlv_parsed tp; + struct gsm48_hdr *hdr48; + int hdr48_len; + int len; + uint8_t msg_type; + + *con_type = NAT_CON_TYPE_NONE; + *imsi = NULL; + + if (parsed->gsm_type != BSS_MAP_MSG_COMPLETE_LAYER_3) { + LOGP(DNAT, LOGL_ERROR, + "Rejecting CR message due wrong GSM Type %d\n", parsed->gsm_type); + return -1; + } + + /* the parsed has had some basic l3 length check */ + len = msg->l3h[1]; + if (msgb_l3len(msg) - 3 < len) { + LOGP(DNAT, LOGL_ERROR, + "The CR Data has not enough space...\n"); + return -1; + } + + msg->l4h = &msg->l3h[3]; + len -= 1; + + tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h, len, 0, 0); + + if (!TLVP_PRESENT(&tp, GSM0808_IE_LAYER_3_INFORMATION)) { + LOGP(DNAT, LOGL_ERROR, "CR Data does not contain layer3 information.\n"); + return -1; + } + + hdr48_len = TLVP_LEN(&tp, GSM0808_IE_LAYER_3_INFORMATION); + + if (hdr48_len < sizeof(*hdr48)) { + LOGP(DNAT, LOGL_ERROR, "GSM48 header does not fit.\n"); + return -1; + } + + hdr48 = (struct gsm48_hdr *) TLVP_VAL(&tp, GSM0808_IE_LAYER_3_INFORMATION); + + msg_type = hdr48->msg_type & 0xbf; + if (hdr48->proto_discr == GSM48_PDISC_MM && + msg_type == GSM48_MT_MM_LOC_UPD_REQUEST) { + *con_type = NAT_CON_TYPE_LU; + return _cr_check_loc_upd(bsc, &hdr48->data[0], hdr48_len - sizeof(*hdr48), imsi); + } else if (hdr48->proto_discr == GSM48_PDISC_MM && + msg_type == GSM48_MT_MM_CM_SERV_REQ) { + *con_type = NAT_CON_TYPE_CM_SERV_REQ; + return _cr_check_cm_serv_req(bsc, &hdr48->data[0], + hdr48_len - sizeof(*hdr48), + con_type, imsi); + } else if (hdr48->proto_discr == GSM48_PDISC_RR && + msg_type == GSM48_MT_RR_PAG_RESP) { + *con_type = NAT_CON_TYPE_PAG_RESP; + return _cr_check_pag_resp(bsc, &hdr48->data[0], hdr48_len - sizeof(*hdr48), imsi); + } else { + /* We only want to filter the above, let other things pass */ + *con_type = NAT_CON_TYPE_OTHER; + return 0; + } +} + +struct gsm48_hdr *bsc_unpack_dtap(struct bsc_nat_parsed *parsed, + struct msgb *msg, uint32_t *len) +{ + /* gsm_type is actually the size of the dtap */ + *len = parsed->gsm_type; + if (*len < msgb_l3len(msg) - 3) { + LOGP(DNAT, LOGL_ERROR, "Not enough space for DTAP.\n"); + return NULL; + } + + if (*len < sizeof(struct gsm48_hdr)) { + LOGP(DNAT, LOGL_ERROR, "GSM48 header does not fit.\n"); + return NULL; + } + + msg->l4h = &msg->l3h[3]; + return (struct gsm48_hdr *) msg->l4h; +} + +int bsc_nat_filter_dt(struct bsc_connection *bsc, struct msgb *msg, + struct sccp_connections *con, struct bsc_nat_parsed *parsed) +{ + uint32_t len; + uint8_t msg_type; + struct gsm48_hdr *hdr48; + + if (con->imsi_checked) + return 0; + + /* only care about DTAP messages */ + if (parsed->bssap != BSSAP_MSG_DTAP) + return 0; + + hdr48 = bsc_unpack_dtap(parsed, msg, &len); + if (!hdr48) + return -1; + + msg_type = hdr48->msg_type & 0xbf; + if (hdr48->proto_discr == GSM48_PDISC_MM && + msg_type == GSM48_MT_MM_ID_RESP) { + return _dt_check_id_resp(bsc, &hdr48->data[0], len - sizeof(*hdr48), con); + } else { + return 0; + } +} + +void bsc_parse_reg(void *ctx, regex_t *reg, char **imsi, int argc, const char **argv) +{ + if (*imsi) { + talloc_free(*imsi); + *imsi = NULL; + } + regfree(reg); + + if (argc > 0) { + *imsi = talloc_strdup(ctx, argv[0]); + regcomp(reg, argv[0], 0); + } +} + +static const char *con_types [] = { + [NAT_CON_TYPE_NONE] = "n/a", + [NAT_CON_TYPE_LU] = "Location Update", + [NAT_CON_TYPE_CM_SERV_REQ] = "CM Serv Req", + [NAT_CON_TYPE_PAG_RESP] = "Paging Response", + [NAT_CON_TYPE_SSA] = "Supplementar Service Activation", + [NAT_CON_TYPE_LOCAL_REJECT] = "Local Reject", + [NAT_CON_TYPE_OTHER] = "Other", +}; + +const char *bsc_con_type_to_string(int type) +{ + return con_types[type]; +} + +struct bsc_nat_acc_lst *bsc_nat_acc_lst_find(struct bsc_nat *nat, const char *name) +{ + struct bsc_nat_acc_lst *lst; + + if (!name) + return NULL; + + llist_for_each_entry(lst, &nat->access_lists, list) + if (strcmp(lst->name, name) == 0) + return lst; + + return NULL; +} + +struct bsc_nat_acc_lst *bsc_nat_acc_lst_get(struct bsc_nat *nat, const char *name) +{ + struct bsc_nat_acc_lst *lst; + + lst = bsc_nat_acc_lst_find(nat, name); + if (lst) + return lst; + + lst = talloc_zero(nat, struct bsc_nat_acc_lst); + if (!lst) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate access list"); + return NULL; + } + + /* TODO: get the index right */ + lst->stats = rate_ctr_group_alloc(lst, &bsc_cfg_acc_list_desc, 0); + if (!lst->stats) { + talloc_free(lst); + return NULL; + } + + INIT_LLIST_HEAD(&lst->fltr_list); + lst->name = talloc_strdup(lst, name); + llist_add_tail(&lst->list, &nat->access_lists); + return lst; +} + +void bsc_nat_acc_lst_delete(struct bsc_nat_acc_lst *lst) +{ + llist_del(&lst->list); + rate_ctr_group_free(lst->stats); + talloc_free(lst); +} + +struct bsc_nat_acc_lst_entry *bsc_nat_acc_lst_entry_create(struct bsc_nat_acc_lst *lst) +{ + struct bsc_nat_acc_lst_entry *entry; + + entry = talloc_zero(lst, struct bsc_nat_acc_lst_entry); + if (!entry) + return NULL; + + llist_add_tail(&entry->list, &lst->fltr_list); + return entry; +} + +int bsc_nat_msc_is_connected(struct bsc_nat *nat) +{ + return nat->msc_con->is_connected; +} + +static const int con_to_ctr[] = { + [NAT_CON_TYPE_NONE] = -1, + [NAT_CON_TYPE_LU] = BCFG_CTR_CON_TYPE_LU, + [NAT_CON_TYPE_CM_SERV_REQ] = BCFG_CTR_CON_CMSERV_RQ, + [NAT_CON_TYPE_PAG_RESP] = BCFG_CTR_CON_PAG_RESP, + [NAT_CON_TYPE_SSA] = BCFG_CTR_CON_SSA, + [NAT_CON_TYPE_LOCAL_REJECT] = -1, + [NAT_CON_TYPE_OTHER] = BCFG_CTR_CON_OTHER, +}; + +int bsc_conn_type_to_ctr(struct sccp_connections *conn) +{ + return con_to_ctr[conn->con_type]; +} + +int bsc_write_cb(struct bsc_fd *bfd, struct msgb *msg) +{ + int rc; + + rc = write(bfd->fd, msg->data, msg->len); + if (rc != msg->len) + LOGP(DNAT, LOGL_ERROR, "Failed to write message to the BSC.\n"); + + return rc; +} + +/** + * Rewrite non global numbers... according to rules based on the IMSI + */ +struct msgb *bsc_nat_rewrite_setup(struct bsc_nat *nat, struct msgb *msg, struct bsc_nat_parsed *parsed, const char *imsi) +{ + struct tlv_parsed tp; + struct gsm48_hdr *hdr48; + uint32_t len; + uint8_t msg_type; + unsigned int payload_len; + struct gsm_mncc_number called; + struct msg_entry *entry; + char *new_number = NULL; + struct msgb *out, *sccp; + uint8_t *outptr; + const uint8_t *msgptr; + int sec_len; + + if (!imsi || strlen(imsi) < 5) + return msg; + + if (!nat->num_rewr) + return msg; + + /* only care about DTAP messages */ + if (parsed->bssap != BSSAP_MSG_DTAP) + return msg; + if (!parsed->dest_local_ref) + return msg; + + hdr48 = bsc_unpack_dtap(parsed, msg, &len); + if (!hdr48) + return msg; + + msg_type = hdr48->msg_type & 0xbf; + if (hdr48->proto_discr != GSM48_PDISC_CC || + msg_type != GSM48_MT_CC_SETUP) + return msg; + + /* decode and rewrite the message */ + payload_len = len - sizeof(*hdr48); + tlv_parse(&tp, &gsm48_att_tlvdef, hdr48->data, payload_len, 0, 0); + + /* no number, well let us ignore it */ + if (!TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) + return msg; + + memset(&called, 0, sizeof(called)); + gsm48_decode_called(&called, + TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) - 1); + + /* check if it looks international and stop */ + if (called.plan != 1) + return msg; + if (called.type == 1) + return msg; + if (strncmp(called.number, "00", 2) == 0) + return msg; + + /* need to find a replacement and then fix it */ + llist_for_each_entry(entry, &nat->num_rewr->entry, list) { + regex_t reg; + regmatch_t matches[2]; + + if (entry->mcc[0] != '*' && strncmp(entry->mcc, imsi, 3) != 0) + continue; + if (entry->mnc[0] != '*' && strncmp(entry->mnc, imsi + 3, 2) != 0) + continue; + + if (entry->text[0] == '+') { + LOGP(DNAT, LOGL_ERROR, + "Plus is not allowed in the number"); + continue; + } + + /* We have an entry for the IMSI. Need to match now */ + if (regcomp(®, entry->option, REG_EXTENDED) != 0) { + LOGP(DNAT, LOGL_ERROR, + "Regexp '%s' is not valid.\n", entry->option); + continue; + } + + /* this regexp matches... */ + if (regexec(®, called.number, 2, matches, 0) == 0 && + matches[1].rm_eo != -1) + new_number = talloc_asprintf(msg, "%s%s", + entry->text, + &called.number[matches[1].rm_so]); + regfree(®); + + if (new_number) + break; + } + + if (!new_number) { + LOGP(DNAT, LOGL_DEBUG, "No IMSI match found, returning message.\n"); + return msg; + } + + if (strlen(new_number) > sizeof(called.number)) { + LOGP(DNAT, LOGL_ERROR, "Number is too long for structure.\n"); + talloc_free(new_number); + return msg; + } + + /* + * Need to create a new message now based on the old onew + * with a new number. We can sadly not patch this in place + * so we will need to regenerate it. + */ + + out = msgb_alloc_headroom(4096, 128, "changed-setup"); + if (!out) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate.\n"); + talloc_free(new_number); + return msg; + } + + /* copy the header */ + outptr = msgb_put(out, sizeof(*hdr48)); + memcpy(outptr, hdr48, sizeof(*hdr48)); + + /* copy everything up to the number */ + sec_len = TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) - 2 - &hdr48->data[0]; + outptr = msgb_put(out, sec_len); + memcpy(outptr, &hdr48->data[0], sec_len); + + /* create the new number */ + if (strncmp(new_number, "00", 2) == 0) { + called.type = 1; + strncpy(called.number, new_number + 2, sizeof(called.number)); + } else { + strncpy(called.number, new_number, sizeof(called.number)); + } + gsm48_encode_called(out, &called); + + /* copy thre rest */ + msgptr = TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) + + TLVP_LEN(&tp, GSM48_IE_CALLED_BCD); + sec_len = payload_len - (msgptr - &hdr48->data[0]); + outptr = msgb_put(out, sec_len); + memcpy(outptr, msgptr, sec_len); + + /* wrap with DTAP, SCCP, then IPA. TODO: Stop copying */ + gsm0808_prepend_dtap_header(out, 0); + sccp = sccp_create_dt1(parsed->dest_local_ref, out->data, out->len); + if (!sccp) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate.\n"); + talloc_free(new_number); + talloc_free(out); + return msg; + } + + ipaccess_prepend_header(sccp, IPAC_PROTO_SCCP); + + /* give up memory, we are done */ + talloc_free(new_number); + /* the parsed hangs off from msg but it needs to survive */ + talloc_steal(sccp, parsed); + msgb_free(msg); + msgb_free(out); + out = NULL; + return sccp; +} + diff --git a/src/osmo-bsc_nat/bsc_nat_vty.c b/src/osmo-bsc_nat/bsc_nat_vty.c new file mode 100644 index 000000000..786db2dc2 --- /dev/null +++ b/src/osmo-bsc_nat/bsc_nat_vty.c @@ -0,0 +1,788 @@ +/* OpenBSC NAT interface to quagga VTY */ +/* (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +static struct bsc_nat *_nat; + +static struct cmd_node nat_node = { + NAT_NODE, + "%s(nat)#", + 1, +}; + +static struct cmd_node bsc_node = { + NAT_BSC_NODE, + "%s(bsc)#", + 1, +}; + +static void write_acc_lst(struct vty *vty, struct bsc_nat_acc_lst *lst) +{ + struct bsc_nat_acc_lst_entry *entry; + + llist_for_each_entry(entry, &lst->fltr_list, list) { + if (entry->imsi_allow) + vty_out(vty, " access-list %s imsi-allow %s%s", + lst->name, entry->imsi_allow, VTY_NEWLINE); + if (entry->imsi_deny) + vty_out(vty, " access-list %s imsi-deny %s%s", + lst->name, entry->imsi_deny, VTY_NEWLINE); + } +} + +static int config_write_nat(struct vty *vty) +{ + struct bsc_nat_acc_lst *lst; + + vty_out(vty, "nat%s", VTY_NEWLINE); + vty_out(vty, " msc ip %s%s", _nat->msc_ip, VTY_NEWLINE); + vty_out(vty, " msc port %d%s", _nat->msc_port, VTY_NEWLINE); + vty_out(vty, " timeout auth %d%s", _nat->auth_timeout, VTY_NEWLINE); + vty_out(vty, " timeout ping %d%s", _nat->ping_timeout, VTY_NEWLINE); + vty_out(vty, " timeout pong %d%s", _nat->pong_timeout, VTY_NEWLINE); + if (_nat->token) + vty_out(vty, " token %s%s", _nat->token, VTY_NEWLINE); + vty_out(vty, " ip-dscp %d%s", _nat->bsc_ip_dscp, VTY_NEWLINE); + if (_nat->acc_lst_name) + vty_out(vty, " access-list-name %s%s", _nat->acc_lst_name, VTY_NEWLINE); + if (_nat->ussd_lst_name) + vty_out(vty, " ussd-list-name %s%s", _nat->ussd_lst_name, VTY_NEWLINE); + if (_nat->ussd_query) + vty_out(vty, " ussd-query %s%s", _nat->ussd_query, VTY_NEWLINE); + if (_nat->ussd_token) + vty_out(vty, " ussd-token %s%s", _nat->ussd_token, VTY_NEWLINE); + if (_nat->ussd_local) + vty_out(vty, " ussd-local-ip %s%s", _nat->ussd_local, VTY_NEWLINE); + + if (_nat->num_rewr_name) + vty_out(vty, " number-rewrite %s%s", _nat->num_rewr_name, VTY_NEWLINE); + + llist_for_each_entry(lst, &_nat->access_lists, list) { + write_acc_lst(vty, lst); + } + + return CMD_SUCCESS; +} + +static void dump_lac(struct vty *vty, struct bsc_config *cfg) +{ + struct bsc_lac_entry *lac; + llist_for_each_entry(lac, &cfg->lac_list, entry) + vty_out(vty, " location_area_code %u%s", lac->lac, VTY_NEWLINE); +} + +static void config_write_bsc_single(struct vty *vty, struct bsc_config *bsc) +{ + vty_out(vty, " bsc %u%s", bsc->nr, VTY_NEWLINE); + vty_out(vty, " token %s%s", bsc->token, VTY_NEWLINE); + dump_lac(vty, bsc); + vty_out(vty, " paging forbidden %d%s", bsc->forbid_paging, VTY_NEWLINE); + if (bsc->description) + vty_out(vty, " description %s%s", bsc->description, VTY_NEWLINE); + if (bsc->acc_lst_name) + vty_out(vty, " access-list-name %s%s", bsc->acc_lst_name, VTY_NEWLINE); + vty_out(vty, " max-endpoints %d%s", bsc->max_endpoints, VTY_NEWLINE); +} + +static int config_write_bsc(struct vty *vty) +{ + struct bsc_config *bsc; + + llist_for_each_entry(bsc, &_nat->bsc_configs, entry) + config_write_bsc_single(vty, bsc); + return CMD_SUCCESS; +} + + +DEFUN(show_sccp, show_sccp_cmd, "show sccp connections", + SHOW_STR "Display information about current SCCP connections") +{ + struct sccp_connections *con; + vty_out(vty, "Listing all open SCCP connections%s", VTY_NEWLINE); + + llist_for_each_entry(con, &_nat->sccp_connections, list_entry) { + vty_out(vty, "For BSC Nr: %d BSC ref: 0x%x; MUX ref: 0x%x; Network has ref: %d ref: 0x%x MSC/BSC mux: 0x%x/0x%x type: %s%s", + con->bsc->cfg ? con->bsc->cfg->nr : -1, + sccp_src_ref_to_int(&con->real_ref), + sccp_src_ref_to_int(&con->patched_ref), + con->has_remote_ref, + sccp_src_ref_to_int(&con->remote_ref), + con->msc_endp, con->bsc_endp, + bsc_con_type_to_string(con->con_type), + VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(show_bsc, show_bsc_cmd, "show bsc connections", + SHOW_STR "Display information about current BSCs") +{ + struct bsc_connection *con; + struct sockaddr_in sock; + socklen_t len = sizeof(sock); + + llist_for_each_entry(con, &_nat->bsc_connections, list_entry) { + getpeername(con->write_queue.bfd.fd, (struct sockaddr *) &sock, &len); + vty_out(vty, "BSC nr: %d auth: %d fd: %d peername: %s%s", + con->cfg ? con->cfg->nr : -1, + con->authenticated, con->write_queue.bfd.fd, + inet_ntoa(sock.sin_addr), VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(show_bsc_mgcp, show_bsc_mgcp_cmd, "show bsc mgcp NR", + SHOW_STR "Display the MGCP status for a given BSC") +{ + struct bsc_connection *con; + int nr = atoi(argv[0]); + int i, j, endp; + + llist_for_each_entry(con, &_nat->bsc_connections, list_entry) { + int max; + if (!con->cfg) + continue; + if (con->cfg->nr != nr) + continue; + + /* this bsc has no audio endpoints yet */ + if (!con->_endpoint_status) + continue; + + vty_out(vty, "MGCP Status for %d%s", con->cfg->nr, VTY_NEWLINE); + max = bsc_mgcp_nr_multiplexes(con->max_endpoints); + for (i = 0; i < max; ++i) { + for (j = 0; j < 32; ++j) { + endp = mgcp_timeslot_to_endpoint(i, j); + vty_out(vty, " Endpoint 0x%x %s%s", endp, + con->_endpoint_status[endp] == 0 + ? "free" : "allocated", + VTY_NEWLINE); + } + } + break; + } + + return CMD_SUCCESS; +} + +DEFUN(show_bsc_cfg, show_bsc_cfg_cmd, "show bsc config", + SHOW_STR "Display information about known BSC configs") +{ + struct bsc_config *conf; + llist_for_each_entry(conf, &_nat->bsc_configs, entry) { + vty_out(vty, "BSC token: '%s' nr: %u%s", + conf->token, conf->nr, VTY_NEWLINE); + if (conf->acc_lst_name) + vty_out(vty, " access-list: %s%s", + conf->acc_lst_name, VTY_NEWLINE); + vty_out(vty, " paging forbidden: %d%s", + conf->forbid_paging, VTY_NEWLINE); + if (conf->description) + vty_out(vty, " description: %s%s", conf->description, VTY_NEWLINE); + else + vty_out(vty, " No description.%s", VTY_NEWLINE); + + } + + return CMD_SUCCESS; +} + +static void dump_stat_total(struct vty *vty, struct bsc_nat *nat) +{ + vty_out(vty, "NAT statistics%s", VTY_NEWLINE); + vty_out(vty, " SCCP Connections %lu total, %lu calls%s", + counter_get(nat->stats.sccp.conn), + counter_get(nat->stats.sccp.calls), VTY_NEWLINE); + vty_out(vty, " MSC Connections %lu%s", + counter_get(nat->stats.msc.reconn), VTY_NEWLINE); + vty_out(vty, " MSC Connected: %d%s", + nat->msc_con->is_connected, VTY_NEWLINE); + vty_out(vty, " BSC Connections %lu total, %lu auth failed.%s", + counter_get(nat->stats.bsc.reconn), + counter_get(nat->stats.bsc.auth_fail), VTY_NEWLINE); +} + +static void dump_stat_bsc(struct vty *vty, struct bsc_config *conf) +{ + int connected = 0; + struct bsc_connection *con; + + vty_out(vty, " BSC nr: %d%s", + conf->nr, VTY_NEWLINE); + vty_out_rate_ctr_group(vty, " ", conf->stats.ctrg); + + llist_for_each_entry(con, &conf->nat->bsc_connections, list_entry) { + if (con->cfg != conf) + continue; + connected = 1; + break; + } + + vty_out(vty, " Connected: %d%s", connected, VTY_NEWLINE); +} + +DEFUN(show_stats, + show_stats_cmd, + "show statistics [NR]", + SHOW_STR "Display network statistics") +{ + struct bsc_config *conf; + + int nr = -1; + + if (argc == 1) + nr = atoi(argv[0]); + + dump_stat_total(vty, _nat); + llist_for_each_entry(conf, &_nat->bsc_configs, entry) { + if (argc == 1 && nr != conf->nr) + continue; + dump_stat_bsc(vty, conf); + } + + return CMD_SUCCESS; +} + +DEFUN(show_stats_lac, + show_stats_lac_cmd, + "show statistics-by-lac <0-65535>", + SHOW_STR "Display network statistics by lac\n" + "The lac of the BSC\n") +{ + int lac; + struct bsc_config *conf; + + lac = atoi(argv[0]); + + dump_stat_total(vty, _nat); + llist_for_each_entry(conf, &_nat->bsc_configs, entry) { + if (!bsc_config_handles_lac(conf, lac)) + continue; + dump_stat_bsc(vty, conf); + } + + return CMD_SUCCESS; +} + +DEFUN(show_msc, + show_msc_cmd, + "show msc connection", + SHOW_STR "Show the status of the MSC connection.") +{ + if (!_nat->msc_con) { + vty_out(vty, "The MSC is not yet configured.\n"); + return CMD_WARNING; + } + + vty_out(vty, "MSC on %s:%d is connected: %d%s\n", + _nat->msc_con->ip, _nat->msc_con->port, + _nat->msc_con->is_connected, VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(close_bsc, + close_bsc_cmd, + "close bsc connection BSC_NR", + "Close the connection with the BSC identified by the config number.") +{ + struct bsc_connection *bsc; + int bsc_nr = atoi(argv[0]); + + llist_for_each_entry(bsc, &_nat->bsc_connections, list_entry) { + if (!bsc->cfg || bsc->cfg->nr != bsc_nr) + continue; + bsc_close_connection(bsc); + break; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_nat, cfg_nat_cmd, "nat", "Configute the NAT") +{ + vty->index = _nat; + vty->node = NAT_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_nat_msc_ip, + cfg_nat_msc_ip_cmd, + "msc ip A.B.C.D", + "Set the IP address of the MSC.") +{ + bsc_nat_set_msc_ip(_nat, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_nat_msc_port, + cfg_nat_msc_port_cmd, + "msc port <1-65500>", + "Set the port of the MSC.") +{ + _nat->msc_port = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_nat_auth_time, + cfg_nat_auth_time_cmd, + "timeout auth <1-256>", + "The time to wait for an auth response.") +{ + _nat->auth_timeout = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_nat_ping_time, + cfg_nat_ping_time_cmd, + "timeout ping NR", + "Send a ping every NR seconds. Negative to disable.") +{ + _nat->ping_timeout = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_nat_pong_time, + cfg_nat_pong_time_cmd, + "timeout pong NR", + "Wait NR seconds for the PONG response. Should be smaller than ping.") +{ + _nat->pong_timeout = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_nat_token, cfg_nat_token_cmd, + "token TOKEN", + "Set a token for the NAT") +{ + bsc_replace_string(_nat, &_nat->token, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_nat_bsc_ip_dscp, cfg_nat_bsc_ip_dscp_cmd, + "ip-dscp <0-255>", + "Set the IP DSCP for the BSCs to use\n" "Set the IP_TOS attribute") +{ + _nat->bsc_ip_dscp = atoi(argv[0]); + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_nat_bsc_ip_dscp, cfg_nat_bsc_ip_tos_cmd, + "ip-tos <0-255>", + "Use ip-dscp in the future.\n" "Set the DSCP\n") + + +DEFUN(cfg_nat_acc_lst_name, + cfg_nat_acc_lst_name_cmd, + "access-list-name NAME", + "Set the name of the access list to use.\n" + "The name of the to be used access list.") +{ + bsc_replace_string(_nat, &_nat->acc_lst_name, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_nat_number_rewrite, + cfg_nat_number_rewrite_cmd, + "number-rewrite FILENAME", + "Set the file with rewriting rules.\n" "Filename") +{ + bsc_replace_string(_nat, &_nat->num_rewr_name, argv[0]); + if (_nat->num_rewr_name) { + if (_nat->num_rewr) + talloc_free(_nat->num_rewr); + _nat->num_rewr = msg_entry_parse(_nat, _nat->num_rewr_name); + return _nat->num_rewr == NULL ? CMD_WARNING : CMD_SUCCESS; + } else { + if (_nat->num_rewr) + talloc_free(_nat->num_rewr); + _nat->num_rewr = NULL; + return CMD_SUCCESS; + } +} + +DEFUN(cfg_nat_ussd_lst_name, + cfg_nat_ussd_lst_name_cmd, + "ussd-list-name NAME", + "Set the name of the access list to check for IMSIs for USSD message\n" + "The name of the access list for HLR USSD handling") +{ + bsc_replace_string(_nat, &_nat->ussd_lst_name, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_nat_ussd_query, + cfg_nat_ussd_query_cmd, + "ussd-query QUERY", + "Set the USSD query to match with the ussd-list-name\n" + "The query to match") +{ + bsc_replace_string(_nat, &_nat->ussd_query, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_nat_ussd_token, + cfg_nat_ussd_token_cmd, + "ussd-token TOKEN", + "Set the token used to identify the USSD module\n" "Secret key\n") +{ + bsc_replace_string(_nat, &_nat->ussd_token, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_nat_ussd_local, + cfg_nat_ussd_local_cmd, + "ussd-local-ip A.B.C.D", + "Set the IP to listen for the USSD Provider\n" "IP Address\n") +{ + bsc_replace_string(_nat, &_nat->ussd_local, argv[0]); + return CMD_SUCCESS; +} + +/* per BSC configuration */ +DEFUN(cfg_bsc, cfg_bsc_cmd, "bsc BSC_NR", "Select a BSC to configure") +{ + int bsc_nr = atoi(argv[0]); + struct bsc_config *bsc; + + if (bsc_nr > _nat->num_bsc) { + vty_out(vty, "%% The next unused BSC number is %u%s", + _nat->num_bsc, VTY_NEWLINE); + return CMD_WARNING; + } else if (bsc_nr == _nat->num_bsc) { + /* allocate a new one */ + bsc = bsc_config_alloc(_nat, "unknown"); + } else + bsc = bsc_config_num(_nat, bsc_nr); + + if (!bsc) + return CMD_WARNING; + + vty->index = bsc; + vty->node = NAT_BSC_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bsc_token, cfg_bsc_token_cmd, "token TOKEN", "Set the token") +{ + struct bsc_config *conf = vty->index; + + bsc_replace_string(conf, &conf->token, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bsc_lac, cfg_bsc_lac_cmd, "location_area_code <0-65535>", + "Set the Location Area Code (LAC) of this BSC") +{ + struct bsc_config *tmp; + struct bsc_config *conf = vty->index; + + int lac = atoi(argv[0]); + + if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) { + vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s", + lac, VTY_NEWLINE); + return CMD_WARNING; + } + + /* verify that the LACs are unique */ + llist_for_each_entry(tmp, &_nat->bsc_configs, entry) { + if (bsc_config_handles_lac(tmp, lac)) { + vty_out(vty, "%% LAC %d is already used.%s", lac, VTY_NEWLINE); + return CMD_ERR_INCOMPLETE; + } + } + + bsc_config_add_lac(conf, lac); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bsc_no_lac, cfg_bsc_no_lac_cmd, + "no location_area_code <0-65535>", + NO_STR "Set the Location Area Code (LAC) of this BSC") +{ + int lac = atoi(argv[0]); + struct bsc_config *conf = vty->index; + + bsc_config_del_lac(conf, lac); + return CMD_SUCCESS; +} + + + +DEFUN(cfg_lst_imsi_allow, + cfg_lst_imsi_allow_cmd, + "access-list NAME imsi-allow [REGEXP]", + "Allow IMSIs matching the REGEXP\n" + "The name of the access-list\n" + "The regexp of allowed IMSIs\n") +{ + struct bsc_nat_acc_lst *acc; + struct bsc_nat_acc_lst_entry *entry; + + acc = bsc_nat_acc_lst_get(_nat, argv[0]); + if (!acc) + return CMD_WARNING; + + entry = bsc_nat_acc_lst_entry_create(acc); + if (!entry) + return CMD_WARNING; + + bsc_parse_reg(acc, &entry->imsi_allow_re, &entry->imsi_allow, argc - 1, &argv[1]); + return CMD_SUCCESS; +} + +DEFUN(cfg_lst_imsi_deny, + cfg_lst_imsi_deny_cmd, + "access-list NAME imsi-deny [REGEXP]", + "Allow IMSIs matching the REGEXP\n" + "The name of the access-list\n" + "The regexp of to be denied IMSIs\n") +{ + struct bsc_nat_acc_lst *acc; + struct bsc_nat_acc_lst_entry *entry; + + acc = bsc_nat_acc_lst_get(_nat, argv[0]); + if (!acc) + return CMD_WARNING; + + entry = bsc_nat_acc_lst_entry_create(acc); + if (!entry) + return CMD_WARNING; + + bsc_parse_reg(acc, &entry->imsi_deny_re, &entry->imsi_deny, argc - 1, &argv[1]); + return CMD_SUCCESS; +} + +/* naming to follow Zebra... */ +DEFUN(cfg_lst_no, + cfg_lst_no_cmd, + "no access-list NAME", + NO_STR "Remove an access-list by name\n" + "The access-list to remove\n") +{ + struct bsc_nat_acc_lst *acc; + acc = bsc_nat_acc_lst_find(_nat, argv[0]); + if (!acc) + return CMD_WARNING; + + bsc_nat_acc_lst_delete(acc); + return CMD_SUCCESS; +} + +DEFUN(show_acc_lst, + show_acc_lst_cmd, + "show access-list NAME", + SHOW_STR "The name of the access list\n") +{ + struct bsc_nat_acc_lst *acc; + acc = bsc_nat_acc_lst_find(_nat, argv[0]); + if (!acc) + return CMD_WARNING; + + vty_out(vty, "access-list %s%s", acc->name, VTY_NEWLINE); + vty_out_rate_ctr_group(vty, " ", acc->stats); + + return CMD_SUCCESS; +} + + +DEFUN(cfg_bsc_acc_lst_name, + cfg_bsc_acc_lst_name_cmd, + "access-list-name NAME", + "Set the name of the access list to use.\n" + "The name of the to be used access list.") +{ + struct bsc_config *conf = vty->index; + + bsc_replace_string(conf, &conf->acc_lst_name, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bsc_max_endps, cfg_bsc_max_endps_cmd, + "max-endpoints <1-1024>", + "Highest endpoint to use (exclusively)\n" "Number of ports\n") +{ + struct bsc_config *conf = vty->index; + + conf->max_endpoints = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bsc_paging, + cfg_bsc_paging_cmd, + "paging forbidden (0|1)", + "Forbid sending PAGING REQUESTS to the BSC.") +{ + struct bsc_config *conf = vty->index; + + if (strcmp("1", argv[0]) == 0) + conf->forbid_paging = 1; + else + conf->forbid_paging = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bsc_desc, + cfg_bsc_desc_cmd, + "description DESC", + "Provide a description for the given BSC.") +{ + struct bsc_config *conf = vty->index; + + bsc_replace_string(conf, &conf->description, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(test_regex, test_regex_cmd, + "test regex PATTERN STRING", + "Check if the string is matching the current pattern.") +{ + regex_t reg; + char *str = NULL; + + memset(®, 0, sizeof(reg)); + bsc_parse_reg(_nat, ®, &str, 1, argv); + + vty_out(vty, "String matches allow pattern: %d%s", + regexec(®, argv[1], 0, NULL, 0) == 0, VTY_NEWLINE); + + talloc_free(str); + regfree(®); + return CMD_SUCCESS; +} + +DEFUN(set_last_endp, set_last_endp_cmd, + "set bsc last-used-endpoint <0-9999999999> <0-1024>", + "Set a value\n" "Operate on a BSC\n" + "Last used endpoint for an assignment\n" "BSC configuration number\n" + "Endpoint number used\n") +{ + struct bsc_connection *con; + int nr = atoi(argv[0]); + int endp = atoi(argv[1]); + + + llist_for_each_entry(con, &_nat->bsc_connections, list_entry) { + if (!con->cfg) + continue; + if (con->cfg->nr != nr) + continue; + + con->last_endpoint = endp; + vty_out(vty, "Updated the last endpoint for %d to %d.%s", + con->cfg->nr, con->last_endpoint, VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +int bsc_nat_vty_init(struct bsc_nat *nat) +{ + _nat = nat; + + /* show commands */ + install_element_ve(&show_sccp_cmd); + install_element_ve(&show_bsc_cmd); + install_element_ve(&show_bsc_cfg_cmd); + install_element_ve(&show_stats_cmd); + install_element_ve(&show_stats_lac_cmd); + install_element_ve(&close_bsc_cmd); + install_element_ve(&show_msc_cmd); + install_element_ve(&test_regex_cmd); + install_element_ve(&show_bsc_mgcp_cmd); + install_element_ve(&show_acc_lst_cmd); + + install_element(ENABLE_NODE, &set_last_endp_cmd); + + /* nat group */ + install_element(CONFIG_NODE, &cfg_nat_cmd); + install_node(&nat_node, config_write_nat); + install_default(NAT_NODE); + install_element(NAT_NODE, &ournode_exit_cmd); + install_element(NAT_NODE, &ournode_end_cmd); + install_element(NAT_NODE, &cfg_nat_msc_ip_cmd); + install_element(NAT_NODE, &cfg_nat_msc_port_cmd); + install_element(NAT_NODE, &cfg_nat_auth_time_cmd); + install_element(NAT_NODE, &cfg_nat_ping_time_cmd); + install_element(NAT_NODE, &cfg_nat_pong_time_cmd); + install_element(NAT_NODE, &cfg_nat_token_cmd); + install_element(NAT_NODE, &cfg_nat_bsc_ip_dscp_cmd); + install_element(NAT_NODE, &cfg_nat_bsc_ip_tos_cmd); + install_element(NAT_NODE, &cfg_nat_acc_lst_name_cmd); + install_element(NAT_NODE, &cfg_nat_ussd_lst_name_cmd); + install_element(NAT_NODE, &cfg_nat_ussd_query_cmd); + install_element(NAT_NODE, &cfg_nat_ussd_token_cmd); + install_element(NAT_NODE, &cfg_nat_ussd_local_cmd); + + /* access-list */ + install_element(NAT_NODE, &cfg_lst_imsi_allow_cmd); + install_element(NAT_NODE, &cfg_lst_imsi_deny_cmd); + install_element(NAT_NODE, &cfg_lst_no_cmd); + + /* number rewriting */ + install_element(NAT_NODE, &cfg_nat_number_rewrite_cmd); + + /* BSC subgroups */ + install_element(NAT_NODE, &cfg_bsc_cmd); + install_node(&bsc_node, config_write_bsc); + install_default(NAT_BSC_NODE); + install_element(NAT_BSC_NODE, &ournode_exit_cmd); + install_element(NAT_BSC_NODE, &ournode_end_cmd); + install_element(NAT_BSC_NODE, &cfg_bsc_token_cmd); + install_element(NAT_BSC_NODE, &cfg_bsc_lac_cmd); + install_element(NAT_BSC_NODE, &cfg_bsc_no_lac_cmd); + install_element(NAT_BSC_NODE, &cfg_bsc_paging_cmd); + install_element(NAT_BSC_NODE, &cfg_bsc_desc_cmd); + install_element(NAT_BSC_NODE, &cfg_bsc_acc_lst_name_cmd); + install_element(NAT_BSC_NODE, &cfg_bsc_max_endps_cmd); + + mgcp_vty_init(); + + return 0; +} + + +/* called by the telnet interface... we have our own init above */ +int bsc_vty_init(void) +{ + return 0; +} diff --git a/src/osmo-bsc_nat/bsc_sccp.c b/src/osmo-bsc_nat/bsc_sccp.c new file mode 100644 index 000000000..72de11201 --- /dev/null +++ b/src/osmo-bsc_nat/bsc_sccp.c @@ -0,0 +1,249 @@ +/* SCCP patching and handling routines */ +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include + +#include + +#include +#include + +static int equal(struct sccp_source_reference *ref1, struct sccp_source_reference *ref2) +{ + return memcmp(ref1, ref2, sizeof(*ref1)) == 0; +} + +/* + * SCCP patching below + */ + +/* check if we are using this ref for patched already */ +static int sccp_ref_is_free(struct sccp_source_reference *ref, struct bsc_nat *nat) +{ + struct sccp_connections *conn; + + llist_for_each_entry(conn, &nat->sccp_connections, list_entry) { + if (memcmp(ref, &conn->patched_ref, sizeof(*ref)) == 0) + return -1; + } + + return 0; +} + +/* copied from sccp.c */ +static int assign_src_local_reference(struct sccp_source_reference *ref, struct bsc_nat *nat) +{ + static uint32_t last_ref = 0x50000; + int wrapped = 0; + + do { + struct sccp_source_reference reference; + reference.octet1 = (last_ref >> 0) & 0xff; + reference.octet2 = (last_ref >> 8) & 0xff; + reference.octet3 = (last_ref >> 16) & 0xff; + + ++last_ref; + /* do not use the reversed word and wrap around */ + if ((last_ref & 0x00FFFFFF) == 0x00FFFFFF) { + LOGP(DNAT, LOGL_NOTICE, "Wrapped searching for a free code\n"); + last_ref = 0; + ++wrapped; + } + + if (sccp_ref_is_free(&reference, nat) == 0) { + *ref = reference; + return 0; + } + } while (wrapped != 2); + + LOGP(DNAT, LOGL_ERROR, "Finding a free reference failed\n"); + return -1; +} + +struct sccp_connections *create_sccp_src_ref(struct bsc_connection *bsc, + struct bsc_nat_parsed *parsed) +{ + struct sccp_connections *conn; + + /* Some commercial BSCs like to reassign there SRC ref */ + llist_for_each_entry(conn, &bsc->nat->sccp_connections, list_entry) { + if (conn->bsc != bsc) + continue; + if (memcmp(&conn->real_ref, parsed->src_local_ref, sizeof(conn->real_ref)) != 0) + continue; + + /* the BSC has reassigned the SRC ref and we failed to keep track */ + memset(&conn->remote_ref, 0, sizeof(conn->remote_ref)); + if (assign_src_local_reference(&conn->patched_ref, bsc->nat) != 0) { + LOGP(DNAT, LOGL_ERROR, "BSC %d reused src ref: %d and we failed to generate a new id.\n", + bsc->cfg->nr, sccp_src_ref_to_int(parsed->src_local_ref)); + bsc_mgcp_dlcx(conn); + llist_del(&conn->list_entry); + talloc_free(conn); + return NULL; + } else { + clock_gettime(CLOCK_MONOTONIC, &conn->creation_time); + bsc_mgcp_dlcx(conn); + return conn; + } + } + + + conn = talloc_zero(bsc->nat, struct sccp_connections); + if (!conn) { + LOGP(DNAT, LOGL_ERROR, "Memory allocation failure.\n"); + return NULL; + } + + conn->bsc = bsc; + clock_gettime(CLOCK_MONOTONIC, &conn->creation_time); + conn->real_ref = *parsed->src_local_ref; + if (assign_src_local_reference(&conn->patched_ref, bsc->nat) != 0) { + LOGP(DNAT, LOGL_ERROR, "Failed to assign a ref.\n"); + talloc_free(conn); + return NULL; + } + + bsc_mgcp_init(conn); + llist_add_tail(&conn->list_entry, &bsc->nat->sccp_connections); + rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_SCCP_CONN]); + counter_inc(bsc->cfg->nat->stats.sccp.conn); + + LOGP(DNAT, LOGL_DEBUG, "Created 0x%x <-> 0x%x mapping for con %p\n", + sccp_src_ref_to_int(&conn->real_ref), + sccp_src_ref_to_int(&conn->patched_ref), bsc); + + return conn; +} + +int update_sccp_src_ref(struct sccp_connections *sccp, struct bsc_nat_parsed *parsed) +{ + if (!parsed->dest_local_ref || !parsed->src_local_ref) { + LOGP(DNAT, LOGL_ERROR, "CC MSG should contain both local and dest address.\n"); + return -1; + } + + sccp->remote_ref = *parsed->src_local_ref; + sccp->has_remote_ref = 1; + LOGP(DNAT, LOGL_DEBUG, "Updating 0x%x to remote 0x%x on %p\n", + sccp_src_ref_to_int(&sccp->patched_ref), + sccp_src_ref_to_int(&sccp->remote_ref), sccp->bsc); + + return 0; +} + +void remove_sccp_src_ref(struct bsc_connection *bsc, struct msgb *msg, struct bsc_nat_parsed *parsed) +{ + struct sccp_connections *conn; + + llist_for_each_entry(conn, &bsc->nat->sccp_connections, list_entry) { + if (memcmp(parsed->src_local_ref, + &conn->patched_ref, sizeof(conn->patched_ref)) == 0) { + + sccp_connection_destroy(conn); + return; + } + } + + LOGP(DNAT, LOGL_ERROR, "Can not remove connection: 0x%x\n", + sccp_src_ref_to_int(parsed->src_local_ref)); +} + +/* + * We have a message from the MSC to the BSC. The MSC is using + * an address that was assigned by the MUX, we need to update the + * dest reference to the real network. + */ +struct sccp_connections *patch_sccp_src_ref_to_bsc(struct msgb *msg, + struct bsc_nat_parsed *parsed, + struct bsc_nat *nat) +{ + struct sccp_connections *conn; + + if (!parsed->dest_local_ref) { + LOGP(DNAT, LOGL_ERROR, "MSG should contain dest_local_ref.\n"); + return NULL; + } + + + llist_for_each_entry(conn, &nat->sccp_connections, list_entry) { + if (!equal(parsed->dest_local_ref, &conn->patched_ref)) + continue; + + /* Change the dest address to the real one */ + *parsed->dest_local_ref = conn->real_ref; + return conn; + } + + return NULL; +} + +/* + * These are message to the MSC. We will need to find the BSC + * Connection by either the SRC or the DST local reference. + * + * In case of a CR we need to work by the SRC local reference + * in all other cases we need to work by the destination local + * reference.. + */ +struct sccp_connections *patch_sccp_src_ref_to_msc(struct msgb *msg, + struct bsc_nat_parsed *parsed, + struct bsc_connection *bsc) +{ + struct sccp_connections *conn; + + llist_for_each_entry(conn, &bsc->nat->sccp_connections, list_entry) { + if (conn->bsc != bsc) + continue; + + if (parsed->src_local_ref) { + if (equal(parsed->src_local_ref, &conn->real_ref)) { + *parsed->src_local_ref = conn->patched_ref; + return conn; + } + } else if (parsed->dest_local_ref) { + if (equal(parsed->dest_local_ref, &conn->remote_ref)) + return conn; + } else { + LOGP(DNAT, LOGL_ERROR, "Header has neither loc/dst ref.\n"); + return NULL; + } + } + + return NULL; +} + +struct sccp_connections *bsc_nat_find_con_by_bsc(struct bsc_nat *nat, + struct sccp_source_reference *ref) +{ + struct sccp_connections *conn; + + llist_for_each_entry(conn, &nat->sccp_connections, list_entry) { + if (memcmp(ref, &conn->real_ref, sizeof(*ref)) == 0) + return conn; + } + + return NULL; +} diff --git a/src/osmo-bsc_nat/bsc_ussd.c b/src/osmo-bsc_nat/bsc_ussd.c new file mode 100644 index 000000000..c121abe5d --- /dev/null +++ b/src/osmo-bsc_nat/bsc_ussd.c @@ -0,0 +1,363 @@ +/* USSD Filter Code */ + +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +struct bsc_nat_ussd_con { + struct write_queue queue; + struct bsc_nat *nat; + int authorized; + + struct timer_list auth_timeout; +}; + +static void ussd_auth_con(struct tlv_parsed *, struct bsc_nat_ussd_con *); + +static struct bsc_nat_ussd_con *bsc_nat_ussd_alloc(struct bsc_nat *nat) +{ + struct bsc_nat_ussd_con *con; + + con = talloc_zero(nat, struct bsc_nat_ussd_con); + if (!con) + return NULL; + + con->nat = nat; + return con; +} + +static void bsc_nat_ussd_destroy(struct bsc_nat_ussd_con *con) +{ + if (con->nat->ussd_con == con) { + bsc_close_ussd_connections(con->nat); + con->nat->ussd_con = NULL; + } + + close(con->queue.bfd.fd); + bsc_unregister_fd(&con->queue.bfd); + bsc_del_timer(&con->auth_timeout); + write_queue_clear(&con->queue); + talloc_free(con); +} + +static int forward_sccp(struct bsc_nat *nat, struct msgb *msg) +{ + struct sccp_connections *con; + struct bsc_nat_parsed *parsed; + + + parsed = bsc_nat_parse(msg); + if (!parsed) { + LOGP(DNAT, LOGL_ERROR, "Can not parse msg from USSD.\n"); + msgb_free(msg); + return -1; + } + + if (!parsed->dest_local_ref) { + LOGP(DNAT, LOGL_ERROR, "No destination local reference.\n"); + msgb_free(msg); + return -1; + } + + con = bsc_nat_find_con_by_bsc(nat, parsed->dest_local_ref); + if (!con || !con->bsc) { + LOGP(DNAT, LOGL_ERROR, "No active connection found.\n"); + msgb_free(msg); + return -1; + } + + talloc_free(parsed); + bsc_write_msg(&con->bsc->write_queue, msg); + return 0; +} + +static int ussd_read_cb(struct bsc_fd *bfd) +{ + int error; + struct bsc_nat_ussd_con *conn = bfd->data; + struct msgb *msg = ipaccess_read_msg(bfd, &error); + struct ipaccess_head *hh; + + if (!msg) { + LOGP(DNAT, LOGL_ERROR, "USSD Connection was lost.\n"); + bsc_nat_ussd_destroy(conn); + return -1; + } + + LOGP(DNAT, LOGL_NOTICE, "MSG from USSD: %s proto: %d\n", + hexdump(msg->data, msg->len), msg->l2h[0]); + hh = (struct ipaccess_head *) msg->data; + + if (hh->proto == IPAC_PROTO_IPACCESS) { + if (msg->l2h[0] == IPAC_MSGT_ID_RESP) { + struct tlv_parsed tvp; + ipaccess_idtag_parse(&tvp, + (unsigned char *) msg->l2h + 2, + msgb_l2len(msg) - 2); + if (TLVP_PRESENT(&tvp, IPAC_IDTAG_UNITNAME)) + ussd_auth_con(&tvp, conn); + } + + msgb_free(msg); + } else if (hh->proto == IPAC_PROTO_SCCP) { + forward_sccp(conn->nat, msg); + } else { + msgb_free(msg); + } + + return 0; +} + +static void ussd_auth_cb(void *_data) +{ + LOGP(DNAT, LOGL_ERROR, "USSD module didn't authenticate\n"); + bsc_nat_ussd_destroy((struct bsc_nat_ussd_con *) _data); +} + +static void ussd_auth_con(struct tlv_parsed *tvp, struct bsc_nat_ussd_con *conn) +{ + const char *token; + int len; + if (!conn->nat->ussd_token) { + LOGP(DNAT, LOGL_ERROR, "No USSD token set. Closing\n"); + bsc_nat_ussd_destroy(conn); + return; + } + + token = (const char *) TLVP_VAL(tvp, IPAC_IDTAG_UNITNAME); + len = TLVP_LEN(tvp, IPAC_IDTAG_UNITNAME); + if (strncmp(conn->nat->ussd_token, token, len) != 0) { + LOGP(DNAT, LOGL_ERROR, "Wrong USSD token by client: %d\n", + conn->queue.bfd.fd); + bsc_nat_ussd_destroy(conn); + return; + } + + /* it is authenticated now */ + if (conn->nat->ussd_con && conn->nat->ussd_con != conn) + bsc_nat_ussd_destroy(conn->nat->ussd_con); + + LOGP(DNAT, LOGL_ERROR, "USSD token specified. USSD provider is connected.\n"); + bsc_del_timer(&conn->auth_timeout); + conn->authorized = 1; + conn->nat->ussd_con = conn; +} + +static void ussd_start_auth(struct bsc_nat_ussd_con *conn) +{ + struct msgb *msg; + + conn->auth_timeout.data = conn; + conn->auth_timeout.cb = ussd_auth_cb; + bsc_schedule_timer(&conn->auth_timeout, conn->nat->auth_timeout, 0); + + msg = msgb_alloc_headroom(4096, 128, "auth message"); + if (!msg) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate auth msg\n"); + return; + } + + msgb_v_put(msg, IPAC_MSGT_ID_GET); + bsc_do_write(&conn->queue, msg, IPAC_PROTO_IPACCESS); +} + +static int ussd_listen_cb(struct bsc_fd *bfd, unsigned int what) +{ + struct bsc_nat_ussd_con *conn; + struct bsc_nat *nat; + struct sockaddr_in sa; + socklen_t sa_len = sizeof(sa); + int fd; + + if (!(what & BSC_FD_READ)) + return 0; + + fd = accept(bfd->fd, (struct sockaddr *) &sa, &sa_len); + if (fd < 0) { + perror("accept"); + return fd; + } + + nat = (struct bsc_nat *) bfd->data; + counter_inc(nat->stats.ussd.reconn); + + conn = bsc_nat_ussd_alloc(nat); + if (!conn) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate USSD con struct.\n"); + close(fd); + return -1; + } + + write_queue_init(&conn->queue, 10); + conn->queue.bfd.data = conn; + conn->queue.bfd.fd = fd; + conn->queue.bfd.when = BSC_FD_READ; + conn->queue.read_cb = ussd_read_cb; + conn->queue.write_cb = bsc_write_cb; + + if (bsc_register_fd(&conn->queue.bfd) < 0) { + LOGP(DNAT, LOGL_ERROR, "Failed to register USSD fd.\n"); + bsc_nat_ussd_destroy(conn); + return -1; + } + + LOGP(DNAT, LOGL_NOTICE, "USSD Connection on %d with IP: %s\n", + fd, inet_ntoa(sa.sin_addr)); + + /* do authentication */ + ussd_start_auth(conn); + return 0; +} + +int bsc_ussd_init(struct bsc_nat *nat) +{ + struct in_addr addr; + + addr.s_addr = INADDR_ANY; + if (nat->ussd_local) + inet_aton(nat->ussd_local, &addr); + + nat->ussd_listen.data = nat; + return make_sock(&nat->ussd_listen, IPPROTO_TCP, + ntohl(addr.s_addr), 5001, ussd_listen_cb); +} + +static int forward_ussd(struct sccp_connections *con, const struct ussd_request *req, + struct msgb *input) +{ + struct msgb *msg, *copy; + struct ipac_msgt_sccp_state *state; + struct bsc_nat_ussd_con *ussd; + + if (!con->bsc->nat->ussd_con) + return -1; + + msg = msgb_alloc_headroom(4096, 128, "forward ussd"); + if (!msg) { + LOGP(DNAT, LOGL_ERROR, "Allocation failed, not forwarding.\n"); + return -1; + } + + copy = msgb_alloc_headroom(4096, 128, "forward bts"); + if (!copy) { + LOGP(DNAT, LOGL_ERROR, "Allocation failed, not forwarding.\n"); + msgb_free(msg); + return -1; + } + + copy->l2h = msgb_put(copy, msgb_l2len(input)); + memcpy(copy->l2h, input->l2h, msgb_l2len(input)); + + msg->l2h = msgb_put(msg, 1); + msg->l2h[0] = IPAC_MSGT_SCCP_OLD; + + /* fill out the data */ + state = (struct ipac_msgt_sccp_state *) msgb_put(msg, sizeof(*state)); + state->trans_id = req->transaction_id; + state->invoke_id = req->invoke_id; + memcpy(&state->src_ref, &con->remote_ref, sizeof(con->remote_ref)); + memcpy(&state->dst_ref, &con->real_ref, sizeof(con->real_ref)); + memcpy(state->imsi, con->imsi, strlen(con->imsi)); + + ussd = con->bsc->nat->ussd_con; + bsc_do_write(&ussd->queue, msg, IPAC_PROTO_IPACCESS); + bsc_do_write(&ussd->queue, copy, IPAC_PROTO_SCCP); + + return 0; +} + +int bsc_check_ussd(struct sccp_connections *con, struct bsc_nat_parsed *parsed, + struct msgb *msg) +{ + uint32_t len; + uint8_t msg_type; + struct gsm48_hdr *hdr48; + struct bsc_nat_acc_lst *lst; + struct ussd_request req; + + /* + * various checks to avoid the decoding work. Right now we only want to + * decode if the connection was created for USSD, we do have a USSD access + * list, a query, a IMSI and such... + */ + if (con->con_type != NAT_CON_TYPE_SSA) + return 0; + + if (!con->imsi) + return 0; + + if (!con->bsc->nat->ussd_lst_name) + return 0; + if (!con->bsc->nat->ussd_query) + return 0; + + if (parsed->bssap != BSSAP_MSG_DTAP) + return 0; + + if (strlen(con->imsi) > GSM_IMSI_LENGTH) + return 0; + + hdr48 = bsc_unpack_dtap(parsed, msg, &len); + if (!hdr48) + return 0; + + msg_type = hdr48->msg_type & 0xbf; + if (hdr48->proto_discr != GSM48_PDISC_NC_SS || msg_type != GSM0480_MTYPE_REGISTER) + return 0; + + /* now check if it is a IMSI we care about */ + lst = bsc_nat_acc_lst_find(con->bsc->nat, con->bsc->nat->ussd_lst_name); + if (!lst) + return 0; + + if (bsc_nat_lst_check_allow(lst, con->imsi) != 0) + return 0; + + /* now decode the message and see if we really want to handle it */ + memset(&req, 0, sizeof(req)); + if (gsm0480_decode_ussd_request(hdr48, len, &req) != 1) + return 0; + if (req.text[0] == 0xff) + return 0; + + if (strcmp(req.text, con->bsc->nat->ussd_query) != 0) + return 0; + + /* found a USSD query for our subscriber */ + LOGP(DNAT, LOGL_NOTICE, "Found USSD query for %s\n", con->imsi); + if (forward_ussd(con, &req, msg) != 0) + return 0; + return 1; +} diff --git a/src/osmo-nitb/Makefile.am b/src/osmo-nitb/Makefile.am new file mode 100644 index 000000000..44cb0239f --- /dev/null +++ b/src/osmo-nitb/Makefile.am @@ -0,0 +1,14 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) + +bin_PROGRAMS = osmo-nitb + +osmo_nitb_SOURCES = bsc_hack.c +osmo_nitb_LDADD = -ldl -ldbi $(LIBCRYPT) $(LIBOSMOVTY_LIBS) \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libcommon/libcommon.a diff --git a/src/osmo-nitb/Makefile.in b/src/osmo-nitb/Makefile.in new file mode 100644 index 000000000..8e94435ba --- /dev/null +++ b/src/osmo-nitb/Makefile.in @@ -0,0 +1,496 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +bin_PROGRAMS = osmo-nitb$(EXEEXT) +subdir = src/osmo-nitb +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_osmo_nitb_OBJECTS = bsc_hack.$(OBJEXT) +osmo_nitb_OBJECTS = $(am_osmo_nitb_OBJECTS) +am__DEPENDENCIES_1 = +osmo_nitb_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libcommon/libcommon.a +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(osmo_nitb_SOURCES) +DIST_SOURCES = $(osmo_nitb_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) +osmo_nitb_SOURCES = bsc_hack.c +osmo_nitb_LDADD = -ldl -ldbi $(LIBCRYPT) $(LIBOSMOVTY_LIBS) \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libcommon/libcommon.a + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/osmo-nitb/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/osmo-nitb/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +osmo-nitb$(EXEEXT): $(osmo_nitb_OBJECTS) $(osmo_nitb_DEPENDENCIES) + @rm -f osmo-nitb$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(osmo_nitb_OBJECTS) $(osmo_nitb_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_hack.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-binPROGRAMS + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/osmo-nitb/bsc_hack.c b/src/osmo-nitb/bsc_hack.c new file mode 100644 index 000000000..357ec7ac4 --- /dev/null +++ b/src/osmo-nitb/bsc_hack.c @@ -0,0 +1,313 @@ +/* A hackish minimal BSC (+MSC +HLR) implementation */ + +/* (C) 2008-2010 by Harald Welte + * (C) 2009 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#define _GNU_SOURCE +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../bscconfig.h" + +/* MCC and MNC for the Location Area Identifier */ +static struct log_target *stderr_target; +struct gsm_network *bsc_gsmnet = 0; +static const char *database_name = "hlr.sqlite3"; +static const char *config_file = "openbsc.cfg"; +extern const char *openbsc_copyright; +static int daemonize = 0; +static int use_mncc_sock = 0; + +/* timer to store statistics */ +#define DB_SYNC_INTERVAL 60, 0 +static struct timer_list db_sync_timer; + +extern int bsc_bootstrap_network(int (*mncc_recv)(struct gsm_network *, struct msgb *), + const char *cfg_file); +extern int bsc_shutdown_net(struct gsm_network *net); + +static void create_pcap_file(char *file) +{ + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + int fd = open(file, O_WRONLY|O_TRUNC|O_CREAT, mode); + + if (fd < 0) { + perror("Failed to open file for pcap"); + return; + } + + e1_set_pcap_fd(fd); +} + +static void print_usage() +{ + printf("Usage: bsc_hack\n"); +} + +static void print_help() +{ + printf(" Some useful help...\n"); + printf(" -h --help this text\n"); + printf(" -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM enable debugging\n"); + printf(" -D --daemonize Fork the process into a background daemon\n"); + printf(" -c --config-file filename The config file to use.\n"); + printf(" -s --disable-color\n"); + printf(" -l --database db-name The database to use\n"); + printf(" -a --authorize-everyone. Authorize every new subscriber. Dangerous!.\n"); + printf(" -p --pcap file The filename of the pcap file\n"); + printf(" -T --timestamp Prefix every log line with a timestamp\n"); + printf(" -V --version. Print the version of OpenBSC.\n"); + printf(" -P --rtp-proxy Enable the RTP Proxy code inside OpenBSC\n"); + printf(" -e --log-level number. Set a global loglevel.\n"); + printf(" -m --mncc-sock Disable built-in MNCC handler and offer socket\n"); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"debug", 1, 0, 'd'}, + {"daemonize", 0, 0, 'D'}, + {"config-file", 1, 0, 'c'}, + {"disable-color", 0, 0, 's'}, + {"database", 1, 0, 'l'}, + {"authorize-everyone", 0, 0, 'a'}, + {"pcap", 1, 0, 'p'}, + {"timestamp", 0, 0, 'T'}, + {"version", 0, 0, 'V' }, + {"rtp-proxy", 0, 0, 'P'}, + {"log-level", 1, 0, 'e'}, + {"mncc-sock", 0, 0, 'm'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hd:Dsl:ar:p:TPVc:e:m", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage(); + print_help(); + exit(0); + case 's': + log_set_use_color(stderr_target, 0); + break; + case 'd': + log_parse_category_mask(stderr_target, optarg); + break; + case 'D': + daemonize = 1; + break; + case 'l': + database_name = strdup(optarg); + break; + case 'c': + config_file = strdup(optarg); + break; + case 'p': + create_pcap_file(optarg); + break; + case 'T': + log_set_print_timestamp(stderr_target, 1); + break; + case 'P': + ipacc_rtp_direct = 0; + break; + case 'e': + log_set_log_level(stderr_target, atoi(optarg)); + break; + case 'm': + use_mncc_sock = 1; + break; + case 'V': + print_version(1); + exit(0); + break; + default: + /* ignore */ + break; + } + } +} + +extern void *tall_vty_ctx; +static void signal_handler(int signal) +{ + fprintf(stdout, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + bsc_shutdown_net(bsc_gsmnet); + dispatch_signal(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL); + sleep(3); + exit(0); + break; + case SIGABRT: + /* in case of abort, we want to obtain a talloc report + * and then return to the caller, who will abort the process */ + case SIGUSR1: + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(tall_bsc_ctx, stderr); + break; + case SIGUSR2: + talloc_report_full(tall_vty_ctx, stderr); + break; + default: + break; + } +} + +/* timer handling */ +static int _db_store_counter(struct counter *counter, void *data) +{ + return db_store_counter(counter); +} + +static void db_sync_timer_cb(void *data) +{ + /* store counters to database and re-schedule */ + counters_for_each(_db_store_counter, NULL); + bsc_schedule_timer(&db_sync_timer, DB_SYNC_INTERVAL); +} + +extern int bts_model_unknown_init(void); +extern int bts_model_bs11_init(void); +extern int bts_model_nanobts_init(void); +extern int bts_model_rbs2k_init(void); +extern int bts_model_hslfemto_init(void); +void talloc_ctx_init(void); + +extern enum node_type bsc_vty_go_parent(struct vty *vty); + +static struct vty_app_info vty_info = { + .name = "OpenBSC", + .version = PACKAGE_VERSION, + .go_parent_cb = bsc_vty_go_parent, + .is_config_node = bsc_vty_is_config_node, +}; + +int main(int argc, char **argv) +{ + int rc; + + vty_info.copyright = openbsc_copyright; + + log_init(&log_info); + tall_bsc_ctx = talloc_named_const(NULL, 1, "openbsc"); + talloc_ctx_init(); + on_dso_load_token(); + on_dso_load_rrlp(); + on_dso_load_ho_dec(); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + + bts_model_unknown_init(); + bts_model_bs11_init(); + bts_model_nanobts_init(); + bts_model_rbs2k_init(); + bts_model_hslfemto_init(); + + e1inp_init(); + + /* enable filters */ + log_set_all_filter(stderr_target, 1); + + /* This needs to precede handle_options() */ + vty_init(&vty_info); + bsc_vty_init(); + + /* parse options */ + handle_options(argc, argv); + + /* internal MNCC handler or MNCC socket? */ + if (use_mncc_sock) { + rc = bsc_bootstrap_network(mncc_sock_from_cc, config_file); + if (rc >= 0) + mncc_sock_init(bsc_gsmnet); + } else + rc = bsc_bootstrap_network(int_mncc_recv, config_file); + if (rc < 0) + exit(1); + bsc_api_init(bsc_gsmnet, msc_bsc_api()); + mncc_sock_init(bsc_gsmnet); + + /* seed the PRNG */ + srand(time(NULL)); + + if (db_init(database_name)) { + printf("DB: Failed to init database. Please check the option settings.\n"); + return -1; + } + printf("DB: Database initialized.\n"); + + if (db_prepare()) { + printf("DB: Failed to prepare database.\n"); + return -1; + } + printf("DB: Database prepared.\n"); + + /* setup the timer */ + db_sync_timer.cb = db_sync_timer_cb; + db_sync_timer.data = NULL; + bsc_schedule_timer(&db_sync_timer, DB_SYNC_INTERVAL); + + signal(SIGINT, &signal_handler); + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + signal(SIGPIPE, SIG_IGN); + + /* start the SMS queue */ + if (sms_queue_start(bsc_gsmnet, 20) != 0) + return -1; + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + while (1) { + log_reset_context(); + bsc_select_main(0); + } +} diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am new file mode 100644 index 000000000..2351f8a46 --- /dev/null +++ b/src/utils/Makefile.am @@ -0,0 +1,12 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) + +bin_PROGRAMS = bs11_config isdnsync + +bs11_config_SOURCES = bs11_config.c rs232.c +bs11_config_LDADD = $(top_builddir)/src/libcommon/libcommon.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libbsc/libbsc.a + +isdnsync_SOURCES = isdnsync.c diff --git a/src/utils/Makefile.in b/src/utils/Makefile.in new file mode 100644 index 000000000..17e8ad6e6 --- /dev/null +++ b/src/utils/Makefile.in @@ -0,0 +1,496 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +bin_PROGRAMS = bs11_config$(EXEEXT) isdnsync$(EXEEXT) +subdir = src/utils +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_bs11_config_OBJECTS = bs11_config.$(OBJEXT) rs232.$(OBJEXT) +bs11_config_OBJECTS = $(am_bs11_config_OBJECTS) +bs11_config_DEPENDENCIES = $(top_builddir)/src/libcommon/libcommon.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libbsc/libbsc.a +am_isdnsync_OBJECTS = isdnsync.$(OBJEXT) +isdnsync_OBJECTS = $(am_isdnsync_OBJECTS) +isdnsync_LDADD = $(LDADD) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(bs11_config_SOURCES) $(isdnsync_SOURCES) +DIST_SOURCES = $(bs11_config_SOURCES) $(isdnsync_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS) +bs11_config_SOURCES = bs11_config.c rs232.c +bs11_config_LDADD = $(top_builddir)/src/libcommon/libcommon.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libbsc/libbsc.a + +isdnsync_SOURCES = isdnsync.c +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/utils/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/utils/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +bs11_config$(EXEEXT): $(bs11_config_OBJECTS) $(bs11_config_DEPENDENCIES) + @rm -f bs11_config$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(bs11_config_OBJECTS) $(bs11_config_LDADD) $(LIBS) +isdnsync$(EXEEXT): $(isdnsync_OBJECTS) $(isdnsync_DEPENDENCIES) + @rm -f isdnsync$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(isdnsync_OBJECTS) $(isdnsync_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bs11_config.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/isdnsync.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rs232.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-binPROGRAMS + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/utils/bs11_config.c b/src/utils/bs11_config.c new file mode 100644 index 000000000..eaed8b75d --- /dev/null +++ b/src/utils/bs11_config.c @@ -0,0 +1,918 @@ +/* Siemens BS-11 microBTS configuration tool */ + +/* (C) 2009-2010 by Harald Welte + * All Rights Reserved + * + * This software is based on ideas (but not code) of BS11Config + * (C) 2009 by Dieter Spaar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* state of our bs11_config application */ +enum bs11cfg_state { + STATE_NONE, + STATE_LOGON_WAIT, + STATE_LOGON_ACK, + STATE_SWLOAD, + STATE_QUERY, +}; +static enum bs11cfg_state bs11cfg_state = STATE_NONE; +static char *command, *value; +struct timer_list status_timer; + +static const u_int8_t obj_li_attr[] = { + NM_ATT_BS11_BIT_ERR_THESH, 0x09, 0x00, + NM_ATT_BS11_L1_PROT_TYPE, 0x00, + NM_ATT_BS11_LINE_CFG, 0x00, +}; +static const u_int8_t obj_bbsig0_attr[] = { + NM_ATT_BS11_RSSI_OFFS, 0x02, 0x00, 0x00, + NM_ATT_BS11_DIVERSITY, 0x01, 0x00, +}; +static const u_int8_t obj_pa0_attr[] = { + NM_ATT_BS11_TXPWR, 0x01, BS11_TRX_POWER_GSM_30mW, +}; +static const char *trx1_password = "1111111111"; +#define TEI_OML 25 + +static const u_int8_t too_fast[] = { 0x12, 0x80, 0x00, 0x00, 0x02, 0x02 }; + +static struct log_target *stderr_target; + +/* dummy function to keep gsm_data.c happy */ +struct counter *counter_alloc(const char *name) +{ + return NULL; +} + +int handle_serial_msg(struct msgb *rx_msg); + +/* create all objects for an initial configuration */ +static int create_objects(struct gsm_bts *bts) +{ + fprintf(stdout, "Crating Objects for minimal config\n"); + abis_nm_bs11_create_object(bts, BS11_OBJ_LI, 0, sizeof(obj_li_attr), + obj_li_attr); + abis_nm_bs11_create_object(bts, BS11_OBJ_GPSU, 0, 0, NULL); + abis_nm_bs11_create_object(bts, BS11_OBJ_ALCO, 0, 0, NULL); + abis_nm_bs11_create_object(bts, BS11_OBJ_CCLK, 0, 0, NULL); + abis_nm_bs11_create_object(bts, BS11_OBJ_BBSIG, 0, + sizeof(obj_bbsig0_attr), obj_bbsig0_attr); + abis_nm_bs11_create_object(bts, BS11_OBJ_PA, 0, + sizeof(obj_pa0_attr), obj_pa0_attr); + abis_nm_bs11_create_envaBTSE(bts, 0); + abis_nm_bs11_create_envaBTSE(bts, 1); + abis_nm_bs11_create_envaBTSE(bts, 2); + abis_nm_bs11_create_envaBTSE(bts, 3); + + abis_nm_bs11_conn_oml_tei(bts, 0, 1, 0xff, TEI_OML); + + abis_nm_bs11_set_trx_power(bts->c0, BS11_TRX_POWER_GSM_30mW); + + sleep(1); + + abis_nm_bs11_set_trx1_pw(bts, trx1_password); + + sleep(1); + + return 0; +} + +static int create_trx1(struct gsm_bts *bts) +{ + u_int8_t bbsig1_attr[sizeof(obj_bbsig0_attr)+12]; + u_int8_t *cur = bbsig1_attr; + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, 1); + + if (!trx) + trx = gsm_bts_trx_alloc(bts); + + fprintf(stdout, "Crating Objects for TRX1\n"); + + abis_nm_bs11_set_trx1_pw(bts, trx1_password); + + sleep(1); + + cur = tlv_put(cur, NM_ATT_BS11_PASSWORD, 10, + (u_int8_t *)trx1_password); + memcpy(cur, obj_bbsig0_attr, sizeof(obj_bbsig0_attr)); + abis_nm_bs11_create_object(bts, BS11_OBJ_BBSIG, 1, + sizeof(bbsig1_attr), bbsig1_attr); + abis_nm_bs11_create_object(bts, BS11_OBJ_PA, 1, + sizeof(obj_pa0_attr), obj_pa0_attr); + abis_nm_bs11_set_trx_power(trx, BS11_TRX_POWER_GSM_30mW); + + return 0; +} + +static char *serial_port = "/dev/ttyUSB0"; +static char *fname_safety = "BTSBMC76.SWI"; +static char *fname_software = "HS011106.SWL"; +static int delay_ms = 0; +static int win_size = 8; +static int param_disconnect = 0; +static int param_restart = 0; +static int param_forced = 0; +static struct gsm_bts *g_bts; + +static int file_is_readable(const char *fname) +{ + int rc; + struct stat st; + + rc = stat(fname, &st); + if (rc < 0) + return 0; + + if (S_ISREG(st.st_mode) && (st.st_mode & S_IRUSR)) + return 1; + + return 0; +} + +static int percent; +static int percent_old; + +/* callback function passed to the ABIS OML code */ +static int swload_cbfn(unsigned int hook, unsigned int event, struct msgb *msg, + void *data, void *param) +{ + if (hook != GSM_HOOK_NM_SWLOAD) + return 0; + + switch (event) { + case NM_MT_LOAD_INIT_ACK: + fprintf(stdout, "Software Load Initiate ACK\n"); + break; + case NM_MT_LOAD_INIT_NACK: + fprintf(stderr, "ERROR: Software Load Initiate NACK\n"); + exit(5); + break; + case NM_MT_LOAD_END_ACK: + if (data) { + /* we did a safety load and must activate it */ + abis_nm_software_activate(g_bts, fname_safety, + swload_cbfn, g_bts); + sleep(5); + } + break; + case NM_MT_LOAD_END_NACK: + fprintf(stderr, "ERROR: Software Load End NACK\n"); + exit(3); + break; + case NM_MT_ACTIVATE_SW_NACK: + fprintf(stderr, "ERROR: Activate Software NACK\n"); + exit(4); + break; + case NM_MT_ACTIVATE_SW_ACK: + bs11cfg_state = STATE_NONE; + + break; + case NM_MT_LOAD_SEG_ACK: + percent = abis_nm_software_load_status(g_bts); + if (percent > percent_old) + printf("Software Download Progress: %d%%\n", percent); + percent_old = percent; + break; + } + return 0; +} + +static const char *bs11_link_state[] = { + [0x00] = "Down", + [0x01] = "Up", + [0x02] = "Restoring", +}; + +static const char *linkstate_name(u_int8_t linkstate) +{ + if (linkstate > ARRAY_SIZE(bs11_link_state)) + return "Unknown"; + + return bs11_link_state[linkstate]; +} + +static const char *mbccu_load[] = { + [0] = "No Load", + [1] = "Load BTSCAC", + [2] = "Load BTSDRX", + [3] = "Load BTSBBX", + [4] = "Load BTSARC", + [5] = "Load", +}; + +static const char *mbccu_load_name(u_int8_t linkstate) +{ + if (linkstate > ARRAY_SIZE(mbccu_load)) + return "Unknown"; + + return mbccu_load[linkstate]; +} + +static const char *bts_phase_name(u_int8_t phase) +{ + switch (phase) { + case BS11_STATE_WARM_UP: + case BS11_STATE_WARM_UP_2: + return "Warm Up"; + break; + case BS11_STATE_LOAD_SMU_SAFETY: + return "Load SMU Safety"; + break; + case BS11_STATE_LOAD_SMU_INTENDED: + return "Load SMU Intended"; + break; + case BS11_STATE_LOAD_MBCCU: + return "Load MBCCU"; + break; + case BS11_STATE_SOFTWARE_RQD: + return "Software required"; + break; + case BS11_STATE_WAIT_MIN_CFG: + case BS11_STATE_WAIT_MIN_CFG_2: + return "Wait minimal config"; + break; + case BS11_STATE_MAINTENANCE: + return "Maintenance"; + break; + case BS11_STATE_NORMAL: + return "Normal"; + break; + case BS11_STATE_ABIS_LOAD: + return "Abis load"; + break; + default: + return "Unknown"; + break; + } +} + +static const char *trx_power_name(u_int8_t pwr) +{ + switch (pwr) { + case BS11_TRX_POWER_GSM_2W: + return "2W (GSM)"; + case BS11_TRX_POWER_GSM_250mW: + return "250mW (GSM)"; + case BS11_TRX_POWER_GSM_80mW: + return "80mW (GSM)"; + case BS11_TRX_POWER_GSM_30mW: + return "30mW (GSM)"; + case BS11_TRX_POWER_DCS_3W: + return "3W (DCS)"; + case BS11_TRX_POWER_DCS_1W6: + return "1.6W (DCS)"; + case BS11_TRX_POWER_DCS_500mW: + return "500mW (DCS)"; + case BS11_TRX_POWER_DCS_160mW: + return "160mW (DCS)"; + default: + return "unknown value"; + } +} + +static const char *pll_mode_name(u_int8_t mode) +{ + switch (mode) { + case BS11_LI_PLL_LOCKED: + return "E1 Locked"; + case BS11_LI_PLL_STANDALONE: + return "Standalone"; + default: + return "unknown"; + } +} + +static const char *cclk_acc_name(u_int8_t acc) +{ + switch (acc) { + case 0: + /* Out of the demanded +/- 0.05ppm */ + return "Medium"; + case 1: + /* Synchronized with Abis, within demanded tolerance +/- 0.05ppm */ + return "High"; + default: + return "unknown"; + } +} + +static const char *bport_lcfg_name(u_int8_t lcfg) +{ + switch (lcfg) { + case BS11_LINE_CFG_STAR: + return "Star"; + case BS11_LINE_CFG_MULTIDROP: + return "Multi-Drop"; + default: + return "unknown"; + } +} + +static const char *obj_name(struct abis_om_fom_hdr *foh) +{ + static char retbuf[256]; + + retbuf[0] = 0; + + switch (foh->obj_class) { + case NM_OC_BS11: + strcat(retbuf, "BS11 "); + switch (foh->obj_inst.bts_nr) { + case BS11_OBJ_PA: + sprintf(retbuf+strlen(retbuf), "Power Amplifier %d ", + foh->obj_inst.ts_nr); + break; + case BS11_OBJ_LI: + sprintf(retbuf+strlen(retbuf), "Line Interface "); + break; + case BS11_OBJ_CCLK: + sprintf(retbuf+strlen(retbuf), "CCLK "); + break; + } + break; + case NM_OC_SITE_MANAGER: + strcat(retbuf, "SITE MANAGER "); + break; + case NM_OC_BS11_BPORT: + sprintf(retbuf+strlen(retbuf), "BPORT%u ", + foh->obj_inst.bts_nr); + break; + } + return retbuf; +} + +static void print_state(struct tlv_parsed *tp) +{ + if (TLVP_PRESENT(tp, NM_ATT_BS11_BTS_STATE)) { + u_int8_t phase, mbccu; + if (TLVP_LEN(tp, NM_ATT_BS11_BTS_STATE) >= 1) { + phase = *TLVP_VAL(tp, NM_ATT_BS11_BTS_STATE); + printf("PHASE: %u %-20s ", phase & 0xf, + bts_phase_name(phase)); + } + if (TLVP_LEN(tp, NM_ATT_BS11_BTS_STATE) >= 2) { + mbccu = *(TLVP_VAL(tp, NM_ATT_BS11_BTS_STATE)+1); + printf("MBCCU0: %-11s MBCCU1: %-11s ", + mbccu_load_name(mbccu & 0xf), mbccu_load_name(mbccu >> 4)); + } + } + if (TLVP_PRESENT(tp, NM_ATT_BS11_E1_STATE) && + TLVP_LEN(tp, NM_ATT_BS11_E1_STATE) >= 1) { + u_int8_t e1_state = *TLVP_VAL(tp, NM_ATT_BS11_E1_STATE); + printf("Abis-link: %-9s ", linkstate_name(e1_state & 0xf)); + } + printf("\n"); +} + +static int print_attr(struct tlv_parsed *tp) +{ + if (TLVP_PRESENT(tp, NM_ATT_BS11_ESN_PCB_SERIAL)) { + printf("\tBS-11 ESN PCB Serial Number: %s\n", + TLVP_VAL(tp, NM_ATT_BS11_ESN_PCB_SERIAL)); + } + if (TLVP_PRESENT(tp, NM_ATT_BS11_ESN_HW_CODE_NO)) { + printf("\tBS-11 ESN Hardware Code Number: %s\n", + TLVP_VAL(tp, NM_ATT_BS11_ESN_HW_CODE_NO)+6); + } + if (TLVP_PRESENT(tp, NM_ATT_BS11_ESN_FW_CODE_NO)) { + printf("\tBS-11 ESN Firmware Code Number: %s\n", + TLVP_VAL(tp, NM_ATT_BS11_ESN_FW_CODE_NO)+6); + } +#if 0 + if (TLVP_PRESENT(tp, NM_ATT_BS11_BOOT_SW_VERS)) { + printf("BS-11 Boot Software Version: %s\n", + TLVP_VAL(tp, NM_ATT_BS11_BOOT_SW_VERS)+6); + } +#endif + if (TLVP_PRESENT(tp, NM_ATT_ABIS_CHANNEL) && + TLVP_LEN(tp, NM_ATT_ABIS_CHANNEL) >= 3) { + const u_int8_t *chan = TLVP_VAL(tp, NM_ATT_ABIS_CHANNEL); + printf("\tE1 Channel: Port=%u Timeslot=%u ", + chan[0], chan[1]); + if (chan[2] == 0xff) + printf("(Full Slot)\n"); + else + printf("Subslot=%u\n", chan[2]); + } + if (TLVP_PRESENT(tp, NM_ATT_TEI)) + printf("\tTEI: %d\n", *TLVP_VAL(tp, NM_ATT_TEI)); + if (TLVP_PRESENT(tp, NM_ATT_BS11_TXPWR) && + TLVP_LEN(tp, NM_ATT_BS11_TXPWR) >= 1) { + printf("\tTRX Power: %s\n", + trx_power_name(*TLVP_VAL(tp, NM_ATT_BS11_TXPWR))); + } + if (TLVP_PRESENT(tp, NM_ATT_BS11_PLL_MODE) && + TLVP_LEN(tp, NM_ATT_BS11_PLL_MODE) >= 1) { + printf("\tPLL Mode: %s\n", + pll_mode_name(*TLVP_VAL(tp, NM_ATT_BS11_PLL_MODE))); + } + if (TLVP_PRESENT(tp, NM_ATT_BS11_PLL) && + TLVP_LEN(tp, NM_ATT_BS11_PLL) >= 4) { + const u_int8_t *vp = TLVP_VAL(tp, NM_ATT_BS11_PLL); + printf("\tPLL Set Value=%d, Work Value=%d\n", + vp[0] << 8 | vp[1], vp[2] << 8 | vp[3]); + } + if (TLVP_PRESENT(tp, NM_ATT_BS11_CCLK_ACCURACY) && + TLVP_LEN(tp, NM_ATT_BS11_CCLK_ACCURACY) >= 1) { + const u_int8_t *acc = TLVP_VAL(tp, NM_ATT_BS11_CCLK_ACCURACY); + printf("\tCCLK Accuracy: %s (%d)\n", cclk_acc_name(*acc), *acc); + } + if (TLVP_PRESENT(tp, NM_ATT_BS11_CCLK_TYPE) && + TLVP_LEN(tp, NM_ATT_BS11_CCLK_TYPE) >= 1) { + const u_int8_t *acc = TLVP_VAL(tp, NM_ATT_BS11_CCLK_TYPE); + printf("\tCCLK Type=%d\n", *acc); + } + if (TLVP_PRESENT(tp, NM_ATT_BS11_LINE_CFG) && + TLVP_LEN(tp, NM_ATT_BS11_LINE_CFG) >= 1) { + const u_int8_t *lcfg = TLVP_VAL(tp, NM_ATT_BS11_LINE_CFG); + printf("\tLine Configuration: %s (%d)\n", + bport_lcfg_name(*lcfg), *lcfg); + } + + + + return 0; +} + +static void cmd_query(void) +{ + struct gsm_bts_trx *trx = g_bts->c0; + + bs11cfg_state = STATE_QUERY; + abis_nm_bs11_get_serno(g_bts); + abis_nm_bs11_get_oml_tei_ts(g_bts); + abis_nm_bs11_get_pll_mode(g_bts); + abis_nm_bs11_get_cclk(g_bts); + abis_nm_bs11_get_trx_power(trx); + trx = gsm_bts_trx_num(g_bts, 1); + if (trx) + abis_nm_bs11_get_trx_power(trx); + abis_nm_bs11_get_bport_line_cfg(g_bts, 0); + abis_nm_bs11_get_bport_line_cfg(g_bts, 1); + sleep(1); + abis_nm_bs11_factory_logon(g_bts, 0); + command = NULL; +} + +/* handle a response from the BTS to a GET STATE command */ +static int handle_state_resp(enum abis_bs11_phase state) +{ + int rc = 0; + + switch (state) { + case BS11_STATE_WARM_UP: + case BS11_STATE_LOAD_SMU_SAFETY: + case BS11_STATE_LOAD_SMU_INTENDED: + case BS11_STATE_LOAD_MBCCU: + break; + case BS11_STATE_SOFTWARE_RQD: + bs11cfg_state = STATE_SWLOAD; + /* send safety load. Use g_bts as private 'param' + * argument, so our swload_cbfn can distinguish + * a safety load from a regular software */ + if (file_is_readable(fname_safety)) + rc = abis_nm_software_load(g_bts, 0xff, fname_safety, + win_size, param_forced, + swload_cbfn, g_bts); + else + fprintf(stderr, "No valid Safety Load file \"%s\"\n", + fname_safety); + break; + case BS11_STATE_WAIT_MIN_CFG: + case BS11_STATE_WAIT_MIN_CFG_2: + bs11cfg_state = STATE_SWLOAD; + rc = create_objects(g_bts); + break; + case BS11_STATE_MAINTENANCE: + if (command) { + if (!strcmp(command, "disconnect")) + abis_nm_bs11_factory_logon(g_bts, 0); + else if (!strcmp(command, "reconnect")) + rc = abis_nm_bs11_bsc_disconnect(g_bts, 1); + else if (!strcmp(command, "software") + && bs11cfg_state != STATE_SWLOAD) { + bs11cfg_state = STATE_SWLOAD; + /* send software (FIXME: over A-bis?) */ + if (file_is_readable(fname_software)) + rc = abis_nm_bs11_load_swl(g_bts, fname_software, + win_size, param_forced, + swload_cbfn); + else + fprintf(stderr, "No valid Software file \"%s\"\n", + fname_software); + } else if (!strcmp(command, "delete-trx1")) { + printf("Locing BBSIG and PA objects of TRX1\n"); + abis_nm_chg_adm_state(g_bts, NM_OC_BS11, + BS11_OBJ_BBSIG, 0, 1, + NM_STATE_LOCKED); + abis_nm_chg_adm_state(g_bts, NM_OC_BS11, + BS11_OBJ_PA, 0, 1, + NM_STATE_LOCKED); + sleep(1); + printf("Deleting BBSIG and PA objects of TRX1\n"); + abis_nm_bs11_delete_object(g_bts, BS11_OBJ_BBSIG, 1); + abis_nm_bs11_delete_object(g_bts, BS11_OBJ_PA, 1); + sleep(1); + abis_nm_bs11_factory_logon(g_bts, 0); + command = NULL; + } else if (!strcmp(command, "create-trx1")) { + create_trx1(g_bts); + sleep(1); + abis_nm_bs11_factory_logon(g_bts, 0); + command = NULL; + } else if (!strcmp(command, "pll-e1-locked")) { + abis_nm_bs11_set_pll_locked(g_bts, 1); + sleep(1); + abis_nm_bs11_factory_logon(g_bts, 0); + command = NULL; + } else if (!strcmp(command, "pll-standalone")) { + abis_nm_bs11_set_pll_locked(g_bts, 0); + sleep(1); + abis_nm_bs11_factory_logon(g_bts, 0); + command = NULL; + } else if (!strcmp(command, "pll-setvalue")) { + abis_nm_bs11_set_pll(g_bts, atoi(value)); + sleep(1); + abis_nm_bs11_factory_logon(g_bts, 0); + command = NULL; + } else if (!strcmp(command, "pll-workvalue")) { + /* To set the work value we need to login as FIELD */ + abis_nm_bs11_factory_logon(g_bts, 0); + sleep(1); + abis_nm_bs11_infield_logon(g_bts, 1); + sleep(1); + abis_nm_bs11_set_pll(g_bts, atoi(value)); + sleep(1); + abis_nm_bs11_infield_logon(g_bts, 0); + command = NULL; + } else if (!strcmp(command, "oml-tei")) { + abis_nm_bs11_conn_oml_tei(g_bts, 0, 1, 0xff, TEI_OML); + command = NULL; + } else if (!strcmp(command, "restart")) { + abis_nm_bs11_restart(g_bts); + command = NULL; + } else if (!strcmp(command, "query")) { + cmd_query(); + } else if (!strcmp(command, "create-bport1")) { + abis_nm_bs11_create_bport(g_bts, 1); + sleep(1); + abis_nm_bs11_factory_logon(g_bts, 0); + command = NULL; + } else if (!strcmp(command, "delete-bport1")) { + abis_nm_chg_adm_state(g_bts, NM_OC_BS11_BPORT, 1, 0xff, 0xff, NM_STATE_LOCKED); + sleep(1); + abis_nm_bs11_delete_bport(g_bts, 1); + sleep(1); + abis_nm_bs11_factory_logon(g_bts, 0); + command = NULL; + } else if (!strcmp(command, "bport0-star")) { + abis_nm_bs11_set_bport_line_cfg(g_bts, 0, BS11_LINE_CFG_STAR); + sleep(1); + abis_nm_bs11_factory_logon(g_bts, 0); + command = NULL; + } else if (!strcmp(command, "bport0-multidrop")) { + abis_nm_bs11_set_bport_line_cfg(g_bts, 0, BS11_LINE_CFG_MULTIDROP); + sleep(1); + abis_nm_bs11_factory_logon(g_bts, 0); + command = NULL; + } else if (!strcmp(command, "bport1-multidrop")) { + abis_nm_bs11_set_bport_line_cfg(g_bts, 1, BS11_LINE_CFG_MULTIDROP); + sleep(1); + abis_nm_bs11_factory_logon(g_bts, 0); + command = NULL; + } + + } + break; + case BS11_STATE_NORMAL: + if (command) { + if (!strcmp(command, "reconnect")) + abis_nm_bs11_factory_logon(g_bts, 0); + else if (!strcmp(command, "disconnect")) + abis_nm_bs11_bsc_disconnect(g_bts, 0); + else if (!strcmp(command, "query")) { + cmd_query(); + } + } else if (param_disconnect) { + param_disconnect = 0; + abis_nm_bs11_bsc_disconnect(g_bts, 0); + if (param_restart) { + param_restart = 0; + abis_nm_bs11_restart(g_bts); + } + } + break; + default: + break; + } + return rc; +} + +/* handle a fully-received message/packet from the RS232 port */ +int handle_serial_msg(struct msgb *rx_msg) +{ + struct abis_om_hdr *oh; + struct abis_om_fom_hdr *foh; + struct tlv_parsed tp; + int rc = -1; + +#if 0 + if (rx_msg->len < LAPD_HDR_LEN + + sizeof(struct abis_om_fom_hdr) + + sizeof(struct abis_om_hdr)) { + if (!memcmp(rx_msg->data + 2, too_fast, + sizeof(too_fast))) { + fprintf(stderr, "BS11 tells us we're too " + "fast, try --delay bigger than %u\n", + delay_ms); + return -E2BIG; + } else + fprintf(stderr, "unknown BS11 message\n"); + } +#endif + + oh = (struct abis_om_hdr *) msgb_l2(rx_msg); + foh = (struct abis_om_fom_hdr *) oh->data; + switch (foh->msg_type) { + case NM_MT_BS11_LMT_LOGON_ACK: + printf("LMT LOGON: ACK\n\n"); + if (bs11cfg_state == STATE_NONE) + bs11cfg_state = STATE_LOGON_ACK; + rc = abis_nm_bs11_get_state(g_bts); + break; + case NM_MT_BS11_LMT_LOGOFF_ACK: + printf("LMT LOGOFF: ACK\n"); + exit(0); + break; + case NM_MT_BS11_GET_STATE_ACK: + rc = abis_nm_tlv_parse(&tp, g_bts, foh->data, oh->length-sizeof(*foh)); + print_state(&tp); + if (TLVP_PRESENT(&tp, NM_ATT_BS11_BTS_STATE) && + TLVP_LEN(&tp, NM_ATT_BS11_BTS_STATE) >= 1) + rc = handle_state_resp(*TLVP_VAL(&tp, NM_ATT_BS11_BTS_STATE)); + break; + case NM_MT_GET_ATTR_RESP: + printf("\n%sATTRIBUTES:\n", obj_name(foh)); + abis_nm_tlv_parse(&tp, g_bts, foh->data, oh->length-sizeof(*foh)); + rc = print_attr(&tp); + //hexdump(foh->data, oh->length-sizeof(*foh)); + break; + case NM_MT_BS11_SET_ATTR_ACK: + printf("SET ATTRIBUTE ObjClass=0x%02x ObjInst=(%d,%d,%d) ACK\n", + foh->obj_class, foh->obj_inst.bts_nr, + foh->obj_inst.trx_nr, foh->obj_inst.ts_nr); + rc = 0; + break; + case NM_MT_BS11_SET_ATTR_NACK: + printf("SET ATTRIBUTE ObjClass=0x%02x ObjInst=(%d,%d,%d) NACK\n", + foh->obj_class, foh->obj_inst.bts_nr, + foh->obj_inst.trx_nr, foh->obj_inst.ts_nr); + break; + case NM_MT_GET_ATTR_NACK: + printf("\n%sGET ATTR NACK\n", obj_name(foh)); + break; + case NM_MT_BS11_CREATE_OBJ_ACK: + printf("\n%sCREATE OBJECT ACK\n", obj_name(foh)); + break; + case NM_MT_BS11_CREATE_OBJ_NACK: + printf("\n%sCREATE OBJECT NACK\n", obj_name(foh)); + break; + case NM_MT_BS11_DELETE_OBJ_ACK: + printf("\n%sDELETE OBJECT ACK\n", obj_name(foh)); + break; + case NM_MT_BS11_DELETE_OBJ_NACK: + printf("\n%sDELETE OBJECT NACK\n", obj_name(foh)); + break; + default: + rc = abis_nm_rcvmsg(rx_msg); + } + if (rc < 0) { + perror("ERROR in main loop"); + //break; + } + if (rc == 1) + return rc; + + switch (bs11cfg_state) { + case STATE_NONE: + abis_nm_bs11_factory_logon(g_bts, 1); + break; + case STATE_LOGON_ACK: + bsc_schedule_timer(&status_timer, 5, 0); + break; + default: + break; + } + + return rc; +} + +void status_timer_cb(void *data) +{ + abis_nm_bs11_get_state(g_bts); +} + +static void print_banner(void) +{ + printf("bs11_config (C) 2009-2010 by Harald Welte and Dieter Spaar\n"); + printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n"); +} + +static void print_help(void) +{ + printf("bs11_config [options] [command]\n"); + printf("\nSupported options:\n"); + printf("\t-h --help\t\t\tPrint this help text\n"); + printf("\t-p --port \t\tSpecify serial port\n"); + printf("\t-s --software \t\tSpecify Software file\n"); + printf("\t-S --safety \t\tSpecify Safety Load file\n"); + printf("\t-d --delay \t\t\tSpecify delay in milliseconds\n"); + printf("\t-D --disconnect\t\t\tDisconnect BTS from BSC\n"); + printf("\t-w --win-size \t\tSpecify Window Size\n"); + printf("\t-f --forced\t\t\tForce Software Load\n"); + printf("\nSupported commands:\n"); + printf("\tquery\t\t\tQuery the BS-11 about serial number and configuration\n"); + printf("\tdisconnect\t\tDisconnect A-bis link (go into administrative state)\n"); + printf("\tresconnect\t\tReconnect A-bis link (go into normal state)\n"); + printf("\trestart\t\t\tRestart the BTS\n"); + printf("\tsoftware\t\tDownload Software (only in administrative state)\n"); + printf("\tcreate-trx1\t\tCreate objects for TRX1 (Danger: Your BS-11 might overheat)\n"); + printf("\tdelete-trx1\t\tDelete objects for TRX1\n"); + printf("\tpll-e1-locked\t\tSet the PLL to be locked to E1 clock\n"); + printf("\tpll-standalone\t\tSet the PLL to be in standalone mode\n"); + printf("\tpll-setvalue \tSet the PLL set value\n"); + printf("\tpll-workvalue \tSet the PLL work value\n"); + printf("\toml-tei\t\t\tSet OML E1 TS and TEI\n"); + printf("\tbport0-star\t\tSet BPORT0 line config to star\n"); + printf("\tbport0-multidrop\tSet BPORT0 line config to multidrop\n"); + printf("\tbport1-multidrop\tSet BPORT1 line config to multidrop\n"); + printf("\tcreate-bport1\t\tCreate BPORT1 object\n"); + printf("\tdelete-bport1\t\tDelete BPORT1 object\n"); +} + +static void handle_options(int argc, char **argv) +{ + int option_index = 0; + print_banner(); + + while (1) { + int c; + static struct option long_options[] = { + { "help", 0, 0, 'h' }, + { "port", 1, 0, 'p' }, + { "software", 1, 0, 's' }, + { "safety", 1, 0, 'S' }, + { "delay", 1, 0, 'd' }, + { "disconnect", 0, 0, 'D' }, + { "win-size", 1, 0, 'w' }, + { "forced", 0, 0, 'f' }, + { "restart", 0, 0, 'r' }, + { "debug", 1, 0, 'b'}, + }; + + c = getopt_long(argc, argv, "hp:s:S:td:Dw:fra:", + long_options, &option_index); + + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(); + exit(0); + case 'p': + serial_port = optarg; + break; + case 'b': + log_parse_category_mask(stderr_target, optarg); + break; + case 's': + fname_software = optarg; + break; + case 'S': + fname_safety = optarg; + break; + case 'd': + delay_ms = atoi(optarg); + break; + case 'w': + win_size = atoi(optarg); + break; + case 'D': + param_disconnect = 1; + break; + case 'f': + param_forced = 1; + break; + case 'r': + param_disconnect = 1; + param_restart = 1; + break; + default: + break; + } + } + if (optind < argc) + command = argv[optind]; + if (optind+1 < argc) + value = argv[optind+1]; + +} + +static int num_sigint; + +static void signal_handler(int signal) +{ + fprintf(stdout, "\nsignal %u received\n", signal); + + switch (signal) { + case SIGINT: + num_sigint++; + abis_nm_bs11_factory_logon(g_bts, 0); + if (num_sigint >= 3) + exit(0); + break; + } +} + +extern int bts_model_bs11_init(void); +int main(int argc, char **argv) +{ + struct gsm_network *gsmnet; + int rc; + + log_init(&log_info); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + log_set_all_filter(stderr_target, 1); + handle_options(argc, argv); + bts_model_bs11_init(); + + gsmnet = gsm_network_init(1, 1, NULL); + if (!gsmnet) { + fprintf(stderr, "Unable to allocate gsm network\n"); + exit(1); + } + g_bts = gsm_bts_alloc(gsmnet, GSM_BTS_TYPE_BS11, HARDCODED_TSC, + HARDCODED_BSIC); + + rc = rs232_setup(serial_port, delay_ms, g_bts); + if (rc < 0) { + fprintf(stderr, "Problem setting up serial port\n"); + exit(1); + } + + signal(SIGINT, &signal_handler); + + abis_nm_bs11_factory_logon(g_bts, 1); + //abis_nm_bs11_get_serno(g_bts); + + status_timer.cb = status_timer_cb; + + while (1) { + bsc_select_main(0); + } + + abis_nm_bs11_factory_logon(g_bts, 0); + + exit(0); +} + +/* dummy to be able to compile */ +void gsm_net_update_ctype(struct gsm_network *net) +{ +} diff --git a/src/utils/isdnsync.c b/src/utils/isdnsync.c new file mode 100644 index 000000000..1c4aa5d6e --- /dev/null +++ b/src/utils/isdnsync.c @@ -0,0 +1,191 @@ +/* isdnsync.c + * + * Author Andreas Eversberg + * + * All rights reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mISDNif.h" +#define MISDN_OLD_AF_COMPATIBILITY +#define AF_COMPATIBILITY_FUNC +#include "compat_af_isdn.h" + +int card = 0; +int sock = -1; + +int mISDN_open(void) +{ + int fd, ret; + struct mISDN_devinfo devinfo; + struct sockaddr_mISDN l2addr; + + fd = socket(PF_ISDN, SOCK_RAW, ISDN_P_BASE); + if (fd < 0) { + fprintf(stderr, "could not open socket (%s)\n", strerror(errno)); + return fd; + } + devinfo.id = card; + ret = ioctl(fd, IMGETDEVINFO, &devinfo); + if (ret < 0) { + fprintf(stderr,"could not send IOCTL IMGETCOUNT (%s)\n", strerror(errno)); + close(fd); + return ret; + } + close(fd); + if (!(devinfo.Dprotocols & (1 << ISDN_P_TE_S0)) + && !(devinfo.Dprotocols & (1 << ISDN_P_TE_E1))) { + fprintf(stderr,"Interface does not support TE mode (%s)\n", strerror(errno)); + close(fd); + return ret; + } + fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_LAPD_TE); + if (fd < 0) { + fprintf(stderr,"could not open ISDN_P_LAPD_TE socket (%s)\n", strerror(errno)); + return fd; + } + l2addr.family = AF_ISDN; + l2addr.dev = card; + l2addr.channel = 0; + l2addr.sapi = 0; + l2addr.tei = 0; + ret = bind(fd, (struct sockaddr *)&l2addr, sizeof(l2addr)); + if (ret < 0) { + fprintf(stderr,"could not bind socket for card %d (%s)\n", card, strerror(errno)); + close(fd); + return ret; + } + sock = fd; + + return sock; +} + + +void mISDN_handle(void) +{ + int ret; + fd_set rfd; + struct timeval tv; + struct sockaddr_mISDN addr; + socklen_t alen; + unsigned char buffer[2048]; + struct mISDNhead *hh = (struct mISDNhead *)buffer; + int l1 = 0, l2 = 0, tei = 0; + + while(1) { +again: + FD_ZERO(&rfd); + FD_SET(sock, &rfd); + tv.tv_sec = 2; + tv.tv_usec = 0; + ret = select(sock+1, &rfd, NULL, NULL, &tv); + if (ret < 0) { + if (errno == EINTR) + continue; + fprintf(stderr, "%s aborted: %s\n", __FUNCTION__, strerror(errno)); + break; + } + if (FD_ISSET(sock, &rfd)) { + alen = sizeof(addr); + ret = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *) &addr, &alen); + if (ret < 0) { + fprintf(stderr, "%s read socket error %s\n", __FUNCTION__, strerror(errno)); + } else if (ret < MISDN_HEADER_LEN) { + fprintf(stderr, "%s read socket shor frame\n", __FUNCTION__); + } else { + switch(hh->prim) { + case MPH_ACTIVATE_IND: + case PH_ACTIVATE_IND: + if (!l1) { + printf("PH_ACTIVATE\n"); + printf("*** Sync available from interface :-)\n"); + l1 = 1; + } + goto again; + break; + case MPH_DEACTIVATE_IND: + case PH_DEACTIVATE_IND: + if (l1) { + printf("PH_DEACTIVATE\n"); + printf("*** Lost sync on interface :-(\n"); + l1 = 0; + } + goto again; + break; + case DL_ESTABLISH_IND: + case DL_ESTABLISH_CNF: + printf("DL_ESTABLISH\n"); + l2 = 1; + goto again; + break; + case DL_RELEASE_IND: + case DL_RELEASE_CNF: + printf("DL_RELEASE\n"); + l2 = 0; + goto again; + break; + case DL_INFORMATION_IND: + printf("DL_INFORMATION (tei %d sapi %d)\n", addr.tei, addr.sapi); + tei = 1; + break; + default: +// printf("prim %x\n", hh->prim); + goto again; + } + } + } + if (tei && !l2) { + hh->prim = DL_ESTABLISH_REQ; + printf("-> activating layer 2\n"); + sendto(sock, buffer, MISDN_HEADER_LEN, 0, (struct sockaddr *) &addr, alen); + } + } +} + +int main(int argc, char *argv[]) +{ + int ret; + + if (argc <= 1) + { + printf("Usage: %s \n\n", argv[0]); + printf("Opens given card number in TE-mode PTP and tries to keep layer 2 established.\n"); + printf("This keeps layer 1 activated to retrieve a steady sync signal from network.\n"); + return(0); + } + + card = atoi(argv[1]); + + init_af_isdn(); + + if ((ret = mISDN_open() < 0)) + return(ret); + + mISDN_handle(); + + close(sock); + + return 0; +} diff --git a/src/utils/rs232.c b/src/utils/rs232.c new file mode 100644 index 000000000..75505710f --- /dev/null +++ b/src/utils/rs232.c @@ -0,0 +1,248 @@ +/* OpenBSC BS-11 T-Link interface using POSIX serial port */ + +/* (C) 2008-2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* adaption layer from GSM 08.59 + 12.21 to RS232 */ + +struct serial_handle { + struct bsc_fd fd; + struct llist_head tx_queue; + + struct msgb *rx_msg; + unsigned int rxmsg_bytes_missing; + + unsigned int delay_ms; + struct gsm_bts *bts; +}; + +/* FIXME: this needs to go */ +static struct serial_handle _ser_handle, *ser_handle = &_ser_handle; + +#define LAPD_HDR_LEN 10 + +static int handle_ser_write(struct bsc_fd *bfd); + +/* callback from abis_nm */ +int _abis_nm_sendmsg(struct msgb *msg, int to_trx_oml) +{ + struct serial_handle *sh = ser_handle; + u_int8_t *lapd; + unsigned int len; + + msg->l2h = msg->data; + + /* prepend LAPD header */ + lapd = msgb_push(msg, LAPD_HDR_LEN); + + len = msg->len - 2; + + lapd[0] = (len >> 8) & 0xff; + lapd[1] = len & 0xff; /* length of bytes startign at lapd[2] */ + lapd[2] = 0x00; + lapd[3] = 0x07; + lapd[4] = 0x01; + lapd[5] = 0x3e; + lapd[6] = 0x00; + lapd[7] = 0x00; + lapd[8] = msg->len - 10; /* length of bytes starting at lapd[10] */ + lapd[9] = lapd[8] ^ 0x38; + + msgb_enqueue(&sh->tx_queue, msg); + sh->fd.when |= BSC_FD_WRITE; + + /* we try to immediately send */ + handle_ser_write(&sh->fd); + + return 0; +} + +/* select.c callback in case we can write to the RS232 */ +static int handle_ser_write(struct bsc_fd *bfd) +{ + struct serial_handle *sh = bfd->data; + struct msgb *msg; + int written; + + msg = msgb_dequeue(&sh->tx_queue); + if (!msg) { + bfd->when &= ~BSC_FD_WRITE; + return 0; + } + + DEBUGP(DMI, "RS232 TX: %s\n", hexdump(msg->data, msg->len)); + + /* send over serial line */ + written = write(bfd->fd, msg->data, msg->len); + if (written < msg->len) { + perror("short write:"); + msgb_free(msg); + return -1; + } + + msgb_free(msg); + usleep(sh->delay_ms*1000); + + return 0; +} + +#define SERIAL_ALLOC_SIZE 300 + +/* select.c callback in case we can read from the RS232 */ +static int handle_ser_read(struct bsc_fd *bfd) +{ + struct serial_handle *sh = bfd->data; + struct msgb *msg; + int rc = 0; + + if (!sh->rx_msg) { + sh->rx_msg = msgb_alloc(SERIAL_ALLOC_SIZE, "RS232 Rx"); + sh->rx_msg->l2h = NULL; + sh->rx_msg->trx = sh->bts->c0; + } + msg = sh->rx_msg; + + /* first read two byes to obtain length */ + if (msg->len < 2) { + rc = read(sh->fd.fd, msg->tail, 2 - msg->len); + if (rc < 0) { + perror("ERROR reading from serial port"); + msgb_free(msg); + return rc; + } + msgb_put(msg, rc); + + if (msg->len >= 2) { + /* parse LAPD payload length */ + if (msg->data[0] != 0) + fprintf(stderr, "Suspicious header byte 0: 0x%02x\n", + msg->data[0]); + + sh->rxmsg_bytes_missing = msg->data[0] << 8; + sh->rxmsg_bytes_missing += msg->data[1]; + + if (sh->rxmsg_bytes_missing < LAPD_HDR_LEN -2) + fprintf(stderr, "Invalid length in hdr: %u\n", + sh->rxmsg_bytes_missing); + } + } else { + /* try to read as many of the missing bytes as are available */ + rc = read(sh->fd.fd, msg->tail, sh->rxmsg_bytes_missing); + if (rc < 0) { + perror("ERROR reading from serial port"); + msgb_free(msg); + return rc; + } + msgb_put(msg, rc); + sh->rxmsg_bytes_missing -= rc; + + if (sh->rxmsg_bytes_missing == 0) { + /* we have one complete message now */ + sh->rx_msg = NULL; + + if (msg->len > LAPD_HDR_LEN) + msg->l2h = msg->data + LAPD_HDR_LEN; + + DEBUGP(DMI, "RS232 RX: %s\n", hexdump(msg->data, msg->len)); + rc = handle_serial_msg(msg); + } + } + + return rc; +} + +/* select.c callback */ +static int serial_fd_cb(struct bsc_fd *bfd, unsigned int what) +{ + int rc = 0; + + if (what & BSC_FD_READ) + rc = handle_ser_read(bfd); + + if (rc < 0) + return rc; + + if (what & BSC_FD_WRITE) + rc = handle_ser_write(bfd); + + return rc; +} + +int rs232_setup(const char *serial_port, unsigned int delay_ms, + struct gsm_bts *bts) +{ + int rc, serial_fd; + struct termios tio; + + serial_fd = open(serial_port, O_RDWR); + if (serial_fd < 0) { + perror("cannot open serial port:"); + return serial_fd; + } + + /* set baudrate */ + rc = tcgetattr(serial_fd, &tio); + if (rc < 0) { + perror("tcgetattr()"); + return rc; + } + cfsetispeed(&tio, B19200); + cfsetospeed(&tio, B19200); + tio.c_cflag |= (CREAD | CLOCAL | CS8); + tio.c_cflag &= ~(PARENB | CSTOPB | CSIZE | CRTSCTS); + tio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + tio.c_iflag |= (INPCK | ISTRIP); + tio.c_iflag &= ~(ISTRIP | IXON | IXOFF | IGNBRK | INLCR | ICRNL | IGNCR); + tio.c_oflag &= ~(OPOST); + rc = tcsetattr(serial_fd, TCSADRAIN, &tio); + if (rc < 0) { + perror("tcsetattr()"); + return rc; + } + + INIT_LLIST_HEAD(&ser_handle->tx_queue); + ser_handle->fd.fd = serial_fd; + ser_handle->fd.when = BSC_FD_READ; + ser_handle->fd.cb = serial_fd_cb; + ser_handle->fd.data = ser_handle; + ser_handle->delay_ms = delay_ms; + ser_handle->bts = bts; + rc = bsc_register_fd(&ser_handle->fd); + if (rc < 0) { + fprintf(stderr, "could not register FD: %s\n", + strerror(rc)); + return rc; + } + + return 0; +} diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 000000000..1968119f8 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,5 @@ +SUBDIRS = debug gsm0408 db channel mgcp + +if BUILD_NAT +SUBDIRS += bsc-nat +endif diff --git a/tests/Makefile.in b/tests/Makefile.in new file mode 100644 index 000000000..f5e61eb4f --- /dev/null +++ b/tests/Makefile.in @@ -0,0 +1,538 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +@BUILD_NAT_TRUE@am__append_1 = bsc-nat +subdir = tests +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \ + html-recursive info-recursive install-data-recursive \ + install-dvi-recursive install-exec-recursive \ + install-html-recursive install-info-recursive \ + install-pdf-recursive install-ps-recursive install-recursive \ + installcheck-recursive installdirs-recursive pdf-recursive \ + ps-recursive uninstall-recursive +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \ + $(RECURSIVE_CLEAN_TARGETS:-recursive=) tags TAGS ctags CTAGS \ + distdir +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = debug gsm0408 db channel mgcp bsc-nat +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = debug gsm0408 db channel mgcp $(am__append_1) +all: all-recursive + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu tests/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +# This directory's subdirectories are mostly independent; you can cd +# into them and run `make' without going through this Makefile. +# To change the values of `make' variables: instead of editing Makefiles, +# (1) if the variable is set in `config.status', edit `config.status' +# (which will cause the Makefiles to be regenerated when you run `make'); +# (2) otherwise, pass the desired values on the `make' command line. +$(RECURSIVE_TARGETS): + @fail= failcom='exit 1'; \ + for f in x $$MAKEFLAGS; do \ + case $$f in \ + *=* | --[!k]*);; \ + *k*) failcom='fail=yes';; \ + esac; \ + done; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +$(RECURSIVE_CLEAN_TARGETS): + @fail= failcom='exit 1'; \ + for f in x $$MAKEFLAGS; do \ + case $$f in \ + *=* | --[!k]*);; \ + *k*) failcom='fail=yes';; \ + esac; \ + done; \ + dot_seen=no; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + rev=''; for subdir in $$list; do \ + if test "$$subdir" = "."; then :; else \ + rev="$$subdir $$rev"; \ + fi; \ + done; \ + rev="$$rev ."; \ + target=`echo $@ | sed s/-recursive//`; \ + for subdir in $$rev; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done && test -z "$$fail" +tags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \ + done +ctags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \ + done + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: tags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: ctags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) ctags-recursive \ + install-am install-strip tags-recursive + +.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \ + all all-am check check-am clean clean-generic ctags \ + ctags-recursive distclean distclean-generic distclean-tags \ + distdir dvi dvi-am html html-am info info-am install \ + install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + installdirs-am maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \ + tags-recursive uninstall uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/tests/bsc-nat/Makefile.am b/tests/bsc-nat/Makefile.am new file mode 100644 index 000000000..04bdb9789 --- /dev/null +++ b/tests/bsc-nat/Makefile.am @@ -0,0 +1,19 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(COVERAGE_LDFLAGS) + +EXTRA_DIST = bsc_data.c + +noinst_PROGRAMS = bsc_nat_test + +bsc_nat_test_SOURCES = bsc_nat_test.c \ + $(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c \ + $(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c \ + $(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c \ + $(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c +bsc_nat_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_srcdir)/src/libmgcp/libmgcp.a \ + $(top_srcdir)/src/libabis/libabis.a \ + $(top_srcdir)/src/libtrau/libtrau.a \ + $(top_srcdir)/src/libcommon/libcommon.a \ + $(LIBOSMOCORE_LIBS) -lrt $(LIBOSMOSCCP_LIBS) $(LIBOSMOVTY_LIBS) diff --git a/tests/bsc-nat/Makefile.in b/tests/bsc-nat/Makefile.in new file mode 100644 index 000000000..280d22d7a --- /dev/null +++ b/tests/bsc-nat/Makefile.in @@ -0,0 +1,535 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +noinst_PROGRAMS = bsc_nat_test$(EXEEXT) +subdir = tests/bsc-nat +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +PROGRAMS = $(noinst_PROGRAMS) +am_bsc_nat_test_OBJECTS = bsc_nat_test.$(OBJEXT) bsc_filter.$(OBJEXT) \ + bsc_sccp.$(OBJEXT) bsc_nat_utils.$(OBJEXT) \ + bsc_mgcp_utils.$(OBJEXT) +bsc_nat_test_OBJECTS = $(am_bsc_nat_test_OBJECTS) +am__DEPENDENCIES_1 = +bsc_nat_test_DEPENDENCIES = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_srcdir)/src/libmgcp/libmgcp.a \ + $(top_srcdir)/src/libabis/libabis.a \ + $(top_srcdir)/src/libtrau/libtrau.a \ + $(top_srcdir)/src/libcommon/libcommon.a $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +AM_V_lt = $(am__v_lt_$(V)) +am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY)) +am__v_lt_0 = --silent +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(bsc_nat_test_SOURCES) +DIST_SOURCES = $(bsc_nat_test_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(COVERAGE_LDFLAGS) +EXTRA_DIST = bsc_data.c +bsc_nat_test_SOURCES = bsc_nat_test.c \ + $(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c \ + $(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c \ + $(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c \ + $(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c + +bsc_nat_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_srcdir)/src/libmgcp/libmgcp.a \ + $(top_srcdir)/src/libabis/libabis.a \ + $(top_srcdir)/src/libtrau/libtrau.a \ + $(top_srcdir)/src/libcommon/libcommon.a \ + $(LIBOSMOCORE_LIBS) -lrt $(LIBOSMOSCCP_LIBS) $(LIBOSMOVTY_LIBS) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/bsc-nat/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu tests/bsc-nat/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS) +bsc_nat_test$(EXEEXT): $(bsc_nat_test_OBJECTS) $(bsc_nat_test_DEPENDENCIES) + @rm -f bsc_nat_test$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(bsc_nat_test_OBJECTS) $(bsc_nat_test_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_filter.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_mgcp_utils.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_nat_test.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_nat_utils.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_sccp.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +bsc_filter.o: $(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_filter.o -MD -MP -MF $(DEPDIR)/bsc_filter.Tpo -c -o bsc_filter.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bsc_filter.Tpo $(DEPDIR)/bsc_filter.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c' object='bsc_filter.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_filter.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c + +bsc_filter.obj: $(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_filter.obj -MD -MP -MF $(DEPDIR)/bsc_filter.Tpo -c -o bsc_filter.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bsc_filter.Tpo $(DEPDIR)/bsc_filter.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c' object='bsc_filter.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_filter.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c'; fi` + +bsc_sccp.o: $(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_sccp.o -MD -MP -MF $(DEPDIR)/bsc_sccp.Tpo -c -o bsc_sccp.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bsc_sccp.Tpo $(DEPDIR)/bsc_sccp.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c' object='bsc_sccp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_sccp.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c + +bsc_sccp.obj: $(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_sccp.obj -MD -MP -MF $(DEPDIR)/bsc_sccp.Tpo -c -o bsc_sccp.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bsc_sccp.Tpo $(DEPDIR)/bsc_sccp.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c' object='bsc_sccp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_sccp.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c'; fi` + +bsc_nat_utils.o: $(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_nat_utils.o -MD -MP -MF $(DEPDIR)/bsc_nat_utils.Tpo -c -o bsc_nat_utils.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bsc_nat_utils.Tpo $(DEPDIR)/bsc_nat_utils.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c' object='bsc_nat_utils.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_nat_utils.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c + +bsc_nat_utils.obj: $(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_nat_utils.obj -MD -MP -MF $(DEPDIR)/bsc_nat_utils.Tpo -c -o bsc_nat_utils.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bsc_nat_utils.Tpo $(DEPDIR)/bsc_nat_utils.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c' object='bsc_nat_utils.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_nat_utils.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c'; fi` + +bsc_mgcp_utils.o: $(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_mgcp_utils.o -MD -MP -MF $(DEPDIR)/bsc_mgcp_utils.Tpo -c -o bsc_mgcp_utils.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bsc_mgcp_utils.Tpo $(DEPDIR)/bsc_mgcp_utils.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c' object='bsc_mgcp_utils.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_mgcp_utils.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c + +bsc_mgcp_utils.obj: $(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_mgcp_utils.obj -MD -MP -MF $(DEPDIR)/bsc_mgcp_utils.Tpo -c -o bsc_mgcp_utils.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bsc_mgcp_utils.Tpo $(DEPDIR)/bsc_mgcp_utils.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c' object='bsc_mgcp_utils.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_mgcp_utils.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c'; fi` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-noinstPROGRAMS ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/tests/bsc-nat/bsc_data.c b/tests/bsc-nat/bsc_data.c new file mode 100644 index 000000000..04755233c --- /dev/null +++ b/tests/bsc-nat/bsc_data.c @@ -0,0 +1,187 @@ +/* test data */ + +/* BSC -> MSC, CR */ +static const uint8_t bsc_cr[] = { +0x00, 0x2e, 0xfd, +0x01, 0x00, 0x00, 0x15, 0x02, 0x02, 0x04, 0x02, +0x42, 0xfe, 0x0f, 0x21, 0x00, 0x1f, 0x57, 0x05, +0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x1c, 0xc3, +0x51, 0x17, 0x12, 0x05, 0x08, 0x20, 0x72, 0xf4, +0x90, 0x20, 0x1d, 0x50, 0x08, 0x29, 0x47, 0x80, +0x00, 0x00, 0x00, 0x00, 0x80, 0x00 }; + +static const uint8_t bsc_cr_patched[] = { +0x00, 0x2e, 0xfd, +0x01, 0x00, 0x00, 0x05, 0x02, 0x02, 0x04, 0x02, +0x42, 0xfe, 0x0f, 0x21, 0x00, 0x1f, 0x57, 0x05, +0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x1c, 0xc3, +0x51, 0x17, 0x12, 0x05, 0x08, 0x20, 0x72, 0xf4, +0x90, 0x20, 0x1d, 0x50, 0x08, 0x29, 0x47, 0x80, +0x00, 0x00, 0x00, 0x00, 0x80, 0x00 }; + +/* CC, MSC -> BSC */ +static const uint8_t msc_cc[] = { +0x00, 0x0a, 0xfd, +0x02, 0x00, 0x00, 0x05, 0x01, 0x1f, 0xe4, 0x02, +0x01, 0x00 }; +static const uint8_t msc_cc_patched[] = { +0x00, 0x0a, 0xfd, +0x02, 0x00, 0x00, 0x15, 0x01, 0x1f, 0xe4, 0x02, +0x01, 0x00 }; + +/* Classmark, BSC -> MSC */ +static const uint8_t bsc_dtap[] = { +0x00, 0x17, 0xfd, +0x06, 0x01, 0x1f, 0xe4, 0x00, 0x01, 0x10, 0x00, +0x0e, 0x54, 0x12, 0x03, 0x50, 0x18, 0x93, 0x13, +0x06, 0x60, 0x14, 0x45, 0x00, 0x81, 0x00 }; + +static const uint8_t bsc_dtap_patched[] = { +0x00, 0x17, 0xfd, +0x06, 0x01, 0x1f, 0xe4, 0x00, 0x01, 0x10, 0x00, +0x0e, 0x54, 0x12, 0x03, 0x50, 0x18, 0x93, 0x13, +0x06, 0x60, 0x14, 0x45, 0x00, 0x81, 0x00 }; + +/* Clear command, MSC -> BSC */ +static const uint8_t msc_dtap[] = { +0x00, 0x0d, 0xfd, +0x06, 0x00, 0x00, 0x05, 0x00, 0x01, 0x06, 0x00, +0x04, 0x20, 0x04, 0x01, 0x09 }; +static const uint8_t msc_dtap_patched[] = { +0x00, 0x0d, 0xfd, +0x06, 0x00, 0x00, 0x15, 0x00, 0x01, 0x06, 0x00, +0x04, 0x20, 0x04, 0x01, 0x09 }; + +/*RLSD, MSC -> BSC */ +static const uint8_t msc_rlsd[] = { +0x00, 0x0a, 0xfd, +0x04, 0x00, 0x00, 0x05, 0x01, 0x1f, 0xe4, 0x00, +0x01, 0x00 }; +static const uint8_t msc_rlsd_patched[] = { +0x00, 0x0a, 0xfd, +0x04, 0x00, 0x00, 0x15, 0x01, 0x1f, 0xe4, 0x00, +0x01, 0x00 }; + +/* RLC, BSC -> MSC */ +static const uint8_t bsc_rlc[] = { +0x00, 0x07, 0xfd, +0x05, 0x01, 0x1f, 0xe4, 0x00, 0x00, 0x15 }; + +static const uint8_t bsc_rlc_patched[] = { +0x00, 0x07, 0xfd, +0x05, 0x01, 0x1f, 0xe4, 0x00, 0x00, 0x05 }; + + +/* a paging command */ +static const uint8_t paging_by_lac_cmd[] = { +0x00, 0x22, 0xfd, 0x09, +0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x02, 0x00, +0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x12, 0x00, +0x10, 0x52, 0x08, 0x08, 0x29, 0x47, 0x10, 0x02, +0x01, 0x50, 0x02, 0x30, 0x1a, 0x03, 0x05, 0x20, +0x15 }; + +/* an assignment command */ +static const uint8_t ass_cmd[] = { +0x00, 0x12, 0xfd, 0x06, +0x00, 0x00, 0x49, 0x00, 0x01, 0x0b, 0x00, 0x09, +0x01, 0x0b, 0x03, 0x01, 0x0a, 0x11, 0x01, 0x00, +0x01 }; + +/* identity response */ +static const uint8_t id_resp[] = { +0x00, 0x15, 0xfd, 0x06, 0x01, 0x1c, 0xdc, +0x00, 0x01, 0x0e, 0x01, 0x00, 0x0b, 0x05, 0x59, +0x08, 0x29, 0x40, 0x21, 0x03, 0x07, 0x48, 0x66, +0x31 +}; + +/* + * MGCP messages + */ + +/* nothing to patch */ +static const char crcx[] = "CRCX 23265295 8@mgw MGCP 1.0\r\nC: 394b0439fb\r\nL: p:20, a:AMR, nt:IN\r\nM: recvonly\r\n"; +static const char crcx_patched[] = "CRCX 23265295 1e@mgw MGCP 1.0\r\nC: 394b0439fb\r\nL: p:20, a:AMR, nt:IN\r\nM: recvonly\r\n"; + + +/* patch the ip and port */ +static const char crcx_resp[] = "200 23265295\r\nI: 1\r\n\r\nv=0\r\nc=IN IP4 172.16.18.2\r\nm=audio 4002 RTP/AVP 98\r\na=rtpmap:98 AMR/8000\r\n"; +static const char crcx_resp_patched[] = "200 23265295\r\nI: 1\r\n\r\nv=0\r\nc=IN IP4 10.0.0.1\r\nm=audio 999 RTP/AVP 98\r\na=rtpmap:98 AMR/8000\r\n"; + +/* patch the ip and port */ +static const char mdcx[] = "MDCX 23330829 8@mgw MGCP 1.0\r\nC: 394b0439fb\r\nI: 1\r\nL: p:20, a:AMR, nt:IN\r\nM: recvonly\r\n\r\nv=0\r\no=- 1049380491 0 IN IP4 172.16.18.2\r\ns=-\r\nc=IN IP4 172.16.18.2\r\nt=0 0\r\nm=audio 4410 RTP/AVP 126\r\na=rtpmap:126 AMR/8000/1\r\na=fmtp:126 mode-set=2;start-mode=0\r\na=ptime:20\r\na=recvonly\r\nm=image 4412 udptl t38\r\na=T38FaxVersion:0\r\na=T38MaxBitRate:14400\r\n"; +static const char mdcx_patched[] = "MDCX 23330829 1e@mgw MGCP 1.0\r\nC: 394b0439fb\r\nI: 1\r\nL: p:20, a:AMR, nt:IN\r\nM: recvonly\r\n\r\nv=0\r\no=- 1049380491 0 IN IP4 172.16.18.2\r\ns=-\r\nc=IN IP4 10.0.0.23\r\nt=0 0\r\nm=audio 6666 RTP/AVP 126\r\na=rtpmap:126 AMR/8000/1\r\na=fmtp:126 mode-set=2;start-mode=0\r\na=ptime:20\r\na=recvonly\r\nm=image 4412 udptl t38\r\na=T38FaxVersion:0\r\na=T38MaxBitRate:14400\r\n"; + + +static const char mdcx_resp[] = "200 23330829\r\n\r\nv=0\r\nc=IN IP4 172.16.18.2\r\nm=audio 4002 RTP/AVP 98\r\na=rtpmap:98 AMR/8000\r\n"; +static const char mdcx_resp_patched[] = "200 23330829\r\n\r\nv=0\r\nc=IN IP4 10.0.0.23\r\nm=audio 5555 RTP/AVP 98\r\na=rtpmap:98 AMR/8000\r\n"; + +/* different line ending */ +static const char mdcx_resp2[] = "200 33330829\n\nv=0\nc=IN IP4 172.16.18.2\nm=audio 4002 RTP/AVP 98\na=rtpmap:98 AMR/8000\n"; +static const char mdcx_resp_patched2[] = "200 33330829\n\nv=0\nc=IN IP4 10.0.0.23\nm=audio 5555 RTP/AVP 98\na=rtpmap:98 AMR/8000\n"; + +struct mgcp_patch_test { + const char *orig; + const char *patch; + const char *ip; + const int port; +}; + +static const struct mgcp_patch_test mgcp_messages[] = { + { + .orig = crcx, + .patch = crcx_patched, + .ip = "0.0.0.0", + .port = 2323, + }, + { + .orig = crcx_resp, + .patch = crcx_resp_patched, + .ip = "10.0.0.1", + .port = 999, + }, + { + .orig = mdcx, + .patch = mdcx_patched, + .ip = "10.0.0.23", + .port = 6666, + }, + { + .orig = mdcx_resp, + .patch = mdcx_resp_patched, + .ip = "10.0.0.23", + .port = 5555, + }, + { + .orig = mdcx_resp2, + .patch = mdcx_resp_patched2, + .ip = "10.0.0.23", + .port = 5555, + }, +}; + +/* CC Setup messages */ +static const uint8_t cc_setup_national[] = { + 0x00, 0x20, 0xfd, 0x06, 0x01, 0x12, + 0x6d, 0x00, 0x01, 0x19, 0x01, 0x00, 0x16, 0x03, + 0x05, 0x04, 0x06, 0x60, 0x04, 0x02, 0x00, 0x05, + 0x81, 0x5e, 0x06, 0x81, 0x10, 0x27, 0x33, 0x63, + 0x66, 0x15, 0x02, 0x11, 0x01 +}; + +static const uint8_t cc_setup_national_patched[] = { + 0x00, 0x21, 0xfd, 0x06, 0x01, 0x12, + 0x6d, 0x00, 0x01, 0x1a, 0x01, 0x00, 0x17, 0x03, + 0x05, 0x04, 0x06, 0x60, 0x04, 0x02, 0x00, 0x05, + 0x81, 0x5e, 0x07, 0x91, 0x94, 0x71, 0x32, 0x33, + 0x66, 0xf6, 0x15, 0x02, 0x11, 0x01 +}; + +static const uint8_t cc_setup_international[] = { + 0x00, 0x22, 0xfd, 0x06, 0x01, 0x13, + 0xe7, 0x00, 0x01, 0x1b, 0x01, 0x00, 0x18, 0x03, + 0x45, 0x04, 0x06, 0x60, 0x04, 0x02, 0x00, 0x05, + 0x81, 0x5e, 0x08, 0x81, 0x00, 0x94, 0x71, 0x33, + 0x63, 0x66, 0x03, 0x15, 0x02, 0x11, 0x01 +}; diff --git a/tests/bsc-nat/bsc_nat_test.c b/tests/bsc-nat/bsc_nat_test.c new file mode 100644 index 000000000..504b69196 --- /dev/null +++ b/tests/bsc-nat/bsc_nat_test.c @@ -0,0 +1,1016 @@ +/* + * BSC NAT Message filtering + * + * (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by On-Waves + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +/* test messages for ipa */ +static uint8_t ipa_id[] = { + 0x00, 0x01, 0xfe, 0x06, +}; + +/* SCCP messages are below */ +static uint8_t gsm_reset[] = { + 0x00, 0x12, 0xfd, + 0x09, 0x00, 0x03, 0x05, 0x07, 0x02, 0x42, 0xfe, + 0x02, 0x42, 0xfe, 0x06, 0x00, 0x04, 0x30, 0x04, + 0x01, 0x20, +}; + +static const uint8_t gsm_reset_ack[] = { + 0x00, 0x13, 0xfd, + 0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01, + 0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x03, + 0x00, 0x01, 0x31, +}; + +static const uint8_t gsm_paging[] = { + 0x00, 0x20, 0xfd, + 0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01, + 0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x10, + 0x00, 0x0e, 0x52, 0x08, 0x08, 0x29, 0x47, 0x10, + 0x02, 0x01, 0x31, 0x97, 0x61, 0x1a, 0x01, 0x06, +}; + +/* BSC -> MSC connection open */ +static const uint8_t bssmap_cr[] = { + 0x00, 0x2c, 0xfd, + 0x01, 0x01, 0x02, 0x03, 0x02, 0x02, 0x04, 0x02, + 0x42, 0xfe, 0x0f, 0x1f, 0x00, 0x1d, 0x57, 0x05, + 0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x12, 0xc3, + 0x50, 0x17, 0x10, 0x05, 0x24, 0x11, 0x03, 0x33, + 0x19, 0xa2, 0x08, 0x29, 0x47, 0x10, 0x02, 0x01, + 0x31, 0x97, 0x61, 0x00 +}; + +/* MSC -> BSC connection confirm */ +static const uint8_t bssmap_cc[] = { + 0x00, 0x0a, 0xfd, + 0x02, 0x01, 0x02, 0x03, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, +}; + +/* MSC -> BSC released */ +static const uint8_t bssmap_released[] = { + 0x00, 0x0e, 0xfd, + 0x04, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03, 0x00, 0x01, 0x0f, + 0x02, 0x23, 0x42, 0x00, +}; + +/* BSC -> MSC released */ +static const uint8_t bssmap_release_complete[] = { + 0x00, 0x07, 0xfd, + 0x05, 0x01, 0x02, 0x03, 0x00, 0x00, 0x03 +}; + +/* both directions IT timer */ +static const uint8_t connnection_it[] = { + 0x00, 0x0b, 0xfd, + 0x10, 0x01, 0x02, 0x03, 0x01, 0x02, 0x03, + 0x00, 0x00, 0x00, 0x00, +}; + +/* error in both directions */ +static const uint8_t proto_error[] = { + 0x00, 0x05, 0xfd, + 0x0f, 0x22, 0x33, 0x44, 0x00, +}; + +/* MGCP wrap... */ +static const uint8_t mgcp_msg[] = { + 0x00, 0x03, 0xfc, + 0x20, 0x20, 0x20, +}; + +/* location updating request */ +static const uint8_t bss_lu[] = { + 0x00, 0x2e, 0xfd, + 0x01, 0x91, 0x45, 0x14, 0x02, 0x02, 0x04, 0x02, + 0x42, 0xfe, 0x0f, 0x21, 0x00, 0x1f, 0x57, 0x05, + 0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x14, 0xc3, + 0x50, 0x17, 0x12, 0x05, 0x08, 0x70, 0x72, 0xf4, + 0x80, 0xff, 0xfe, 0x30, 0x08, 0x29, 0x44, 0x50, + 0x12, 0x03, 0x24, 0x01, 0x95, 0x00 +}; + +/* paging response */ +static const uint8_t pag_resp[] = { + 0x00, 0x2c, 0xfd, 0x01, 0xe5, 0x68, + 0x14, 0x02, 0x02, 0x04, 0x02, 0x42, 0xfe, 0x0f, + 0x1f, 0x00, 0x1d, 0x57, 0x05, 0x08, 0x00, 0x72, + 0xf4, 0x80, 0x20, 0x16, 0xc3, 0x50, 0x17, 0x10, + 0x06, 0x27, 0x01, 0x03, 0x30, 0x18, 0x96, 0x08, + 0x29, 0x26, 0x30, 0x32, 0x11, 0x42, 0x01, 0x19, + 0x00 +}; + +struct filter_result { + const uint8_t *data; + const uint16_t length; + const int dir; + const int result; +}; + +static const struct filter_result results[] = { + { + .data = ipa_id, + .length = ARRAY_SIZE(ipa_id), + .dir = DIR_MSC, + .result = 1, + }, + { + .data = gsm_reset, + .length = ARRAY_SIZE(gsm_reset), + .dir = DIR_MSC, + .result = 1, + }, + { + .data = gsm_reset_ack, + .length = ARRAY_SIZE(gsm_reset_ack), + .dir = DIR_BSC, + .result = 1, + }, + { + .data = gsm_paging, + .length = ARRAY_SIZE(gsm_paging), + .dir = DIR_BSC, + .result = 0, + }, + { + .data = bssmap_cr, + .length = ARRAY_SIZE(bssmap_cr), + .dir = DIR_MSC, + .result = 0, + }, + { + .data = bssmap_cc, + .length = ARRAY_SIZE(bssmap_cc), + .dir = DIR_BSC, + .result = 0, + }, + { + .data = bssmap_released, + .length = ARRAY_SIZE(bssmap_released), + .dir = DIR_MSC, + .result = 0, + }, + { + .data = bssmap_release_complete, + .length = ARRAY_SIZE(bssmap_release_complete), + .dir = DIR_BSC, + .result = 0, + }, + { + .data = mgcp_msg, + .length = ARRAY_SIZE(mgcp_msg), + .dir = DIR_MSC, + .result = 0, + }, + { + .data = connnection_it, + .length = ARRAY_SIZE(connnection_it), + .dir = DIR_BSC, + .result = 0, + }, + { + .data = connnection_it, + .length = ARRAY_SIZE(connnection_it), + .dir = DIR_MSC, + .result = 0, + }, + { + .data = proto_error, + .length = ARRAY_SIZE(proto_error), + .dir = DIR_BSC, + .result = 0, + }, + { + .data = proto_error, + .length = ARRAY_SIZE(proto_error), + .dir = DIR_MSC, + .result = 0, + }, + +}; + +static void test_filter(void) +{ + int i; + + + /* start testinh with proper messages */ + fprintf(stderr, "Testing BSS Filtering.\n"); + for (i = 0; i < ARRAY_SIZE(results); ++i) { + int result; + struct bsc_nat_parsed *parsed; + struct msgb *msg = msgb_alloc(4096, "test-message"); + + fprintf(stderr, "Going to test item: %d\n", i); + memcpy(msg->data, results[i].data, results[i].length); + msg->l2h = msgb_put(msg, results[i].length); + + parsed = bsc_nat_parse(msg); + if (!parsed) { + fprintf(stderr, "FAIL: Failed to parse the message\n"); + continue; + } + + result = bsc_nat_filter_ipa(results[i].dir, msg, parsed); + if (result != results[i].result) { + fprintf(stderr, "FAIL: Not the expected result got: %d wanted: %d\n", + result, results[i].result); + } + + msgb_free(msg); + } +} + +#include "bsc_data.c" + +static void copy_to_msg(struct msgb *msg, const uint8_t *data, unsigned int length) +{ + msgb_reset(msg); + msg->l2h = msgb_put(msg, length); + memcpy(msg->l2h, data, msgb_l2len(msg)); +} + +#define VERIFY(con_found, con, msg, ver, str) \ + if (!con_found || con_found->bsc != con) { \ + fprintf(stderr, "Failed to find the con: %p\n", con_found); \ + abort(); \ + } \ + if (memcmp(msg->data, ver, sizeof(ver)) != 0) { \ + fprintf(stderr, "Failed to patch the %s msg.\n", str); \ + abort(); \ + } + +/* test conn tracking once */ +static void test_contrack() +{ + struct bsc_nat *nat; + struct bsc_connection *con; + struct sccp_connections *con_found; + struct sccp_connections *rc_con; + struct bsc_nat_parsed *parsed; + struct msgb *msg; + + fprintf(stderr, "Testing connection tracking.\n"); + nat = bsc_nat_alloc(); + con = bsc_connection_alloc(nat); + con->cfg = bsc_config_alloc(nat, "foo"); + bsc_config_add_lac(con->cfg, 23); + bsc_config_add_lac(con->cfg, 49); + bsc_config_add_lac(con->cfg, 42); + bsc_config_del_lac(con->cfg, 49); + bsc_config_add_lac(con->cfg, 1111); + msg = msgb_alloc(4096, "test"); + + /* 1.) create a connection */ + copy_to_msg(msg, bsc_cr, sizeof(bsc_cr)); + parsed = bsc_nat_parse(msg); + con_found = patch_sccp_src_ref_to_msc(msg, parsed, con); + if (con_found != NULL) { + fprintf(stderr, "Con should not exist %p\n", con_found); + abort(); + } + rc_con = create_sccp_src_ref(con, parsed); + if (!rc_con) { + fprintf(stderr, "Failed to create a ref\n"); + abort(); + } + con_found = patch_sccp_src_ref_to_msc(msg, parsed, con); + if (!con_found || con_found->bsc != con) { + fprintf(stderr, "Failed to find the con: %p\n", con_found); + abort(); + } + if (con_found != rc_con) { + fprintf(stderr, "Failed to find the right connection.\n"); + abort(); + } + if (memcmp(msg->data, bsc_cr_patched, sizeof(bsc_cr_patched)) != 0) { + fprintf(stderr, "Failed to patch the BSC CR msg.\n"); + abort(); + } + talloc_free(parsed); + + /* 2.) get the cc */ + copy_to_msg(msg, msc_cc, sizeof(msc_cc)); + parsed = bsc_nat_parse(msg); + con_found = patch_sccp_src_ref_to_bsc(msg, parsed, nat); + VERIFY(con_found, con, msg, msc_cc_patched, "MSC CC"); + if (update_sccp_src_ref(con_found, parsed) != 0) { + fprintf(stderr, "Failed to update the SCCP con.\n"); + abort(); + } + + /* 3.) send some data */ + copy_to_msg(msg, bsc_dtap, sizeof(bsc_dtap)); + parsed = bsc_nat_parse(msg); + con_found = patch_sccp_src_ref_to_msc(msg, parsed, con); + VERIFY(con_found, con, msg, bsc_dtap_patched, "BSC DTAP"); + + /* 4.) receive some data */ + copy_to_msg(msg, msc_dtap, sizeof(msc_dtap)); + parsed = bsc_nat_parse(msg); + con_found = patch_sccp_src_ref_to_bsc(msg, parsed, nat); + VERIFY(con_found, con, msg, msc_dtap_patched, "MSC DTAP"); + + /* 5.) close the connection */ + copy_to_msg(msg, msc_rlsd, sizeof(msc_rlsd)); + parsed = bsc_nat_parse(msg); + con_found = patch_sccp_src_ref_to_bsc(msg, parsed, nat); + VERIFY(con_found, con, msg, msc_rlsd_patched, "MSC RLSD"); + + /* 6.) confirm the connection close */ + copy_to_msg(msg, bsc_rlc, sizeof(bsc_rlc)); + parsed = bsc_nat_parse(msg); + con_found = patch_sccp_src_ref_to_msc(msg, parsed, con); + if (!con_found || con_found->bsc != con) { + fprintf(stderr, "Failed to find the con: %p\n", con_found); + abort(); + } + if (memcmp(msg->data, bsc_rlc_patched, sizeof(bsc_rlc_patched)) != 0) { + fprintf(stderr, "Failed to patch the BSC CR msg.\n"); + abort(); + } + remove_sccp_src_ref(con, msg, parsed); + talloc_free(parsed); + + copy_to_msg(msg, bsc_rlc, sizeof(bsc_rlc)); + parsed = bsc_nat_parse(msg); + con_found = patch_sccp_src_ref_to_msc(msg, parsed, con); + + /* verify that it is gone */ + if (con_found != NULL) { + fprintf(stderr, "Con should be gone. %p\n", con_found); + abort(); + } + talloc_free(parsed); + + + bsc_config_free(con->cfg); + talloc_free(nat); + msgb_free(msg); +} + +static void test_paging(void) +{ + int lac; + struct bsc_nat *nat; + struct bsc_connection *con; + struct bsc_nat_parsed *parsed; + struct bsc_config *cfg; + struct msgb *msg; + + fprintf(stderr, "Testing paging by lac.\n"); + + nat = bsc_nat_alloc(); + con = bsc_connection_alloc(nat); + cfg = bsc_config_alloc(nat, "unknown"); + con->cfg = cfg; + bsc_config_add_lac(cfg, 23); + con->authenticated = 1; + llist_add(&con->list_entry, &nat->bsc_connections); + msg = msgb_alloc(4096, "test"); + + /* Test completely bad input */ + copy_to_msg(msg, paging_by_lac_cmd, sizeof(paging_by_lac_cmd)); + if (bsc_nat_find_bsc(nat, msg, &lac) != 0) { + fprintf(stderr, "Should have not found anything.\n"); + abort(); + } + + /* Test it by not finding it */ + copy_to_msg(msg, paging_by_lac_cmd, sizeof(paging_by_lac_cmd)); + parsed = bsc_nat_parse(msg); + if (bsc_nat_find_bsc(nat, msg, &lac) != 0) { + fprintf(stderr, "Should have not found aynthing.\n"); + abort(); + } + talloc_free(parsed); + + /* Test by finding it */ + bsc_config_del_lac(cfg, 23); + bsc_config_add_lac(cfg, 8213); + copy_to_msg(msg, paging_by_lac_cmd, sizeof(paging_by_lac_cmd)); + parsed = bsc_nat_parse(msg); + if (bsc_nat_find_bsc(nat, msg, &lac) != con) { + fprintf(stderr, "Should have found it.\n"); + abort(); + } + talloc_free(parsed); +} + +static void test_mgcp_allocations(void) +{ +#if 0 + struct bsc_connection *bsc; + struct bsc_nat *nat; + struct sccp_connections con; + int i, j, multiplex; + + fprintf(stderr, "Testing MGCP.\n"); + memset(&con, 0, sizeof(con)); + + nat = bsc_nat_alloc(); + nat->bsc_endpoints = talloc_zero_array(nat, + struct bsc_endpoint, + 65); + nat->mgcp_cfg = mgcp_config_alloc(); + nat->mgcp_cfg->trunk.number_endpoints = 64; + + bsc = bsc_connection_alloc(nat); + bsc->cfg = bsc_config_alloc(nat, "foo"); + bsc->cfg->max_endpoints = 60; + bsc_config_add_lac(bsc->cfg, 2323); + bsc->last_endpoint = 0x22; + con.bsc = bsc; + + bsc_init_endps_if_needed(bsc); + + i = 1; + do { + if (bsc_assign_endpoint(bsc, &con) != 0) { + fprintf(stderr, "failed to allocate... on iteration %d\n", i); + break; + } + ++i; + } while(1); + + multiplex = bsc_mgcp_nr_multiplexes(bsc->cfg->max_endpoints); + for (i = 0; i < multiplex; ++i) { + for (j = 0; j < 32; ++j) + printf("%d", bsc->_endpoint_status[i*32 + j]); + printf(": %d of %d\n", i*32 + 32, 32 * 8); + } +#endif +} + +static void test_mgcp_ass_tracking(void) +{ + struct bsc_connection *bsc; + struct bsc_nat *nat; + struct sccp_connections con; + struct bsc_nat_parsed *parsed; + struct msgb *msg; + + fprintf(stderr, "Testing MGCP.\n"); + memset(&con, 0, sizeof(con)); + + nat = bsc_nat_alloc(); + nat->bsc_endpoints = talloc_zero_array(nat, + struct bsc_endpoint, + 33); + nat->mgcp_cfg = mgcp_config_alloc(); + nat->mgcp_cfg->trunk.number_endpoints = 64; + + bsc = bsc_connection_alloc(nat); + bsc->cfg = bsc_config_alloc(nat, "foo"); + bsc_config_add_lac(bsc->cfg, 2323); + bsc->last_endpoint = 0x1e; + con.bsc = bsc; + + msg = msgb_alloc(4096, "foo"); + copy_to_msg(msg, ass_cmd, sizeof(ass_cmd)); + parsed = bsc_nat_parse(msg); + + if (msg->l2h[16] != 0 || + msg->l2h[17] != 0x1) { + fprintf(stderr, "Input is not as expected.. %s 0x%x\n", + hexdump(msg->l2h, msgb_l2len(msg)), + msg->l2h[17]); + abort(); + } + + if (bsc_mgcp_assign_patch(&con, msg) != 0) { + fprintf(stderr, "Failed to handle assignment.\n"); + abort(); + } + + if (con.msc_endp != 1) { + fprintf(stderr, "Timeslot should be 1.\n"); + abort(); + } + + if (con.bsc_endp != 0x1) { + fprintf(stderr, "Assigned timeslot should have been 1.\n"); + abort(); + } + if (con.bsc->_endpoint_status[0x1] != 1) { + fprintf(stderr, "The status on the BSC is wrong.\n"); + abort(); + } + + int multiplex, timeslot; + mgcp_endpoint_to_timeslot(0x1, &multiplex, ×lot); + + uint16_t cic = htons(timeslot & 0x1f); + if (memcmp(&cic, &msg->l2h[16], sizeof(cic)) != 0) { + fprintf(stderr, "Message was not patched properly\n"); + fprintf(stderr, "data cic: 0x%x %s\n", cic, hexdump(msg->l2h, msgb_l2len(msg))); + abort(); + } + + talloc_free(parsed); + + bsc_mgcp_dlcx(&con); + if (con.bsc_endp != -1 || con.msc_endp != -1 || + con.bsc->_endpoint_status[1] != 0 || con.bsc->last_endpoint != 0x1) { + fprintf(stderr, "Clearing should remove the mapping.\n"); + abort(); + } + + bsc_config_free(bsc->cfg); + talloc_free(nat); +} + +/* test the code to find a given connection */ +static void test_mgcp_find(void) +{ + struct bsc_nat *nat; + struct bsc_connection *con; + struct sccp_connections *sccp_con; + + fprintf(stderr, "Testing finding of a BSC Connection\n"); + + nat = bsc_nat_alloc(); + con = bsc_connection_alloc(nat); + llist_add(&con->list_entry, &nat->bsc_connections); + + sccp_con = talloc_zero(con, struct sccp_connections); + sccp_con->msc_endp = 12; + sccp_con->bsc_endp = 12; + sccp_con->bsc = con; + llist_add(&sccp_con->list_entry, &nat->sccp_connections); + + if (bsc_mgcp_find_con(nat, 11) != NULL) { + fprintf(stderr, "Found the wrong connection.\n"); + abort(); + } + + if (bsc_mgcp_find_con(nat, 12) != sccp_con) { + fprintf(stderr, "Didn't find the connection\n"); + abort(); + } + + /* free everything */ + talloc_free(nat); +} + +static void test_mgcp_rewrite(void) +{ + int i; + struct msgb *output; + fprintf(stderr, "Test rewriting MGCP messages.\n"); + + for (i = 0; i < ARRAY_SIZE(mgcp_messages); ++i) { + const char *orig = mgcp_messages[i].orig; + const char *patc = mgcp_messages[i].patch; + const char *ip = mgcp_messages[i].ip; + const int port = mgcp_messages[i].port; + + char *input = strdup(orig); + + output = bsc_mgcp_rewrite(input, strlen(input), 0x1e, ip, port); + if (msgb_l2len(output) != strlen(patc)) { + fprintf(stderr, "Wrong sizes for test: %d %d != %d != %d\n", i, msgb_l2len(output), strlen(patc), strlen(orig)); + fprintf(stderr, "String '%s' vs '%s'\n", (const char *) output->l2h, patc); + abort(); + } + + if (memcmp(output->l2h, patc, msgb_l2len(output)) != 0) { + fprintf(stderr, "Broken on %d msg: '%s'\n", i, (const char *) output->l2h); + abort(); + } + + msgb_free(output); + free(input); + } +} + +static void test_mgcp_parse(void) +{ + int code, ci; + char transaction[60]; + + fprintf(stderr, "Test MGCP response parsing.\n"); + + if (bsc_mgcp_parse_response(crcx_resp, &code, transaction) != 0) { + fprintf(stderr, "Failed to parse CRCX resp.\n"); + abort(); + } + + if (code != 200) { + fprintf(stderr, "Failed to parse the CODE properly. Got: %d\n", code); + abort(); + } + + if (strcmp(transaction, "23265295") != 0) { + fprintf(stderr, "Failed to parse transaction id: '%s'\n", transaction); + abort(); + } + + ci = bsc_mgcp_extract_ci(crcx_resp); + if (ci != 1) { + fprintf(stderr, "Failed to parse the CI. Got: %d\n", ci); + abort(); + } +} + +struct cr_filter { + const uint8_t *data; + int length; + int result; + int contype; + + const char *bsc_imsi_allow; + const char *bsc_imsi_deny; + const char *nat_imsi_deny; +}; + +static struct cr_filter cr_filter[] = { + { + .data = bssmap_cr, + .length = sizeof(bssmap_cr), + .result = 1, + .contype = NAT_CON_TYPE_CM_SERV_REQ, + }, + { + .data = bss_lu, + .length = sizeof(bss_lu), + .result = 1, + .contype = NAT_CON_TYPE_LU, + }, + { + .data = pag_resp, + .length = sizeof(pag_resp), + .result = 1, + .contype = NAT_CON_TYPE_PAG_RESP, + }, + { + /* nat deny is before blank/null BSC */ + .data = bss_lu, + .length = sizeof(bss_lu), + .result = -3, + .nat_imsi_deny = "[0-9]*", + .contype = NAT_CON_TYPE_LU, + }, + { + /* BSC allow is before NAT deny */ + .data = bss_lu, + .length = sizeof(bss_lu), + .result = 1, + .nat_imsi_deny = "[0-9]*", + .bsc_imsi_allow = "2440[0-9]*", + .contype = NAT_CON_TYPE_LU, + }, + { + /* BSC allow is before NAT deny */ + .data = bss_lu, + .length = sizeof(bss_lu), + .result = 1, + .bsc_imsi_allow = "[0-9]*", + .nat_imsi_deny = "[0-9]*", + .contype = NAT_CON_TYPE_LU, + }, + { + /* filter as deny is first */ + .data = bss_lu, + .length = sizeof(bss_lu), + .result = 1, + .bsc_imsi_deny = "[0-9]*", + .bsc_imsi_allow = "[0-9]*", + .nat_imsi_deny = "[0-9]*", + .contype = NAT_CON_TYPE_LU, + }, + { + /* deny by nat rule */ + .data = bss_lu, + .length = sizeof(bss_lu), + .result = -3, + .bsc_imsi_deny = "000[0-9]*", + .nat_imsi_deny = "[0-9]*", + .contype = NAT_CON_TYPE_LU, + }, + { + /* deny by bsc rule */ + .data = bss_lu, + .length = sizeof(bss_lu), + .result = -2, + .bsc_imsi_deny = "[0-9]*", + .contype = NAT_CON_TYPE_LU, + }, + +}; + +static void test_cr_filter() +{ + int i, res, contype; + struct msgb *msg = msgb_alloc(4096, "test_cr_filter"); + struct bsc_nat_parsed *parsed; + struct bsc_nat_acc_lst *nat_lst, *bsc_lst; + struct bsc_nat_acc_lst_entry *nat_entry, *bsc_entry; + + struct bsc_nat *nat = bsc_nat_alloc(); + struct bsc_connection *bsc = bsc_connection_alloc(nat); + bsc->cfg = bsc_config_alloc(nat, "foo"); + bsc_config_add_lac(bsc->cfg, 1234); + bsc->cfg->acc_lst_name = "bsc"; + nat->acc_lst_name = "nat"; + + nat_lst = bsc_nat_acc_lst_get(nat, "nat"); + bsc_lst = bsc_nat_acc_lst_get(nat, "bsc"); + + bsc_entry = bsc_nat_acc_lst_entry_create(bsc_lst); + nat_entry = bsc_nat_acc_lst_entry_create(nat_lst); + + for (i = 0; i < ARRAY_SIZE(cr_filter); ++i) { + char *imsi; + msgb_reset(msg); + copy_to_msg(msg, cr_filter[i].data, cr_filter[i].length); + + nat_lst = bsc_nat_acc_lst_get(nat, "nat"); + bsc_lst = bsc_nat_acc_lst_get(nat, "bsc"); + + bsc_parse_reg(nat_entry, &nat_entry->imsi_deny_re, &nat_entry->imsi_deny, + cr_filter[i].nat_imsi_deny ? 1 : 0, + &cr_filter[i].nat_imsi_deny); + bsc_parse_reg(bsc_entry, &bsc_entry->imsi_allow_re, &bsc_entry->imsi_allow, + cr_filter[i].bsc_imsi_allow ? 1 : 0, + &cr_filter[i].bsc_imsi_allow); + bsc_parse_reg(bsc_entry, &bsc_entry->imsi_deny_re, &bsc_entry->imsi_deny, + cr_filter[i].bsc_imsi_deny ? 1 : 0, + &cr_filter[i].bsc_imsi_deny); + + parsed = bsc_nat_parse(msg); + if (!parsed) { + fprintf(stderr, "FAIL: Failed to parse the message\n"); + abort(); + } + + res = bsc_nat_filter_sccp_cr(bsc, msg, parsed, &contype, &imsi); + if (res != cr_filter[i].result) { + fprintf(stderr, "FAIL: Wrong result %d for test %d.\n", res, i); + abort(); + } + + if (contype != cr_filter[i].contype) { + fprintf(stderr, "FAIL: Wrong contype %d for test %d.\n", res, contype); + abort(); + } + + talloc_steal(parsed, imsi); + talloc_free(parsed); + } + + msgb_free(msg); +} + +static void test_dt_filter() +{ + int i; + struct msgb *msg = msgb_alloc(4096, "test_dt_filter"); + struct bsc_nat_parsed *parsed; + + struct bsc_nat *nat = bsc_nat_alloc(); + struct bsc_connection *bsc = bsc_connection_alloc(nat); + struct sccp_connections *con = talloc_zero(0, struct sccp_connections); + + bsc->cfg = bsc_config_alloc(nat, "foo"); + bsc_config_add_lac(bsc->cfg, 23); + con->bsc = bsc; + + msgb_reset(msg); + copy_to_msg(msg, id_resp, ARRAY_SIZE(id_resp)); + + parsed = bsc_nat_parse(msg); + if (!parsed) { + fprintf(stderr, "FAIL: Could not parse ID resp\n"); + abort(); + } + + if (parsed->bssap != BSSAP_MSG_DTAP) { + fprintf(stderr, "FAIL: It should be dtap\n"); + abort(); + } + + /* gsm_type is actually the size of the dtap */ + if (parsed->gsm_type < msgb_l3len(msg) - 3) { + fprintf(stderr, "FAIL: Not enough space for the content\n"); + abort(); + } + + if (bsc_nat_filter_dt(bsc, msg, con, parsed) != 1) { + fprintf(stderr, "FAIL: Should have passed..\n"); + abort(); + } + + /* just some basic length checking... */ + for (i = ARRAY_SIZE(id_resp); i >= 0; --i) { + msgb_reset(msg); + copy_to_msg(msg, id_resp, ARRAY_SIZE(id_resp)); + + parsed = bsc_nat_parse(msg); + if (!parsed) + continue; + + con->imsi_checked = 0; + bsc_nat_filter_dt(bsc, msg, con, parsed); + } +} + +static void test_setup_rewrite() +{ + struct msgb *msg = msgb_alloc(4096, "test_dt_filter"); + struct msgb *out; + struct bsc_nat_parsed *parsed; + const char *imsi = "27408000001234"; + + struct bsc_nat *nat = bsc_nat_alloc(); + + /* a fake list */ + struct msg_entries entries; + struct msg_entry entry; + + INIT_LLIST_HEAD(&entries.entry); + entry.mcc = "274"; + entry.mnc = "08"; + entry.option = "^0([1-9])"; + entry.text = "0049"; + llist_add_tail(&entry.list, &entries.entry); + nat->num_rewr = &entries; + + /* verify that nothing changed */ + msgb_reset(msg); + copy_to_msg(msg, cc_setup_international, ARRAY_SIZE(cc_setup_international)); + parsed = bsc_nat_parse(msg); + if (!parsed) { + fprintf(stderr, "FAIL: Could not parse ID resp\n"); + abort(); + } + + out = bsc_nat_rewrite_setup(nat, msg, parsed, imsi); + if (msg != out) { + fprintf(stderr, "FAIL: The message should not have been changed\n"); + abort(); + } + + if (out->len != ARRAY_SIZE(cc_setup_international)) { + fprintf(stderr, "FAIL: Length of message changed\n"); + abort(); + } + + if (memcmp(out->data, cc_setup_international, out->len) != 0) { + fprintf(stderr, "FAIL: Content modified..\n"); + abort(); + } + talloc_free(parsed); + + /* verify that something in the message changes */ + msgb_reset(msg); + copy_to_msg(msg, cc_setup_national, ARRAY_SIZE(cc_setup_national)); + parsed = bsc_nat_parse(msg); + if (!parsed) { + fprintf(stderr, "FAIL: Could not parse ID resp\n"); + abort(); + } + + out = bsc_nat_rewrite_setup(nat, msg, parsed, imsi); + if (!out) { + fprintf(stderr, "FAIL: A new message should be created.\n"); + abort(); + } + + if (msg == out) { + fprintf(stderr, "FAIL: The message should have changed\n"); + abort(); + } + + if (out->len != ARRAY_SIZE(cc_setup_national_patched)) { + fprintf(stderr, "FAIL: Length is wrong.\n"); + abort(); + } + + if (memcmp(cc_setup_national_patched, out->data, out->len) != 0) { + fprintf(stderr, "FAIL: Data is wrong.\n"); + fprintf(stderr, "Data was: %s\n", hexdump(out->data, out->len)); + abort(); + } + + msgb_free(out); + + /* Make sure that a wildcard is matching */ + entry.mnc = "*"; + msg = msgb_alloc(4096, "test_dt_filter"); + copy_to_msg(msg, cc_setup_national, ARRAY_SIZE(cc_setup_national)); + parsed = bsc_nat_parse(msg); + if (!parsed) { + fprintf(stderr, "FAIL: Could not parse ID resp\n"); + abort(); + } + + out = bsc_nat_rewrite_setup(nat, msg, parsed, imsi); + if (!out) { + fprintf(stderr, "FAIL: A new message should be created.\n"); + abort(); + } + + if (msg == out) { + fprintf(stderr, "FAIL: The message should have changed\n"); + abort(); + } + + if (out->len != ARRAY_SIZE(cc_setup_national_patched)) { + fprintf(stderr, "FAIL: Length is wrong.\n"); + abort(); + } + + if (memcmp(cc_setup_national_patched, out->data, out->len) != 0) { + fprintf(stderr, "FAIL: Data is wrong.\n"); + fprintf(stderr, "Data was: %s\n", hexdump(out->data, out->len)); + abort(); + } + + msgb_free(out); + + /* Make sure that a wildcard is matching */ + entry.mnc = "09"; + msg = msgb_alloc(4096, "test_dt_filter"); + copy_to_msg(msg, cc_setup_national, ARRAY_SIZE(cc_setup_national)); + parsed = bsc_nat_parse(msg); + if (!parsed) { + fprintf(stderr, "FAIL: Could not parse ID resp\n"); + abort(); + } + + out = bsc_nat_rewrite_setup(nat, msg, parsed, imsi); + if (out != msg) { + fprintf(stderr, "FAIL: The message should be unchanged.\n"); + abort(); + } + + if (out->len != ARRAY_SIZE(cc_setup_national)) { + fprintf(stderr, "FAIL: Foo\n"); + abort(); + } + + if (memcmp(out->data, cc_setup_national, ARRAY_SIZE(cc_setup_national)) != 0) { + fprintf(stderr, "FAIL: The message should really be unchanged.\n"); + abort(); + } + + msgb_free(out); +} + +int main(int argc, char **argv) +{ + struct log_target *stderr_target; + + sccp_set_log_area(DSCCP); + log_init(&log_info); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + log_set_all_filter(stderr_target, 1); + + test_filter(); + test_contrack(); + test_paging(); + test_mgcp_ass_tracking(); + test_mgcp_find(); + test_mgcp_rewrite(); + test_mgcp_parse(); + test_cr_filter(); + test_dt_filter(); + test_setup_rewrite(); + test_mgcp_allocations(); + return 0; +} diff --git a/tests/channel/Makefile.am b/tests/channel/Makefile.am new file mode 100644 index 000000000..bf709ff28 --- /dev/null +++ b/tests/channel/Makefile.am @@ -0,0 +1,10 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) + +noinst_PROGRAMS = channel_test + +channel_test_SOURCES = channel_test.c +channel_test_LDADD = -ldl -ldbi $(LIBOSMOCORE_LIBS) \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a diff --git a/tests/channel/Makefile.in b/tests/channel/Makefile.in new file mode 100644 index 000000000..de4b45e42 --- /dev/null +++ b/tests/channel/Makefile.in @@ -0,0 +1,451 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +noinst_PROGRAMS = channel_test$(EXEEXT) +subdir = tests/channel +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +PROGRAMS = $(noinst_PROGRAMS) +am_channel_test_OBJECTS = channel_test.$(OBJEXT) +channel_test_OBJECTS = $(am_channel_test_OBJECTS) +am__DEPENDENCIES_1 = +channel_test_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(channel_test_SOURCES) +DIST_SOURCES = $(channel_test_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) +channel_test_SOURCES = channel_test.c +channel_test_LDADD = -ldl -ldbi $(LIBOSMOCORE_LIBS) \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/channel/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu tests/channel/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS) +channel_test$(EXEEXT): $(channel_test_OBJECTS) $(channel_test_DEPENDENCIES) + @rm -f channel_test$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(channel_test_OBJECTS) $(channel_test_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/channel_test.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-noinstPROGRAMS ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/tests/channel/channel_test.c b/tests/channel/channel_test.c new file mode 100644 index 000000000..4f3c59335 --- /dev/null +++ b/tests/channel/channel_test.c @@ -0,0 +1,84 @@ +/* + * (C) 2009 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include + +#include +#include +#include + +/* our handler */ +static int subscr_cb(unsigned int hook, unsigned int event, struct msgb *msg, void *data, void *param) +{ + assert(hook == 101); + assert(event == 200); + assert(msg == (void*)0x1323L); + assert(data == (void*)0x4242L); + assert(param == (void*)0x2342L); + printf("Reached, didn't crash, test passed\n"); + return 0; +} + +/* mock object for testing, directly invoke the cb... maybe later through the timer */ +void paging_request(struct gsm_bts *bts, struct gsm_subscriber *subscriber, int type, gsm_cbfn *cbfn, void *data) +{ + cbfn(101, 200, (void*)0x1323L, (void*)0x4242L, data); +} + + +int main(int argc, char **argv) +{ + struct gsm_network *network; + struct gsm_bts *bts; + + printf("Testing the gsm_subscriber chan logic\n"); + + /* Create a dummy network */ + network = gsm_network_init(1, 1, NULL); + if (!network) + exit(1); + bts = gsm_bts_alloc(network, GSM_BTS_TYPE_BS11, 0, 0); + bts->location_area_code = 23; + + /* Create a dummy subscriber */ + struct gsm_subscriber *subscr = subscr_alloc(); + subscr->lac = 23; + subscr->net = network; + + /* Ask for a channel... */ + subscr_get_channel(subscr, RSL_CHANNEED_TCH_F, subscr_cb, (void*)0x2342L); + + while (1) { + bsc_select_main(0); + } +} + +void _abis_nm_sendmsg() {} +void sms_alloc() {} +void gsm_net_update_ctype(struct gsm_network *network) {} +void gsm48_secure_channel() {} +void paging_request_stop() {} +void vty_out() {} + + +struct tlv_definition nm_att_tlvdef; + diff --git a/tests/db/Makefile.am b/tests/db/Makefile.am new file mode 100644 index 000000000..a4395aefa --- /dev/null +++ b/tests/db/Makefile.am @@ -0,0 +1,15 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(COVERAGE_LDFLAGS) + +noinst_PROGRAMS = db_test + +db_test_SOURCES = db_test.c +db_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(LIBOSMOCORE_LIBS) -ldl -ldbi + diff --git a/tests/db/Makefile.in b/tests/db/Makefile.in new file mode 100644 index 000000000..5fd1244a6 --- /dev/null +++ b/tests/db/Makefile.in @@ -0,0 +1,458 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +noinst_PROGRAMS = db_test$(EXEEXT) +subdir = tests/db +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +PROGRAMS = $(noinst_PROGRAMS) +am_db_test_OBJECTS = db_test.$(OBJEXT) +db_test_OBJECTS = $(am_db_test_OBJECTS) +am__DEPENDENCIES_1 = +db_test_DEPENDENCIES = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(am__DEPENDENCIES_1) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(db_test_SOURCES) +DIST_SOURCES = $(db_test_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(COVERAGE_LDFLAGS) +db_test_SOURCES = db_test.c +db_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libabis/libabis.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(LIBOSMOCORE_LIBS) -ldl -ldbi + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/db/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu tests/db/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS) +db_test$(EXEEXT): $(db_test_OBJECTS) $(db_test_DEPENDENCIES) + @rm -f db_test$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(db_test_OBJECTS) $(db_test_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db_test.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-noinstPROGRAMS ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/tests/db/db_test.c b/tests/db/db_test.c new file mode 100644 index 000000000..236dc3747 --- /dev/null +++ b/tests/db/db_test.c @@ -0,0 +1,105 @@ +/* (C) 2008 by Jan Luebbe + * (C) 2009 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include +#include +#include + +#define COMPARE(original, copy) \ + if (original->id != copy->id) \ + fprintf(stderr, "Ids do not match in %s:%d %llu %llu\n", \ + __FUNCTION__, __LINE__, original->id, copy->id); \ + if (original->lac != copy->lac) \ + fprintf(stderr, "LAC do not match in %s:%d %d %d\n", \ + __FUNCTION__, __LINE__, original->lac, copy->lac); \ + if (original->authorized != copy->authorized) \ + fprintf(stderr, "Authorize do not match in %s:%d %d %d\n", \ + __FUNCTION__, __LINE__, original->authorized, \ + copy->authorized); \ + if (strcmp(original->imsi, copy->imsi) != 0) \ + fprintf(stderr, "IMSIs do not match in %s:%d '%s' '%s'\n", \ + __FUNCTION__, __LINE__, original->imsi, copy->imsi); \ + if (original->tmsi != copy->tmsi) \ + fprintf(stderr, "TMSIs do not match in %s:%d '%u' '%u'\n", \ + __FUNCTION__, __LINE__, original->tmsi, copy->tmsi); \ + if (strcmp(original->name, copy->name) != 0) \ + fprintf(stderr, "names do not match in %s:%d '%s' '%s'\n", \ + __FUNCTION__, __LINE__, original->name, copy->name); \ + if (strcmp(original->extension, copy->extension) != 0) \ + fprintf(stderr, "names do not match in %s:%d '%s' '%s'\n", \ + __FUNCTION__, __LINE__, original->extension, copy->extension); \ + +int main() { + + if (db_init("hlr.sqlite3")) { + printf("DB: Failed to init database. Please check the option settings.\n"); + return 1; + } + printf("DB: Database initialized.\n"); + + if (db_prepare()) { + printf("DB: Failed to prepare database.\n"); + return 1; + } + printf("DB: Database prepared.\n"); + + struct gsm_subscriber *alice = NULL; + struct gsm_subscriber *alice_db; + + char *alice_imsi = "3243245432345"; + alice = db_create_subscriber(NULL, alice_imsi); + db_sync_subscriber(alice); + alice_db = db_get_subscriber(NULL, GSM_SUBSCRIBER_IMSI, alice->imsi); + COMPARE(alice, alice_db); + subscr_put(alice_db); + subscr_put(alice); + + alice_imsi = "3693245423445"; + alice = db_create_subscriber(NULL, alice_imsi); + db_subscriber_assoc_imei(alice, "1234567890"); + db_subscriber_alloc_tmsi(alice); + alice->lac=42; + db_sync_subscriber(alice); + alice_db = db_get_subscriber(NULL, GSM_SUBSCRIBER_IMSI, alice_imsi); + COMPARE(alice, alice_db); + subscr_put(alice); + subscr_put(alice_db); + + alice_imsi = "9993245423445"; + alice = db_create_subscriber(NULL, alice_imsi); + db_subscriber_alloc_tmsi(alice); + alice->lac=42; + db_sync_subscriber(alice); + db_subscriber_assoc_imei(alice, "1234567890"); + db_subscriber_assoc_imei(alice, "6543560920"); + alice_db = db_get_subscriber(NULL, GSM_SUBSCRIBER_IMSI, alice_imsi); + COMPARE(alice, alice_db); + subscr_put(alice); + subscr_put(alice_db); + + db_fini(); + + return 0; +} + +/* stubs */ +void vty_out() {} diff --git a/tests/debug/Makefile.am b/tests/debug/Makefile.am new file mode 100644 index 000000000..5d6b363fe --- /dev/null +++ b/tests/debug/Makefile.am @@ -0,0 +1,7 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) +noinst_PROGRAMS = debug_test + +debug_test_SOURCES = debug_test.c +debug_test_LDADD = $(LIBOSMOCORE_LIBS) \ + $(top_builddir)/src/libcommon/libcommon.a diff --git a/tests/debug/Makefile.in b/tests/debug/Makefile.in new file mode 100644 index 000000000..c87c169dc --- /dev/null +++ b/tests/debug/Makefile.in @@ -0,0 +1,447 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +noinst_PROGRAMS = debug_test$(EXEEXT) +subdir = tests/debug +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +PROGRAMS = $(noinst_PROGRAMS) +am_debug_test_OBJECTS = debug_test.$(OBJEXT) +debug_test_OBJECTS = $(am_debug_test_OBJECTS) +am__DEPENDENCIES_1 = +debug_test_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(top_builddir)/src/libcommon/libcommon.a +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(debug_test_SOURCES) +DIST_SOURCES = $(debug_test_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) +debug_test_SOURCES = debug_test.c +debug_test_LDADD = $(LIBOSMOCORE_LIBS) \ + $(top_builddir)/src/libcommon/libcommon.a + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/debug/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu tests/debug/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS) +debug_test$(EXEEXT): $(debug_test_OBJECTS) $(debug_test_DEPENDENCIES) + @rm -f debug_test$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(debug_test_OBJECTS) $(debug_test_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/debug_test.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-noinstPROGRAMS ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/tests/debug/debug_test.c b/tests/debug/debug_test.c new file mode 100644 index 000000000..6800021b8 --- /dev/null +++ b/tests/debug/debug_test.c @@ -0,0 +1,42 @@ +/* simple test for the debug interface */ +/* + * (C) 2008, 2009 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + + +int main(int argc, char **argv) +{ + struct log_target *stderr_target; + + log_init(&log_info); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + log_set_all_filter(stderr_target, 1); + + log_parse_category_mask(stderr_target, "DRLL"); + DEBUGP(DCC, "You should not see this\n"); + + log_parse_category_mask(stderr_target, "DRLL:DCC"); + DEBUGP(DRLL, "You should see this\n"); + DEBUGP(DCC, "You should see this\n"); + DEBUGP(DMM, "You should not see this\n"); + + return 0; +} diff --git a/tests/gsm0408/Makefile.am b/tests/gsm0408/Makefile.am new file mode 100644 index 000000000..de6feb272 --- /dev/null +++ b/tests/gsm0408/Makefile.am @@ -0,0 +1,9 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) +noinst_PROGRAMS = gsm0408_test + +gsm0408_test_SOURCES = gsm0408_test.c +gsm0408_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(LIBOSMOCORE_LIBS) -ldbi diff --git a/tests/gsm0408/Makefile.in b/tests/gsm0408/Makefile.in new file mode 100644 index 000000000..dc7d625fb --- /dev/null +++ b/tests/gsm0408/Makefile.in @@ -0,0 +1,450 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +noinst_PROGRAMS = gsm0408_test$(EXEEXT) +subdir = tests/gsm0408 +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +PROGRAMS = $(noinst_PROGRAMS) +am_gsm0408_test_OBJECTS = gsm0408_test.$(OBJEXT) +gsm0408_test_OBJECTS = $(am_gsm0408_test_OBJECTS) +am__DEPENDENCIES_1 = +gsm0408_test_DEPENDENCIES = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libbsc/libbsc.a $(am__DEPENDENCIES_1) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(gsm0408_test_SOURCES) +DIST_SOURCES = $(gsm0408_test_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) +gsm0408_test_SOURCES = gsm0408_test.c +gsm0408_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(LIBOSMOCORE_LIBS) -ldbi + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/gsm0408/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu tests/gsm0408/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS) +gsm0408_test$(EXEEXT): $(gsm0408_test_OBJECTS) $(gsm0408_test_DEPENDENCIES) + @rm -f gsm0408_test$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(gsm0408_test_OBJECTS) $(gsm0408_test_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm0408_test.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-noinstPROGRAMS ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/tests/gsm0408/gsm0408_test.c b/tests/gsm0408/gsm0408_test.c new file mode 100644 index 000000000..e8998c375 --- /dev/null +++ b/tests/gsm0408/gsm0408_test.c @@ -0,0 +1,103 @@ +/* simple test for the gsm0408 formatting functions */ +/* + * (C) 2008 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include + +#include +#include +#include + +#define COMPARE(result, op, value) \ + if (!((result) op (value))) {\ + fprintf(stderr, "Compare failed. Was %x should be %x in %s:%d\n",result, value, __FILE__, __LINE__); \ + exit(-1); \ + } + +#define COMPARE_STR(result, value) \ + if (strcmp(result, value) != 0) { \ + fprintf(stderr, "Compare failed. Was %s should be %s in %s:%d\n",result, value, __FILE__, __LINE__); \ + exit(-1); \ + } + +/* + * Test Location Area Identifier formatting. Table 10.5.3 of 04.08 + */ +static void test_location_area_identifier(void) +{ + struct gsm48_loc_area_id lai48; + + printf("Testing test location area identifier\n"); + + /* + * Test the default/test setup. Coming from + * bsc_hack.c dumps + */ + gsm48_generate_lai(&lai48, 1, 1, 1); + COMPARE(lai48.digits[0], ==, 0x00); + COMPARE(lai48.digits[1], ==, 0xF1); + COMPARE(lai48.digits[2], ==, 0x10); + COMPARE(lai48.lac, ==, htons(0x0001)); + + gsm48_generate_lai(&lai48, 602, 1, 15); + COMPARE(lai48.digits[0], ==, 0x06); + COMPARE(lai48.digits[1], ==, 0xF2); + COMPARE(lai48.digits[2], ==, 0x10); + COMPARE(lai48.lac, ==, htons(0x000f)); +} + +static void test_mi_functionality(void) +{ + const char *imsi_odd = "987654321098763"; + const char *imsi_even = "9876543210987654"; + const u_int32_t tmsi = 0xfabeacd0; + u_int8_t mi[128]; + unsigned int mi_len; + char mi_parsed[GSM48_MI_SIZE]; + + printf("Testing parsing and generating TMSI/IMSI\n"); + + /* tmsi code */ + mi_len = gsm48_generate_mid_from_tmsi(mi, tmsi); + gsm48_mi_to_string(mi_parsed, sizeof(mi_parsed), mi + 2, mi_len - 2); + COMPARE((u_int32_t)strtoul(mi_parsed, NULL, 10), ==, tmsi); + + /* imsi code */ + mi_len = gsm48_generate_mid_from_imsi(mi, imsi_odd); + gsm48_mi_to_string(mi_parsed, sizeof(mi_parsed), mi + 2, mi_len -2); + printf("hex: %s\n", hexdump(mi, mi_len)); + COMPARE_STR(mi_parsed, imsi_odd); + + mi_len = gsm48_generate_mid_from_imsi(mi, imsi_even); + gsm48_mi_to_string(mi_parsed, sizeof(mi_parsed), mi + 2, mi_len -2); + printf("hex: %s\n", hexdump(mi, mi_len)); + COMPARE_STR(mi_parsed, imsi_even); +} + +int main(int argc, char **argv) +{ + test_location_area_identifier(); + test_mi_functionality(); + + exit(0); +} diff --git a/tests/mgcp/Makefile.am b/tests/mgcp/Makefile.am new file mode 100644 index 000000000..472368cb3 --- /dev/null +++ b/tests/mgcp/Makefile.am @@ -0,0 +1,12 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(COVERAGE_LDFLAGS) + +noinst_PROGRAMS = mgcp_test + +mgcp_test_SOURCES = mgcp_test.c + +mgcp_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmgcp/libmgcp.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(LIBOSMOCORE_LIBS) -lrt $(LIBOSMOSCCP_LIBS) $(LIBOSMOVTY_LIBS) diff --git a/tests/mgcp/Makefile.in b/tests/mgcp/Makefile.in new file mode 100644 index 000000000..a8df76645 --- /dev/null +++ b/tests/mgcp/Makefile.in @@ -0,0 +1,453 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +noinst_PROGRAMS = mgcp_test$(EXEEXT) +subdir = tests/mgcp +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/bscconfig.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +PROGRAMS = $(noinst_PROGRAMS) +am_mgcp_test_OBJECTS = mgcp_test.$(OBJEXT) +mgcp_test_OBJECTS = $(am_mgcp_test_OBJECTS) +am__DEPENDENCIES_1 = +mgcp_test_DEPENDENCIES = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmgcp/libmgcp.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(mgcp_test_SOURCES) +DIST_SOURCES = $(mgcp_test_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COVERAGE_CFLAGS = @COVERAGE_CFLAGS@ +COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GPRS_LIBGTP = @GPRS_LIBGTP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@ +LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@ +LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@ +LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@ +LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@ +LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(COVERAGE_LDFLAGS) +mgcp_test_SOURCES = mgcp_test.c +mgcp_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libmgcp/libmgcp.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(LIBOSMOCORE_LIBS) -lrt $(LIBOSMOSCCP_LIBS) $(LIBOSMOVTY_LIBS) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/mgcp/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu tests/mgcp/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS) +mgcp_test$(EXEEXT): $(mgcp_test_OBJECTS) $(mgcp_test_DEPENDENCIES) + @rm -f mgcp_test$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(mgcp_test_OBJECTS) $(mgcp_test_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mgcp_test.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-noinstPROGRAMS ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/tests/mgcp/mgcp_test.c b/tests/mgcp/mgcp_test.c new file mode 100644 index 000000000..4052377ec --- /dev/null +++ b/tests/mgcp/mgcp_test.c @@ -0,0 +1,85 @@ +/* + * (C) 2011 by Holger Hans Peter Freyther + * (C) 2011 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include + +static struct msgb *create_auep1() +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(4096, 128, "MGCP msg"); + int len = sprintf((char *)msg->data, "AUEP 158663169 ds/e1-1/2@172.16.6.66 MGCP 1.0\r\n"); + msg->l2h = msgb_put(msg, len); + return msg; +} + +static struct msgb *create_auep2() +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(4096, 128, "MGCP msg"); + int len = sprintf((char *)msg->data, "AUEP 18983213 ds/e1-2/1@172.16.6.66 MGCP 1.0\r\n"); + msg->l2h = msgb_put(msg, len); + return msg; +} + +static void test_auep(void) +{ + struct msgb *inp; + struct msgb *msg; + struct mgcp_config *cfg = mgcp_config_alloc(); + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + inp = create_auep1(); + msg = mgcp_handle_message(cfg, inp); + msgb_free(inp); + if (strcmp((char *) msg->data, "200 158663169 OK\r\n") != 0) + printf("Result1 failed '%s'\n", (char *) msg->data); + /* Verify that the endpoint is fine */ + msgb_free(msg); + + inp = create_auep2(); + msg = mgcp_handle_message(cfg, inp); + msgb_free(inp); + /* Verify that the endpoint is not fine */ + if (strcmp((char *) msg->data, "500 18983213 FAIL\r\n") != 0) + printf("Result2 failed '%s'\n", (char *) msg->data); + msgb_free(msg); + + talloc_free(cfg); +} + +int main(int argc, char **argv) +{ + struct log_target *stderr_target; + log_init(&log_info); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + log_set_all_filter(stderr_target, 1); + + test_auep(); + return 0; +} -- cgit v1.2.3