[GPRS] Add Frame Relay in GRE encapsulation for NS
diff --git a/openbsc/include/openbsc/gprs_ns.h b/openbsc/include/openbsc/gprs_ns.h
index 08519f2..319ff3d 100644
--- a/openbsc/include/openbsc/gprs_ns.h
+++ b/openbsc/include/openbsc/gprs_ns.h
@@ -107,6 +107,7 @@
 enum gprs_ns_ll {
 	GPRS_NS_LL_UDP,
 	GPRS_NS_LL_E1,
+	GPRS_NS_LL_FR_GRE,
 };
 
 enum gprs_ns_evt {
@@ -130,15 +131,14 @@
 
 	uint16_t timeout[NS_TIMERS_COUNT];
 
-	/* which link-layer are we based on? */
-	enum gprs_ns_ll ll;
-
-	union {
-		/* NS-over-IP specific bits */
-		struct {
-			struct bsc_fd fd;
-		} nsip;
-	};
+	/* NS-over-IP specific bits */
+	struct {
+		struct bsc_fd fd;
+	} nsip;
+	/* NS-over-FR-over-GRE-over-IP specific bits */
+	struct {
+		struct bsc_fd fd;
+	} frgre;
 };
 
 enum nsvc_timer_mode {
@@ -168,10 +168,16 @@
 
 	struct rate_ctr_group *ctrg;
 
+	/* which link-layer are we based on? */
+	enum gprs_ns_ll ll;
+
 	union {
 		struct {
 			struct sockaddr_in bts_addr;
 		} ip;
+		struct {
+			struct sockaddr_in bts_addr;
+		} frgre;
 	};
 };
 
@@ -181,15 +187,11 @@
 /* Destroy a NS protocol instance */
 void gprs_ns_destroy(struct gprs_ns_inst *nsi);
 
-/* Listen for incoming GPRS packets */
-int nsip_listen(struct gprs_ns_inst *nsi, uint16_t udp_port);
+/* Listen for incoming GPRS packets via NS/UDP */
+int nsip_listen(struct gprs_ns_inst *nsi, uint32_t ip, uint16_t udp_port);
 
 struct sockaddr_in;
 
-/* main entry point, here incoming NS frames enter */
-int gprs_ns_rcvmsg(struct gprs_ns_inst *nsi, struct msgb *msg,
-		   struct sockaddr_in *saddr);
-
 /* main function for higher layers (BSSGP) to send NS messages */
 int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg);
 
@@ -197,8 +199,8 @@
 int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause);
 int gprs_ns_tx_unblock(struct gprs_nsvc *nsvc);
 
-/* Listen for incoming GPRS packets */
-int nsip_listen(struct gprs_ns_inst *nsi, uint16_t udp_port);
+/* Listen for incoming GPRS packets via NS/FR/GRE */
+int gprs_ns_frgre_listen(struct gprs_ns_inst *nsi, uint32_t ip);
 
 /* Establish a connection (from the BSS) to the SGSN */
 struct gprs_nsvc *nsip_connect(struct gprs_ns_inst *nsi,
@@ -216,4 +218,7 @@
 /* Add NS-specific VTY stuff */
 int gprs_ns_vty_init(struct gprs_ns_inst *nsi);
 
+#define NS_ALLOC_SIZE	1024
+
+
 #endif
diff --git a/openbsc/src/gprs/gprs_ns.c b/openbsc/src/gprs/gprs_ns.c
index 6d028ab..4ef3603 100644
--- a/openbsc/src/gprs/gprs_ns.c
+++ b/openbsc/src/gprs/gprs_ns.c
@@ -70,8 +70,6 @@
 #include <openbsc/gprs_ns.h>
 #include <openbsc/gprs_bssgp.h>
 
-#define NS_ALLOC_SIZE	1024
-
 static const struct tlv_definition ns_att_tlvdef = {
 	.def = {
 		[NS_IE_CAUSE]	= { TLV_TYPE_TvLV, 0 },
@@ -204,6 +202,7 @@
 }
 
 static int nsip_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg);
+extern int grps_ns_frgre_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg);
 
 static int gprs_ns_tx(struct gprs_nsvc *nsvc, struct msgb *msg)
 {
@@ -215,12 +214,15 @@
 	rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_PKTS_OUT]);
 	rate_ctr_add(&nsvc->ctrg->ctr[NS_CTR_BYTES_OUT], msgb_l2len(msg));
 
