gtphub: fix Echo behavior: respond directly.

Up to now I used the Echo as a test for sequence nr mappings. But Echos
should be handled differently: they are scoped on the link and an Echo
response should be sent right back to the requester.

Sponsored-by: On-Waves ehi
diff --git a/openbsc/include/openbsc/gtphub.h b/openbsc/include/openbsc/gtphub.h
index 05eee85..514b1a3 100644
--- a/openbsc/include/openbsc/gtphub.h
+++ b/openbsc/include/openbsc/gtphub.h
@@ -396,6 +396,8 @@
 	struct osmo_timer_list gc_timer;
 	struct expiry expire_seq_maps;
 	struct expiry expire_tei_maps;
+
+	uint16_t restart_counter;
 };
 
 struct gtp_packet_desc;
@@ -425,6 +427,7 @@
 				 uint8_t *buf,
 				 size_t received,
 				 time_t now,
+				 uint8_t **reply_buf,
 				 struct osmo_fd **to_ofd,
 				 struct osmo_sockaddr *to_addr);
 
@@ -434,6 +437,7 @@
 				 uint8_t *buf,
 				 size_t received,
 				 time_t now,
+				 uint8_t **reply_buf,
 				 struct osmo_fd **to_ofd,
 				 struct osmo_sockaddr *to_addr);
 
diff --git a/openbsc/src/gprs/gtphub.c b/openbsc/src/gprs/gtphub.c
index eae4cf2..389a0ca 100644
--- a/openbsc/src/gprs/gtphub.c
+++ b/openbsc/src/gprs/gtphub.c
@@ -1222,6 +1222,7 @@
 	struct osmo_sockaddr to_addr;
 	struct osmo_fd *to_ofd;
 	size_t len;
+	uint8_t *reply_buf;
 
 	len = gtphub_read(from_ggsns_ofd, &from_addr, buf, sizeof(buf));
 	if (len < 1)
@@ -1229,11 +1230,11 @@
 
 	len = gtphub_from_ggsns_handle_buf(hub, plane_idx, &from_addr, buf, len,
 					   gtphub_now(),
-					   &to_ofd, &to_addr);
+					   &reply_buf, &to_ofd, &to_addr);
 	if (len < 1)
 		return 0;
 
-	return gtphub_write(to_ofd, &to_addr, buf, len);
+	return gtphub_write(to_ofd, &to_addr, reply_buf, len);
 }
 
 static int gtphub_unmap(struct gtphub *hub,
@@ -1311,10 +1312,33 @@
 	return osmo_sockaddr_init_udp(dst, gsn_addr_to_str(src), port);
 }
 
-static int gtphub_handle_echo(const struct gtp_packet_desc *p)
+/* If p is an Echo request, replace p's data with the matching response and
+ * return 1. If p is no Echo request, return 0, or -1 if an invalid packet is
+ * detected. */
+static int gtphub_handle_echo(struct gtphub *hub, struct gtp_packet_desc *p, uint8_t **reply_buf)
 {
-	/* TODO */
-	return 0;
+	if (p->type != GTP_ECHO_REQ)
+		return 0;
+
+	static uint8_t echo_response_data[14] = {
+		0x32,	/* flags */
+		GTP_ECHO_RSP,
+		0x00, 14 - 8, /* Length in network byte order */
+		0x00, 0x00, 0x00, 0x00,	/* Zero TEI */
+		0, 0,	/* Seq, to be replaced */
+		0, 0,	/* no extensions */
+		0x0e,	/* Recovery IE */
+		0	/* Recovery counter, to be replaced */
+	};
+	uint16_t *seq = (uint16_t*)&echo_response_data[8];
+	uint8_t *recovery = &echo_response_data[13];
+
+	*seq = hton16(p->seq);
+	*recovery = hub->restart_counter;
+
+	*reply_buf = echo_response_data;
+
+	return sizeof(echo_response_data);
 }
 
 /* Parse buffer as GTP packet, replace elements in-place and return the ofd and
@@ -1328,29 +1352,30 @@
 				 uint8_t *buf,
 				 size_t received,
 				 time_t now,
+				 uint8_t **reply_buf,
 				 struct osmo_fd **to_ofd,
 				 struct osmo_sockaddr *to_addr)
 {
 	LOG("<- rx from GGSN %s\n", osmo_sockaddr_to_str(from_addr));
 
-	*to_ofd = &hub->to_sgsns[plane_idx].ofd;
-
 	static struct gtp_packet_desc p;
 	gtp_decode(buf, received, plane_idx, &p);
 
 	if (p.rc <= 0)
 		return -1;
 
-	int rc;
-	rc = gtphub_handle_echo(&p);
-	if (rc == 1) {
-		/* It was en echo. Nothing left to do. */
-		/* (*to_ofd already set above.) */
+	int reply_len;
+	reply_len = gtphub_handle_echo(hub, &p, reply_buf);
+	if (reply_len > 0) {
+		/* It was an echo. Nothing left to do. */
 		osmo_sockaddr_copy(to_addr, from_addr);
-		return 0;
+		*to_ofd = &hub->to_ggsns[plane_idx].ofd;
+		return reply_len;
 	}
