Subject: [PATCH net-next v2 2/6] net/x25: make neighbour params configurable

From: Martin Schiller <[email-masked]>
Date: Mon, 16 Nov 2020 14:55:20 +0100

Extended struct x25_neigh and x25_subscrip_struct to configure following
params through SIOCX25SSUBSCRIP:
  o mode (DTE/DCE)
  o number of channels
  o facilities (packet size, window size)
  o timer T20

Based on this configuration options the following changes/extensions
where made:
  o DTE/DCE handling to select the next lc (DCE=from bottom / DTE=from
    top)
  o DTE/DCE handling to set correct clear/reset/restart cause
  o take default facilities from neighbour settings

Signed-off-by: Martin Schiller <[email-masked]>
---

Change from v1:
o fix 'subject_prefix' and 'checkpatch' warnings
o fix incompatible assignment of 'struct compat_x25_facilities'

---
 include/net/x25.h        |   8 ++-
 include/uapi/linux/x25.h |  56 ++++++++-------
 net/x25/af_x25.c         | 145 ++++++++++++++++++++++++++++++++-------
 net/x25/x25_facilities.c |   6 +-
 net/x25/x25_link.c       |  97 ++++++++++++++++++++++----
 net/x25/x25_subr.c       |  22 +++++-
 6 files changed, 268 insertions(+), 66 deletions(-)

