From 215ef618a4aa1b27af736f48e17a1a6d4671c540 Mon Sep 17 00:00:00 2001
From: Luo Longjun <luolongjun@huawei.com>
Date: Mon, 7 Jun 2021 11:50:42 +0800
Subject: [PATCH 20/54] unix socket: add support for unix stream socket

When dump unix stream socket with external connections,
we will tell kernel to turn repair mode on for this sock.
And then kernel will keep this sock before restoring it.
In this process, the other socket which communicates with
this sock in repair mode will get EAGAIN or blocked.

Signed-off-by: Luo Longjun <luolongjun@huawei.com>

fix unix socket dump and restore err

Fix name-less unix socket dump and restore problem.

Signed-off-by: Jingxian He <hejingxian@huawei.com>

unix socket:ignore repair error from kernel

leave error for applications to deal with.

Signed-off-by: Luo Longjun <luolongjun@huawei.com>

update
---
 criu/cr-dump.c         |   1 +
 criu/include/sockets.h |   1 +
 criu/sk-unix.c         | 105 +++++++++++++++++++++++++++++++++++++----
 images/sk-unix.proto   |   1 +
 4 files changed, 99 insertions(+), 9 deletions(-)

diff --git a/criu/cr-dump.c b/criu/cr-dump.c
index 9ba27a2..2bbcef3 100644
--- a/criu/cr-dump.c
+++ b/criu/cr-dump.c
@@ -1715,6 +1715,7 @@ static int cr_dump_finish(int ret)
 
 	cr_plugin_fini(CR_PLUGIN_STAGE__DUMP, ret);
 	cgp_fini();
+	unix_stream_unlock(ret);
 
 	if (!ret) {
 		/*
diff --git a/criu/include/sockets.h b/criu/include/sockets.h
index 74c5ae4..c9cf427 100644
--- a/criu/include/sockets.h
+++ b/criu/include/sockets.h
@@ -43,6 +43,7 @@ extern int add_fake_unix_queuers(void);
 extern int fix_external_unix_sockets(void);
 extern int prepare_scms(void);
 extern int unix_note_scm_rights(int id_for, uint32_t *file_ids, int *fds, int n_ids);
+extern void unix_stream_unlock(int ret);
 
 extern struct collect_image_info netlink_sk_cinfo;
 
diff --git a/criu/sk-unix.c b/criu/sk-unix.c
index 00d09cc..14e8adc 100644
--- a/criu/sk-unix.c
+++ b/criu/sk-unix.c
@@ -72,6 +72,7 @@ struct unix_sk_desc {
 	char			*name;
 	unsigned int		nr_icons;
 	unsigned int		*icons;
+	int					repair_ino;
 
 	unsigned int		vfs_dev;
 	unsigned int		vfs_ino;
@@ -92,6 +93,11 @@ struct unix_sk_desc {
 	UnixSkEntry		*ue;
 };
 
+struct unix_stream_extern_socket_desc {
+	struct list_head	list;
+	int fd;
+};
+
 /*
  * The mutex_ghost is accessed from different tasks,
  * so make sure it is in shared memory.
@@ -99,6 +105,7 @@ struct unix_sk_desc {
 static mutex_t *mutex_ghost;
 
 static LIST_HEAD(unix_sockets);
+static LIST_HEAD(unix_stream_external_sockets);
 static LIST_HEAD(unix_ghost_addr);
 
 static int unix_resolve_name(int lfd, uint32_t id, struct unix_sk_desc *d,
@@ -117,6 +124,26 @@ struct unix_sk_listen_icon {
 
 static struct unix_sk_listen_icon *unix_listen_icons[SK_HASH_SIZE];
 
+static int unix_stream_repair_on(int fd)
+{
+	int ret, aux = 1;
+	ret = setsockopt(fd, SOL_TCP, TCP_REPAIR_OPTIONS, &aux, sizeof(aux));
+	if (ret < 0)
+		pr_err("Can't turn repair mod for unix stream on. \n");
+
+	return ret;
+}
+
+static int unix_stream_repair_off(int fd)
+{
+	int ret, aux = 0;
+	ret = setsockopt(fd, SOL_TCP, TCP_REPAIR_OPTIONS, &aux, sizeof(aux));
+	if (ret < 0)
+		pr_err("Can't turn repair mod for unix stream off. \n");
+
+	return ret;
+}
+
 static struct unix_sk_listen_icon *lookup_unix_listen_icons(unsigned int peer_ino)
 {
 	struct unix_sk_listen_icon *ic;
@@ -338,6 +365,8 @@ static int dump_one_unix_fd(int lfd, uint32_t id, const struct fd_parms *p)
 	FilePermsEntry *perms;
 	FownEntry *fown;
 	void *m;
+	unsigned int len;
+	int ret;
 
 	m = xmalloc(sizeof(UnixSkEntry) +
 		    sizeof(SkOptsEntry) +
@@ -431,6 +460,31 @@ static int dump_one_unix_fd(int lfd, uint32_t id, const struct fd_parms *p)
 			goto err;
 		}
 
+		if (peer->name && ue->type == SOCK_STREAM) {
+			struct unix_stream_extern_socket_desc *d;
+
+			/* Attention: used for upgrade in the same machine
+			 * May in conflict with original usage
+			 */
+			pr_info("set %d unix stream repair on \n", lfd);
+			ret = unix_stream_repair_on(lfd);
+			if (ret < 0)
+				goto err;
+
+			d = xzalloc(sizeof(*d));
+			if (!d)
+				goto err;
+
+			d->fd = dup(lfd);
+			pr_info("add %d into unix_stream_external_sockets \n", d->fd);
+			list_add_tail(&d->list, &unix_stream_external_sockets);
+
+			len = sizeof(ue->repair_ino);
+			ret = getsockopt(lfd, SOL_TCP, TCP_REPAIR_OPTIONS, &ue->repair_ino, &len);
+			if (ret < 0)
+				goto err;
+		}
+
 		/*
 		 * Peer should have us as peer or have a name by which
 		 * we can access one.
@@ -810,16 +864,18 @@ static int __dump_external_socket(struct unix_sk_desc *sk,
 		return -1;
 	}
 
-	if (peer->type != SOCK_DGRAM) {
-		show_one_unix("Ext stream not supported", peer);
-		pr_err("Can't dump half of stream unix connection.\n");
+	if (peer->type != SOCK_DGRAM &&
+		peer->type != SOCK_STREAM) {
+		show_one_unix("Ext unix type not supported", peer);
+		pr_err("Can't dump this kind of unix connection.\n");
 		return -1;
 	}
 
-	if (!peer->name) {
+	/* part 1: prevent NULL pointer oops */
+	if (!peer->name && !sk->name) {
 		show_one_unix("Ext dgram w/o name", peer);
+		show_one_unix("Ext dgram w/o name", sk);
 		pr_err("Can't dump name-less external socket.\n");
-		pr_err("%d\n", sk->fd);
 		return -1;
 	}
 