-	if (rc < 0)
-		return -1; /* Invalid packet. */
+	if (reply_len < 0)
+		return -1;
+
+	*to_ofd = &hub->to_sgsns[plane_idx].ofd;
 
 	/* If a GGSN proxy is configured, check that it's indeed that proxy
 	 * talking to us. A proxy is a forced 1:1 connection, e.g. to another
@@ -1418,6 +1443,8 @@
 		gtphub_map_seq(&p, ggsn, sgsn, now);
 
 	osmo_sockaddr_copy(to_addr, &sgsn->sa);
+
+	*reply_buf = (uint8_t*)p.data;
 	return received;
 }
 
@@ -1437,6 +1464,7 @@
 	struct osmo_sockaddr to_addr;
 	struct osmo_fd *to_ofd;
 	size_t len;
+	uint8_t *reply_buf;
 
 	len = gtphub_read(from_sgsns_ofd, &from_addr, buf, sizeof(buf));
 	if (len < 1)
@@ -1444,11 +1472,11 @@
 
 	len = gtphub_from_sgsns_handle_buf(hub, plane_idx, &from_addr, buf, len,
 					   gtphub_now(),
-					   &to_ofd, &to_addr);
+					   &reply_buf, &to_ofd, &to_addr);
 	if (len < 1)
 		return 0;
 
-	return gtphub_write(to_ofd, &to_addr, buf, len);
+	return gtphub_write(to_ofd, &to_addr, reply_buf, len);
 }
 
 /* Analogous to gtphub_from_ggsns_handle_buf(), see the comment there. */
@@ -1458,29 +1486,30 @@
 				 uint8_t *buf,
 				 size_t received,
 				 time_t now,