-	switch (nsvc->nsi->ll) {
+	switch (nsvc->ll) {
 	case GPRS_NS_LL_UDP:
 		ret = nsip_sendmsg(nsvc, msg);
 		break;
+	case GPRS_NS_LL_FR_GRE:
+		ret = gprs_ns_frgre_sendmsg(nsvc, msg);
+		break;
 	default:
-		LOGP(DNS, LOGL_ERROR, "unsupported NS linklayer %u\n", nsvc->nsi->ll);
+		LOGP(DNS, LOGL_ERROR, "unsupported NS linklayer %u\n", nsvc->ll);
 		msgb_free(msg);
 		ret = -EIO;
 		break;
@@ -644,7 +646,7 @@
 
 /* main entry point, here incoming NS frames enter */
 int gprs_ns_rcvmsg(struct gprs_ns_inst *nsi, struct msgb *msg,
-		   struct sockaddr_in *saddr)
+		   struct sockaddr_in *saddr, enum gprs_ns_ll ll)
 {
 	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
 	struct gprs_nsvc *nsvc;
@@ -667,6 +669,7 @@
 			nsvc->nsvci = nsvc->nsei = 0xfffe;
 			nsvc->ip.bts_addr = *saddr;
 			nsvc->state = NSE_S_ALIVE;
+			nsvc->ll = ll;
 #if 0
 			return gprs_ns_tx_reset(nsvc, NS_CAUSE_PDU_INCOMP_PSTATE);
 #else
@@ -691,6 +694,7 @@
 		nsvc = nsvc_by_nsei(nsi, nsei);
 		if (!nsvc) {
 			nsvc = nsvc_create(nsi, 0xffff);
+			nsvc->ll = ll;
 			log_set_context(BSC_CTX_NSVC, nsvc);
 			LOGP(DNS, LOGL_INFO, "Creating NS-VC for BSS at %s:%u\n",
 				inet_ntoa(saddr->sin_addr), ntohs(saddr->sin_port));
@@ -858,7 +862,7 @@
 	if (!msg)
 		return error;
 
-	error = gprs_ns_rcvmsg(nsi, msg, &saddr);
+	error = gprs_ns_rcvmsg(nsi, msg, &saddr, GPRS_NS_LL_UDP);
 
 	msgb_free(msg);
 
@@ -871,7 +875,7 @@
 	return -EIO;
 }
 
-int nsip_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg)
+static int nsip_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg)
 {
 	int rc;
 	struct gprs_ns_inst *nsi = nsvc->nsi;
@@ -898,19 +902,15 @@
 	return rc;
 }
 
-extern int make_sock(struct bsc_fd *bfd, int proto, uint16_t port,
-		     int (*cb)(struct bsc_fd *fd, unsigned int what));
-
 /* Listen for incoming GPRS packets */
