[PATCH 3/3] Implement OAP for SGSN registration.

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/OpenBSC@lists.osmocom.org/.

Neels Hofmeyr nhofmeyr at sysmocom.de
Thu Sep 24 11:44:08 UTC 2015


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




More information about the OpenBSC mailing list