+				 uint8_t **reply_buf,
 				 struct osmo_fd **to_ofd,
 				 struct osmo_sockaddr *to_addr)
 {
 	LOG("-> rx from SGSN %s\n", osmo_sockaddr_to_str(from_addr));
 
-	*to_ofd = &hub->to_ggsns[plane_idx].ofd;
-
 	static struct gtp_packet_desc p;
 	gtp_decode(buf, received, plane_idx, &p);
 
 	if (p.rc <= 0)
 		return -1;
 
-	int rc;
-	rc = gtphub_handle_echo(&p);
-	if (rc == 1) {
-		/* It was en echo. Nothing left to do. */
-		/* (*to_ofd already set above.) */
+	int reply_len;
+	reply_len = gtphub_handle_echo(hub, &p, reply_buf);
+	if (reply_len > 0) {
+		/* It was an echo. Nothing left to do. */
 		osmo_sockaddr_copy(to_addr, from_addr);
-		return 0;
+		*to_ofd = &hub->to_ggsns[plane_idx].ofd;
+		return reply_len;
 	}
-	if (rc < 0)
-		return -1; /* Invalid packet. */
+	if (reply_len < 0)
+		return -1;
+
+	*to_ofd = &hub->to_ggsns[plane_idx].ofd;
 
 	/* If an SGSN proxy is configured, check that it's indeed that proxy
 	 * talking to us. A proxy is a forced 1:1 connection, e.g. to another
@@ -1583,6 +1612,7 @@
 
 	osmo_sockaddr_copy(to_addr, &ggsn->sa);
 
+	*reply_buf = (uint8_t*)p.data;
 	return received;
 }
 
@@ -1764,6 +1794,8 @@
 	gtphub_init(hub);
 	gtphub_ares_init(hub);
 
+	/* TODO set hub->restart_counter from external file. */
+
 	int plane_idx;
 	for (plane_idx = 0; plane_idx < GTPH_PLANE_N; plane_idx++) {
 		rc = gtphub_bind_start(&hub->to_ggsns[plane_idx],
diff --git a/openbsc/tests/gtphub/gtphub_test.c b/openbsc/tests/gtphub/gtphub_test.c
index 81875f1..7839050 100644
--- a/openbsc/tests/gtphub/gtphub_test.c
+++ b/openbsc/tests/gtphub/gtphub_test.c
@@ -399,6 +399,7 @@
 
 #define buf_len 1024
 static uint8_t buf[buf_len];
+static uint8_t *reply_buf;
 
 static unsigned int msg(const char *hex)
 {
@@ -410,16 +411,16 @@
 /* Compare static buf to given string constant. The amount of bytes is obtained
  * from parsing the GTP header in buf.  hex must match an osmo_hexdump() of the
  * desired message. Return 1 if size and content match. */
-#define msg_is(MSG) _msg_is(MSG, __FILE__, __LINE__)
-static int _msg_is(const char *hex, const char *file, int line)
+#define reply_is(MSG) _reply_is(MSG, __FILE__, __LINE__)
+static int _reply_is(const char *hex, const char *file, int line)
 {
-	struct gtp1_header_long *h = (void*)buf;
+	struct gtp1_header_long *h = (void*)reply_buf;
 	int len = ntoh16(h->length) + 8;
-	const char *dump = osmo_hexdump_nospc(buf, len);
+	const char *dump = osmo_hexdump_nospc(reply_buf, len);
 	int cmp = strcmp(dump, hex);
 
 	if (cmp != 0) {
-		printf("\n%s:%d: msg_is(): MISMATCH\n"
+		printf("\n%s:%d: reply_is(): MISMATCH\n"
 		       "  expecting:\n'%s'\n"
 		       "        got:\n'%s'\n\n",
 		       file,
@@ -466,87 +467,82 @@
 	time_t now = 123;
 
 	gtphub_init(hub);
-
-	/* TODO This test should test for gtphub echoing back to each side.
-	 * Echos must not be routed through. */
+	hub->restart_counter = 0x23;
 
 	const char *gtp_ping_from_sgsn =
 		"32"	/* 0b001'1 0010: version 1, protocol GTP, with seq nr. */
 		"01"	/* type 01: Echo request */
 		"0004"	/* length of 4 after header TEI */
 		"00000000" /* header TEI == 0 in Echo */
-		"abcd"	/* some 16 octet sequence nr */
+		"abcd"	/* some 2 octet sequence nr */
 		"0000"	/* N-PDU 0, no extension header (why is this here?) */
 		;
 
-	/* Same with mapped sequence number */
-	const char *gtp_ping_to_ggsn =
-		"32" "01" "0004" "00000000"
-		"6d31"	/* mapped seq */
-		"00" "00";
-
-	const char *gtp_pong_from_ggsn =
+	const char *gtp_pong_to_sgsn =
 		"32"
 		"02"	/* type 02: Echo response */
-		"0006"	/* len */
-		"00000000" /* tei */
-		"6d31"	/* mapped seq */
-		"0000"	/* ext */
-		"0e01"	/* 0e: Recovery, val == 1 */
+		"0006"	/* length of 6 after header TEI */
+		"00000000" /* header TEI == 0 in Echo */
+		"abcd"	/* same sequence nr */
+		"0000"
+		"0e23"	/* Recovery with restart counter */
 		;
-	/* Same with unmapped sequence number */
-	const char *gtp_pong_to_sgsn =
-		"32" "02" "0006" "00000000"
-		"abcd"	/* unmapped seq */
-		"00" "00" "0e01";
-
-	/* Set the GGSN address that gtphub is forced to resolve to. */
-	const char *resolved_ggsn_str = "192.168.43.34";
-	resolved_ggsn_port = 434;
-	OSMO_ASSERT(gsn_addr_from_str(&resolved_ggsn_addr, resolved_ggsn_str)
-		    == 0);
-
-	/* A sockaddr for comparing later */
-	struct osmo_sockaddr resolved_ggsn_sa;
-	OSMO_ASSERT(osmo_sockaddr_init_udp(&resolved_ggsn_sa,
-					   resolved_ggsn_str,
-					   resolved_ggsn_port)
-		    == 0);
 
 	struct osmo_sockaddr orig_sgsn_addr;
-	OSMO_ASSERT(osmo_sockaddr_init(&orig_sgsn_addr,
-				       AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
-				       "192.168.42.23", 423) == 0);
+	OSMO_ASSERT(osmo_sockaddr_init_udp(&orig_sgsn_addr,
+					   "192.168.42.23", 423) == 0);
 	struct osmo_fd *ggsn_ofd = NULL;
-	struct osmo_sockaddr ggsn_addr;
+	struct osmo_sockaddr to_addr;
 	int send;
 	send = gtphub_from_sgsns_handle_buf(hub, GTPH_PLANE_CTRL, &orig_sgsn_addr,
 					    buf, msg(gtp_ping_from_sgsn), now,
-					    &ggsn_ofd, &ggsn_addr);
+					    &reply_buf, &ggsn_ofd, &to_addr);
 	OSMO_ASSERT(send > 0);
-	OSMO_ASSERT(ggsn_addr.l);
-	OSMO_ASSERT(same_addr(&ggsn_addr, &resolved_ggsn_sa));
-	OSMO_ASSERT(msg_is(gtp_ping_to_ggsn));
+	OSMO_ASSERT(to_addr.l);
+	OSMO_ASSERT(same_addr(&to_addr, &orig_sgsn_addr));
+	OSMO_ASSERT(reply_is(gtp_pong_to_sgsn));
 
-	struct osmo_fd *sgsn_ofd;
-	struct osmo_sockaddr sgsn_addr;
-	send = gtphub_from_ggsns_handle_buf(hub, GTPH_PLANE_CTRL, &ggsn_addr,
-					    buf, msg(gtp_pong_from_ggsn), now,
-					    &sgsn_ofd, &sgsn_addr);
+	struct gtphub_peer_port *sgsn_port =
+		gtphub_port_find_sa(&hub->to_sgsns[GTPH_PLANE_CTRL],
+				    &orig_sgsn_addr);
+	/* We don't record Echo peers. */
+	OSMO_ASSERT(!sgsn_port);
+
+	const char *gtp_ping_from_ggsn =
+		"32"	/* 0b001'1 0010: version 1, protocol GTP, with seq nr. */
+		"01"	/* type 01: Echo request */
+		"0004"	/* length of 4 after header TEI */
+		"00000000" /* header TEI == 0 in Echo */
+		"cdef"	/* some 2 octet sequence nr */
+		"0000"	/* N-PDU 0, no extension header (why is this here?) */
+		;
+
+	const char *gtp_pong_to_ggsn =
+		"32"
+		"02"	/* type 02: Echo response */
+		"0006"	/* length of 6 after header TEI */
+		"00000000" /* header TEI == 0 in Echo */
+		"cdef"	/* same sequence nr */
+		"0000"
+		"0e23"	/* Recovery with restart counter */
+		;
+
+	struct osmo_sockaddr orig_ggsn_addr;
+	OSMO_ASSERT(osmo_sockaddr_init_udp(&orig_ggsn_addr,
+					   "192.168.24.32", 321) == 0);
+	struct osmo_fd *sgsn_ofd = NULL;
+	send = gtphub_from_ggsns_handle_buf(hub, GTPH_PLANE_CTRL, &orig_ggsn_addr,
+					    buf, msg(gtp_ping_from_ggsn), now,
+					    &reply_buf, &sgsn_ofd, &to_addr);
 	OSMO_ASSERT(send > 0);
-	OSMO_ASSERT(same_addr(&sgsn_addr, &orig_sgsn_addr));
-	OSMO_ASSERT(msg_is(gtp_pong_to_sgsn));
+	OSMO_ASSERT(same_addr(&to_addr, &orig_ggsn_addr));
+	OSMO_ASSERT(reply_is(gtp_pong_to_ggsn));
 
 	struct gtphub_peer_port *ggsn_port =
 		gtphub_port_find_sa(&hub->to_ggsns[GTPH_PLANE_CTRL],
-				    &resolved_ggsn_sa);
-	OSMO_ASSERT(ggsn_port);
-	struct gtphub_peer *ggsn = ggsn_port->peer_addr->peer;
-	/* now == 123; now + 30 == 153. */
-	OSMO_ASSERT(nr_map_is(&ggsn->seq_map, "(43981->27953@153), "));
-
-	OSMO_ASSERT(nr_map_is(&hub->tei_map[GTPH_PLANE_CTRL], ""));
-	OSMO_ASSERT(nr_map_is(&hub->tei_map[GTPH_PLANE_USER], ""));
+				    &orig_sgsn_addr);
+	/* We don't record Echo peers. */
+	OSMO_ASSERT(!ggsn_port);
 
 	gtphub_gc(hub, now + EXPIRE_ALL);
 }
@@ -707,20 +703,20 @@
 	int send;
 	send = gtphub_from_sgsns_handle_buf(hub, GTPH_PLANE_CTRL, &orig_sgsn_addr,
 					    buf, msg(gtp_req_from_sgsn), now,
-					    &ggsn_ofd, &ggsn_addr);
+					    &reply_buf, &ggsn_ofd, &ggsn_addr);
 	OSMO_ASSERT(send > 0);
 	OSMO_ASSERT(same_addr(&ggsn_addr, &resolved_ggsn_sa));
-	OSMO_ASSERT(msg_is(gtp_req_to_ggsn));
+	OSMO_ASSERT(reply_is(gtp_req_to_ggsn));
 	OSMO_ASSERT(was_resolved_for("240010123456789", "internet"));
 
 	struct osmo_fd *sgsn_ofd;
 	struct osmo_sockaddr sgsn_addr;
 	send = gtphub_from_ggsns_handle_buf(hub, GTPH_PLANE_CTRL, &ggsn_addr,
 					    buf, msg(gtp_resp_from_ggsn), now,
-					    &sgsn_ofd, &sgsn_addr);
+					    &reply_buf, &sgsn_ofd, &sgsn_addr);
 	OSMO_ASSERT(send > 0);
 	OSMO_ASSERT(same_addr(&sgsn_addr, &orig_sgsn_addr));
-	OSMO_ASSERT(msg_is(gtp_resp_to_sgsn));
+	OSMO_ASSERT(reply_is(gtp_resp_to_sgsn));
 
 	struct gtphub_peer_port *ggsn_port =
 		gtphub_port_find_sa(&hub->to_ggsns[GTPH_PLANE_CTRL],
diff --git a/openbsc/tests/gtphub/gtphub_test.ok b/openbsc/tests/gtphub/gtphub_test.ok
index 8d1075a..eac6f0c 100644
--- a/openbsc/tests/gtphub/gtphub_test.ok
+++ b/openbsc/tests/gtphub/gtphub_test.ok
@@ -1,3 +1,2 @@
-Wrap: returning GGSN addr from imsi (null) ni (null): 192.168.43.34 port 434
 Wrap: returning GGSN addr from imsi 240010123456789 ni internet: 192.168.43.34 port 434
 Done