/* sockdestroy_probe.c — inet_diag SOCK_DESTROY 内核可用性探针(dev 工具,不入固件/不部署)。 * * 目的:内核 4.4(R3 Pro II)是否支持 netlink SOCK_DESTROY 连接级杀 socket。NETWORK_HW_FACTS §5 待验证项。 * 安全:只对「本进程自建」的 127.0.0.1 临时 TCP 连接发 SOCK_DESTROY,绝不动设备现有连接。 * * 方法:建 loopback TCP 服务端+客户端,accept 得已连接 socket。用 inet_diag DUMP(SOCK_DIAG_BY_FAMILY) * 列出全部 TCP,按 tuple(sport/dport/src/dst)匹配出自建 socket 的 idiag_cookie(与 ss -K 同法—— * 无需 SO_COOKIE);再发 SOCK_DESTROY(tuple + dump cookie),对 accept 出的 socket recv 判是否已杀。 * 输出:SD_DUMP_DUMPED= SD_MATCH=<0|1> SD_DESTROY_RC= SD_DESTROYED=<0|1> * 结论:SD_DESTROY_RC==0 && SD_DESTROYED==1 → 内核支持;-EPERM → 需 root/CAP_NET_ADMIN;-EOPNOTSUPP/ENOENT → 不支持。 * * 用法:交叉编译后 scp 到设备 /usr/data 前台跑(root):`./sockdestroy_probe`(无副作用残留)。 */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include static int nl = -1; static void open_nl(void) { nl = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG); if (nl < 0) { perror("netlink socket"); exit(2); } struct sockaddr_nl sa; memset(&sa, 0, sizeof sa); sa.nl_family = AF_NETLINK; if (bind(nl, (struct sockaddr *)&sa, sizeof sa) < 0) { perror("bind"); exit(2); } } /* dump 全部 TCP,找匹配 tuple 的卡片,取回 cookie。返回 0=找到。 */ static int diag_find_cookie(uint16_t sport, uint16_t dport, uint32_t sip, uint32_t dip, uint32_t *cookie_c0, uint32_t *cookie_c1) { struct { struct nlmsghdr nlh; struct inet_diag_req_v2 req; } r; memset(&r, 0, sizeof r); r.nlh.nlmsg_len = sizeof r; r.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY; r.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; r.req.sdiag_family = AF_INET; r.req.sdiag_protocol = IPPROTO_TCP; r.req.idiag_states = 0xFFFFFFFF; struct sockaddr_nl sa; memset(&sa, 0, sizeof sa); sa.nl_family = AF_NETLINK; if (sendto(nl, &r, sizeof r, 0, (struct sockaddr *)&sa, sizeof sa) < 0) return -1; struct timeval tv = { 2, 0 }; setsockopt(nl, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); int dumped = 0; for (;;) { char buf[8192]; ssize_t n = recv(nl, buf, sizeof buf, 0); if (n <= 0) break; for (struct nlmsghdr *h = (struct nlmsghdr *)buf; NLMSG_OK(h, (unsigned)n); h = NLMSG_NEXT(h, n)) { if (h->nlmsg_type == NLMSG_DONE) goto done; if (h->nlmsg_type == NLMSG_ERROR) { int e = ((struct nlmsgerr *)NLMSG_DATA(h))->error; if (e) return e; continue; } if (h->nlmsg_type == SOCK_DIAG_BY_FAMILY) { struct inet_diag_msg *m = (struct inet_diag_msg *)NLMSG_DATA(h); dumped++; if (ntohs(m->id.idiag_sport) == sport && ntohs(m->id.idiag_dport) == dport && (ntohl(m->id.idiag_src[0]) == sip) && (ntohl(m->id.idiag_dst[0]) == dip)) { *cookie_c0 = m->id.idiag_cookie[0]; *cookie_c1 = m->id.idiag_cookie[1]; printf("SD_DUMPED=%d SD_MATCH=1\n", dumped); return 0; } } } } done: printf("SD_DUMPED=%d SD_MATCH=0\n", dumped); return -1; } static int sd_try(uint16_t sport, uint16_t dport, uint32_t sip, uint32_t dip, uint32_t c0, uint32_t c1, int with_cookie) { struct { struct nlmsghdr nlh; struct inet_diag_req_v2 req; } r; memset(&r, 0, sizeof r); r.nlh.nlmsg_len = sizeof r; r.nlh.nlmsg_type = SOCK_DESTROY; r.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; r.req.sdiag_family = AF_INET; r.req.sdiag_protocol = IPPROTO_TCP; r.req.idiag_states = 0xFFFFFFFF; r.req.id.idiag_sport = htons(sport); r.req.id.idiag_dport = htons(dport); r.req.id.idiag_src[0] = htonl(sip); r.req.id.idiag_dst[0] = htonl(dip); r.req.id.idiag_cookie[0] = with_cookie ? c0 : 0; r.req.id.idiag_cookie[1] = with_cookie ? c1 : 0; struct sockaddr_nl sa; memset(&sa, 0, sizeof sa); sa.nl_family = AF_NETLINK; if (sendto(nl, &r, sizeof r, 0, (struct sockaddr *)&sa, sizeof sa) < 0) return -errno; char buf[512]; struct timeval tv = { 0, 300000 }; setsockopt(nl, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); ssize_t rr = recv(nl, buf, sizeof buf, 0); if (rr < 0) return -errno; struct nlmsghdr *h = (struct nlmsghdr *)buf; if (h->nlmsg_type == NLMSG_ERROR) return ((struct nlmsgerr *)NLMSG_DATA(h))->error; return 0; } static int check_destroyed(int fd) { fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); char b[16]; ssize_t r = recv(fd, b, sizeof b, 0); if (r == 0) return 1; if (r < 0) return (errno == ECONNRESET || errno == ECONNABORTED) ? 1 : 0; return 0; } int main(void) { int lfd = socket(AF_INET, SOCK_STREAM, 0); int cfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in addr; memset(&addr, 0, sizeof addr); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); addr.sin_port = htons(0); int one = 1; setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one); bind(lfd, (struct sockaddr *)&addr, sizeof addr); listen(lfd, 8); socklen_t alen = sizeof addr; getsockname(lfd, (struct sockaddr *)&addr, &alen); connect(cfd, (struct sockaddr *)&addr, sizeof addr); int sfd = accept(lfd, NULL, NULL); struct sockaddr_in sa, sc; socklen_t sla = sizeof sa, slc = sizeof sc; getsockname(sfd, (struct sockaddr *)&sa, &sla); getpeername(sfd, (struct sockaddr *)&sc, &slc); uint16_t sport = ntohs(sa.sin_port); uint16_t dport = ntohs(sc.sin_port); uint32_t sip = ntohl(sa.sin_addr.s_addr); uint32_t dip = ntohl(sc.sin_addr.s_addr); open_nl(); uint32_t c0 = 0, c1 = 0; int found = (diag_find_cookie(sport, dport, sip, dip, &c0, &c1) == 0); if (found) { /* 主测靠谱路径:cookie 版(与 ss -K 同法;无需 SO_COOKIE)。 */ int rc_c = sd_try(sport, dport, sip, dip, c0, c1, 1); int dead_c = check_destroyed(sfd); printf("SD_DESTROY(cookie) rc=%d destroyed=%d\n", rc_c, dead_c); if (dead_c) { printf("CONCL=SOCK_DESTROY_SUPPORTED\n"); } else if (rc_c == -EPERM) { printf("CONCL=SOCK_DESTROY_NEED_CAP(rc=-EPERM; 需 root/CAP_NET_ADMIN)\n"); } else if (rc_c == 0) { printf("CONCL=SOCK_DESTROY_NOOP(rc=0 但未杀; 内核未真正销毁——视为不可用)\n"); } else { printf("CONCL=SOCK_DESTROY_UNSUPPORTED(rc=%d)\n", rc_c); } /* 附带:tuple+cookie=0 结果(诊断参考) */ int rc_t = sd_try(sport, dport, sip, dip, c0, c1, 0); printf("SD_DESTROY(tuple,cookie=0) rc=%d destroyed=%d\n", rc_t, check_destroyed(sfd)); } else { printf("CONCL=SOCK_DESTROY_UNKNOWN(dump no match)\n"); } close(cfd); close(sfd); close(lfd); return 0; }