@@ -866,7 +922,7 @@ int fix_external_unix_sockets(void)
 
 		fd_id_generate_special(NULL, &e.id);
 		e.ino		= sk->sd.ino;
-		e.type		= SOCK_DGRAM;
+		e.type		= sk->type;
 		e.state		= TCP_LISTEN;
 		e.name.data	= (void *)sk->name;
 		e.name.len	= (size_t)sk->namelen;
@@ -893,6 +949,19 @@ err:
 	return -1;
 }
 
+void unix_stream_unlock(int ret)
+{
+	struct unix_stream_extern_socket_desc *d;
+	pr_debug("Unlocking unix stream sockets\n");
+	list_for_each_entry(d, &unix_stream_external_sockets, list) {
+		if (ret) {
+			pr_debug("unlock fd %d \n", d->fd);
+			unix_stream_repair_off(d->fd);
+		}
+		close_safe(&d->fd);
+	}
+}
+
 struct unix_sk_info {
 	UnixSkEntry		*ue;
 	struct list_head	list;
@@ -1278,6 +1347,7 @@ static int post_open_standalone(struct file_desc *d, int fd)
 	struct unix_sk_info *peer;
 	struct sockaddr_un addr;
 	int cwd_fd = -1, root_fd = -1, ns_fd = -1;
+	int ret, value;
 
 	ui = container_of(d, struct unix_sk_info, d);
 	BUG_ON((ui->flags & (USK_PAIR_MASTER | USK_PAIR_SLAVE)) ||
@@ -1335,7 +1405,22 @@ static int post_open_standalone(struct file_desc *d, int fd)
 	 * while we're connecting in sake of ghost sockets.
 	 */
 	mutex_lock(mutex_ghost);
-	if (connect(fd, (struct sockaddr *)&addr, sizeof(addr.sun_family) + len) < 0) {
+
+	/* we handle unix stream with external connections here */
+	if (peer->name && ui->ue->type == SOCK_STREAM) {
+		value = ui->ue->repair_ino;
+		ret = setsockopt(fd, SOL_TCP, TCP_REPAIR, &value, sizeof(value));
+		if (ret < 0) {
+		/* permit the unix sk resume successfully when the peer has been
+		 * closed, just warn here */
+			pr_warn("Can't repair %d socket\n", value);
+		}
+
+		ret = unix_stream_repair_off(fd);
+		if (ret < 0) {
+			goto err_revert_and_exit;
+		}
+	} else if (connect(fd, (struct sockaddr *)&addr, sizeof(addr.sun_family) + len) < 0) {
 		pr_perror("Can't connect %d socket", ui->ue->ino);
 		goto err_revert_and_exit;
 	}
@@ -2037,8 +2122,10 @@ static int init_unix_sk_info(struct unix_sk_info *ui, UnixSkEntry *ue)
 		}
 
 		ui->name = (void *)ue->name.data;
-	} else
-		ui->name = NULL;
+	} else {
+		/* part 2: prevent NULL pointer oops */
+		ui->name = "";
+	}
 	ui->name_dir = (void *)ue->name_dir;
 
 	ui->flags		= 0;
diff --git a/images/sk-unix.proto b/images/sk-unix.proto
index 2a3a7cc..610080a 100644
--- a/images/sk-unix.proto
+++ b/images/sk-unix.proto
@@ -52,4 +52,5 @@ message unix_sk_entry {
 	optional uint32			ns_id		= 16;
 	optional sint32			mnt_id		= 17 [default = -1];
 	/* Please, don't use field with number 18. */
+	required sint32			repair_ino  = 19;
 }
-- 
2.33.1