-int nsip_listen(struct gprs_ns_inst *nsi, uint16_t udp_port)
+int nsip_listen(struct gprs_ns_inst *nsi, uint32_t ip, uint16_t udp_port)
 {
 	int ret;
 
-	ret = make_sock(&nsi->nsip.fd, IPPROTO_UDP, udp_port, nsip_fd_cb);
+	ret = make_sock(&nsi->nsip.fd, IPPROTO_UDP, ip, udp_port, nsip_fd_cb);
 	if (ret < 0)
 		return ret;
 
-	nsi->ll = GPRS_NS_LL_UDP;
 	nsi->nsip.fd.data = nsi;
 
 	return ret;
diff --git a/openbsc/src/gprs/gprs_ns_frgre.c b/openbsc/src/gprs/gprs_ns_frgre.c
new file mode 100644
index 0000000..d58c1d9
--- /dev/null
+++ b/openbsc/src/gprs/gprs_ns_frgre.c
@@ -0,0 +1,222 @@
+/* GPRS Networks Service (NS) messages on the Gb interface
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) */
+
+/* NS-over-FR-over-GRE implementation */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocore/select.h>
+#include <osmocore/msgb.h>
+#include <osmocore/talloc.h>
+
+#include <openbsc/socket.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_ns.h>
+
+#define GRE_PTYPE_FR	0x6559
+
+struct gre_hdr {
+	uint16_t flags;
+	uint16_t ptype;
+} __attribute__ ((packed));
+
+static struct msgb *read_nsfrgre_msg(struct bsc_fd *bfd, int *error,
+					struct sockaddr_in *saddr)
+{
+	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR/GRE Rx");
+	int ret = 0;
+	socklen_t saddr_len = sizeof(*saddr);
+	struct gre_hdr *greh;
+	uint8_t *frh;
+	uint32_t dlci;
+
+	if (!msg) {
+		*error = -ENOMEM;
+		return NULL;
+	}
+
+	ret = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE, 0,
+			(struct sockaddr *)saddr, &saddr_len);
+	if (ret < 0) {
+		LOGP(DNS, LOGL_ERROR, "recv error %s during NS-FR-GRE recv\n",
+			strerror(errno));
+		*error = ret;
+		return NULL;
+	} else if (ret == 0) {
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	}
+
+	msgb_put(msg, ret);
+
+	if (msg->len < sizeof(*greh)) {
+		LOGP(DNS, LOGL_ERROR, "Short GRE packet: %u bytes\n", msg->len);
+		*error = -EIO;
+		goto out_err;
+	}
+
+	greh = (struct gre_hdr *) msg->data;
+	if (greh->flags) {
+		LOGP(DNS, LOGL_NOTICE, "Unknown GRE flags 0x%04x\n",
+			ntohs(greh->flags));
+	}
+	if (greh->ptype != htons(GRE_PTYPE_FR)) {
+		LOGP(DNS, LOGL_NOTICE, "Unknown GRE protocol 0x%04x != FR\n",
+			ntohs(greh->ptype));
+		*error = -EIO;
+		goto out_err;
+	}
+
+	if (msg->len < sizeof(*greh) + 2) {
+		LOGP(DNS, LOGL_ERROR, "Short FR header: %u bytes\n", msg->len);
+		*error = -EIO;
+		goto out_err;
+	}
+
+	frh = msg->data + sizeof(*greh);
+	if (frh[0] & 0x01) {
+		LOGP(DNS, LOGL_NOTICE, "Unsupported single-byte FR address\n");
+		*error = -EIO;
+		goto out_err;
+	}
+	dlci = (frh[0] & 0xfc << 2);
+	if ((frh[1] & 0x0f) != 0x01) {
+		LOGP(DNS, LOGL_NOTICE, "Unknown second FR octet 0x%02x\n",
+			frh[1]);
+		*error = -EIO;
+		goto out_err;
+	}
+	dlci |= frh[1] >> 4;
+	if (dlci > 0xffff) {
+		LOGP(DNS, LOGL_ERROR, "We don't support DLCI > 65535 (%u)\n",
+			dlci);
+		*error = -EINVAL;
+		goto out_err;
+	}
+
+	msg->l2h = msg->data + sizeof(*greh) + 2;
+
+	/* Store DLCI in NETWORK BYTEORDER in sockaddr port member */
+	saddr->sin_port = htons(dlci & 0xffff);
+
+	return msg;
+
+out_err:
+	msgb_free(msg);
+	return NULL;
+}
+
+int gprs_ns_rcvmsg(struct gprs_ns_inst *nsi, struct msgb *msg,
+		   struct sockaddr_in *saddr, enum gprs_ns_ll ll);
+
+static int handle_nsfrgre_read(struct bsc_fd *bfd)
+{
+	int error;
+	struct sockaddr_in saddr;
+	struct gprs_ns_inst *nsi = bfd->data;
+	struct msgb *msg = read_nsfrgre_msg(bfd, &error, &saddr);
+
+	if (!msg)
+		return error;
+
+	error = gprs_ns_rcvmsg(nsi, msg, &saddr, GPRS_NS_LL_FR_GRE);
+
+	msgb_free(msg);
+
+	return error;
+}
+
+static int handle_nsfrgre_write(struct bsc_fd *bfd)
+{
+	/* FIXME: actually send the data here instead of nsip_sendmsg() */
+	return -EIO;
+}
+
+int gprs_ns_frgre_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	int rc;
+	struct gprs_ns_inst *nsi = nsvc->nsi;
+	struct sockaddr_in daddr;
+	uint16_t dlci = ntohs(nsvc->frgre.bts_addr.sin_port);
+	uint8_t *frh;
+	struct gre_hdr *greh;
+
+	/* Build socket address for the packet destionation */
+	daddr.sin_addr = nsvc->frgre.bts_addr.sin_addr;
+	daddr.sin_port = IPPROTO_GRE;
+
+	/* Prepend the FR header */
+	frh = msgb_push(msg, sizeof(frh));
+	frh[0] = (dlci >> 2) & 0xfc;
+	frh[1] = (dlci & 0xf0) | 0x01;
+
+	/* Prepend the GRE header */
+	greh = (struct gre_hdr *) msgb_push(msg, sizeof(*greh));
+	greh->flags = 0;
+	greh->ptype = GRE_PTYPE_FR;
+
+	rc = sendto(nsi->frgre.fd.fd, msg->data, msg->len, 0,
+		  (struct sockaddr *)&daddr, sizeof(daddr));
+
+	talloc_free(msg);
+
+	return rc;
+}
+
+static int nsfrgre_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	int rc = 0;
+
+	if (what & BSC_FD_READ)
+		rc = handle_nsfrgre_read(bfd);
+	if (what & BSC_FD_WRITE)
+		rc = handle_nsfrgre_write(bfd);
+
+	return rc;
+}
+
+int gprs_ns_frgre_listen(struct gprs_ns_inst *nsi, uint32_t ip)
+{
+	int rc;
+
+	/* Make sure we close any existing socket before changing it */
+	if (nsi->frgre.fd.fd)
+		close(nsi->frgre.fd.fd);
+
+	rc = make_sock(&nsi->frgre.fd, IPPROTO_GRE, ip, 0, nsfrgre_fd_cb);
+	if (rc < 0) {
+		LOGP(DNS, LOGL_ERROR, "Error creating GRE socket (%s)\n",
+			strerror(errno));
+		return rc;
+	}
+	nsi->frgre.fd.data = nsi;
+
+	return rc;
+}
diff --git a/openbsc/src/gprs/gprs_ns_vty.c b/openbsc/src/gprs/gprs_ns_vty.c
index c20016a..7d43944 100644
--- a/openbsc/src/gprs/gprs_ns_vty.c
+++ b/openbsc/src/gprs/gprs_ns_vty.c
@@ -79,7 +79,10 @@
 		vty_out(vty, " nse %u remote-role %s%s",
 			nsvc->nsei, nsvc->remote_end_is_sgsn ? "sgsn" : "bss",
 			VTY_NEWLINE);
