Needs a new ipaccess_proto_ext enum value IPAC_PROTO_EXT_OAP from libosmocore,
added in libosmocore.git:5eeb17a0178a72d291cb99f2391d8ea7e9b65dd4.

Implement the Osmocom Authentication Protocol, to allow an SGSN to register
with an IPA peer. The aim is to allow multiple SGSNs talking to a single MAP
proxy.

Have this API separation:
- ipa_client_conn provides a bare connection (unchanged).
- ipa_client provides general IPA connection verification using timeouts and an
  IPA ping/pong.
- gprs_ipa_client muxes an ipa_client to the GSUP and OAP APIs.

While ipa_client_conn and ipa_client above are very general, gprs_ipa_client is
a specific use "with real data".

ipa_client has previously been gprs_gsup_client. Remove GSUP specifics, change
naming and log output to say "IPA" instead. (A previous commit has already
renamed the gprs_gsup_client files to make this commit easier to read.)

Add gprs_ipa_client to soak up the GSUP specifics from ipa_client (basically
just the protocol numbers). Also soak up gprs_subscr_init() and gsup_read_cb()
from gprs_subscriber.c. And, of course, apply the OAP API.

Add gprs_oap_messages.{h,c} and gprs_oap.{h,c} to implement the OAP protocol.
Add a gprs_oap_state field to (new) gprs_gsup_client.

Add a gprs_oap_config field to sgsn_config.
Rename sgsn_config.gsup_server_* to ipa_server_*. Apply this in sgsn_vty.c.

Change from gprs_gsup_client to the new gprs_ipa_client API in
- gprs_subscriber.c
- sgsn_main.c (gprs_subscr_init() has become gprs_ipa_client_init())

Move some static functions to gprs_utils.h to avoid code duplication, I hope
the location is a sufficiently good choice:
- constant_time_cmp() from bsc_nat.c for gprs_oap_evaluate_challenge(), now
  called gprs_constant_time_cmp().
- encode_big_endian() and decode_big_endian() from gprs_gsup_messages.c for
  gprs_oap_decode() and gprs_oap_encode(), now called gprs_encode_big_endian()
  and gps_decode_big_endian().
Apply the function renames in the mentioned .c files.

Add OAP unit tests to sgsn_test.c: test_oap() and test_sgsn_registration().
Update sgsn_test.ok accordingly.

Sponsored-by: On-Waves ehf
---
 openbsc/include/openbsc/Makefile.am         |   8 +-
 openbsc/include/openbsc/gprs_ipa_client.h   |  53 ++++
 openbsc/include/openbsc/gprs_oap.h          |  65 +++++
 openbsc/include/openbsc/gprs_oap_messages.h |  70 ++++++
 openbsc/include/openbsc/gprs_utils.h        |   5 +
 openbsc/include/openbsc/ipa_client.h        |  43 ++--
 openbsc/include/openbsc/sgsn.h              |  13 +-
 openbsc/src/gprs/Makefile.am                |   6 +-
 openbsc/src/gprs/gprs_gsup_messages.c       |  44 +---
 openbsc/src/gprs/gprs_ipa_client.c          | 159 ++++++++++++
 openbsc/src/gprs/gprs_oap.c                 | 207 ++++++++++++++++
 openbsc/src/gprs/gprs_oap_messages.c        | 179 ++++++++++++++
 openbsc/src/gprs/gprs_subscriber.c          |  43 +---
 openbsc/src/gprs/gprs_utils.c               |  39 +++
 openbsc/src/gprs/ipa_client.c               | 233 +++++++++---------
 openbsc/src/gprs/sgsn_main.c                |   5 +-
 openbsc/src/gprs/sgsn_vty.c                 |  49 ++--
 openbsc/src/osmo-bsc_nat/bsc_nat.c          |  16 +-
 openbsc/tests/sgsn/Makefile.am              |   6 +-
 openbsc/tests/sgsn/sgsn_test.c              | 361 ++++++++++++++++++++++++++--
 openbsc/tests/sgsn/sgsn_test.ok             |   5 +
 21 files changed, 1343 insertions(+), 266 deletions(-)
 create mode 100644 openbsc/include/openbsc/gprs_ipa_client.h
 create mode 100644 openbsc/include/openbsc/gprs_oap.h
 create mode 100644 openbsc/include/openbsc/gprs_oap_messages.h
 create mode 100644 openbsc/src/gprs/gprs_ipa_client.c
 create mode 100644 openbsc/src/gprs/gprs_oap.c
 create mode 100644 openbsc/src/gprs/gprs_oap_messages.c

diff --git a/openbsc/include/openbsc/Makefile.am 
b/openbsc/include/openbsc/Makefile.am
index 7bc9d95..492f6e2 100644
--- a/openbsc/include/openbsc/Makefile.am
+++ b/openbsc/include/openbsc/Makefile.am
@@ -15,8 +15,12 @@ noinst_HEADERS = abis_nm.h abis_rsl.h db.h gsm_04_08.h 
gsm_data.h \
                bss.h gsm_data_shared.h ipaccess.h mncc_int.h \
                arfcn_range_encode.h nat_rewrite_trie.h bsc_nat_callstats.h \
                osmux.h mgcp_transcode.h gprs_utils.h \
-                gprs_gb_parse.h smpp.h meas_feed.h gprs_gsup_messages.h \
-                ipa_client.h bsc_msg_filter.h
+                gprs_gb_parse.h smpp.h meas_feed.h \
+                bsc_msg_filter.h \
+                ipa_client.h \
+                gprs_ipa_client.h \
+                gprs_gsup_messages.h gprs_oap_messages.h \
+                gprs_oap.h

 openbsc_HEADERS = gsm_04_08.h meas_rep.h bsc_api.h
 openbscdir = $(includedir)/openbsc
