aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configure.ac1
-rw-r--r--sql/hlr.sql3
-rw-r--r--src/db.c2
-rw-r--r--src/db.h5
-rw-r--r--src/db_auc.c16
-rw-r--r--src/db_test.c2
-rw-r--r--src/gsup_server.c45
-rw-r--r--src/gsup_server.h2
-rw-r--r--src/hlr.c3
-rw-r--r--tests/Makefile.am1
-rw-r--r--tests/gsup_server/Makefile.am40
-rw-r--r--tests/gsup_server/gsup_server_test.c145
-rw-r--r--tests/gsup_server/gsup_server_test.err0
-rw-r--r--tests/gsup_server/gsup_server_test.ok94
-rw-r--r--tests/testsuite.at7
15 files changed, 354 insertions, 12 deletions
diff --git a/configure.ac b/configure.ac
index a04185f..958b48b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -50,4 +50,5 @@ AC_OUTPUT(
tests/Makefile
tests/auc/Makefile
tests/auc/gen_ts_55_205_test_sets/Makefile
+ tests/gsup_server/Makefile
)
diff --git a/sql/hlr.sql b/sql/hlr.sql
index 9238871..5fbc712 100644
--- a/sql/hlr.sql
+++ b/sql/hlr.sql
@@ -63,7 +63,8 @@ CREATE TABLE auc_3g (
k VARCHAR(32) NOT NULL, -- hex string: subscriber's secret key (128bit)
op VARCHAR(32), -- hex string: operator's secret key (128bit)
opc VARCHAR(32), -- hex string: derived from OP and K (128bit)
- sqn INTEGER NOT NULL DEFAULT 0 -- sequence number of key usage
+ sqn INTEGER NOT NULL DEFAULT 0, -- sequence number of key usage
+ ind_bitlen INTEGER NOT NULL DEFAULT 5 -- nr of index bits at lower SQN end
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_subscr_imsi ON subscriber (imsi);
diff --git a/src/db.c b/src/db.c
index aa4726c..aaf6fe2 100644
--- a/src/db.c
+++ b/src/db.c
@@ -29,7 +29,7 @@ static const char *stmt_sql[] = {
[SEL_BY_IMSI] = "SELECT id,imsi,msisdn,vlr_number,sgsn_number,sgsn_address,periodic_lu_tmr,periodic_rau_tau_tmr,nam_cs,nam_ps,lmsi,ms_purged_cs,ms_purged_ps FROM subscriber WHERE imsi = ?",
[UPD_VLR_BY_ID] = "UPDATE subscriber SET vlr_number = ? WHERE id = ?",
[UPD_SGSN_BY_ID] = "UPDATE subscriber SET sgsn_number = ? WHERE id = ?",
- [AUC_BY_IMSI] = "SELECT id, algo_id_2g, ki, algo_id_3g, k, op, opc, sqn FROM subscriber LEFT JOIN auc_2g ON auc_2g.subscriber_id = subscriber.id LEFT JOIN auc_3g ON auc_3g.subscriber_id = subscriber.id WHERE imsi = ?",
+ [AUC_BY_IMSI] = "SELECT id, algo_id_2g, ki, algo_id_3g, k, op, opc, sqn, ind_bitlen FROM subscriber LEFT JOIN auc_2g ON auc_2g.subscriber_id = subscriber.id LEFT JOIN auc_3g ON auc_3g.subscriber_id = subscriber.id WHERE imsi = ?",
[AUC_UPD_SQN] = "UPDATE auc_3g SET sqn = ? WHERE subscriber_id = ?",
[UPD_PURGE_CS_BY_IMSI] = "UPDATE subscriber SET ms_purged_cs=1 WHERE imsi = ?",
[UPD_PURGE_PS_BY_IMSI] = "UPDATE subscriber SET ms_purged_ps=1 WHERE imsi = ?",
diff --git a/src/db.h b/src/db.h
index 0b3df88..a60cf62 100644
--- a/src/db.h
+++ b/src/db.h
@@ -39,8 +39,9 @@ int db_update_sqn(struct db_context *dbc, uint64_t id,
uint64_t new_sqn);
int db_get_auc(struct db_context *dbc, const char *imsi,
- struct osmo_auth_vector *vec, unsigned int num_vec,
- const uint8_t *rand_auts, const uint8_t *auts);
+ unsigned int auc_3g_ind, struct osmo_auth_vector *vec,
+ unsigned int num_vec, const uint8_t *rand_auts,
+ const uint8_t *auts);
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/protocol/gsm_23_003.h>
diff --git a/src/db_auc.c b/src/db_auc.c
index ac81404..8a369b5 100644
--- a/src/db_auc.c
+++ b/src/db_auc.c
@@ -159,6 +159,7 @@ int db_get_auth_data(struct db_context *dbc, const char *imsi,
aud3g->u.umts.opc_is_op = 1;
}
aud3g->u.umts.sqn = sqlite3_column_int64(stmt, 7);
+ aud3g->u.umts.ind_bitlen = sqlite3_column_int(stmt, 8);
/* FIXME: amf? */
aud3g->type = OSMO_AUTH_TYPE_UMTS;
} else
@@ -186,8 +187,9 @@ out:
/* return -1 in case of error, 0 for unknown imsi, positive for number
* of vectors generated */
int db_get_auc(struct db_context *dbc, const char *imsi,
- struct osmo_auth_vector *vec, unsigned int num_vec,
- const uint8_t *rand_auts, const uint8_t *auts)
+ unsigned int auc_3g_ind, struct osmo_auth_vector *vec,
+ unsigned int num_vec, const uint8_t *rand_auts,
+ const uint8_t *auts)
{
struct osmo_sub_auth_data aud2g, aud3g;
uint64_t subscr_id;
@@ -198,6 +200,16 @@ int db_get_auc(struct db_context *dbc, const char *imsi,
if (rc <= 0)
return rc;
+ aud3g.u.umts.ind = auc_3g_ind;
+ if (aud3g.type == OSMO_AUTH_TYPE_UMTS
+ && aud3g.u.umts.ind >= (1U << aud3g.u.umts.ind_bitlen)) {
+ LOGAUC(imsi, LOGL_NOTICE, "3G auth: SQN's IND bitlen %u is"
+ " too small to hold an index of %u. Truncating. This"
+ " may cause numerous additional AUTS resyncing.\n",
+ aud3g.u.umts.ind_bitlen, aud3g.u.umts.ind);
+ aud3g.u.umts.ind &= (1U << aud3g.u.umts.ind_bitlen) - 1;
+ }
+
LOGAUC(imsi, LOGL_DEBUG, "Calling to generate %u vectors\n", num_vec);
rc = auc_compute_vectors(vec, num_vec, &aud2g, &aud3g, rand_auts, auts);
if (rc < 0) {
diff --git a/src/db_test.c b/src/db_test.c
index 998a37a..0e823f9 100644
--- a/src/db_test.c
+++ b/src/db_test.c
@@ -20,7 +20,7 @@ static int test(const char *imsi, struct db_context *dbc)
for (i = 0; i < ARRAY_SIZE(vec); i++)
vec[i].res_len = 0;
- rc = db_get_auc(dbc, imsi, vec, ARRAY_SIZE(vec), NULL, NULL);
+ rc = db_get_auc(dbc, imsi, 0, vec, ARRAY_SIZE(vec), NULL, NULL);
if (rc <= 0) {
LOGP(DMAIN, LOGL_ERROR, "Cannot obtain auth tuples for '%s'\n", imsi);
return rc;
diff --git a/src/gsup_server.c b/src/gsup_server.c
index b0e1858..b382c86 100644
--- a/src/gsup_server.c
+++ b/src/gsup_server.c
@@ -211,6 +211,43 @@ static int osmo_gsup_server_closed_cb(struct ipa_server_conn *conn)
return 0;
}
+/* Add conn to the clients list in a way that conn->auc_3g_ind takes the lowest
+ * unused integer and the list of clients remains sorted by auc_3g_ind.
+ * Keep this function non-static to allow linking in a unit test. */
+void osmo_gsup_server_add_conn(struct llist_head *clients,
+ struct osmo_gsup_conn *conn)
+{
+ struct osmo_gsup_conn *c;
+ struct osmo_gsup_conn *prev_conn;
+
+ c = llist_first_entry_or_null(clients, struct osmo_gsup_conn, list);
+
+ /* Is the first index, 0, unused? */
+ if (!c || c->auc_3g_ind > 0) {
+ conn->auc_3g_ind = 0;
+ llist_add(&conn->list, clients);
+ return;
+ }
+
+ /* Look for a gap later on */
+ prev_conn = NULL;
+ llist_for_each_entry(c, clients, list) {
+ /* skip first item, we know it has auc_3g_ind == 0. */
+ if (!prev_conn) {
+ prev_conn = c;
+ continue;
+ }
+ if (c->auc_3g_ind > prev_conn->auc_3g_ind + 1)
+ break;
+ prev_conn = c;
+ }
+
+ OSMO_ASSERT(prev_conn);
+
+ conn->auc_3g_ind = prev_conn->auc_3g_ind + 1;
+ llist_add(&conn->list, &prev_conn->list);
+}
+
/* a client has connected to the server socket and we have accept()ed it */
static int osmo_gsup_server_accept_cb(struct ipa_server_link *link, int fd)
{
@@ -225,15 +262,15 @@ static int osmo_gsup_server_accept_cb(struct ipa_server_link *link, int fd)
conn->conn = ipa_server_conn_create(gsups, link, fd,
osmo_gsup_server_read_cb,
osmo_gsup_server_closed_cb, conn);
- conn->conn->ccm_cb = osmo_gsup_server_ccm_cb;
OSMO_ASSERT(conn->conn);
+ conn->conn->ccm_cb = osmo_gsup_server_ccm_cb;
/* link data structure with server structure */
conn->server = gsups;
- llist_add_tail(&conn->list, &gsups->clients);
+ osmo_gsup_server_add_conn(&gsups->clients, conn);
- LOGP(DLGSUP, LOGL_INFO, "New GSUP client %s:%d\n",
- conn->conn->addr, conn->conn->port);
+ LOGP(DLGSUP, LOGL_INFO, "New GSUP client %s:%d (IND=%u)\n",
+ conn->conn->addr, conn->conn->port, conn->auc_3g_ind);
/* request the identity of the client */
rc = ipa_ccm_send_id_req(fd);
diff --git a/src/gsup_server.h b/src/gsup_server.h
index 885fe52..74062d4 100644
--- a/src/gsup_server.h
+++ b/src/gsup_server.h
@@ -31,6 +31,8 @@ struct osmo_gsup_conn {
struct ipa_server_conn *conn;
//struct oap_state oap_state;
struct tlv_parsed ccm;
+
+ unsigned int auc_3g_ind; /*!< IND index used for UMTS AKA SQN */
};
diff --git a/src/hlr.c b/src/hlr.c
index 00a6459..b5777e2 100644
--- a/src/hlr.c
+++ b/src/hlr.c
@@ -63,7 +63,8 @@ static int rx_send_auth_info(struct osmo_gsup_conn *conn,
memset(&gsup_out, 0, sizeof(gsup_out));
memcpy(&gsup_out.imsi, &gsup->imsi, sizeof(gsup_out.imsi));
- rc = db_get_auc(dbc, gsup->imsi, gsup_out.auth_vectors,
+ rc = db_get_auc(dbc, gsup->imsi, conn->auc_3g_ind,
+ gsup_out.auth_vectors,
ARRAY_SIZE(gsup_out.auth_vectors),
gsup->rand, gsup->auts);
if (rc < 0) {
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 21c0e21..0bd0820 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,5 +1,6 @@
SUBDIRS = \
auc \
+ gsup_server \
$(NULL)
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
diff --git a/tests/gsup_server/Makefile.am b/tests/gsup_server/Makefile.am
new file mode 100644
index 0000000..fee60f5
--- /dev/null
+++ b/tests/gsup_server/Makefile.am
@@ -0,0 +1,40 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/src \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ -ggdb3 \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(NULL)
+
+EXTRA_DIST = \
+ gsup_server_test.ok \
+ gsup_server_test.err \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ gsup_server_test \
+ $(NULL)
+
+gsup_server_test_SOURCES = \
+ gsup_server_test.c \
+ $(NULL)
+
+gsup_server_test_LDADD = \
+ $(top_srcdir)/src/gsup_server.c \
+ $(top_srcdir)/src/gsup_router.c \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(NULL)
+
+.PHONY: update_exp
+update_exp:
+ $(builddir)/gsup_server_test >"$(srcdir)/gsup_server_test.ok" 2>"$(srcdir)/gsup_server_test.err"
diff --git a/tests/gsup_server/gsup_server_test.c b/tests/gsup_server/gsup_server_test.c
new file mode 100644
index 0000000..cc475be
--- /dev/null
+++ b/tests/gsup_server/gsup_server_test.c
@@ -0,0 +1,145 @@
+/* (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <osmocom/core/utils.h>
+#include "gsup_server.h"
+
+#define comment_start() printf("\n===== %s\n", __func__)
+#define comment_end() printf("===== %s: SUCCESS\n\n", __func__)
+#define btw(fmt, args...) printf("\n" fmt "\n", ## args)
+
+#define VERBOSE_ASSERT(val, expect_op, fmt) \
+ do { \
+ printf(#val " == " fmt "\n", (val)); \
+ OSMO_ASSERT((val) expect_op); \
+ } while (0)
+
+void osmo_gsup_server_add_conn(struct llist_head *clients,
+ struct osmo_gsup_conn *conn);
+
+static void test_add_conn(void)
+{
+ struct llist_head _list;
+ struct llist_head *clients = &_list;
+ struct osmo_gsup_conn conn_inst[23] = {};
+ struct osmo_gsup_conn *conn;
+ unsigned int i;
+
+ comment_start();
+
+ INIT_LLIST_HEAD(clients);
+
+ btw("Add 10 items");
+ for (i = 0; i < 10; i++) {
+ osmo_gsup_server_add_conn(clients, &conn_inst[i]);
+ printf("conn_inst[%u].auc_3g_ind == %u\n", i, conn_inst[i].auc_3g_ind);
+ OSMO_ASSERT(clients->next == &conn_inst[0].list);
+ }
+
+ btw("Expecting a list of 0..9");
+ i = 0;
+ llist_for_each_entry(conn, clients, list) {
+ printf("conn[%u].auc_3g_ind == %u\n", i, conn->auc_3g_ind);
+ OSMO_ASSERT(conn->auc_3g_ind == i);
+ OSMO_ASSERT(conn == &conn_inst[i]);
+ i++;
+ }
+
+ btw("Punch two holes in the sequence in arbitrary order,"
+ " a larger one from 2..4 and a single one at 7.");
+ llist_del(&conn_inst[4].list);
+ llist_del(&conn_inst[2].list);
+ llist_del(&conn_inst[3].list);
+ llist_del(&conn_inst[7].list);
+
+ btw("Expecting a list of 0,1, 5,6, 8,9");
+ i = 0;
+ llist_for_each_entry(conn, clients, list) {
+ printf("conn[%u].auc_3g_ind == %u\n", i, conn->auc_3g_ind);
+ i++;
+ }
+
+ btw("Add conns, expecting them to take the open slots");
+ osmo_gsup_server_add_conn(clients, &conn_inst[12]);
+ VERBOSE_ASSERT(conn_inst[12].auc_3g_ind, == 2, "%u");
+
+ osmo_gsup_server_add_conn(clients, &conn_inst[13]);
+ VERBOSE_ASSERT(conn_inst[13].auc_3g_ind, == 3, "%u");
+
+ osmo_gsup_server_add_conn(clients, &conn_inst[14]);
+ VERBOSE_ASSERT(conn_inst[14].auc_3g_ind, == 4, "%u");
+
+ osmo_gsup_server_add_conn(clients, &conn_inst[17]);
+ VERBOSE_ASSERT(conn_inst[17].auc_3g_ind, == 7, "%u");
+
+ osmo_gsup_server_add_conn(clients, &conn_inst[18]);
+ VERBOSE_ASSERT(conn_inst[18].auc_3g_ind, == 10, "%u");
+
+ btw("Expecting a list of 0..10");
+ i = 0;
+ llist_for_each_entry(conn, clients, list) {
+ printf("conn[%u].auc_3g_ind == %u\n", i, conn->auc_3g_ind);
+ OSMO_ASSERT(conn->auc_3g_ind == i);
+ i++;
+ }
+
+ btw("Does it also work for the first item?");
+ llist_del(&conn_inst[0].list);
+
+ btw("Expecting a list of 1..10");
+ i = 0;
+ llist_for_each_entry(conn, clients, list) {
+ printf("conn[%u].auc_3g_ind == %u\n", i, conn->auc_3g_ind);
+ OSMO_ASSERT(conn->auc_3g_ind == i + 1);
+ i++;
+ }
+
+ btw("Add another conn, should take auc_3g_ind == 0");
+ osmo_gsup_server_add_conn(clients, &conn_inst[20]);
+ VERBOSE_ASSERT(conn_inst[20].auc_3g_ind, == 0, "%u");
+
+ btw("Expecting a list of 0..10");
+ i = 0;
+ llist_for_each_entry(conn, clients, list) {
+ printf("conn[%u].auc_3g_ind == %u\n", i, conn->auc_3g_ind);
+ OSMO_ASSERT(conn->auc_3g_ind == i);
+ i++;
+ }
+
+ btw("If a client reconnects, it will (likely) get the same auc_3g_ind");
+ VERBOSE_ASSERT(conn_inst[5].auc_3g_ind, == 5, "%u");
+ llist_del(&conn_inst[5].list);
+ conn_inst[5].auc_3g_ind = 423;
+ osmo_gsup_server_add_conn(clients, &conn_inst[5]);
+ VERBOSE_ASSERT(conn_inst[5].auc_3g_ind, == 5, "%u");
+
+ comment_end();
+}
+
+int main(int argc, char **argv)
+{
+ printf("test_gsup_server.c\n");
+
+ test_add_conn();
+
+ printf("Done\n");
+ return 0;
+}
diff --git a/tests/gsup_server/gsup_server_test.err b/tests/gsup_server/gsup_server_test.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/gsup_server/gsup_server_test.err
diff --git a/tests/gsup_server/gsup_server_test.ok b/tests/gsup_server/gsup_server_test.ok
new file mode 100644
index 0000000..80d944c
--- /dev/null
+++ b/tests/gsup_server/gsup_server_test.ok
@@ -0,0 +1,94 @@
+test_gsup_server.c
+
+===== test_add_conn
+
+Add 10 items
+conn_inst[0].auc_3g_ind == 0
+conn_inst[1].auc_3g_ind == 1
+conn_inst[2].auc_3g_ind == 2
+conn_inst[3].auc_3g_ind == 3
+conn_inst[4].auc_3g_ind == 4
+conn_inst[5].auc_3g_ind == 5
+conn_inst[6].auc_3g_ind == 6
+conn_inst[7].auc_3g_ind == 7
+conn_inst[8].auc_3g_ind == 8
+conn_inst[9].auc_3g_ind == 9
+
+Expecting a list of 0..9
+conn[0].auc_3g_ind == 0
+conn[1].auc_3g_ind == 1
+conn[2].auc_3g_ind == 2
+conn[3].auc_3g_ind == 3
+conn[4].auc_3g_ind == 4
+conn[5].auc_3g_ind == 5
+conn[6].auc_3g_ind == 6
+conn[7].auc_3g_ind == 7
+conn[8].auc_3g_ind == 8
+conn[9].auc_3g_ind == 9
+
+Punch two holes in the sequence in arbitrary order, a larger one from 2..4 and a single one at 7.
+
+Expecting a list of 0,1, 5,6, 8,9
+conn[0].auc_3g_ind == 0
+conn[1].auc_3g_ind == 1
+conn[2].auc_3g_ind == 5
+conn[3].auc_3g_ind == 6
+conn[4].auc_3g_ind == 8
+conn[5].auc_3g_ind == 9
+
+Add conns, expecting them to take the open slots
+conn_inst[12].auc_3g_ind == 2
+conn_inst[13].auc_3g_ind == 3
+conn_inst[14].auc_3g_ind == 4
+conn_inst[17].auc_3g_ind == 7
+conn_inst[18].auc_3g_ind == 10
+
+Expecting a list of 0..10
+conn[0].auc_3g_ind == 0
+conn[1].auc_3g_ind == 1
+conn[2].auc_3g_ind == 2
+conn[3].auc_3g_ind == 3
+conn[4].auc_3g_ind == 4
+conn[5].auc_3g_ind == 5
+conn[6].auc_3g_ind == 6
+conn[7].auc_3g_ind == 7
+conn[8].auc_3g_ind == 8
+conn[9].auc_3g_ind == 9
+conn[10].auc_3g_ind == 10
+
+Does it also work for the first item?
+
+Expecting a list of 1..10
+conn[0].auc_3g_ind == 1
+conn[1].auc_3g_ind == 2
+conn[2].auc_3g_ind == 3
+conn[3].auc_3g_ind == 4
+conn[4].auc_3g_ind == 5
+conn[5].auc_3g_ind == 6
+conn[6].auc_3g_ind == 7
+conn[7].auc_3g_ind == 8
+conn[8].auc_3g_ind == 9
+conn[9].auc_3g_ind == 10
+
+Add another conn, should take auc_3g_ind == 0
+conn_inst[20].auc_3g_ind == 0
+
+Expecting a list of 0..10
+conn[0].auc_3g_ind == 0
+conn[1].auc_3g_ind == 1
+conn[2].auc_3g_ind == 2
+conn[3].auc_3g_ind == 3
+conn[4].auc_3g_ind == 4
+conn[5].auc_3g_ind == 5
+conn[6].auc_3g_ind == 6
+conn[7].auc_3g_ind == 7
+conn[8].auc_3g_ind == 8
+conn[9].auc_3g_ind == 9
+conn[10].auc_3g_ind == 10
+
+If a client reconnects, it will (likely) get the same auc_3g_ind
+conn_inst[5].auc_3g_ind == 5
+conn_inst[5].auc_3g_ind == 5
+===== test_add_conn: SUCCESS
+
+Done
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 46d1456..a969082 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -14,3 +14,10 @@ cat $abs_srcdir/auc/auc_ts_55_205_test_sets.ok > expout
cat $abs_srcdir/auc/auc_ts_55_205_test_sets.err > experr
AT_CHECK([$abs_top_builddir/tests/auc/auc_ts_55_205_test_sets], [], [expout], [experr])
AT_CLEANUP
+
+AT_SETUP([gsup_server])
+AT_KEYWORDS([gsup_server])
+cat $abs_srcdir/gsup_server/gsup_server_test.ok > expout
+cat $abs_srcdir/gsup_server/gsup_server_test.err > experr
+AT_CHECK([$abs_top_builddir/tests/gsup_server/gsup_server_test], [], [expout], [experr])
+AT_CLEANUP