-		if (nsvc->nsi->ll == GPRS_NS_LL_UDP) {
+		switch (nsvc->ll) {
+		case GPRS_NS_LL_UDP:
+			vty_out(vty, " nse %u encapsulation udp%s", nsvc->nsei,
+				VTY_NEWLINE);
 			vty_out(vty, " nse %u remote-ip %s%s",
 				nsvc->nsei,
 				inet_ntoa(nsvc->ip.bts_addr.sin_addr),
@@ -87,6 +90,19 @@
 			vty_out(vty, " nse %u remote-port %u%s",
 				nsvc->nsei, ntohs(nsvc->ip.bts_addr.sin_port),
 				VTY_NEWLINE);
+			break;
+		case GPRS_NS_LL_FR_GRE:
+			vty_out(vty, " nse %u encapsulation framerelay-gre%s",
+				nsvc->nsei, VTY_NEWLINE);
+			vty_out(vty, " nse %u remote-ip %s%s",
+				nsvc->nsei,
+				inet_ntoa(nsvc->frgre.bts_addr.sin_addr),
+				VTY_NEWLINE);
+			vty_out(vty, " nse %u fr-dlci %u%s",
+				nsvc->nsei, ntohs(nsvc->frgre.bts_addr.sin_port),
+				VTY_NEWLINE);
+		default:
+			break;
 		}
 	}
 
@@ -113,8 +129,9 @@
 		nsvc->remote_end_is_sgsn ? "SGSN" : "BSS",
 		nsvc->state & NSE_S_ALIVE ? "ALIVE" : "DEAD",
 		nsvc->state & NSE_S_BLOCKED ? "BLOCKED" : "UNBLOCKED");