diff --git a/include/net/x25.h b/include/net/x25.h
index 4c1502e8b2b2..ec00f595fcc6 100644
--- a/include/net/x25.h
+++ b/include/net/x25.h
@@ -140,6 +140,9 @@ struct x25_neigh {
 	struct net_device	*dev;
 	unsigned int		state;
 	unsigned int		extended;
+	unsigned int		dce;
+	unsigned int		lc;
+	struct x25_facilities	facilities;
 	struct sk_buff_head	queue;
 	unsigned long		t20;
 	struct timer_list	t20timer;
@@ -164,6 +167,8 @@ struct x25_sock {
 	struct timer_list	timer;
 	struct x25_causediag	causediag;
 	struct x25_facilities	facilities;
+	/* set, if facilities changed by SIOCX25SFACILITIES */
+	unsigned int		socket_defined_facilities;
 	struct x25_dte_facilities dte_facilities;
 	struct x25_calluserdata	calluserdata;
 	unsigned long 		vc_facil_mask;	/* inc_call facilities mask */
@@ -215,7 +220,8 @@ int x25_create_facilities(unsigned char *, struct x25_facilities *,
 			  struct x25_dte_facilities *, unsigned long);
 int x25_negotiate_facilities(struct sk_buff *, struct sock *,
 			     struct x25_facilities *,
-			     struct x25_dte_facilities *);
+				struct x25_dte_facilities *,
+				struct x25_neigh *);
 void x25_limit_facilities(struct x25_facilities *, struct x25_neigh *);
 
 /* x25_forward.c */
diff --git a/include/uapi/linux/x25.h b/include/uapi/linux/x25.h
index 034b7dc5593a..094dc2cff37b 100644
--- a/include/uapi/linux/x25.h
+++ b/include/uapi/linux/x25.h
@@ -63,31 +63,6 @@ struct sockaddr_x25 {
 	struct x25_address sx25_addr;		/* X.121 Address */
 };
 
-/*
- *	DTE/DCE subscription options.
- *
- *      As this is missing lots of options, user should expect major
- *	changes of this structure in 2.5.x which might break compatibilty.
- *      The somewhat ugly dimension 200-sizeof() is needed to maintain
- *	backward compatibility.
- */
-struct x25_subscrip_struct {
-	char device[200-sizeof(unsigned long)];
-	unsigned long	global_facil_mask;	/* 0 to disable negotiation */
-	unsigned int	extended;
-};
-
-/* values for above global_facil_mask */
-
-#define	X25_MASK_REVERSE	0x01	
-#define	X25_MASK_THROUGHPUT	0x02
-#define	X25_MASK_PACKET_SIZE	0x04
-#define	X25_MASK_WINDOW_SIZE	0x08
-
-#define X25_MASK_CALLING_AE 0x10
-#define X25_MASK_CALLED_AE 0x20
-
-
 /*
  *	Routing table control structure.
  */
@@ -127,6 +102,37 @@ struct x25_dte_facilities {
 	__u8 called_ae[20];
 };
 
+/*
+ *	DTE/DCE subscription options.
+ *
+ *      As this is missing lots of options, user should expect major
+ *	changes of this structure in 2.5.x which might break compatibility.
+ *      The somewhat ugly dimension 200-sizeof() is needed to maintain
+ *	backward compatibility.
+ */
+struct x25_subscrip_struct {
+	char device[200 - ((2 * sizeof(unsigned long)) +
+		    sizeof(struct x25_facilities) +
+		    (2 * sizeof(unsigned int)))];
+	unsigned int		dce;
+	unsigned int		lc;
+	struct x25_facilities	facilities;
+	unsigned long		t20;
+	unsigned long		global_facil_mask;	/* 0 to disable negotiation */
+	unsigned int		extended;
+};
+
+/* values for above global_facil_mask */
+
+#define	X25_MASK_REVERSE	0x01
+#define	X25_MASK_THROUGHPUT	0x02
+#define	X25_MASK_PACKET_SIZE	0x04
+#define	X25_MASK_WINDOW_SIZE	0x08
+
+#define X25_MASK_CALLING_AE 0x10
+#define X25_MASK_CALLED_AE 0x20
+
+
 /*
  *	Call User Data structure.
  */
diff --git a/net/x25/af_x25.c b/net/x25/af_x25.c
index d2a52c254cca..4c2a395fdbdb 100644
--- a/net/x25/af_x25.c
+++ b/net/x25/af_x25.c
@@ -72,8 +72,21 @@ static const struct proto_ops x25_proto_ops;
 static const struct x25_address null_x25_address = {"               "};
 
 #ifdef CONFIG_COMPAT
+struct compat_x25_facilities {
+	compat_uint_t	winsize_in, winsize_out;
+	compat_uint_t	pacsize_in, pacsize_out;
+	compat_uint_t	throughput;
+	compat_uint_t	reverse;
+};
+
 struct compat_x25_subscrip_struct {
-	char device[200-sizeof(compat_ulong_t)];
+	char device[200 - ((2 * sizeof(compat_ulong_t)) +
+		    sizeof(struct compat_x25_facilities) +
+		    (2 * sizeof(compat_uint_t)))];
+	compat_uint_t		dce;
+	compat_uint_t		lc;
+	struct compat_x25_facilities	facilities;
+	compat_ulong_t		t20;
 	compat_ulong_t global_facil_mask;
 	compat_uint_t extended;
 };
@@ -373,13 +386,26 @@ static unsigned int x25_new_lci(struct x25_neigh *nb)
 	unsigned int lci = 1;
 	struct sock *sk;
 
-	while ((sk = x25_find_socket(lci, nb)) != NULL) {
-		sock_put(sk);
-		if (++lci == 4096) {
-			lci = 0;
-			break;
+	if (nb->dce) {
+		while ((sk = x25_find_socket(lci, nb)) != NULL) {
+			sock_put(sk);
+			if (++lci > nb->lc) {
+				lci = 0;
+				break;
+			}
+			cond_resched();
+		}
+	} else {
+		lci = nb->lc;
+
+		while ((sk = x25_find_socket(lci, nb)) != NULL) {
+			sock_put(sk);
+			if (--lci == 0) {
+				lci = 0;
+				break;
+			}
+			cond_resched();
 		}
-		cond_resched();
 	}
 
 	return lci;
@@ -813,6 +839,10 @@ static int x25_connect(struct socket *sock, struct sockaddr *uaddr,
 	if (!x25->neighbour)
 		goto out_put_route;
 
+	if (!x25->socket_defined_facilities)
+		memcpy(&x25->facilities, &x25->neighbour->facilities,
+		       sizeof(struct x25_facilities));
+
 	x25_limit_facilities(&x25->facilities, x25->neighbour);
 
 	x25->lci = x25_new_lci(x25->neighbour);
@@ -1046,7 +1076,7 @@ int x25_rx_call_request(struct sk_buff *skb, struct x25_neigh *nb,
 	/*
 	 *	Try to reach a compromise on the requested facilities.
 	 */
-	len = x25_negotiate_facilities(skb, sk, &facilities, &dte_facilities);
+	len = x25_negotiate_facilities(skb, sk, &facilities, &dte_facilities, nb);
 	if (len == -1)
 		goto out_sock_put;
 
@@ -1460,10 +1490,15 @@ static int x25_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
 		rc = x25_subscr_ioctl(cmd, argp);
 		break;
 	case SIOCX25GFACILITIES: {
+		rc = -EINVAL;
 		lock_sock(sk);
+		if (sk->sk_state != TCP_ESTABLISHED &&
+		    !x25->socket_defined_facilities)
+			goto out_gfac_release;
 		rc = copy_to_user(argp, &x25->facilities,
 				  sizeof(x25->facilities))
 			? -EFAULT : 0;
+out_gfac_release:
 		release_sock(sk);
 		break;
 	}
@@ -1477,16 +1512,16 @@ static int x25_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
 		lock_sock(sk);
 		if (sk->sk_state != TCP_LISTEN &&
 		    sk->sk_state != TCP_CLOSE)
-			goto out_fac_release;
+			goto out_sfac_release;
 		if (facilities.pacsize_in < X25_PS16 ||
 		    facilities.pacsize_in > X25_PS4096)
-			goto out_fac_release;
+			goto out_sfac_release;
 		if (facilities.pacsize_out < X25_PS16 ||
 		    facilities.pacsize_out > X25_PS4096)
-			goto out_fac_release;
+			goto out_sfac_release;
 		if (facilities.winsize_in < 1 ||
 		    facilities.winsize_in > 127)
-			goto out_fac_release;
+			goto out_sfac_release;
 		if (facilities.throughput) {
 			int out = facilities.throughput & 0xf0;
 			int in  = facilities.throughput & 0x0f;
@@ -1494,19 +1529,20 @@ static int x25_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
 				facilities.throughput |=
 					X25_DEFAULT_THROUGHPUT << 4;
 			else if (out < 0x30 || out > 0xD0)
-				goto out_fac_release;
+				goto out_sfac_release;
 			if (!in)
 				facilities.throughput |=
 					X25_DEFAULT_THROUGHPUT;
 			else if (in < 0x03 || in > 0x0D)
-				goto out_fac_release;
+				goto out_sfac_release;
 		}
 		if (facilities.reverse &&
 		    (facilities.reverse & 0x81) != 0x81)
-			goto out_fac_release;
+			goto out_sfac_release;
 		x25->facilities = facilities;
+		x25->socket_defined_facilities = 1;
 		rc = 0;
-out_fac_release:
+out_sfac_release:
 		release_sock(sk);
 		break;
 	}
@@ -1658,6 +1694,9 @@ static int compat_x25_subscr_ioctl(unsigned int cmd,
 	struct net_device *dev;
 	int rc = -EINVAL;
 
+	if (cmd != SIOCX25GSUBSCRIP && cmd != SIOCX25SSUBSCRIP)
+		goto out;
+
 	rc = -EFAULT;
 	if (copy_from_user(&x25_subscr, x25_subscr32, sizeof(*x25_subscr32)))
 		goto out;
@@ -1671,28 +1710,86 @@ static int compat_x25_subscr_ioctl(unsigned int cmd,
 	if (nb == NULL)
 		goto out_dev_put;
 
-	dev_put(dev);
-
 	if (cmd == SIOCX25GSUBSCRIP) {
 		read_lock_bh(&x25_neigh_list_lock);
 		x25_subscr.extended = nb->extended;
+		x25_subscr.dce		     = nb->dce;
+		x25_subscr.lc		     = nb->lc;
+		x25_subscr.facilities.winsize_in = nb->facilities.winsize_in;
+		x25_subscr.facilities.winsize_out = nb->facilities.winsize_out;
+		x25_subscr.facilities.pacsize_in = nb->facilities.pacsize_in;
+		x25_subscr.facilities.pacsize_out = nb->facilities.pacsize_out;
+		x25_subscr.facilities.throughput = nb->facilities.throughput;
+		x25_subscr.facilities.reverse = nb->facilities.reverse;
+		x25_subscr.t20		     = nb->t20;
 		x25_subscr.global_facil_mask = nb->global_facil_mask;
 		read_unlock_bh(&x25_neigh_list_lock);
 		rc = copy_to_user(x25_subscr32, &x25_subscr,
 				sizeof(*x25_subscr32)) ? -EFAULT : 0;
 	} else {
 		rc = -EINVAL;
-		if (x25_subscr.extended == 0 || x25_subscr.extended == 1) {
-			rc = 0;
-			write_lock_bh(&x25_neigh_list_lock);
-			nb->extended = x25_subscr.extended;
-			nb->global_facil_mask = x25_subscr.global_facil_mask;
-			write_unlock_bh(&x25_neigh_list_lock);
+
+		if (dev->flags & IFF_UP)
+			return -EBUSY;
+
+		if (x25_subscr.extended != 0 && x25_subscr.extended != 1)
+			goto out_dev_and_neigh_put;
+		if (x25_subscr.dce != 0 && x25_subscr.dce != 1)
+			goto out_dev_and_neigh_put;
+		if (x25_subscr.lc < 1 || x25_subscr.lc > 4095)
+			goto out_dev_and_neigh_put;
+		if (x25_subscr.facilities.pacsize_in < X25_PS16 ||
+		    x25_subscr.facilities.pacsize_in > X25_PS4096)
+			goto out_dev_and_neigh_put;
+		if (x25_subscr.facilities.pacsize_out < X25_PS16 ||
+		    x25_subscr.facilities.pacsize_out > X25_PS4096)
+			goto out_dev_and_neigh_put;
+		if (x25_subscr.facilities.winsize_in < 1 ||
+		    x25_subscr.facilities.winsize_in > 127)
+			goto out_dev_and_neigh_put;
+		if (x25_subscr.facilities.throughput) {
+			int out = x25_subscr.facilities.throughput & 0xf0;
+			int in  = x25_subscr.facilities.throughput & 0x0f;
+
+			if (!out)
+				x25_subscr.facilities.throughput |=
+					X25_DEFAULT_THROUGHPUT << 4;
+			else if (out < 0x30 || out > 0xD0)
+				goto out_dev_and_neigh_put;
+			if (!in)
+				x25_subscr.facilities.throughput |=
+					X25_DEFAULT_THROUGHPUT;
+			else if (in < 0x03 || in > 0x0D)
+				goto out_dev_and_neigh_put;
 		}
+		if (x25_subscr.facilities.reverse &&
+		    (x25_subscr.facilities.reverse & 0x81) != 0x81)
+			goto out_dev_and_neigh_put;
+		if (x25_subscr.t20 < 1 * HZ || x25_subscr.t20 > 300 * HZ)
+			goto out_dev_and_neigh_put;
+
+		rc = 0;
+		write_lock_bh(&x25_neigh_list_lock);
+		nb->extended	      = x25_subscr.extended;
+		nb->dce		      = x25_subscr.dce;
+		nb->lc		      = x25_subscr.lc;
+		nb->facilities.winsize_in = x25_subscr.facilities.winsize_in;
+		nb->facilities.winsize_out = x25_subscr.facilities.winsize_out;
+		nb->facilities.pacsize_in = x25_subscr.facilities.pacsize_in;
+		nb->facilities.pacsize_out = x25_subscr.facilities.pacsize_out;
+		nb->facilities.throughput = x25_subscr.facilities.throughput;
+		nb->facilities.reverse = x25_subscr.facilities.reverse;
+		nb->t20		      = x25_subscr.t20;
+		nb->global_facil_mask = x25_subscr.global_facil_mask;
+		write_unlock_bh(&x25_neigh_list_lock);
 	}
+	dev_put(dev);
+
 	x25_neigh_put(nb);
 out:
 	return rc;
+out_dev_and_neigh_put:
+	x25_neigh_put(nb);
 out_dev_put:
 	dev_put(dev);
 	goto out;
diff --git a/net/x25/x25_facilities.c b/net/x25/x25_facilities.c
index 8e1a49b0c0dc..e6c9f9376206 100644
--- a/net/x25/x25_facilities.c
+++ b/net/x25/x25_facilities.c
@@ -263,13 +263,17 @@ int x25_create_facilities(unsigned char *buffer,
  *	The only real problem is with reverse charging.
  */
 int x25_negotiate_facilities(struct sk_buff *skb, struct sock *sk,
-		struct x25_facilities *new, struct x25_dte_facilities *dte)
+		struct x25_facilities *new, struct x25_dte_facilities *dte,
+		struct x25_neigh *nb)
 {
 	struct x25_sock *x25 = x25_sk(sk);
 	struct x25_facilities *ours = &x25->facilities;
 	struct x25_facilities theirs;
 	int len;
 
+	if (!x25->socket_defined_facilities)
+		ours = &nb->facilities;
+
 	memset(&theirs, 0, sizeof(theirs));
 	memcpy(new, ours, sizeof(*new));
 	memset(dte, 0, sizeof(*dte));
diff --git a/net/x25/x25_link.c b/net/x25/x25_link.c
index 92828a8a4ada..2af50d585b4b 100644
--- a/net/x25/x25_link.c
+++ b/net/x25/x25_link.c
@@ -125,8 +125,16 @@ static void x25_transmit_restart_request(struct x25_neigh *nb)
 	*dptr++ = nb->extended ? X25_GFI_EXTSEQ : X25_GFI_STDSEQ;
 	*dptr++ = 0x00;
 	*dptr++ = X25_RESTART_REQUEST;
-	*dptr++ = 0x00;
-	*dptr++ = 0;
+
+	*dptr = 0x00;	/* cause */
+
+	/* set bit 8, if DTE and cause != 0x00 */
+	if (!nb->dce && *dptr != 0x00)
+		*dptr |= (unsigned char)0x80;
+
+	dptr++;
+
+	*dptr++ = 0x00;	/* diagnostic */
 
 	skb->sk = NULL;
 
@@ -181,8 +189,16 @@ void x25_transmit_clear_request(struct x25_neigh *nb, unsigned int lci,
 					 X25_GFI_STDSEQ);
 	*dptr++ = (lci >> 0) & 0xFF;
 	*dptr++ = X25_CLEAR_REQUEST;
-	*dptr++ = cause;
-	*dptr++ = 0x00;
+
+	*dptr = cause;	/* cause */
+
+	/* set bit 8, if DTE and cause != 0x00 */
+	if (!nb->dce && *dptr != 0x00)
+		*dptr |= (unsigned char)0x80;
+
+	dptr++;
+
+	*dptr++ = 0x00;	/* diagnostic */
 
 	skb->sk = NULL;
 
@@ -261,6 +277,15 @@ void x25_link_device_add(struct net_device *dev)
 	nb->dev      = dev;
 	nb->state    = X25_LINK_STATE_0;
 	nb->extended = 0;
+	nb->dce      = 0;
+	nb->lc       = 10;
+	nb->facilities.winsize_in  = X25_DEFAULT_WINDOW_SIZE;
+	nb->facilities.winsize_out = X25_DEFAULT_WINDOW_SIZE;
+	nb->facilities.pacsize_in  = X25_DEFAULT_PACKET_SIZE;
+	nb->facilities.pacsize_out = X25_DEFAULT_PACKET_SIZE;
+	/* by default don't negotiate throughput */
+	nb->facilities.throughput  = 0;
+	nb->facilities.reverse     = X25_DEFAULT_REVERSE;
 	/*
 	 * Enables negotiation
 	 */
@@ -389,28 +414,76 @@ int x25_subscr_ioctl(unsigned int cmd, void __user *arg)
 	if ((nb = x25_get_neigh(dev)) == NULL)
 		goto out_dev_put;
 
-	dev_put(dev);
-
 	if (cmd == SIOCX25GSUBSCRIP) {
 		read_lock_bh(&x25_neigh_list_lock);
 		x25_subscr.extended	     = nb->extended;
+		x25_subscr.dce		     = nb->dce;
+		x25_subscr.lc		     = nb->lc;
+		x25_subscr.facilities	     = nb->facilities;
+		x25_subscr.t20		     = nb->t20;
 		x25_subscr.global_facil_mask = nb->global_facil_mask;
 		read_unlock_bh(&x25_neigh_list_lock);
 		rc = copy_to_user(arg, &x25_subscr,
 				  sizeof(x25_subscr)) ? -EFAULT : 0;
 	} else {
 		rc = -EINVAL;
-		if (!(x25_subscr.extended && x25_subscr.extended != 1)) {
-			rc = 0;
-			write_lock_bh(&x25_neigh_list_lock);
-			nb->extended	     = x25_subscr.extended;
-			nb->global_facil_mask = x25_subscr.global_facil_mask;
-			write_unlock_bh(&x25_neigh_list_lock);
+
+		if (dev->flags & IFF_UP)
+			return -EBUSY;
+
+		if (x25_subscr.extended != 0 && x25_subscr.extended != 1)
+			goto out_dev_and_neigh_put;
+		if (x25_subscr.dce != 0 && x25_subscr.dce != 1)
+			goto out_dev_and_neigh_put;
+		if (x25_subscr.lc < 1 || x25_subscr.lc > 4095)
+			goto out_dev_and_neigh_put;
+		if (x25_subscr.facilities.pacsize_in < X25_PS16 ||
+		    x25_subscr.facilities.pacsize_in > X25_PS4096)
+			goto out_dev_and_neigh_put;
+		if (x25_subscr.facilities.pacsize_out < X25_PS16 ||
+		    x25_subscr.facilities.pacsize_out > X25_PS4096)
+			goto out_dev_and_neigh_put;
+		if (x25_subscr.facilities.winsize_in < 1 ||
+		    x25_subscr.facilities.winsize_in > 127)
+			goto out_dev_and_neigh_put;
+		if (x25_subscr.facilities.throughput) {
+			int out = x25_subscr.facilities.throughput & 0xf0;
+			int in  = x25_subscr.facilities.throughput & 0x0f;
+
+			if (!out)
+				x25_subscr.facilities.throughput |=
+					X25_DEFAULT_THROUGHPUT << 4;
+			else if (out < 0x30 || out > 0xD0)
+				goto out_dev_and_neigh_put;
+			if (!in)
+				x25_subscr.facilities.throughput |=
+					X25_DEFAULT_THROUGHPUT;
+			else if (in < 0x03 || in > 0x0D)
+				goto out_dev_and_neigh_put;
 		}
+		if (x25_subscr.facilities.reverse &&
+		    (x25_subscr.facilities.reverse & 0x81) != 0x81)
+			goto out_dev_and_neigh_put;
+		if (x25_subscr.t20 < 1 * HZ || x25_subscr.t20 > 300 * HZ)
+			goto out_dev_and_neigh_put;
+
+		rc = 0;
+		write_lock_bh(&x25_neigh_list_lock);
+		nb->extended	      = x25_subscr.extended;
+		nb->dce		      = x25_subscr.dce;
+		nb->lc		      = x25_subscr.lc;
+		nb->facilities	      = x25_subscr.facilities;
+		nb->t20		      = x25_subscr.t20;
+		nb->global_facil_mask = x25_subscr.global_facil_mask;
+		write_unlock_bh(&x25_neigh_list_lock);
 	}
+	dev_put(dev);
+
 	x25_neigh_put(nb);
 out:
 	return rc;
+out_dev_and_neigh_put:
+	x25_neigh_put(nb);
 out_dev_put:
 	dev_put(dev);
 	goto out;
diff --git a/net/x25/x25_subr.c b/net/x25/x25_subr.c
index 0285aaa1e93c..c195d1c89ad7 100644
--- a/net/x25/x25_subr.c
+++ b/net/x25/x25_subr.c
@@ -218,15 +218,31 @@ void x25_write_internal(struct sock *sk, int frametype)
 		case X25_CLEAR_REQUEST:
 			dptr    = skb_put(skb, 3);
 			*dptr++ = frametype;
-			*dptr++ = x25->causediag.cause;
+
+			*dptr = x25->causediag.cause;
+
+			/* set bit 8, if DTE and cause != 0x00 */
+			if (!x25->neighbour->dce && *dptr != 0x00)
+				*dptr |= (unsigned char)0x80;
+
+			dptr++;
+
 			*dptr++ = x25->causediag.diagnostic;
 			break;
 
 		case X25_RESET_REQUEST:
 			dptr    = skb_put(skb, 3);
 			*dptr++ = frametype;
-			*dptr++ = 0x00;		/* XXX */
-			*dptr++ = 0x00;		/* XXX */
+
+			*dptr = 0x00;	/* cause */
+
+			/* set bit 8, if DTE and cause != 0x00 */
+			if (!x25->neighbour->dce && *dptr != 0x00)
+				*dptr |= (unsigned char)0x80;
+
+			dptr++;
+
+			*dptr++ = 0x00;	/* diagnostic */
 			break;
 
 		case X25_RR:
-- 
2.20.1