diff --git a/criu/sk-inet.c b/criu/sk-inet.c index 92f53e5697..8bd5356e7b 100644 --- a/criu/sk-inet.c +++ b/criu/sk-inet.c @@ -32,6 +32,7 @@ #include "rst-malloc.h" #include "sockets.h" #include "sk-inet.h" +#include "sysctl.h" #include "protobuf.h" #include "util.h" #include "namespaces.h" @@ -125,6 +126,8 @@ static int can_dump_ipproto(unsigned int ino, int proto, int type) case IPPROTO_TCP: case IPPROTO_UDP: case IPPROTO_UDPLITE: + case IPPROTO_ICMP: + case IPPROTO_ICMPV6: break; default: pr_err("Unsupported proto %d for socket %x\n", proto, ino); @@ -575,6 +578,26 @@ static int do_dump_one_inet_fd(int lfd, u32 id, const struct fd_parms *p, int fa if (dump_socket_opts(lfd, &skopts)) goto err; + if (type == SOCK_DGRAM && (proto == IPPROTO_ICMP || proto == IPPROTO_ICMPV6)) { + char buffer[16]; + + struct sysctl_req req[] = { + { "net/ipv4/ping_group_range", &buffer, CTL_STR(16) }, + }; + + ret = sysctl_op(req, ARRAY_SIZE(req), CTL_READ, CLONE_NEWNET); + if (ret < 0) { + pr_perror("Failed to read ping group range"); + goto err; + } + + buffer[strlen(buffer)] = '\0'; + + ie.ping_grp_range = xstrdup(buffer); + if (!ie.ping_grp_range) + goto err; + } + pr_info("Dumping inet socket at %d\n", p->fd); show_one_inet("Dumping", sk); show_one_inet_img("Dumped", &ie); @@ -625,6 +648,7 @@ static int do_dump_one_inet_fd(int lfd, u32 id, const struct fd_parms *p, int fa xfree(ie.src_addr); xfree(ie.dst_addr); xfree(ie.ifname); + xfree(ie.ping_grp_range); return err; } @@ -875,6 +899,20 @@ static int open_inet_sk(struct file_desc *d, int *new_fd) if (run_setsockcreatecon(fle->fe)) return -1; + if (ie->type == SOCK_DGRAM && (ie->proto == IPPROTO_ICMP || ie->proto == IPPROTO_ICMPV6)) { + if (strlen(ie->ping_grp_range) > 1) { + int ret; + struct sysctl_req req[] = { + { "net/ipv4/ping_group_range", ie->ping_grp_range, CTL_STR(strlen(ie->ping_grp_range)) }, + }; + ret = sysctl_op(req, ARRAY_SIZE(req), CTL_WRITE, CLONE_NEWNET); + if (ret < 0) { + pr_perror("Failed to set ping_group_range"); + return -1; + } + } + } + sk = socket(ie->family, ie->type, ie->proto); if (sk < 0) { pr_perror("Can't create inet socket"); @@ -915,8 +953,9 @@ static int open_inet_sk(struct file_desc *d, int *new_fd) } if (ie->src_port) { - if (inet_bind(sk, ii)) - goto err; + if (ie->proto != IPPROTO_ICMP && ie->proto != IPPROTO_ICMPV6) + if (inet_bind(sk, ii)) + goto err; } /* diff --git a/criu/sockets.c b/criu/sockets.c index f9ce999bed..0affccad02 100644 --- a/criu/sockets.c +++ b/criu/sockets.c @@ -65,7 +65,7 @@ const char *socket_proto_name(unsigned int proto, char *nm, size_t size) [IPPROTO_IPV6] = __stringify_1(IPPROTO_IPV6), [IPPROTO_RSVP] = __stringify_1(IPPROTO_RSVP), [IPPROTO_GRE] = __stringify_1(IPPROTO_GRE), [IPPROTO_ESP] = __stringify_1(IPPROTO_ESP), [IPPROTO_AH] = __stringify_1(IPPROTO_AH), [IPPROTO_UDPLITE] = __stringify_1(IPPROTO_UDPLITE), - [IPPROTO_RAW] = __stringify_1(IPPROTO_RAW), + [IPPROTO_RAW] = __stringify_1(IPPROTO_RAW), [IPPROTO_ICMPV6] = __stringify_1(IPPROTO_ICMPV6), }; return __socket_const_name(nm, size, protos, ARRAY_SIZE(protos), proto); } @@ -131,10 +131,12 @@ enum socket_cl_bits { INET_UDP_CL_BIT, INET_UDPLITE_CL_BIT, INET_RAW_CL_BIT, + INET_ICMP_CL_BIT, INET6_TCP_CL_BIT, INET6_UDP_CL_BIT, INET6_UDPLITE_CL_BIT, INET6_RAW_CL_BIT, + INET6_ICMP_CL_BIT, UNIX_CL_BIT, PACKET_CL_BIT, _MAX_CL_BIT, @@ -161,6 +163,8 @@ static inline enum socket_cl_bits get_collect_bit_nr(unsigned int family, unsign return INET_UDPLITE_CL_BIT; if (proto == IPPROTO_RAW) return INET_RAW_CL_BIT; + if (proto == IPPROTO_ICMP) + return INET_ICMP_CL_BIT; } if (family == AF_INET6) { if (proto == IPPROTO_TCP) @@ -171,6 +175,8 @@ static inline enum socket_cl_bits get_collect_bit_nr(unsigned int family, unsign return INET6_UDPLITE_CL_BIT; if (proto == IPPROTO_RAW) return INET6_RAW_CL_BIT; + if (proto == IPPROTO_ICMPV6) + return INET6_ICMP_CL_BIT; } pr_err("Unknown pair family %d proto %d\n", family, proto); @@ -282,6 +288,12 @@ void preload_socket_modules(void) req.r.i.sdiag_protocol = IPPROTO_RAW; probe_diag(nl, &req, -ENOENT); + req.r.i.sdiag_protocol = IPPROTO_ICMP; + probe_diag(nl, &req, -ENOENT); + + req.r.i.sdiag_protocol = IPPROTO_ICMPV6; + probe_diag(nl, &req, -ENOENT); + close(nl); pr_info("Done probing\n"); } @@ -773,6 +785,10 @@ static int inet_receive_one(struct nlmsghdr *h, struct ns_id *ns, void *arg) case IPPROTO_RAW: type = SOCK_RAW; break; + case IPPROTO_ICMP: + case IPPROTO_ICMPV6: + type = SOCK_DGRAM; + break; default: BUG_ON(1); return -1; @@ -797,7 +813,7 @@ static int collect_err(int err, struct ns_id *ns, void *arg) char family[32], proto[32]; char msg[256]; - snprintf(msg, sizeof(msg), "Sockects collect procedure family %s proto %s", + snprintf(msg, sizeof(msg), "Sockets collect procedure family %s proto %s", socket_family_name(gr->family, family, sizeof(family)), socket_proto_name(gr->protocol, proto, sizeof(proto))); @@ -905,6 +921,13 @@ int collect_sockets(struct ns_id *ns) if (tmp) err = tmp; + /* Collect IPv4 ICMP sockets */ + req.r.i.sdiag_family = AF_INET; + req.r.i.sdiag_protocol = IPPROTO_ICMP; + req.r.i.idiag_ext = 0; + req.r.i.idiag_states = -1; /* All */ + set_collect_bit(req.r.n.sdiag_family, req.r.n.sdiag_protocol); + /* Collect IPv6 TCP sockets */ req.r.i.sdiag_family = AF_INET6; req.r.i.sdiag_protocol = IPPROTO_TCP; @@ -944,6 +967,13 @@ int collect_sockets(struct ns_id *ns) if (tmp) err = tmp; + /* Collect IPv6 ICMP sockets */ + req.r.i.sdiag_family = AF_INET6; + req.r.i.sdiag_protocol = IPPROTO_ICMPV6; + req.r.i.idiag_ext = 0; + req.r.i.idiag_states = -1; /* All */ + set_collect_bit(req.r.n.sdiag_family, req.r.n.sdiag_protocol); + req.r.p.sdiag_family = AF_PACKET; req.r.p.sdiag_protocol = 0; req.r.p.pdiag_show = PACKET_SHOW_INFO | PACKET_SHOW_MCLIST | PACKET_SHOW_FANOUT | PACKET_SHOW_RING_CFG; diff --git a/images/sk-inet.proto b/images/sk-inet.proto index 2c709e0181..d9cec562b8 100644 --- a/images/sk-inet.proto +++ b/images/sk-inet.proto @@ -58,4 +58,5 @@ message inet_sk_entry { optional uint32 ns_id = 18; optional sk_shutdown shutdown = 19; optional tcp_opts_entry tcp_opts = 20; + optional string ping_grp_range = 21; } diff --git a/test/zdtm/lib/ns.c b/test/zdtm/lib/ns.c index 3c0dbdeb80..e0e8a6e3b1 100644 --- a/test/zdtm/lib/ns.c +++ b/test/zdtm/lib/ns.c @@ -20,6 +20,7 @@ #include #include "zdtmtst.h" +#include "sysctl.h" #include "ns.h" int criu_status_in = -1, criu_status_in_peer = -1, criu_status_out = -1; @@ -157,6 +158,17 @@ static int prepare_mntns(void) return 0; } +static int set_ping_group_range(void) +{ + // Allow GIDs 0-58468 to open an unprivileged ICMP socket + if (sysctl_write_str("/proc/sys/net/ipv4/ping_group_range", "0 58468")) { + fprintf(stderr, "sysctl_write_str() failed: %m\n"); + return -1; + } + + return 0; +} + static int prepare_namespaces(void) { if (setuid(0) || setgid(0) || setgroups(0, NULL)) { @@ -332,6 +344,10 @@ int ns_init(int argc, char **argv) if (create_timens()) exit(1); + if (set_ping_group_range() < 0) { + exit(1); + } + if (init_notify()) { fprintf(stderr, "Can't init pre-dump notification: %m"); exit(1); diff --git a/test/zdtm/lib/sysctl.c b/test/zdtm/lib/sysctl.c index 9583ec3df5..6f0d3ac56c 100644 --- a/test/zdtm/lib/sysctl.c +++ b/test/zdtm/lib/sysctl.c @@ -57,3 +57,30 @@ int sysctl_write_int(const char *name, int val) close(fd); return ret; } + +int sysctl_write_str(const char *name, const char *val) +{ + int fd; + int ret; + char buf[16]; + + fd = open(name, O_WRONLY); + if (fd < 0) { + pr_perror("Can't open %s", name); + return fd; + } + + sprintf(buf, "%s\n", val); + + ret = write(fd, buf, strlen(buf)); + if (ret < 0) { + pr_perror("Can't write %s into %s", val, name); + ret = -errno; + goto err; + } + + ret = 0; +err: + close(fd); + return ret; +} diff --git a/test/zdtm/lib/sysctl.h b/test/zdtm/lib/sysctl.h index 67129102fe..b47829fc9f 100644 --- a/test/zdtm/lib/sysctl.h +++ b/test/zdtm/lib/sysctl.h @@ -3,5 +3,6 @@ extern int sysctl_read_int(const char *name, int *data); extern int sysctl_write_int(const char *name, int val); +extern int sysctl_write_str(const char *name, const char *val); #endif diff --git a/test/zdtm/static/Makefile b/test/zdtm/static/Makefile index 71a1b6a535..e0409f6f84 100644 --- a/test/zdtm/static/Makefile +++ b/test/zdtm/static/Makefile @@ -35,6 +35,8 @@ TST_NOFILE := \ socket_udp-corked \ socket6_udp \ socket_udp_shutdown \ + socket_icmp \ + socket6_icmp \ sk-freebind \ sk-freebind-false \ socket_udplite \ diff --git a/test/zdtm/static/socket6_icmp.c b/test/zdtm/static/socket6_icmp.c new file mode 100644 index 0000000000..5e0ec7bfe4 --- /dev/null +++ b/test/zdtm/static/socket6_icmp.c @@ -0,0 +1,95 @@ +#include "zdtmtst.h" + +const char *test_doc = "static test for IP6/ICMP socket\n"; +const char *test_author = "समीर सिंह Sameer Singh \n"; + +/* Description: + * Send a ping to localhost using IP6/ICMP socket + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PACKET_SIZE 64 +#define RECV_TIMEOUT 1 + +static int echo_id = 1234; + +int main(int argc, char **argv) +{ + int ret, sock, seq = 0, recv_len = 0; + char packet[PACKET_SIZE], recv_packet[PACKET_SIZE]; + + struct timeval tv; + struct icmp6_hdr icmp_header, *icmp_reply; + struct sockaddr_in6 addr, recv_addr; + socklen_t addr_len; + + test_init(argc, argv); + + sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); + if (sock < 0) { + pr_perror("Can't create socket"); + return 1; + } + + tv.tv_sec = RECV_TIMEOUT; + tv.tv_usec = 0; + if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { + pr_perror("Can't set socket option"); + return 1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + inet_pton(AF_INET6, "::1", &addr.sin6_addr); + + memset(&icmp_header, 0, sizeof(icmp_header)); + icmp_header.icmp6_type = ICMP6_ECHO_REQUEST; + icmp_header.icmp6_code = 0; + icmp_header.icmp6_id = echo_id; + icmp_header.icmp6_seq = seq; + + memcpy(packet, &icmp_header, sizeof(icmp_header)); + memset(packet + sizeof(icmp_header), 0xa5, + PACKET_SIZE - sizeof(icmp_header)); + + test_daemon(); + test_waitsig(); + + ret = sendto(sock, packet, PACKET_SIZE, 0, + (struct sockaddr *)&addr, sizeof(addr)); + + if (ret < 0) { + pr_perror("Can't send"); + return 1; + } + + addr_len = sizeof(recv_addr); + + recv_len = recvfrom(sock, recv_packet, sizeof(recv_packet), 0, + (struct sockaddr *)&recv_addr, &addr_len); + + if (recv_len < 0) { + pr_perror("Can't recv"); + return 1; + } + + icmp_reply = (struct icmp6_hdr *)recv_packet; + + if (icmp_reply->icmp6_type != ICMP6_ECHO_REPLY) { + fail("Got no ICMP_ECHO_REPLY"); + return 1; + } + + close(sock); + pass(); + return 0; +} diff --git a/test/zdtm/static/socket6_icmp.desc b/test/zdtm/static/socket6_icmp.desc new file mode 100644 index 0000000000..7b6fcbc22b --- /dev/null +++ b/test/zdtm/static/socket6_icmp.desc @@ -0,0 +1 @@ +{'flavor': 'h ns'} \ No newline at end of file diff --git a/test/zdtm/static/socket_icmp.c b/test/zdtm/static/socket_icmp.c new file mode 100644 index 0000000000..bf47a8cec2 --- /dev/null +++ b/test/zdtm/static/socket_icmp.c @@ -0,0 +1,96 @@ +#include "zdtmtst.h" + +const char *test_doc = "static test for ICMP socket\n"; +const char *test_author = "समीर सिंह Sameer Singh \n"; + +/* Description: + * Send a ping to localhost using ICMP socket + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PACKET_SIZE 64 +#define RECV_TIMEOUT 1 + +static int echo_id = 1234; + +int main(int argc, char **argv) +{ + int ret, sock, seq = 0; + char packet[PACKET_SIZE], recv_packet[PACKET_SIZE]; + + struct timeval tv; + struct icmphdr icmp_header, *icmp_reply; + struct sockaddr_in addr, recv_addr; + socklen_t addr_len; + + test_init(argc, argv); + + sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_ICMP); + if (sock < 0) { + pr_perror("Can't create socket"); + return 1; + } + + tv.tv_sec = RECV_TIMEOUT; + tv.tv_usec = 0; + if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { + pr_perror("Can't set socket option"); + return 1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + + memset(&icmp_header, 0, sizeof(icmp_header)); + icmp_header.type = ICMP_ECHO; + icmp_header.code = 0; + icmp_header.un.echo.id = echo_id; + icmp_header.un.echo.sequence = seq; + + memcpy(packet, &icmp_header, sizeof(icmp_header)); + memset(packet + sizeof(icmp_header), 0xa5, + PACKET_SIZE - sizeof(icmp_header)); + + test_daemon(); + test_waitsig(); + + ret = sendto(sock, packet, PACKET_SIZE, 0, + (struct sockaddr *)&addr, sizeof(addr)); + + if (ret < 0) { + fail("Can't send"); + return 1; + } + + addr_len = sizeof(recv_addr); + + ret = recvfrom(sock, recv_packet, sizeof(recv_packet), 0, + (struct sockaddr *)&recv_addr, &addr_len); + + if (ret < 0) { + fail("Can't recv"); + return 1; + } + + icmp_reply = (struct icmphdr *)recv_packet; + + if (icmp_reply->type != ICMP_ECHOREPLY) { + fail("Got no ICMP_ECHO_REPLY"); + return 1; + } + + close(sock); + + pass(); + return 0; +} diff --git a/test/zdtm/static/socket_icmp.desc b/test/zdtm/static/socket_icmp.desc new file mode 100644 index 0000000000..7b6fcbc22b --- /dev/null +++ b/test/zdtm/static/socket_icmp.desc @@ -0,0 +1 @@ +{'flavor': 'h ns'} \ No newline at end of file diff --git a/test/zdtm_ct.c b/test/zdtm_ct.c index 44316893da..b598cfe06d 100644 --- a/test/zdtm_ct.c +++ b/test/zdtm_ct.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -91,6 +92,33 @@ static int create_timens(void) return 0; } +static int sysctl_write_str(const char *name, const char *val) +{ + int fd; + int ret; + char buf[16]; + + fd = open(name, O_WRONLY); + if (fd < 0) { + fprintf(stderr, "Can't open %s: %m\n", name); + return fd; + } + + sprintf(buf, "%s\n", val); + + ret = write(fd, buf, strlen(buf)); + if (ret < 0) { + fprintf(stderr, "Can't write %s into %s: %m\n", val, name); + ret = -errno; + goto err; + } + + ret = 0; +err: + close(fd); + return ret; +} + int main(int argc, char **argv) { uid_t uid; @@ -112,6 +140,9 @@ int main(int argc, char **argv) if (!uid) { if (create_timens()) exit(1); + // Allow GIDs 0-58468 to open an unprivileged ICMP socket + if (sysctl_write_str("/proc/sys/net/ipv4/ping_group_range", "0 58468")) + exit(1); if (mount(NULL, "/", NULL, MS_REC | MS_SLAVE, NULL)) { fprintf(stderr, "mount(/, S_REC | MS_SLAVE)): %m"); return 1;