-	if (nsvc->nsi->ll == GPRS_NS_LL_UDP)
-		vty_out(vty, ", %15s:%u",
+	if (nsvc->ll == GPRS_NS_LL_UDP || nsvc->ll == GPRS_NS_LL_FR_GRE)
+		vty_out(vty, ", %s %15s:%u",
+			nsvc->ll == GPRS_NS_LL_UDP ? "UDP" : "FR-GRE",
 			inet_ntoa(nsvc->ip.bts_addr.sin_addr),
 			ntohs(nsvc->ip.bts_addr.sin_port));
 	vty_out(vty, "%s", VTY_NEWLINE);
@@ -243,11 +260,68 @@
 		return CMD_WARNING;
 	}
 
+	if (nsvc->ll != GPRS_NS_LL_UDP) {
+		vty_out(vty, "Cannot set UDP Port on non-UDP NSE%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
 	nsvc->ip.bts_addr.sin_port = htons(port);
 
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_nse_fr_dlci, cfg_nse_fr_dlci_cmd,
+	"nse <0-65535> fr-dlci <0-1023>",
+	NSE_CMD_STR
+	"Frame Relay DLCI\n"
+	"Frame Relay DLCI Number\n")
+{
+	uint16_t nsei = atoi(argv[0]);
+	uint16_t dlci = atoi(argv[1]);
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_nsei(vty_nsi, nsei);
+	if (!nsvc) {
+		vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (nsvc->ll != GPRS_NS_LL_FR_GRE) {
+		vty_out(vty, "Cannot set FR DLCI on non-FR NSE%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	nsvc->frgre.bts_addr.sin_port = htons(dlci);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nse_encaps, cfg_nse_encaps_cmd,
+	"nse <0-65535> encapsulation (udp|framerelay-gre)",
+	NSE_CMD_STR
+	"Encapsulation for NS\n"
+	"UDP/IP Encapsulation\n" "Frame-Relay/GRE/IP Encapsulation\n")
+{
+	uint16_t nsei = atoi(argv[0]);
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_nsei(vty_nsi, nsei);
+	if (!nsvc) {
+		vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[1], "udp"))
+		nsvc->ll = GPRS_NS_LL_UDP;
+	else
+		nsvc->ll = GPRS_NS_LL_FR_GRE;
+
+	return CMD_SUCCESS;
+}
+
+
 DEFUN(cfg_nse_remoterole, cfg_nse_remoterole_cmd,
 	"nse <0-65535> remote-role (sgsn|bss)",
 	NSE_CMD_STR
@@ -393,6 +467,8 @@
 	install_element(NS_NODE, &cfg_nse_nsvci_cmd);
 	install_element(NS_NODE, &cfg_nse_remoteip_cmd);
 	install_element(NS_NODE, &cfg_nse_remoteport_cmd);
+	install_element(NS_NODE, &cfg_nse_fr_dlci_cmd);
+	install_element(NS_NODE, &cfg_nse_encaps_cmd);
 	install_element(NS_NODE, &cfg_nse_remoterole_cmd);
 	install_element(NS_NODE, &cfg_no_nse_cmd);
 	install_element(NS_NODE, &cfg_ns_timer_cmd);