diff --git a/openbsc/include/openbsc/gprs_ipa_client.h 
b/openbsc/include/openbsc/gprs_ipa_client.h
new file mode 100644
index 0000000..068d1a1
--- /dev/null
+++ b/openbsc/include/openbsc/gprs_ipa_client.h
@@ -0,0 +1,53 @@
+/* Specific IPA client for GPRS: Multiplex for GSUP and OAP */
+
+/* (C) 2015 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Jacob Erlbeck, Neels Hofmeyr
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/core/timer.h>
+
+#include <openbsc/ipa_client.h>
+#include <openbsc/gprs_oap.h>
+
+struct sgsn_instance;
+
+int gprs_ipa_client_init(struct sgsn_instance *sgsn_inst);
+
+
+struct gprs_ipa_client {
+       struct ipa_client *ipac;
+
+       // sgsn <-> map proxy registration state
+       struct gprs_oap_state oap;
+
+       // TODO registration timeout?
+};
+
+struct gprs_ipa_client *gprs_ipa_client_create(const char *ip_addr,
+                                              unsigned int tcp_port);
+
+int gprs_ipa_client_send_gsup(struct gprs_ipa_client *gipac, struct msgb *msg);
+int gprs_ipa_client_send_oap(struct gprs_ipa_client *gipac, struct msgb *msg);
+
+void gprs_ipa_client_destroy(struct gprs_ipa_client *gipac);
+
+
diff --git a/openbsc/include/openbsc/gprs_oap.h 
b/openbsc/include/openbsc/gprs_oap.h
new file mode 100644
index 0000000..9972a81
--- /dev/null
+++ b/openbsc/include/openbsc/gprs_oap.h
@@ -0,0 +1,65 @@
+/* Osmocom Authentication Protocol API */
+
+/* (C) 2015 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+struct sgsn_instance;
+struct gprs_ipa_client;
+struct msgb;
+
+/* This is the config part for vty. It is essentially copied in gprs_oap_state,
+ * where values are copied over once the config is considered valid. The shared
+ * secret is converted from hex string to octet buffer, the sgsn_id is simply
+ * copied. Is this separation really necessary? */
+struct gprs_oap_config {
+       uint16_t sgsn_id;
+       const char *shared_secret;
+};
+
+struct gprs_oap_state {
+       enum {
+               oap_uninitialized = 0,  // just allocated.
+               oap_disabled,           // disabled by config.
+               oap_config_error, // <-- TODO really?
+               oap_initialized,        // shared_secret valid.
+               oap_requested_challenge,
+               oap_sent_challenge_result,
+               oap_registered
+       } state;
+       uint16_t sgsn_id;
+       uint8_t shared_secret[16];
+       int challenges_count;
+};
+
+int gprs_oap_init(struct gprs_oap_config *config, struct gprs_oap_state 
*state);
+
+int gprs_oap_evaluate_challenge(struct gprs_oap_state *state,
+                               const uint8_t *rx_random,
+                               const uint8_t *rx_autn,
+                               uint8_t *tx_sres,
+                               uint8_t *tx_kc);
+
+int gprs_oap_register(struct gprs_ipa_client *gipac);
+int gprs_oap_rx(struct gprs_ipa_client *gipac, struct msgb *msg);
+
diff --git a/openbsc/include/openbsc/gprs_oap_messages.h 
b/openbsc/include/openbsc/gprs_oap_messages.h
new file mode 100644
index 0000000..b80e5ed
--- /dev/null
+++ b/openbsc/include/openbsc/gprs_oap_messages.h
@@ -0,0 +1,70 @@
+/* Osmocom Authentication Protocol message encoder/decoder */
+
+/* (C) 2015 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include <stdint.h>
+#include <openbsc/gsm_04_08_gprs.h>
+#include <openbsc/gsm_data.h>
+
+/* Some numbers are out of sequence because (so far) they match gprs_gsup_iei.
+ */
+enum gprs_oap_iei {
+       GPRS_OAP_CAUSE_IE                       = 0x02,
+       GPRS_OAP_RAND_IE                        = 0x20,
+       GPRS_OAP_SRES_IE                        = 0x21,
+       GPRS_OAP_KC_IE                          = 0x22,
+       GPRS_OAP_AUTN_IE                        = 0x23,
+       GPRS_OAP_SGSN_ID_IE                     = 0x30,
+};
+
+enum gprs_oap_message_type {
+       GPRS_OAP_MSGT_REGISTER_REQUEST  = 0b00000100,
+       GPRS_OAP_MSGT_REGISTER_ERROR    = 0b00000101,
+       GPRS_OAP_MSGT_REGISTER_RESULT   = 0b00000110,
+
+       GPRS_OAP_MSGT_CHALLENGE_REQUEST = 0b00001000,
+       GPRS_OAP_MSGT_CHALLENGE_ERROR   = 0b00001001,
+       GPRS_OAP_MSGT_CHALLENGE_RESULT  = 0b00001010,
+};
+
+#define GPRS_OAP_IS_MSGT_REQUEST(msgt) (((msgt) & 0b00000011) == 0b00)
+#define GPRS_OAP_IS_MSGT_ERROR(msgt)   (((msgt) & 0b00000011) == 0b01)
+#define GPRS_OAP_TO_MSGT_ERROR(msgt)   (((msgt) & 0b11111100) | 0b01)
+
+struct gprs_oap_message {
+       enum gprs_oap_message_type      message_type;
+       enum gsm48_gmm_cause            cause;
+       uint16_t                        sgsn_id;
+       int                             rand_present;
+       uint8_t                         rand[16];
+       int                             autn_present;
+       uint8_t                         autn[16];
+       int                             sres_present;
+       uint8_t                         sres[4];
+       int                             kc_present;
+       uint8_t                         kc[8];
+};
+
+int gprs_oap_decode(const uint8_t *data, size_t data_len,
+                    struct gprs_oap_message *oap_msg);
+void gprs_oap_encode(struct msgb *msg, const struct gprs_oap_message *oap_msg);
+
diff --git a/openbsc/include/openbsc/gprs_utils.h 
b/openbsc/include/openbsc/gprs_utils.h
index 6880e05..c67cee2 100644
--- a/openbsc/include/openbsc/gprs_utils.h
+++ b/openbsc/include/openbsc/gprs_utils.h
@@ -52,3 +52,8 @@ int gprs_match_tlv(uint8_t **data, size_t *data_len,
 int gprs_shift_lv(uint8_t **data, size_t *data_len,
                  uint8_t **value, size_t *value_len);

+uint64_t gprs_decode_big_endian(const uint8_t *data, size_t data_len);
+/* Not thread safe: returns pointer to static buffer. */
+uint8_t *gprs_encode_big_endian(uint64_t value, size_t data_len);
+
+int gprs_constant_time_cmp(const uint8_t *exp, const uint8_t *rel, const int 
count);
diff --git a/openbsc/include/openbsc/ipa_client.h 
b/openbsc/include/openbsc/ipa_client.h
index 9537db4..531ee5d 100644
--- a/openbsc/include/openbsc/ipa_client.h
+++ b/openbsc/include/openbsc/ipa_client.h
@@ -1,9 +1,10 @@
-/* GPRS Subscriber Update Protocol client */
+/* General IPA client.
+ * ipa_client is ping/pong connection checking on an ipa_client_conn. */

-/* (C) 2014 by Sysmocom s.f.m.c. GmbH
+/* (C) 2015 by Sysmocom s.f.m.c. GmbH
  * All Rights Reserved
  *
- * Author: Jacob Erlbeck
+ * Authors: Jacob Erlbeck, Neels Hofmeyr
  *
  * 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
@@ -21,34 +22,44 @@
  */
 #pragma once

+#include <stdint.h>
 #include <osmocom/core/timer.h>

-#define GPRS_GSUP_RECONNECT_INTERVAL 10
-#define GPRS_GSUP_PING_INTERVAL 20
+#define IPA_CLIENT_RECONNECT_INTERVAL 10
+#define IPA_CLIENT_PING_INTERVAL 20

 struct msgb;
 struct ipa_client_conn;
-struct gprs_gsup_client;
+struct ipa_client;
+
+typedef void (*ipa_client_updown_cb_t)(struct ipa_client *ipac, int up);

 /* Expects message in msg->l2h */
-typedef int (*gprs_gsup_read_cb_t)(struct gprs_gsup_client *gsupc, struct msgb 
*msg);
+typedef void (*ipa_client_read_cb_t)(struct ipa_client *ipac,
+                                    uint8_t proto,
+                                    uint8_t proto_ext,
+                                    struct msgb *msg);

-struct gprs_gsup_client {
-       struct ipa_client_conn  *link;
-       gprs_gsup_read_cb_t     read_cb;
+struct ipa_client {
+       ipa_client_updown_cb_t  updown_cb;
+       ipa_client_read_cb_t    read_cb;
        void                    *data;

+       struct ipa_client_conn  *link;
+
        struct osmo_timer_list  ping_timer;
        struct osmo_timer_list  connect_timer;
        int                     is_connected;
        int                     got_ipa_pong;
 };

-struct gprs_gsup_client *gprs_gsup_client_create(const char *ip_addr,
-                                                unsigned int tcp_port,
-                                                gprs_gsup_read_cb_t read_cb);
+struct ipa_client *ipa_client_create(const char *ip_addr,
+                                    unsigned int tcp_port,
+                                    ipa_client_updown_cb_t updown_cb,
+                                    ipa_client_read_cb_t read_cb,
+                                    void *data);

-void gprs_gsup_client_destroy(struct gprs_gsup_client *gsupc);
-int gprs_gsup_client_send(struct gprs_gsup_client *gsupc, struct msgb *msg);
-struct msgb *gprs_gsup_msgb_alloc(void);
+void ipa_client_destroy(struct ipa_client *ipac);
+int ipa_client_send(struct ipa_client *ipac, uint8_t proto, uint8_t proto_ext, 
struct msgb *msg);
+struct msgb *ipa_client_msgb_alloc(void);

diff --git a/openbsc/include/openbsc/sgsn.h b/openbsc/include/openbsc/sgsn.h
index d4f9913..cff4e9d 100644
--- a/openbsc/include/openbsc/sgsn.h
+++ b/openbsc/include/openbsc/sgsn.h
@@ -6,10 +6,11 @@

 #include <osmocom/gprs/gprs_ns.h>
 #include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_oap.h>

 #include <ares.h>

-struct gprs_gsup_client;
+struct gprs_ipa_client;
 struct hostent;

 enum sgsn_auth_policy {
@@ -36,8 +37,8 @@ struct sgsn_config {
        enum sgsn_auth_policy auth_policy;
        struct llist_head imsi_acl;

-       struct sockaddr_in gsup_server_addr;
-       int gsup_server_port;
+       struct sockaddr_in ipa_server_addr;
+       int ipa_server_port;

        int require_authentication;
        int require_update_location;
@@ -61,6 +62,8 @@ struct sgsn_config {
        } timers;

        int dynamic_lookup;
+
+       struct gprs_oap_config oap;
 };

 struct sgsn_instance {
@@ -74,8 +77,8 @@ struct sgsn_instance {
        struct osmo_timer_list gtp_timer;
        /* GSN instance for libgtp */
        struct gsn_t *gsn;
-       /* Subscriber */
-       struct gprs_gsup_client *gsup_client;
+       /* Subscriber and SGSN registration*/
+       struct gprs_ipa_client *gprs_ipa_client;
        /* LLME inactivity timer */
        struct osmo_timer_list llme_timer;

diff --git a/openbsc/src/gprs/Makefile.am b/openbsc/src/gprs/Makefile.am
index b9c3070..6614a08 100644
--- a/openbsc/src/gprs/Makefile.am
+++ b/openbsc/src/gprs/Makefile.am
@@ -26,8 +26,10 @@ 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_parse.c gprs_llc_vty.c crc24.c \
                        sgsn_ctrl.c sgsn_auth.c gprs_subscriber.c \
-                       gprs_gsup_messages.c gprs_utils.c ipa_client.c \
-                       gsm_04_08_gprs.c sgsn_cdr.c sgsn_ares.c
+                       gprs_gsup_messages.c gprs_oap_messages.c gprs_oap.c \
+                       ipa_client.c gprs_ipa_client.c \
+                       gprs_utils.c gsm_04_08_gprs.c sgsn_cdr.c sgsn_ares.c
+
 osmo_sgsn_LDADD =      \
                        $(top_builddir)/src/libcommon/libcommon.a \
                        -lgtp $(OSMO_LIBS) $(LIBOSMOABIS_LIBS) $(LIBCARES_LIBS) 
-lrt
diff --git a/openbsc/src/gprs/gprs_gsup_messages.c 
b/openbsc/src/gprs/gprs_gsup_messages.c
index bdcff5f..d3cf058 100644
--- a/openbsc/src/gprs/gprs_gsup_messages.c
+++ b/openbsc/src/gprs/gprs_gsup_messages.c
@@ -33,34 +33,6 @@
 #include <stdint.h>


-static uint64_t decode_big_endian(const uint8_t *data, size_t data_len)
-{
-       uint64_t value = 0;
-
-       while (data_len > 0) {
-               value = (value << 8) + *data;
-               data += 1;
-               data_len -= 1;
-       }
-
-       return value;
-}
-
-static uint8_t *encode_big_endian(uint64_t value, size_t data_len)
-{
-       static uint8_t buf[sizeof(uint64_t)];
-       int idx;
-
-       OSMO_ASSERT(data_len <= ARRAY_SIZE(buf));
-
-       for (idx = data_len - 1; idx >= 0; idx--) {
-               buf[idx] = (uint8_t)value;
-               value = value >> 8;
-       }
-
-       return buf;
-}
-
 static int decode_pdp_info(uint8_t *data, size_t data_len,
                          struct gprs_gsup_pdp_info *pdp_info)
 {
@@ -81,12 +53,12 @@ static int decode_pdp_info(uint8_t *data, size_t data_len,

                switch (iei) {
                case GPRS_GSUP_PDP_CONTEXT_ID_IE:
-                       pdp_info->context_id = decode_big_endian(value, 
value_len);
+                       pdp_info->context_id = gprs_decode_big_endian(value, 
value_len);
                        break;

                case GPRS_GSUP_PDP_TYPE_IE:
                        pdp_info->pdp_type =
-                               decode_big_endian(value, value_len) & 0x0fff;
+                               gprs_decode_big_endian(value, value_len) & 
0x0fff;
                        break;

                case GPRS_GSUP_ACCESS_POINT_NAME_IE:
@@ -187,7 +159,7 @@ int gprs_gsup_decode(const uint8_t *const_data, size_t 
data_len,
        if (rc < 0)
                return -GMM_CAUSE_INV_MAND_INFO;

-       gsup_msg->message_type = decode_big_endian(value, 1);
+       gsup_msg->message_type = gprs_decode_big_endian(value, 1);

        rc = gprs_match_tlv(&data, &data_len, GPRS_GSUP_IMSI_IE,
                            &value, &value_len);
@@ -231,12 +203,12 @@ int gprs_gsup_decode(const uint8_t *const_data, size_t 
data_len,
                        continue;

                case GPRS_GSUP_CAUSE_IE:
-                       gsup_msg->cause = decode_big_endian(value, value_len);
+                       gsup_msg->cause = gprs_decode_big_endian(value, 
value_len);
                        break;

                case GPRS_GSUP_CANCEL_TYPE_IE:
                        gsup_msg->cancel_type =
-                               decode_big_endian(value, value_len) + 1;
+                               gprs_decode_big_endian(value, value_len) + 1;
                        break;

                case GPRS_GSUP_PDP_INFO_COMPL_IE:
@@ -272,7 +244,7 @@ int gprs_gsup_decode(const uint8_t *const_data, size_t 
data_len,
                                pdp_info.have_info = 1;
                        } else {
                                pdp_info.context_id =
-                                       decode_big_endian(value, value_len);
+                                       gprs_decode_big_endian(value, 
value_len);
                        }

                        gsup_msg->pdp_infos[gsup_msg->num_pdp_infos++] =
@@ -334,8 +306,8 @@ static void encode_pdp_info(struct msgb *msg, enum 
gprs_gsup_iei iei,
        if (pdp_info->pdp_type) {
                msgb_tlv_put(msg, GPRS_GSUP_PDP_TYPE_IE,
                             GPRS_GSUP_PDP_TYPE_SIZE,
-                            encode_big_endian(pdp_info->pdp_type | 0xf000,
-                                              GPRS_GSUP_PDP_TYPE_SIZE));
+                            gprs_encode_big_endian(pdp_info->pdp_type | 0xf000,
+                                                   GPRS_GSUP_PDP_TYPE_SIZE));
        }

        if (pdp_info->apn_enc) {
diff --git a/openbsc/src/gprs/gprs_ipa_client.c 
b/openbsc/src/gprs/gprs_ipa_client.c
new file mode 100644
index 0000000..9f8f510
--- /dev/null
+++ b/openbsc/src/gprs/gprs_ipa_client.c
@@ -0,0 +1,159 @@
+/* Specific IPA client for GPRS: Multiplex for GSUP and OAP */
+
+/* (C) 2015 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openbsc/gprs_ipa_client.h>
+#include <openbsc/ipa_client.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_oap_messages.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/abis/ipa.h>
+
+#include <openbsc/debug.h>
+
+#include <errno.h>
+#include <string.h>
+
+
+int gprs_ipa_client_init(struct sgsn_instance *sgi)
+{
+       const char *addr_str;
+
+       if (!sgi->cfg.ipa_server_addr.sin_addr.s_addr)
+               return 0;
+
+       addr_str = inet_ntoa(sgi->cfg.ipa_server_addr.sin_addr);
+
+       sgi->gprs_ipa_client = gprs_ipa_client_create(
+               addr_str, sgi->cfg.ipa_server_port);
+
+       if (!sgi->gprs_ipa_client)
+               return -1;
+
+       return 1;
+}
+
+
+
+static void gprs_ipa_client_updown_cb(struct ipa_client *ipac, int up)
+{
+       struct gprs_ipa_client *gipac = ipac->data;
+
+       if (up && (gipac->oap.sgsn_id != 0)) {
+               if (gprs_oap_register(gipac) < 0) {
+                       /* TODO: fail fatally */
+               }
+       }
+}
+
+static void gprs_ipa_client_read_cb(struct ipa_client *ipac,
+                                  uint8_t proto,
+                                  uint8_t proto_ext,
+                                  struct msgb *msg)
+{
+       //int rc = -2;
+       struct gprs_ipa_client *gipac = ipac->data;
+
+       if (proto != IPAC_PROTO_OSMO)
+             goto invalid;
+
+       switch (proto_ext) {
+       case IPAC_PROTO_EXT_GSUP:
+               /*rc =*/ gprs_subscr_rx_gsup_message(msg);
+               break;
+
+       case IPAC_PROTO_EXT_OAP:
+               /*rc =*/ gprs_oap_rx(gipac, msg);
+               break;
+
+       default:
+               goto invalid;
+       }
+
+       /* TODO: error rc? */
+
+       msgb_free(msg);
+       return;
+
+invalid:
+       LOGP(DGPRS, LOGL_NOTICE,
+            "received an invalid IPA message from %s:%d: proto=%d proto_ext=%d 
size=%d\n",
+            ipac->link->addr, (int)ipac->link->port,
+            (int)proto, (int)proto_ext,
+            msgb_length(msg));
+       msgb_free(msg);
+
+       /* TODO: error rc? */
+}
+
+struct gprs_ipa_client *gprs_ipa_client_create(const char *ip_addr,
+                                              unsigned int tcp_port)
+{
+       struct gprs_ipa_client *gipac;
+
+       gipac = talloc_zero(tall_bsc_ctx, struct gprs_ipa_client);
+       OSMO_ASSERT(gipac);
+
+       gipac->ipac = ipa_client_create(ip_addr,
+                                       tcp_port,
+                                       gprs_ipa_client_updown_cb,
+                                       gprs_ipa_client_read_cb,
+                                       /* data */ NULL);
+
+       OSMO_ASSERT(gipac->ipac);
+
+       if (!gipac->ipac)
+               goto failed;
+
+       return gipac;
+
+failed:
+       gprs_ipa_client_destroy(gipac);
+       return NULL;
+}
+
+void gprs_ipa_client_destroy(struct gprs_ipa_client *gipac)
+{
+       if (!gipac)
+               return;
+
+       if (gipac->ipac)
+               ipa_client_destroy(gipac->ipac);
+       gipac->ipac = NULL;
+}
+
+int gprs_ipa_client_send_gsup(struct gprs_ipa_client *gipac, struct msgb *msg)
+{
+       return ipa_client_send(gipac->ipac, IPAC_PROTO_OSMO, 
IPAC_PROTO_EXT_GSUP, msg);
+}
+
+int gprs_ipa_client_send_oap(struct gprs_ipa_client *gipac, struct msgb *msg)
+{
+       return ipa_client_send(gipac->ipac, IPAC_PROTO_OSMO, 
IPAC_PROTO_EXT_OAP, msg);
+}
+
diff --git a/openbsc/src/gprs/gprs_oap.c b/openbsc/src/gprs/gprs_oap.c
new file mode 100644
index 0000000..0c30aa8
--- /dev/null
+++ b/openbsc/src/gprs/gprs_oap.c
@@ -0,0 +1,207 @@
+#include <osmocom/crypt/auth.h>
+#include <osmocom/abis/ipa.h>
+
+#include <openbsc/gprs_oap.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_utils.h>
+#include <openbsc/gprs_ipa_client.h>
+#include <openbsc/gprs_oap_messages.h>
+
+#include <openbsc/gprs_oap.h>
+
+int gprs_oap_init(struct gprs_oap_config *config, struct gprs_oap_state *state)
+{
+       OSMO_ASSERT(state->state == oap_uninitialized);
+
+       if (config->sgsn_id == 0)
+               goto disable;
+
+       if (!(config->shared_secret) || (strlen(config->shared_secret) == 0))
+               goto disable;
+
+       /* this should probably happen in config parsing place?? */
+       int secret_len = osmo_hexparse(config->shared_secret,
+                                      state->shared_secret,
+                                      sizeof(state->shared_secret));
+       if (secret_len < 0)
+               goto failure;
+
+       if (secret_len < 1)
+               goto disable;
+
+       /* zero pad to fill 16 octets */
+       for (; secret_len < 16; secret_len++) {
+               state->shared_secret[secret_len] = 0;
+       }
+
+       state->sgsn_id = config->sgsn_id;
+       state->state = oap_initialized;
+       return 0;
+
+disable:
+       state->state = oap_disabled;
+       return 0;
+
+failure:
+       state->state = oap_config_error;
+       return -1;
+}
+
+
+int gprs_oap_evaluate_challenge(struct gprs_oap_state *state,
+                               const uint8_t *rx_random,
+                               const uint8_t *rx_autn,
+                               uint8_t *tx_sres,
+                               uint8_t *tx_kc)
+{
+       switch(state->state) {
+       case oap_uninitialized:
+       case oap_disabled:
+       case oap_config_error:
+               return -1;
+       default:
+               break;
+       }
+
+       struct osmo_auth_vector vec;
+
+       struct osmo_sub_auth_data auth = {
+               .type           = OSMO_AUTH_TYPE_GSM,
+               .algo           = OSMO_AUTH_ALG_MILENAGE,
+       };
+
+       OSMO_ASSERT(sizeof(auth.u.umts.opc) == sizeof(state->shared_secret));
+       OSMO_ASSERT(sizeof(auth.u.umts.k) == sizeof(state->shared_secret));
+
+       memcpy(auth.u.umts.opc, state->shared_secret, sizeof(auth.u.umts.opc));
+       memcpy(auth.u.umts.k, state->shared_secret, sizeof(auth.u.umts.k));
+       memcpy(auth.u.umts.k, state->shared_secret, sizeof(auth.u.umts.k));
+       memset(auth.u.umts.amf, 0, 2);
+       auth.u.umts.sqn = 42; // TODO?
+
+       memset(&vec, 0, sizeof(vec));
+       osmo_auth_gen_vec(&vec, &auth, rx_random);
+
+       if (vec.res_len != 8) {
+               LOGP(DGPRS, LOGL_ERROR, "OAP: generated res length is wrong: 
%d\n",
+                    vec.res_len);
+               return -3;
+       }
+
+       if (gprs_constant_time_cmp(vec.autn, rx_autn, sizeof(vec.autn)) != 0) {
+               LOGP(DGPRS, LOGL_ERROR, "OAP: AUTN mismatch!\n");
+               LOGP(DGPRS, LOGL_INFO, "OAP: AUTN from server: %s\n",
+                    osmo_hexdump_nospc(rx_autn, sizeof(vec.autn)));
+               LOGP(DGPRS, LOGL_INFO, "OAP: AUTN expected:    %s\n",
+                    osmo_hexdump_nospc(vec.autn, sizeof(vec.autn)));
+               return -2;
+       }
+
+       memcpy(tx_sres, vec.sres, sizeof(vec.sres));
+       memcpy(tx_kc, vec.kc, sizeof(vec.kc));
+       return 0;
+}
+
+int gprs_oap_register(struct gprs_ipa_client *gipac)
+{
+       struct gprs_oap_state *state = &gipac->oap;
+
+       OSMO_ASSERT(state);
+       OSMO_ASSERT(state->sgsn_id);
+
+       struct msgb *msg = ipa_client_msgb_alloc();
+
+       struct gprs_oap_message oap_msg = {0};
+       oap_msg.message_type = GPRS_OAP_MSGT_REGISTER_REQUEST;
+       oap_msg.sgsn_id = state->sgsn_id;
+
+       gprs_oap_encode(msg, &oap_msg);
+
+       state->state = oap_requested_challenge;
+       return gprs_ipa_client_send_oap(gipac, msg);
+}
+
+int gprs_oap_rx(struct gprs_ipa_client *gipac, struct msgb *msg)
+{
+       struct gprs_oap_state *state = &gipac->oap;
+
+       uint8_t *data = msgb_l2(msg);
+       size_t data_len = msgb_l2len(msg);
+       int rc = 0;
+
+       struct gprs_oap_message oap_msg = {0};
+
+       rc = gprs_oap_decode(data, data_len, &oap_msg);
+       if (rc < 0) {
+               LOGP(DGPRS, LOGL_ERROR,
+                    "Decoding OAP message failed with error '%s' (%d)\n",
+                    get_value_string(gsm48_gmm_cause_names, -rc), -rc);
+               return rc;
+       }
+
+       switch (oap_msg.message_type) {
+       case GPRS_OAP_MSGT_CHALLENGE_REQUEST:
+               // reply with challenge result
+               if (!(oap_msg.rand_present && oap_msg.autn_present)) {
+                       LOGP(DGPRS, LOGL_ERROR,
+                            "OAP challenge incomplete (rand_present: %d, 
autn_present: %d)\n",
+                            oap_msg.rand_present, oap_msg.autn_present);
+                       return -1;
+               }
+
+               {
+                       struct gprs_oap_message oap_reply = {0};
+                       oap_reply.message_type = GPRS_OAP_MSGT_CHALLENGE_RESULT;
+
+                       rc = gprs_oap_evaluate_challenge(state,
+                                                        oap_msg.rand,
+                                                        oap_msg.autn,
+                                                        oap_reply.sres,
+                                                        oap_reply.kc);
+                       if (rc < 0)
+                               return rc;
+
+                       oap_reply.sres_present = 1;
+                       oap_reply.kc_present = 1;
+
+                       struct msgb *oap_reply_msg = ipa_client_msgb_alloc();
+                       OSMO_ASSERT(oap_reply_msg);
+
+                       gprs_oap_encode(oap_reply_msg, &oap_reply);
+
+                       state->state = oap_sent_challenge_result;
+                       state->challenges_count ++;
+                       gprs_ipa_client_send_oap(gipac, oap_reply_msg);
+               }
+
+               break;
+
+       case GPRS_OAP_MSGT_REGISTER_RESULT:
+               // successfully registered!
+               state->state = oap_registered;
+               break;
+
+       case GPRS_OAP_MSGT_REGISTER_ERROR:
+               LOGP(DGPRS, LOGL_ERROR,
+                    "OAP registration failed, from %s:%d\n",
+                    gipac->ipac->link->addr, (int)gipac->ipac->link->port);
+               return -1;
+               break;
+
+       case GPRS_OAP_MSGT_REGISTER_REQUEST:
+       case GPRS_OAP_MSGT_CHALLENGE_RESULT:
+               LOGP(DGPRS, LOGL_ERROR,
+                    "Received invalid OAP message type for OAP client side: 
%d\n",
+                    (int)oap_msg.message_type);
+               return -1;
+
+       default:
+               LOGP(DGPRS, LOGL_ERROR,
+                    "Unknown OAP message type: %d\n",
+                    (int)oap_msg.message_type);
+               return -2;
+       }
+
+       return 0;
+}
diff --git a/openbsc/src/gprs/gprs_oap_messages.c 
b/openbsc/src/gprs/gprs_oap_messages.c
new file mode 100644
index 0000000..e79546e
--- /dev/null
+++ b/openbsc/src/gprs/gprs_oap_messages.c
@@ -0,0 +1,179 @@
+/* GPRS Subscriber Update Protocol message encoder/decoder */
+
+/*
+ * (C) 2014 by Sysmocom s.f.m.c. GmbH
+ * (C) 2015 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * Author: Jacob Erlbeck
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/gprs_oap_messages.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gprs_utils.h>
+
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/msgb.h>
+
+#include <stdint.h>
+
+
+int gprs_oap_decode(const uint8_t *const_data, size_t data_len,
+                   struct gprs_oap_message *oap_msg)
+{
+       int rc;
+       uint8_t tag;
+       /* the shift/match functions expect non-const pointers, but we'll
+        * either copy the data or cast pointers back to const before returning
+        * them
+        */
+       uint8_t *data = (uint8_t *)const_data;
+       uint8_t *value;
+       size_t value_len;
+       static const struct gprs_oap_message empty_oap_message = {0};
+
+       *oap_msg = empty_oap_message;
+
+       /* message type */
+       rc = gprs_shift_v_fixed(&data, &data_len, 1, &value);
+       if (rc < 0)
+               return -GMM_CAUSE_INV_MAND_INFO;
+       oap_msg->message_type = gprs_decode_big_endian(value, 1);
+
+       /* specific parts */
+       while (data_len > 0) {
+               enum gprs_oap_iei iei;
+
+               rc = gprs_shift_tlv(&data, &data_len, &tag, &value, &value_len);
+               if (rc < 0)
+                       return -GMM_CAUSE_PROTO_ERR_UNSPEC;
+
+               iei = tag;
+
+               switch (iei) {
+               case GPRS_OAP_SGSN_ID_IE:
+                       if (value_len != 2) {
+                               LOGP(DGPRS, LOGL_NOTICE,
+                                    "OAP IE type SGSN Id (%d) should be 2 
octets, but has %d\n",
+                                    (int)iei, (int)value_len);
+                               return -GMM_CAUSE_PROTO_ERR_UNSPEC;
+                       }
+
+                       oap_msg->sgsn_id = gprs_decode_big_endian(value, 
value_len);
+
+                       if (oap_msg->sgsn_id == 0) {
+                               LOGP(DGPRS, LOGL_NOTICE,
+                                    "OAP IE type SGSN Id (%d): SGSN Id must be 
nonzero.\n",
+                                    (int)iei);
+                               return -GMM_CAUSE_PROTO_ERR_UNSPEC;
+                       }
+                       break;
+
+               case GPRS_OAP_AUTN_IE:
+                       if (value_len != sizeof(oap_msg->autn)) {
+                               LOGP(DGPRS, LOGL_NOTICE,
+                                    "OAP IE type AUTN (%d) should be %d 
octets, but has %d\n",
+                                    (int)iei, (int)sizeof(oap_msg->autn), 
(int)value_len);
+                               return -GMM_CAUSE_PROTO_ERR_UNSPEC;
+                       }
+                       memcpy(oap_msg->autn, value, value_len);
+                       oap_msg->autn_present = value_len;
+                       break;
+
+               case GPRS_OAP_RAND_IE:
+                       if (value_len != sizeof(oap_msg->rand)) {
+                               LOGP(DGPRS, LOGL_NOTICE,
+                                    "OAP IE type RAND (%d) should be %d 
octets, but has %d\n",
+                                    (int)iei, (int)sizeof(oap_msg->rand), 
(int)value_len);
+                               return -GMM_CAUSE_PROTO_ERR_UNSPEC;
+                       }
+                       memcpy(oap_msg->rand, value, value_len);
+                       oap_msg->rand_present = value_len;
+                       break;
+
+               case GPRS_OAP_SRES_IE:
+                       if (value_len != sizeof(oap_msg->sres)) {
+                               LOGP(DGPRS, LOGL_NOTICE,
+                                    "OAP IE type SRES (%d) should be %d 
octets, but has %d\n",
+                                    (int)iei, (int)sizeof(oap_msg->sres), 
(int)value_len);
+                               return -GMM_CAUSE_PROTO_ERR_UNSPEC;
+                       }
+                       memcpy(oap_msg->sres, value, value_len);
+                       oap_msg->sres_present = value_len;
+                       break;
+
+               case GPRS_OAP_KC_IE:
+                       if (value_len != sizeof(oap_msg->kc)) {
+                               LOGP(DGPRS, LOGL_NOTICE,
+                                    "OAP IE type Kc (%d) should be %d octets, 
but has %d\n",
+                                    (int)iei, (int)sizeof(oap_msg->kc), 
(int)value_len);
+                               return -GMM_CAUSE_PROTO_ERR_UNSPEC;
+                       }
+                       memcpy(oap_msg->kc, value, value_len);
+                       oap_msg->kc_present = value_len;
+                       break;
+
+               case GPRS_OAP_CAUSE_IE:
+                       if (value_len > 1) {
+                               LOGP(DGPRS, LOGL_ERROR,
+                                    "OAP cause may not exceed one octet, is 
%d", (int)value_len);
+                               return -GMM_CAUSE_PROTO_ERR_UNSPEC;
+                       }
+                       oap_msg->cause = *value;
+                       break;
+
+               default:
+                       LOGP(DGPRS, LOGL_NOTICE,
+                            "OAP IE type %d unknown\n", iei);
+                       continue;
+               }
+       }
+
+       return 0;
+}
+
+void gprs_oap_encode(struct msgb *msg, const struct gprs_oap_message *oap_msg)
+{
+       uint8_t u8;
+
+       /* generic part */
+       OSMO_ASSERT(oap_msg->message_type);
+       msgb_v_put(msg, oap_msg->message_type);
+
+       /* specific parts */
+       if ((u8 = oap_msg->cause))
+               msgb_tlv_put(msg, GPRS_OAP_CAUSE_IE, sizeof(u8), &u8);
+
+       if (oap_msg->sgsn_id > 0)
+               msgb_tlv_put(msg, GPRS_OAP_SGSN_ID_IE, sizeof(oap_msg->sgsn_id),
+                            gprs_encode_big_endian(oap_msg->sgsn_id, 
sizeof(oap_msg->sgsn_id)));
+
+       if (oap_msg->autn_present)
+               msgb_tlv_put(msg, GPRS_OAP_AUTN_IE, sizeof(oap_msg->autn), 
oap_msg->autn);
+
+       if (oap_msg->rand_present)
+               msgb_tlv_put(msg, GPRS_OAP_RAND_IE, sizeof(oap_msg->rand), 
oap_msg->rand);
+
+       if (oap_msg->sres_present)
+               msgb_tlv_put(msg, GPRS_OAP_SRES_IE, sizeof(oap_msg->sres), 
oap_msg->sres);
+
+       if (oap_msg->kc_present)
+               msgb_tlv_put(msg, GPRS_OAP_KC_IE, sizeof(oap_msg->kc), 
oap_msg->kc);
+}
+
+
diff --git a/openbsc/src/gprs/gprs_subscriber.c 
b/openbsc/src/gprs/gprs_subscriber.c
index 0a3fe19..3d76d19 100644
--- a/openbsc/src/gprs/gprs_subscriber.c
+++ b/openbsc/src/gprs/gprs_subscriber.c
@@ -21,7 +21,7 @@
  */

 #include <openbsc/gsm_subscriber.h>
-#include <openbsc/ipa_client.h>
+#include <openbsc/gprs_ipa_client.h>

 #include <openbsc/sgsn.h>
 #include <openbsc/gprs_sgsn.h>
@@ -44,45 +44,12 @@

 extern void *tall_bsc_ctx;

-static int gsup_read_cb(struct gprs_gsup_client *gsupc, struct msgb *msg);
-
 /* TODO: Some functions are specific to the SGSN, but this file is more general
  * (it has gprs_* name). Either move these functions elsewhere, split them and
  * move a part, or replace the gprs_ prefix by sgsn_. The applies to
- * gprs_subscr_init, gsup_read_cb, and gprs_subscr_tx_gsup_message.
+ * gprs_subscr_tx_gsup_message.
  */

-int gprs_subscr_init(struct sgsn_instance *sgi)
-{
-       const char *addr_str;
-
-       if (!sgi->cfg.gsup_server_addr.sin_addr.s_addr)
-               return 0;
-
-       addr_str = inet_ntoa(sgi->cfg.gsup_server_addr.sin_addr);
-
-       sgi->gsup_client = gprs_gsup_client_create(
-               addr_str, sgi->cfg.gsup_server_port,
-               &gsup_read_cb);
-
-       if (!sgi->gsup_client)
-               return -1;
-
-       return 1;
-}
-
-static int gsup_read_cb(struct gprs_gsup_client *gsupc, struct msgb *msg)
-{
-       int rc;
-
-       rc = gprs_subscr_rx_gsup_message(msg);
-       msgb_free(msg);
-       if (rc < 0)
-               return -1;
-
-       return rc;
-}
-
 int gprs_subscr_purge(struct gsm_subscriber *subscr);

 static struct sgsn_subscriber_data *sgsn_subscriber_data_alloc(void *ctx)
@@ -159,7 +126,7 @@ void gprs_subscr_cancel(struct gsm_subscriber *subscr)
 static int gprs_subscr_tx_gsup_message(struct gsm_subscriber *subscr,
                                       struct gprs_gsup_message *gsup_msg)
 {
-       struct msgb *msg = gprs_gsup_msgb_alloc();
+       struct msgb *msg = ipa_client_msgb_alloc();

        if (strlen(gsup_msg->imsi) == 0 && subscr)
                strncpy(gsup_msg->imsi, subscr->imsi, sizeof(gsup_msg->imsi) - 
1);
@@ -169,12 +136,12 @@ static int gprs_subscr_tx_gsup_message(struct 
gsm_subscriber *subscr,
        LOGGSUBSCRP(LOGL_INFO, subscr,
                    "Sending GSUP, will send: %s\n", msgb_hexdump(msg));

-       if (!sgsn->gsup_client) {
+       if (!sgsn->gprs_ipa_client) {
                msgb_free(msg);
                return -ENOTSUP;
        }

-       return gprs_gsup_client_send(sgsn->gsup_client, msg);
+       return gprs_ipa_client_send_gsup(sgsn->gprs_ipa_client, msg);
 }

 static int gprs_subscr_tx_gsup_error_reply(struct gsm_subscriber *subscr,
diff --git a/openbsc/src/gprs/gprs_utils.c b/openbsc/src/gprs/gprs_utils.c
index 2293f02..475a740 100644
--- a/openbsc/src/gprs/gprs_utils.c
+++ b/openbsc/src/gprs/gprs_utils.c
@@ -397,3 +397,42 @@ fail:
        return -1;
 }

+uint64_t gprs_decode_big_endian(const uint8_t *data, size_t data_len)
+{
+       uint64_t value = 0;
+
+       while (data_len > 0) {
+               value = (value << 8) + *data;
+               data += 1;
+               data_len -= 1;
+       }
+
+       return value;
+}
+
+uint8_t *gprs_encode_big_endian(uint64_t value, size_t data_len)
+{
+       static uint8_t buf[sizeof(uint64_t)];
+       int idx;
+
+       OSMO_ASSERT(data_len <= ARRAY_SIZE(buf));
+
+       for (idx = data_len - 1; idx >= 0; idx--) {
+               buf[idx] = (uint8_t)value;
+               value = value >> 8;
+       }
+
+       return buf;
+}
+
+/* Wishful thinking to generate a constant time compare */
+int gprs_constant_time_cmp(const uint8_t *exp, const uint8_t *rel, const int 
count)
+{
+       int x = 0, i;
+
+       for (i = 0; i < count; ++i)
+               x |= exp[i] ^ rel[i];
+
+       return x != 0;
+}
+
diff --git a/openbsc/src/gprs/ipa_client.c b/openbsc/src/gprs/ipa_client.c
index bec33c4..9d02b44 100644
--- a/openbsc/src/gprs/ipa_client.c
+++ b/openbsc/src/gprs/ipa_client.c
@@ -1,9 +1,9 @@
-/* GPRS Subscriber Update Protocol client */
+/* Osmocom Authentication Protocol client */

-/* (C) 2014 by Sysmocom s.f.m.c. GmbH
+/* (C) 2015 by Sysmocom s.f.m.c. GmbH
  * All Rights Reserved
  *
- * Author: Jacob Erlbeck
+ * Authors: Jakob Erlbeck, Neels Hofmeyr
  *
  * 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
@@ -33,99 +33,102 @@

 extern void *tall_bsc_ctx;

-static void start_test_procedure(struct gprs_gsup_client *gsupc);
+static void start_test_procedure(struct ipa_client *ipac);

-static void gsup_client_send_ping(struct gprs_gsup_client *gsupc)
+static void ipa_client_send_ping(struct ipa_client *ipac)
 {
-       struct msgb *msg = gprs_gsup_msgb_alloc();
+       struct msgb *msg = ipa_client_msgb_alloc();

        msg->l2h = msgb_put(msg, 1);
        msg->l2h[0] = IPAC_MSGT_PING;
        ipa_msg_push_header(msg, IPAC_PROTO_IPACCESS);
-       ipa_client_conn_send(gsupc->link, msg);
+       ipa_client_conn_send(ipac->link, msg);
 }

-static int gsup_client_connect(struct gprs_gsup_client *gsupc)
+static int ipa_client_connect(struct ipa_client *ipac)
 {
        int rc;

-       if (gsupc->is_connected)
+       if (ipac->is_connected)
                return 0;

-       if (osmo_timer_pending(&gsupc->connect_timer)) {
+       if (osmo_timer_pending(&ipac->connect_timer)) {
                LOGP(DLINP, LOGL_DEBUG,
-                    "GSUP connect: connect timer already running\n");
-               osmo_timer_del(&gsupc->connect_timer);
+                    "IPA connect: connect timer already running\n");
+               osmo_timer_del(&ipac->connect_timer);
        }

-       if (osmo_timer_pending(&gsupc->ping_timer)) {
+       if (osmo_timer_pending(&ipac->ping_timer)) {
                LOGP(DLINP, LOGL_DEBUG,
-                    "GSUP connect: ping timer already running\n");
-               osmo_timer_del(&gsupc->ping_timer);
+                    "IPA connect: ping timer already running\n");
+               osmo_timer_del(&ipac->ping_timer);
        }

-       if (ipa_client_conn_clear_queue(gsupc->link) > 0)
-               LOGP(DLINP, LOGL_DEBUG, "GSUP connect: discarded stored 
messages\n");
+       if (ipa_client_conn_clear_queue(ipac->link) > 0)
+               LOGP(DLINP, LOGL_DEBUG, "IPA connect: discarded stored 
messages\n");

-       rc = ipa_client_conn_open(gsupc->link);
+       rc = ipa_client_conn_open(ipac->link);

        if (rc >= 0) {
-               LOGP(DGPRS, LOGL_INFO, "GSUP connecting to %s:%d\n",
-                    gsupc->link->addr, gsupc->link->port);
+               LOGP(DGPRS, LOGL_INFO, "IPA connecting to %s:%d\n",
+                    ipac->link->addr, ipac->link->port);
                return 0;
        }

-       LOGP(DGPRS, LOGL_INFO, "GSUP failed to connect to %s:%d: %s\n",
-            gsupc->link->addr, gsupc->link->port, strerror(-rc));
+       LOGP(DGPRS, LOGL_INFO, "IPA failed to connect to %s:%d: %s\n",
+            ipac->link->addr, ipac->link->port, strerror(-rc));

        if (rc == -EBADF || rc == -ENOTSOCK || rc == -EAFNOSUPPORT ||
            rc == -EINVAL)
                return rc;

-       osmo_timer_schedule(&gsupc->connect_timer, 
GPRS_GSUP_RECONNECT_INTERVAL, 0);
+       osmo_timer_schedule(&ipac->connect_timer, 
IPA_CLIENT_RECONNECT_INTERVAL, 0);

-       LOGP(DGPRS, LOGL_INFO, "Scheduled timer to retry GSUP connect to 
%s:%d\n",
-            gsupc->link->addr, gsupc->link->port);
+       LOGP(DGPRS, LOGL_INFO, "Scheduled timer to retry IPA connect to 
%s:%d\n",
+            ipac->link->addr, ipac->link->port);

        return 0;
 }

-static void connect_timer_cb(void *gsupc_)
+static void connect_timer_cb(void *ipac_)
 {
-       struct gprs_gsup_client *gsupc = gsupc_;
+       struct ipa_client *ipac = ipac_;

-       if (gsupc->is_connected)
+       if (ipac->is_connected)
                return;

-       gsup_client_connect(gsupc);
+       ipa_client_connect(ipac);
 }

-static void gsup_client_updown_cb(struct ipa_client_conn *link, int up)
+static void ipa_client_updown_cb(struct ipa_client_conn *link, int up)
 {
-       struct gprs_gsup_client *gsupc = link->data;
+       struct ipa_client *ipac = link->data;

-       LOGP(DGPRS, LOGL_INFO, "GSUP link to %s:%d %s\n",
-                    link->addr, link->port, up ? "UP" : "DOWN");
+       LOGP(DGPRS, LOGL_INFO, "IPA link to %s:%d %s\n",
+            link->addr, link->port, up ? "UP" : "DOWN");

-       gsupc->is_connected = up;
+       ipac->is_connected = up;

        if (up) {
-               start_test_procedure(gsupc);
+               start_test_procedure(ipac);

-               osmo_timer_del(&gsupc->connect_timer);
+               osmo_timer_del(&ipac->connect_timer);
        } else {
-               osmo_timer_del(&gsupc->ping_timer);
+               osmo_timer_del(&ipac->ping_timer);

-               osmo_timer_schedule(&gsupc->connect_timer,
-                                   GPRS_GSUP_RECONNECT_INTERVAL, 0);
+               osmo_timer_schedule(&ipac->connect_timer,
+                                   IPA_CLIENT_RECONNECT_INTERVAL, 0);
        }
+
+       if (ipac->updown_cb != NULL)
+             ipac->updown_cb(ipac, up);
 }

-static int gsup_client_read_cb(struct ipa_client_conn *link, struct msgb *msg)
+static int ipa_client_read_cb(struct ipa_client_conn *link, struct msgb *msg)
 {
        struct ipaccess_head *hh = (struct ipaccess_head *) msg->data;
        struct ipaccess_head_ext *he = (struct ipaccess_head_ext *) 
msgb_l2(msg);
-       struct gprs_gsup_client *gsupc = (struct gprs_gsup_client *)link->data;
+       struct ipa_client *ipac = (struct ipa_client *)link->data;
        int rc;
        static struct ipaccess_unit ipa_dev = {
                .unit_name = "SGSN"
@@ -137,10 +140,10 @@ static int gsup_client_read_cb(struct ipa_client_conn 
*link, struct msgb *msg)

        if (rc < 0) {
                LOGP(DGPRS, LOGL_NOTICE,
-                    "GSUP received an invalid IPA/CCM message from %s:%d\n",
+                    "received an invalid IPA/CCM message from %s:%d\n",
                     link->addr, link->port);
                /* Link has been closed */
-               gsupc->is_connected = 0;
+               ipac->is_connected = 0;
                msgb_free(msg);
                return -1;
        }
@@ -149,140 +152,154 @@ static int gsup_client_read_cb(struct ipa_client_conn 
*link, struct msgb *msg)
                uint8_t msg_type = *(msg->l2h);
                /* CCM message */
                if (msg_type == IPAC_MSGT_PONG) {
-                       LOGP(DGPRS, LOGL_DEBUG, "GSUP receiving PONG\n");
-                       gsupc->got_ipa_pong = 1;
+                       LOGP(DGPRS, LOGL_DEBUG, "IPA receiving PONG\n");
+                       ipac->got_ipa_pong = 1;
                }

                msgb_free(msg);
                return 0;
        }

-       if (hh->proto != IPAC_PROTO_OSMO)
-               goto invalid;
-
-       if (!he || msgb_l2len(msg) < sizeof(*he) ||
-           he->proto != IPAC_PROTO_EXT_GSUP)
+       if (!he || msgb_l2len(msg) < sizeof(*he))
                goto invalid;

        msg->l2h = &he->data[0];

-       OSMO_ASSERT(gsupc->read_cb != NULL);
-       gsupc->read_cb(gsupc, msg);
+       OSMO_ASSERT(ipac->read_cb != NULL);
+       ipac->read_cb(ipac, hh->proto, he->proto, msg);

        /* Not freeing msg here, because that must be done by the read_cb. */
        return 0;

 invalid:
        LOGP(DGPRS, LOGL_NOTICE,
-            "GSUP received an invalid IPA message from %s:%d, size = %d\n",
+            "received an invalid IPA message from %s:%d, size = %d\n",
             link->addr, link->port, msgb_length(msg));

        msgb_free(msg);
        return -1;
 }

-static void ping_timer_cb(void *gsupc_)
+static void ping_timer_cb(void *ipac_)
 {
-       struct gprs_gsup_client *gsupc = gsupc_;
+       struct ipa_client *ipac = ipac_;

-       LOGP(DGPRS, LOGL_INFO, "GSUP ping callback (%s, %s PONG)\n",
-            gsupc->is_connected ? "connected" : "not connected",
-            gsupc->got_ipa_pong ? "got" : "didn't get");
+       LOGP(DGPRS, LOGL_INFO, "IPA ping callback (%s, %s PONG)\n",
+            ipac->is_connected ? "connected" : "not connected",
+            ipac->got_ipa_pong ? "got" : "didn't get");

-       if (gsupc->got_ipa_pong) {
-               start_test_procedure(gsupc);
+       if (ipac->got_ipa_pong) {
+               start_test_procedure(ipac);
                return;
        }

-       LOGP(DGPRS, LOGL_NOTICE, "GSUP ping timed out, reconnecting\n");
-       ipa_client_conn_close(gsupc->link);
-       gsupc->is_connected = 0;
+       LOGP(DGPRS, LOGL_NOTICE, "IPA ping timed out, reconnecting\n");
+       ipa_client_conn_close(ipac->link);
+       ipac->is_connected = 0;

-       gsup_client_connect(gsupc);
+       ipa_client_connect(ipac);
 }

-static void start_test_procedure(struct gprs_gsup_client *gsupc)
+static void start_test_procedure(struct ipa_client *ipac)
 {
-       gsupc->ping_timer.data = gsupc;
-       gsupc->ping_timer.cb = &ping_timer_cb;
+       ipac->ping_timer.data = ipac;
+       ipac->ping_timer.cb = &ping_timer_cb;

-       gsupc->got_ipa_pong = 0;
-       osmo_timer_schedule(&gsupc->ping_timer, GPRS_GSUP_PING_INTERVAL, 0);
-       LOGP(DGPRS, LOGL_DEBUG, "GSUP sending PING\n");
-       gsup_client_send_ping(gsupc);
+       ipac->got_ipa_pong = 0;
+       osmo_timer_schedule(&ipac->ping_timer, IPA_CLIENT_PING_INTERVAL, 0);
+       LOGP(DGPRS, LOGL_DEBUG, "IPA sending PING\n");
+       ipa_client_send_ping(ipac);
 }

-struct gprs_gsup_client *gprs_gsup_client_create(const char *ip_addr,
-                                                unsigned int tcp_port,
-                                                gprs_gsup_read_cb_t read_cb)
+struct ipa_client *ipa_client_create(const char *ip_addr,
+                                    unsigned int tcp_port,
+                                    ipa_client_updown_cb_t updown_cb,
+                                    ipa_client_read_cb_t read_cb,
+                                    void *data)
 {
-       struct gprs_gsup_client *gsupc;
+       struct ipa_client *ipac;
        int rc;

-       gsupc = talloc_zero(tall_bsc_ctx, struct gprs_gsup_client);
-       OSMO_ASSERT(gsupc);
-
-       gsupc->link = ipa_client_conn_create(gsupc,
-                                            /* no e1inp */ NULL,
-                                            0,
-                                            ip_addr, tcp_port,
-                                            gsup_client_updown_cb,
-                                            gsup_client_read_cb,
-                                            /* default write_cb */ NULL,
-                                            gsupc);
-       if (!gsupc->link)
+       ipac = talloc_zero(tall_bsc_ctx, struct ipa_client);
+       OSMO_ASSERT(ipac);
+
+       ipac->updown_cb = updown_cb;
+       ipac->read_cb = read_cb;
+       ipac->data = data;
+
+       ipac->link = ipa_client_conn_create(ipac,
+                                           /* no e1inp */ NULL,
+                                           0,
+                                           ip_addr, tcp_port,
+                                           ipa_client_updown_cb,
+                                           ipa_client_read_cb,
+                                           /* default write_cb */ NULL,
+                                           ipac);
+       if (!ipac->link)
                goto failed;

-       gsupc->connect_timer.data = gsupc;
-       gsupc->connect_timer.cb = &connect_timer_cb;
+       ipac->connect_timer.data = ipac;
+       ipac->connect_timer.cb = &connect_timer_cb;

-       rc = gsup_client_connect(gsupc);
+       rc = ipa_client_connect(ipac);

        if (rc < 0)
                goto failed;

-       gsupc->read_cb = read_cb;
+       ipac->read_cb = read_cb;

-       return gsupc;
+       return ipac;

 failed:
-       gprs_gsup_client_destroy(gsupc);
+       ipa_client_destroy(ipac);
        return NULL;
 }

-void gprs_gsup_client_destroy(struct gprs_gsup_client *gsupc)
+void ipa_client_destroy(struct ipa_client *ipac)
 {
-       osmo_timer_del(&gsupc->connect_timer);
-       osmo_timer_del(&gsupc->ping_timer);
+       osmo_timer_del(&ipac->connect_timer);
+       osmo_timer_del(&ipac->ping_timer);

-       if (gsupc->link) {
-               ipa_client_conn_close(gsupc->link);
-               ipa_client_conn_destroy(gsupc->link);
-               gsupc->link = NULL;
+       if (ipac->link) {
+               ipa_client_conn_close(ipac->link);
+               ipa_client_conn_destroy(ipac->link);
+               ipac->link = NULL;
        }
-       talloc_free(gsupc);
+       talloc_free(ipac);
 }

-int gprs_gsup_client_send(struct gprs_gsup_client *gsupc, struct msgb *msg)
+int ipa_client_send(struct ipa_client *ipac, uint8_t proto, uint8_t proto_ext,
+                   struct msgb *msg)
 {
-       if (!gsupc) {
+       OSMO_ASSERT(msg);
+
+       if (!ipac) {
                msgb_free(msg);
                return -ENOTCONN;
        }

-       if (!gsupc->is_connected) {
+       if (!ipac->is_connected) {
                msgb_free(msg);
                return -EAGAIN;
        }

-       ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_GSUP);
-       ipa_msg_push_header(msg, IPAC_PROTO_OSMO);
-       ipa_client_conn_send(gsupc->link, msg);
+       // l2h is not sent over the wire, but for the test suite it makes sense
+       // to make l2h point at the IPA message payload.
+       unsigned char *l2h = msg->data;
+
+       ipa_prepend_header_ext(msg, proto);
+       ipa_msg_push_header(msg, proto_ext);
+
+       msg->l2h = l2h;
+
+       ipa_client_conn_send(ipac->link, msg);

        return 0;
 }

-struct msgb *gprs_gsup_msgb_alloc(void)
+struct msgb *ipa_client_msgb_alloc(void)
 {
        return msgb_alloc_headroom(4000, 64, __func__);
 }
+
+
diff --git a/openbsc/src/gprs/sgsn_main.c b/openbsc/src/gprs/sgsn_main.c
index 8cb7499..882b6a3 100644
--- a/openbsc/src/gprs/sgsn_main.c
+++ b/openbsc/src/gprs/sgsn_main.c
@@ -51,6 +51,7 @@
 #include <openbsc/sgsn.h>
 #include <openbsc/gprs_llc.h>
 #include <openbsc/gprs_gmm.h>
+#include <openbsc/gprs_ipa_client.h>
 #include <osmocom/ctrl/control_if.h>
 #include <osmocom/ctrl/ports.h>

@@ -359,9 +360,9 @@ int main(int argc, char **argv)
                exit(2);
        }

-       rc = gprs_subscr_init(&sgsn_inst);
+       rc = gprs_ipa_client_init(&sgsn_inst);
        if (rc < 0) {
-               LOGP(DGPRS, LOGL_FATAL, "Cannot set up subscriber 
management\n");
+               LOGP(DGPRS, LOGL_FATAL, "Cannot establish IPA connection\n");
                exit(2);
        }

diff --git a/openbsc/src/gprs/sgsn_vty.c b/openbsc/src/gprs/sgsn_vty.c
index 00a930f..75974c7 100644
--- a/openbsc/src/gprs/sgsn_vty.c
+++ b/openbsc/src/gprs/sgsn_vty.c
@@ -34,13 +34,14 @@
 #include <openbsc/gprs_sgsn.h>
 #include <openbsc/vty.h>
 #include <openbsc/gsm_04_08_gprs.h>
-#include <openbsc/ipa_client.h>

 #include <osmocom/vty/command.h>
 #include <osmocom/vty/vty.h>
 #include <osmocom/vty/misc.h>

 #include <osmocom/abis/ipa.h>
+#include <openbsc/ipa_client.h>
+#include <openbsc/gprs_ipa_client.h>

 #include <pdp.h>

@@ -208,12 +209,12 @@ static int config_write_sgsn(struct vty *vty)
        vty_out(vty, " auth-policy %s%s",
                get_value_string(sgsn_auth_pol_strs, g_cfg->auth_policy),
                VTY_NEWLINE);
-       if (g_cfg->gsup_server_addr.sin_addr.s_addr)
-               vty_out(vty, " gsup remote-ip %s%s",
-                       inet_ntoa(g_cfg->gsup_server_addr.sin_addr), 
VTY_NEWLINE);
-       if (g_cfg->gsup_server_port)
-               vty_out(vty, " gsup remote-port %d%s",
-                       g_cfg->gsup_server_port, VTY_NEWLINE);
+       if (g_cfg->ipa_server_addr.sin_addr.s_addr)
+               vty_out(vty, " ipa remote-ip %s%s",
+                       inet_ntoa(g_cfg->ipa_server_addr.sin_addr), 
VTY_NEWLINE);
+       if (g_cfg->ipa_server_port)
+               vty_out(vty, " ipa remote-port %d%s",
+                       g_cfg->ipa_server_port, VTY_NEWLINE);
        llist_for_each_entry(acl, &g_cfg->imsi_acl, list)
                vty_out(vty, " imsi-acl add %s%s", acl->imsi, VTY_NEWLINE);

@@ -434,12 +435,12 @@ static void vty_dump_mmctx(struct vty *vty, const char 
*pfx,
 DEFUN(show_sgsn, show_sgsn_cmd, "show sgsn",
       SHOW_STR "Display information about the SGSN")
 {
-       if (sgsn->gsup_client) {
-               struct ipa_client_conn *link = sgsn->gsup_client->link;
+       if (sgsn->gprs_ipa_client) {
+               struct ipa_client *ipac = sgsn->gprs_ipa_client->ipac;
                vty_out(vty,
-                       "  Remote authorization: %sconnected to %s:%d via 
GSUP%s",
-                       sgsn->gsup_client->is_connected ? "" : "not ",
-                       link->addr, link->port,
+                       "  Remote authorization: %sconnected to %s:%d via 
IPA%s",
+                       ipac->is_connected ? "" : "not ",
+                       ipac->link->addr, ipac->link->port,
                        VTY_NEWLINE);
        }
        /* FIXME: statistics */
@@ -873,24 +874,24 @@ DEFUN(update_subscr_update_auth_info, 
update_subscr_update_auth_info_cmd,
        return CMD_SUCCESS;
 }

-DEFUN(cfg_gsup_remote_ip, cfg_gsup_remote_ip_cmd,
-       "gsup remote-ip A.B.C.D",
-       "GSUP Parameters\n"
-       "Set the IP address of the remote GSUP server\n"
+DEFUN(cfg_ipa_remote_ip, cfg_ipa_remote_ip_cmd,
+       "ipa remote-ip A.B.C.D",
+       "IPA Parameters\n"
+       "Set the IP address of the remote IPA (GSUP+OAP) server\n"
        "IPv4 Address\n")
 {
-       inet_aton(argv[0], &g_cfg->gsup_server_addr.sin_addr);
+       inet_aton(argv[0], &g_cfg->ipa_server_addr.sin_addr);

        return CMD_SUCCESS;
 }

-DEFUN(cfg_gsup_remote_port, cfg_gsup_remote_port_cmd,
-       "gsup remote-port <0-65535>",
-       "GSUP Parameters\n"
-       "Set the TCP port of the remote GSUP server\n"
+DEFUN(cfg_ipa_remote_port, cfg_ipa_remote_port_cmd,
+       "ipa remote-port <0-65535>",
+       "IPA Parameters\n"
+       "Set the TCP port of the remote IPA (GSUP+OAP) server\n"
        "Remote TCP port\n")
 {
-       g_cfg->gsup_server_port = atoi(argv[0]);
+       g_cfg->ipa_server_port = atoi(argv[0]);

        return CMD_SUCCESS;
 }
@@ -967,8 +968,8 @@ int sgsn_vty_init(void)
        install_element(SGSN_NODE, &cfg_ggsn_gtp_version_cmd);
        install_element(SGSN_NODE, &cfg_imsi_acl_cmd);
        install_element(SGSN_NODE, &cfg_auth_policy_cmd);
-       install_element(SGSN_NODE, &cfg_gsup_remote_ip_cmd);
-       install_element(SGSN_NODE, &cfg_gsup_remote_port_cmd);
+       install_element(SGSN_NODE, &cfg_ipa_remote_ip_cmd);
+       install_element(SGSN_NODE, &cfg_ipa_remote_port_cmd);
        install_element(SGSN_NODE, &cfg_apn_ggsn_cmd);
        install_element(SGSN_NODE, &cfg_apn_imsi_ggsn_cmd);
        install_element(SGSN_NODE, &cfg_apn_name_cmd);
diff --git a/openbsc/src/osmo-bsc_nat/bsc_nat.c 
b/openbsc/src/osmo-bsc_nat/bsc_nat.c
index 1fc262d..42d7c30 100644
--- a/openbsc/src/osmo-bsc_nat/bsc_nat.c
+++ b/openbsc/src/osmo-bsc_nat/bsc_nat.c
@@ -47,6 +47,7 @@
 #include <openbsc/abis_nm.h>
 #include <openbsc/socket.h>
 #include <openbsc/vty.h>
+#include <openbsc/gprs_utils.h>

 #include <osmocom/ctrl/control_cmd.h>
 #include <osmocom/ctrl/control_if.h>
@@ -987,17 +988,6 @@ static void ipaccess_close_bsc(void *data)
        bsc_close_connection(conn);
 }

-/* Wishful thinking to generate a constant time compare */
-static int constant_time_cmp(const uint8_t *exp, const uint8_t *rel, const int 
count)
-{
-       int x = 0, i;
-
-       for (i = 0; i < count; ++i)
-               x |= exp[i] ^ rel[i];
-
-       return x != 0;
-}
-
 static int verify_key(struct bsc_connection *conn, struct bsc_config *conf, 
const uint8_t *key, const int keylen)
 {
        struct osmo_auth_vector vec;
@@ -1024,11 +1014,11 @@ static int verify_key(struct bsc_connection *conn, 
struct bsc_config *conf, cons

        if (vec.res_len != 8) {
                LOGP(DNAT, LOGL_ERROR, "Res length is wrong: %d for bsc nr 
%d\n",
-                       keylen, conf->nr);
+                       (int)vec.res_len, conf->nr);
                return 0;
        }

-       return constant_time_cmp(vec.res, key, 8) == 0;
+       return gprs_constant_time_cmp(vec.res, key, 8) == 0;
 }

 static void ipaccess_auth_bsc(struct tlv_parsed *tvp, struct bsc_connection 
*bsc)
diff --git a/openbsc/tests/sgsn/Makefile.am b/openbsc/tests/sgsn/Makefile.am
index ea29fce..3b45b3f 100644
--- a/openbsc/tests/sgsn/Makefile.am
+++ b/openbsc/tests/sgsn/Makefile.am
@@ -10,7 +10,8 @@ sgsn_test_LDFLAGS = \
        -Wl,--wrap=sgsn_update_subscriber_data \
        -Wl,--wrap=gprs_subscr_request_update_location \
        -Wl,--wrap=gprs_subscr_request_auth_info \
-       -Wl,--wrap=gprs_gsup_client_send
+       -Wl,--wrap=gprs_ipa_client_send_gsup \
+       -Wl,--wrap=ipa_client_conn_send

 sgsn_test_LDADD = \
        $(top_builddir)/src/gprs/gprs_llc_parse.o \
@@ -24,7 +25,10 @@ sgsn_test_LDADD = \
        $(top_builddir)/src/gprs/sgsn_auth.o \
        $(top_builddir)/src/gprs/sgsn_ares.o \
        $(top_builddir)/src/gprs/gprs_gsup_messages.o \
+       $(top_builddir)/src/gprs/gprs_oap_messages.o \
+       $(top_builddir)/src/gprs/gprs_oap.o \
        $(top_builddir)/src/gprs/ipa_client.o \
+       $(top_builddir)/src/gprs/gprs_ipa_client.o \
        $(top_builddir)/src/gprs/gprs_utils.o \
        $(top_builddir)/src/gprs/gprs_subscriber.o \
        $(top_builddir)/src/gprs/gsm_04_08_gprs.o \
diff --git a/openbsc/tests/sgsn/sgsn_test.c b/openbsc/tests/sgsn/sgsn_test.c
index 251772e..84791d5 100644
--- a/openbsc/tests/sgsn/sgsn_test.c
+++ b/openbsc/tests/sgsn/sgsn_test.c
@@ -24,10 +24,16 @@
 #include <openbsc/gprs_gmm.h>
 #include <openbsc/debug.h>
 #include <openbsc/gsm_subscriber.h>
-#include <openbsc/gprs_gsup_messages.h>
-#include <openbsc/ipa_client.h>
 #include <openbsc/gprs_utils.h>

+#include <osmocom/abis/ipa.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <openbsc/ipa_client.h>
+#include <openbsc/gprs_ipa_client.h>
+#include <openbsc/gprs_gsup_messages.h>
+#include <openbsc/gprs_oap_messages.h>
+#include <openbsc/gprs_oap.h>
+
 #include <osmocom/gprs/gprs_bssgp.h>

 #include <osmocom/gsm/gsm_utils.h>
@@ -89,14 +95,24 @@ int __wrap_gprs_subscr_request_auth_info(struct sgsn_mm_ctx 
*mmctx) {
        return (*subscr_request_auth_info_cb)(mmctx);
 };

-/* override, requires '-Wl,--wrap=gprs_gsup_client_send' */
-int __real_gprs_gsup_client_send(struct gprs_gsup_client *gsupc, struct msgb 
*msg);
-int (*gprs_gsup_client_send_cb)(struct gprs_gsup_client *gsupc, struct msgb 
*msg) =
-       &__real_gprs_gsup_client_send;
+/* override, requires '-Wl,--wrap=gprs_ipa_client_send_gsup' */
+int __real_gprs_ipa_client_send_gsup(struct gprs_ipa_client *gipac, struct 
msgb *msg);
+int (*gprs_ipa_client_send_gsup_cb)(struct gprs_ipa_client *gipac, struct msgb 
*msg) =
+       &__real_gprs_ipa_client_send_gsup;

-int __wrap_gprs_gsup_client_send(struct gprs_gsup_client *gsupc, struct msgb 
*msg)
+int __wrap_gprs_ipa_client_send_gsup(struct gprs_ipa_client *gipac, struct 
msgb *msg)
 {
-       return (*gprs_gsup_client_send_cb)(gsupc, msg);
+       return (*gprs_ipa_client_send_gsup_cb)(gipac, msg);
+};
+
+/* override, requires '-Wl,--wrap=ipa_client_conn_send' */
+void __real_ipa_client_conn_send(struct ipa_client_conn *link, struct msgb 
*msg);
+void (*ipa_client_conn_send_cb)(struct ipa_client_conn *link, struct msgb 
*msg) =
+       &__real_ipa_client_conn_send;
+
+void __wrap_ipa_client_conn_send(struct ipa_client_conn *link, struct msgb 
*msg)
+{
+       return (*ipa_client_conn_send_cb)(link, msg);
 };

 static int count(struct llist_head *head)
@@ -651,7 +667,7 @@ static void test_subscriber_gsup(void)
        update_subscriber_data_cb = __real_sgsn_update_subscriber_data;
 }

-int my_gprs_gsup_client_send_dummy(struct gprs_gsup_client *gsupc, struct msgb 
*msg)
+int my_gprs_ipa_client_send_gsup_dummy(struct gprs_ipa_client *gipac, struct 
msgb *msg)
 {
        msgb_free(msg);
        return 0;
@@ -1201,7 +1217,7 @@ static void test_gmm_attach_subscr_gsup_auth(int retry)
        auth_info_skip = 0;
 }

-int my_gprs_gsup_client_send(struct gprs_gsup_client *gsupc, struct msgb *msg)
+int my_gprs_ipa_client_send_gsup(struct gprs_ipa_client *gipac, struct msgb 
*msg)
 {
        struct gprs_gsup_message to_peer = {0};
        struct gprs_gsup_message from_peer = {0};
@@ -1243,7 +1259,7 @@ int my_gprs_gsup_client_send(struct gprs_gsup_client 
*gsupc, struct msgb *msg)
                return 0;
        }

-       reply_msg = gprs_gsup_msgb_alloc();
+       reply_msg = ipa_client_msgb_alloc();
        reply_msg->l2h = reply_msg->data;
        gprs_gsup_encode(reply_msg, &from_peer);
        gprs_subscr_rx_gsup_message(reply_msg);
@@ -1258,9 +1274,14 @@ static void test_gmm_attach_subscr_real_gsup_auth(int 
retry)
        struct gsm_subscriber *subscr;

        sgsn_inst.cfg.auth_policy = SGSN_AUTH_POLICY_REMOTE;
-       gprs_gsup_client_send_cb = my_gprs_gsup_client_send;
+       gprs_ipa_client_send_gsup_cb = my_gprs_ipa_client_send_gsup;

-       sgsn->gsup_client = talloc_zero(tall_bsc_ctx, struct gprs_gsup_client);
+       /* The sgsn->gprs_ipa_client will not have been initialized, because
+          cfg.ipa_server_addr is unset. Allocate empty structs to use them in
+          these tests. */
+       OSMO_ASSERT(sgsn->gprs_ipa_client == NULL);
+       sgsn->gprs_ipa_client = talloc_zero(tall_bsc_ctx, struct 
gprs_ipa_client);
+       sgsn->gprs_ipa_client->ipac = talloc_zero(tall_bsc_ctx, struct 
ipa_client);

        if (retry) {
                upd_loc_skip = 3;
@@ -1275,11 +1296,13 @@ static void test_gmm_attach_subscr_real_gsup_auth(int 
retry)
        assert_no_subscrs();

        sgsn->cfg.auth_policy = saved_auth_policy;
-       gprs_gsup_client_send_cb = __real_gprs_gsup_client_send;
+       gprs_ipa_client_send_gsup_cb = __real_gprs_ipa_client_send_gsup;
        upd_loc_skip = 0;
        auth_info_skip = 0;
-       talloc_free(sgsn->gsup_client);
-       sgsn->gsup_client = NULL;
+
+       talloc_free(sgsn->gprs_ipa_client->ipac);
+       talloc_free(sgsn->gprs_ipa_client);
+       sgsn->gprs_ipa_client = NULL;
 }

 /*
@@ -1842,7 +1865,7 @@ static void test_ggsn_selection(void)

        printf("Testing GGSN selection\n");

-       gprs_gsup_client_send_cb = my_gprs_gsup_client_send_dummy;
+       gprs_ipa_client_send_gsup_cb = my_gprs_ipa_client_send_gsup_dummy;

        /* Check for emptiness */
        OSMO_ASSERT(gprs_subscr_get_by_imsi(imsi1) == NULL);
@@ -1961,9 +1984,308 @@ static void test_ggsn_selection(void)
        sgsn_ggsn_ctx_free(ggcs[1]);
        sgsn_ggsn_ctx_free(ggcs[2]);

-       gprs_gsup_client_send_cb = __real_gprs_gsup_client_send;
+       gprs_ipa_client_send_gsup_cb = __real_gprs_ipa_client_send_gsup;
+}
+
+static void test_oap(void)
+{
+       printf("Testing OAP API\n  - Config parsing\n");
+
+       // No ipa_server_addr set, so initialization should do nothing.
+       OSMO_ASSERT(gprs_ipa_client_init(sgsn) <= 0);
+       OSMO_ASSERT(sgsn->gprs_ipa_client == NULL);
+
+       struct gprs_oap_config *config = &(sgsn->cfg.oap);
+
+       struct gprs_oap_state _state = {0};
+       struct gprs_oap_state *state = &_state;
+
+       // verify uninitialized state at program start
+       OSMO_ASSERT(state->state == oap_uninitialized);
+
+       // make sure filling with zeros means uninitialized, too
+       memset(state, 0, sizeof(*state));
+       OSMO_ASSERT(state->state == oap_uninitialized);
+
+
+       // invalid sgsn_id and shared secret
+       config->sgsn_id = 0;
+       config->shared_secret = NULL;
+       OSMO_ASSERT( gprs_oap_init(config, state) == 0 );
+       OSMO_ASSERT(state->state == oap_disabled);
+
+       // reset state
+       memset(state, 0, sizeof(*state));
+
+       // only sgsn_id is invalid
+       config->sgsn_id = 0;
+       config->shared_secret = "0102030405060708090a0b0c0d0e0f10";
+       OSMO_ASSERT( gprs_oap_init(config, state) == 0 );
+       OSMO_ASSERT(state->state == oap_disabled);
+
+       memset(state, 0, sizeof(*state));
+
+       // omitted shared_secret
+       config->sgsn_id = 12345;
+       config->shared_secret = NULL;
+       OSMO_ASSERT( gprs_oap_init(config, state) == 0 );
+       OSMO_ASSERT(state->state == oap_disabled);
+
+       memset(state, 0, sizeof(*state));
+
+       // invalid hex chars in shared_secret config
+       config->sgsn_id = 12345;
+       config->shared_secret = "non-hex";
+       OSMO_ASSERT( gprs_oap_init(config, state) < 0 );
+       OSMO_ASSERT(state->state == oap_config_error);
+
+       memset(state, 0, sizeof(*state));
+
+       // shared secret too long (defined to be 16 octets)
+       config->sgsn_id = 12345;
+       config->shared_secret = 
"0102030405060708090a0b0c0d0e0f101112131415161718";
+       OSMO_ASSERT( gprs_oap_init(config, state) < 0 );
+       OSMO_ASSERT(state->state == oap_config_error);
+
+       memset(state, 0, sizeof(*state));
+
+       // odd number of hex chars
+       config->sgsn_id = 12345;
+       config->shared_secret = "01020304050607081";
+       OSMO_ASSERT( gprs_oap_init(config, state) < 0 );
+       OSMO_ASSERT(state->state == oap_config_error);
+
+       memset(state, 0, sizeof(*state));
+
+       // zero padding of shared secret (defined to be 16 octets)
+       config->sgsn_id = 12345;
+       config->shared_secret = "0102030405060708";
+       OSMO_ASSERT( gprs_oap_init(config, state) == 0 );
+       OSMO_ASSERT(state->state == oap_initialized);
+       OSMO_ASSERT(strcmp("01020304050607080000000000000000",
+                          osmo_hexdump_nospc(state->shared_secret, 16)) == 0);
+
+       memset(state, 0, sizeof(*state));
+
+
+
+       // mint configuration
+       config->sgsn_id = 12345;
+       config->shared_secret = "0102030405060708090a0b0c0d0e0f10";
+       OSMO_ASSERT( gprs_oap_init(config, state) == 0 );
+       OSMO_ASSERT(state->state == oap_initialized);
+       OSMO_ASSERT(strcmp(config->shared_secret,
+                          osmo_hexdump_nospc(state->shared_secret, 16)) == 0);
+
+       printf("  - AUTN failure\n");
+       uint8_t rx_random[16];
+       uint8_t rx_autn[16];
+
+       uint8_t tx_sres[4];
+       uint8_t tx_kc[8];
+
+       osmo_hexparse("0102030405060708090a0b0c0d0e0f10",
+                     rx_random, 16);
+
+       // wrong autn (by one bit)
+       osmo_hexparse("247d1e1c7fc1000008cc536e8788b027",
+                     rx_autn, 16);
+       OSMO_ASSERT(gprs_oap_evaluate_challenge(state, rx_random, rx_autn,
+                                               tx_sres, tx_kc)
+                   == -2);
+
+       printf("  - AUTN success\n");
+       // all correct
+       osmo_hexparse("347d1e1c7fc1000008cc536e8788b027",
+                     rx_autn, 16);
+       // a successful return value here indicates correct rx_autn
+       OSMO_ASSERT(gprs_oap_evaluate_challenge(state, rx_random, rx_autn,
+                                               tx_sres, tx_kc)
+                   == 0);
+       OSMO_ASSERT(strcmp("ce9da581", osmo_hexdump_nospc(tx_sres, 
sizeof(tx_sres))) == 0);
+       OSMO_ASSERT(strcmp("0a8356d779b197dd", osmo_hexdump_nospc(tx_kc, 
sizeof(tx_kc))) == 0);
+
+
+       // refuse to evaluate in uninitialized state
+       state->state = oap_uninitialized;
+       OSMO_ASSERT(gprs_oap_evaluate_challenge(state, rx_random, rx_autn,
+                                               tx_sres, tx_kc)
+                   == -1);
+       state->state = oap_disabled;
+       OSMO_ASSERT(gprs_oap_evaluate_challenge(state, rx_random, rx_autn,
+                                               tx_sres, tx_kc)
+                   == -1);
+       state->state = oap_config_error;
+       OSMO_ASSERT(gprs_oap_evaluate_challenge(state, rx_random, rx_autn,
+                                               tx_sres, tx_kc)
+                   == -1);
 }

+static int inject_rx_oap_message(const struct gprs_oap_message *oapm)
+{
+       fprintf(stderr, "inject_rx_oap_message: Injecting OAP Reply: %d\n", 
(int)oapm->message_type);
+
+       struct gprs_ipa_client *gipac = sgsn->gprs_ipa_client;
+       OSMO_ASSERT(gipac);
+
+       struct msgb *msg;
+       int rc;
+
+       msg = ipa_client_msgb_alloc();
+       OSMO_ASSERT(msg != NULL);
+
+       gprs_oap_encode(msg, oapm);
+
+       unsigned char *l2h = msg->data;
+
+       ipa_prepend_header_ext(msg, IPAC_PROTO_OSMO);
+       ipa_msg_push_header(msg, IPAC_PROTO_EXT_OAP);
+
+       msg->l2h = l2h;
+       OSMO_ASSERT(msg->l2h);
+
+       rc = gprs_oap_rx(gipac, msg);
+
+       msgb_free(msg);
+
+       return rc;
+}
+
+
+void my_ipa_client_conn_send(struct ipa_client_conn *link, struct msgb *msg)
+{
+       // Simulate the remote side, which replies to registration etc:
+       // decode the msgb that the sgsn sends, and feed the matching reply to
+       // gprs_oap_rx(), as msgb.
+
+       uint8_t *data = msgb_l2(msg);
+       size_t data_len = msgb_l2len(msg);
+       int rc = 0;
+
+       struct gprs_oap_message oap_msg = {0};
+
+       OSMO_ASSERT(data);
+       rc = gprs_oap_decode(data, data_len, &oap_msg);
+
+       if (rc < 0) {
+               printf("my_ipa_client_conn_send: decoding OAP message fails 
with error '%s' (%d)\n",
+                      get_value_string(gsm48_gmm_cause_names, -rc), -rc);
+               goto dealloc;
+       }
+
+       fprintf(stderr, "my_ipa_client_conn_send: Caught outgoing IPA message: 
%d\n", (int)oap_msg.message_type);
+
+       switch (oap_msg.message_type) {
+
+       case GPRS_OAP_MSGT_REGISTER_REQUEST:
+               // reply with challenge
+               {
+                       OSMO_ASSERT(oap_msg.sgsn_id == 12345);
+                       OSMO_ASSERT(!oap_msg.rand_present);
+                       OSMO_ASSERT(!oap_msg.autn_present);
+                       OSMO_ASSERT(!oap_msg.sres_present);
+                       OSMO_ASSERT(!oap_msg.kc_present);
+
+                       struct gprs_oap_message oap_reply = {0};
+                       oap_reply.message_type = 
GPRS_OAP_MSGT_CHALLENGE_REQUEST;
+
+                       osmo_hexparse("0102030405060708090a0b0c0d0e0f10",
+                                     oap_reply.rand, 16);
+                       oap_reply.rand_present = 1;
+
+                       osmo_hexparse("347d1e1c7fc1000008cc536e8788b027",
+                                     oap_reply.autn, 16);
+                       oap_reply.autn_present = 1;
+
+                       OSMO_ASSERT(inject_rx_oap_message(&oap_reply) >= 0);
+               }
+
+               break;
+
+       case GPRS_OAP_MSGT_CHALLENGE_RESULT:
+               // verify challenge reply, reply with registration ack
+               {
+                       OSMO_ASSERT(!oap_msg.sgsn_id); // not present
+                       OSMO_ASSERT(!oap_msg.rand_present);
+                       OSMO_ASSERT(!oap_msg.autn_present);
+                       OSMO_ASSERT(oap_msg.sres_present);
+                       OSMO_ASSERT(oap_msg.kc_present);
+
+                       OSMO_ASSERT(strcmp("ce9da581", 
osmo_hexdump_nospc(oap_msg.sres, sizeof(oap_msg.sres))) == 0);
+                       OSMO_ASSERT(strcmp("0a8356d779b197dd", 
osmo_hexdump_nospc(oap_msg.kc, sizeof(oap_msg.kc))) == 0);
+
+                       struct gprs_oap_message oap_reply = {0};
+                       oap_reply.message_type = GPRS_OAP_MSGT_REGISTER_RESULT;
+
+                       OSMO_ASSERT(inject_rx_oap_message(&oap_reply) >= 0);
+               }
+               break;
+
+       case GPRS_OAP_MSGT_REGISTER_ERROR:
+       case GPRS_OAP_MSGT_REGISTER_RESULT:
+       case GPRS_OAP_MSGT_CHALLENGE_REQUEST:
+       case GPRS_OAP_MSGT_CHALLENGE_ERROR:
+               printf("Not handled in this test: OAP message type %d\n", 
(int)oap_msg.message_type);
+               rc = -1;
+               goto dealloc;
+
+       default:
+               printf("Unknown OAP message type: %d\n", 
(int)oap_msg.message_type);
+               rc = -2;
+               goto dealloc;
+       }
+
+dealloc:
+       msgb_free(msg);
+}
+
+static void test_sgsn_registration(void)
+{
+       printf("Testing SGSN registration\n");
+
+       /* The sgsn->gprs_ipa_client will not have been initialized, because
+          cfg.ipa_server_addr is unset. Allocate empty structs to use them in
+          these tests. */
+       OSMO_ASSERT(sgsn->gprs_ipa_client == NULL);
+       sgsn->gprs_ipa_client = talloc_zero(tall_bsc_ctx, struct 
gprs_ipa_client);
+       sgsn->gprs_ipa_client->ipac = talloc_zero(tall_bsc_ctx, struct 
ipa_client);
+       OSMO_ASSERT(sgsn->gprs_ipa_client && sgsn->gprs_ipa_client->ipac);
+
+       // simulate working connection.
+       ipa_client_conn_send_cb = my_ipa_client_conn_send;
+       sgsn->gprs_ipa_client->ipac->is_connected = 1;
+
+       struct gprs_ipa_client *gipac = sgsn->gprs_ipa_client;
+
+       struct gprs_oap_config *config = &sgsn->cfg.oap;
+       struct gprs_oap_state *state = &gipac->oap;
+
+       // make sure of clean slate
+       memset(state, 0, sizeof(*state));
+
+       config->sgsn_id = 12345;
+       config->shared_secret = "0102030405060708090a0b0c0d0e0f10";
+       OSMO_ASSERT(gprs_oap_init(config, state) == 0);
+
+       OSMO_ASSERT(gprs_oap_register(gipac) == 0);
+
+       // my_ipa_client_conn_send (wrapped) will have caught this registration
+       // request and sent the test reply to gprs_oap_rx, which has in turn
+       // sent the next response using my_ipa_client_conn_send, and again
+       // gprs_oap_rx has evaluated the final result, all of this before above
+       // gprs_oap_register() has exited. So just evaluate the results.
+
+       OSMO_ASSERT(state->state == oap_registered);
+       OSMO_ASSERT(state->challenges_count == 1);
+
+       ipa_client_conn_send_cb = __real_ipa_client_conn_send;
+
+       talloc_free(sgsn->gprs_ipa_client->ipac);
+       talloc_free(sgsn->gprs_ipa_client);
+       sgsn->gprs_ipa_client = NULL;
+}
+
+
 static struct log_info_cat gprs_categories[] = {
        [DMM] = {
                .name = "DMM",
@@ -2029,7 +2351,6 @@ int main(int argc, char **argv)
        tall_msgb_ctx = talloc_named_const(osmo_sgsn_ctx, 0, "msgb");

        sgsn_auth_init();
-       gprs_subscr_init(sgsn);

        test_llme();
        test_subscriber();
@@ -2052,6 +2373,8 @@ int main(int argc, char **argv)
        test_gmm_ptmsi_allocation();
        test_apn_matching();
        test_ggsn_selection();
+       test_oap();
+       test_sgsn_registration();
        printf("Done\n");

        talloc_report_full(osmo_sgsn_ctx, stderr);
diff --git a/openbsc/tests/sgsn/sgsn_test.ok b/openbsc/tests/sgsn/sgsn_test.ok
index 7913a39..0eea1dd 100644
--- a/openbsc/tests/sgsn/sgsn_test.ok
+++ b/openbsc/tests/sgsn/sgsn_test.ok
@@ -27,4 +27,9 @@ Testing P-TMSI allocation
   - Repeated RA Update Request
 Testing APN matching
 Testing GGSN selection
+Testing OAP API
+  - Config parsing
+  - AUTN failure
+  - AUTN success
+Testing SGSN registration
 Done
-- 
2.1.4

Reply via email to