记一次传递文件句柄引发的血案 (续)

记一次传递文件句柄引发的血案 之后,这个 demo 又引发了一次血案,现录如下。

这次我是在 linux 上测试文件句柄的传递,linux 上并没有 STREAMS 系统,

因此是采用 unix domain socket 的 sendmsg/recvmsg 中控制消息部分来传递句柄的。

代码的主要修改部分集中于发送 fd 与接收 fd 处,一开始代码是这样的,运行良好。

spipe_fd.c

1 #define MAXLINE 128  2 #define RIGHTSLEN CMSG_LEN(sizeof(int))  3 #define CREDSLEN CMSG_LEN(sizeof(struct CREDSTRUCT))  4 #define CONTROLLEN (RIGHTSLEN+CREDSLEN)  5   6 int send_fd (int fd, int fd_to_send)  7 {  8     struct iovec iov[1];   9     struct msghdr msg;  10     struct cmsghdr *cmptr = NULL;  11     char buf[2];  12  13     iov[0].iov_base = buf;  14     iov[0].iov_len = 2;  15  16     msg.msg_iov = iov;  17     msg.msg_iovlen = 1;  18     msg.msg_name = NULL;  19     msg.msg_namelen = 0;  20     msg.msg_flags = 0;  21  22     if (fd_to_send < 0) { 23         msg.msg_control = NULL;  24         msg.msg_controllen = 0;  25         buf[1] = -fd_to_send;  26         if (buf[1] == 0) 27             buf[1] = 1;  28     } else { 29         if ((cmptr = malloc(CONTROLLEN)) == NULL) { 30             fprintf (stderr, "malloc memory failed\n");  31             return -1;  32         } 33  34         msg.msg_control = cmptr;  35         msg.msg_controllen = CONTROLLEN;  36  37         cmptr->cmsg_level = SOL_SOCKET;  38         cmptr->cmsg_type = SCM_RIGHTS;  39         cmptr->cmsg_len = CONTROLLEN;  40  41         *(int *) CMSG_DATA(cmptr) = fd_to_send;  42         buf[1] = 0;  43     } 44  45     buf[0] = 0;  46     if (sendmsg(fd, &msg, 0) != 2) { 47         free (cmptr);  48         return -1;  49     } 50  51     free (cmptr);  52     return 0;  53 }

以上是发送句柄部分,重点位于 37-39 行,设置了控制消息的类型与句柄的值。

sendmsg 中的数据消息部分,用来兼容出错的场景(出错时可以提供一个-1~-255的错误码,及一段描述信息),关键信息位于控制部分。

下面来看消息的接收:

1 int recv_fd (int fd, uid_t *uidptr, ssize_t (*userfunc) (int, const void*, size_t)) 2 { 3     struct cmsghdr *cmptr = NULL;  4     int newfd, nr, status;  5     char *ptr;  6     char buf[MAXLINE];  7     struct iovec iov[1];  8     struct msghdr msg;  9 10     status = -1; 11     newfd = -1; 12 13     for (;;) {14         iov[0].iov_base = buf; 15         iov[0].iov_len = sizeof (buf); 16 17         msg.msg_iov = iov; 18         msg.msg_iovlen = 1; 19         msg.msg_name = NULL; 20         msg.msg_namelen = 0; 21 22         if ((cmptr = malloc (CONTROLLEN)) == NULL) {23             fprintf (stderr, "malloc error\n"); 24             return -1; 25         }26 27         msg.msg_control = cmptr; 28         msg.msg_controllen = CONTROLLEN; 29 30         if ((nr = recvmsg (fd, &msg, 0)) < 0) { 31             fprintf (stderr, "recvmsg error\n"); 32             free (cmptr); 33             return -1; 34         } else if (nr == 0) {35             fprintf (stderr, "connection closed by server\n"); 36             free (cmptr); 37             return -1; 38         }39 40         for (ptr = buf; ptr < &buf[nr]; ) {41             if (*ptr ++ == 0) {42                 if (ptr != &buf[nr-1]) {43                     fprintf (stderr, "message format error"); 44                     free (cmptr); 45                     return -1; 46                 }47 48                 status = *ptr & 0xff; 49                 if (status == 0) {50                     if (msg.msg_controllen != CONTROLLEN) { 51                         fprintf (stderr, "status = 0 but no fd\n"); 52                         free (cmptr); 53                         return -1; 54                     }55 56                     newfd = *(int *) CMSG_DATA(cmptr); 57                 } else { 58                     newfd = -status; 59                 }60 61                 nr -= 2; 62             }63         }64 65         free(cmptr); 66         if (nr > 0 && (*userfunc)(STDERR_FILENO, buf, nr) != nr)67             return -1; 68 69         if (status >= 0)70             return newfd; 71     }72 73     return -1; 74 }

接收部分的重点位于 56 行,这里取得了对方传递过来的文件句柄(注意不是简单的值传递!参考上篇文章)

其它一些代码则用来处理出错信息,当出现错误时,调用 userfunc 打印错误信息 (用户一般传递 write) 。

另外接口中 uidptr 参数并没有用,这个是为将来扩展预留的。

使用之前的 demo (spipe_server.c / spipe_client.c)编译、运行,输出结果如下:

./spipe_server ./spipe_clientcreate pipe 3.43 7create temp file /tmp/outliqA3i with fd 4seek to headsend fd 4 to peerrecv fd 3, position 0create temp file /tmp/inaLr30i with fd 4source: 3 7seek to headsend fd 4recv fd 5 from peer, position 010

可以看到通过新的方式传递的文件句柄值也发生了变化(从 4 变为 3),且也需要对文件偏移进行重置,否则还会掉到之前文章说的那个坑里。

问题出现在增加一些代码来传递发送进程凭证(如uid)时,此时发送方需要传递两个控制子消息(分别表示句柄与凭证),接收方也需要处理两个子消息。

新的发送代码如下:

1 #define MAXLINE 128 2 #if defined(SCM_CREDS) // on BSD 3 #define CREDSTRUCT cmsgcred 4 #define CR_UID cmcred_uid 5 #define CREDOPT LOCAL_PEERCRED 6 #define SCM_CREDTYPE SCM_CREDS 7 #elif defined(SCM_CREDENTIALS)  // on linux 8 #define CREDSTRUCT ucred 9 #define CR_UID uid10 #define CREDOPT SO_PASSCRED11 #define SCM_CREDTYPE SCM_CREDENTIALS12 #else13 #error passing credentials is unsupported!14 #endif15 16 #define RIGHTSLEN CMSG_LEN(sizeof(int))17 #define CREDSLEN CMSG_LEN(sizeof(struct CREDSTRUCT))18 #define CONTROLLEN (RIGHTSLEN+CREDSLEN)19 20 21 int send_fd (int fd, int fd_to_send)22 {23     struct iovec iov[1]; 24     struct msghdr msg; 25     struct cmsghdr *cmptr = NULL; 26     char buf[2]; 27     struct CREDSTRUCT *credp; 28     struct cmsghdr *cmp; 29 30     iov[0].iov_base = buf; 31     iov[0].iov_len = 2; 32 33     msg.msg_iov = iov; 34     msg.msg_iovlen = 1; 35     msg.msg_name = NULL; 36     msg.msg_namelen = 0; 37     msg.msg_flags = 0; 38 39     if (fd_to_send < 0) {40         msg.msg_control = NULL; 41         msg.msg_controllen = 0; 42         buf[1] = -fd_to_send; 43         if (buf[1] == 0)44             buf[1] = 1; 45     } else {46         if ((cmptr = malloc(CONTROLLEN)) == NULL) {47             fprintf (stderr, "malloc memory failed\n"); 48             return -1; 49         }50 51         msg.msg_control = cmptr; 52         msg.msg_controllen = CONTROLLEN; 53 54         cmp = cmptr; 55         cmp->cmsg_level = SOL_SOCKET; 56         cmp->cmsg_type = SCM_RIGHTS; 57         cmp->cmsg_len = RIGHTSLEN; 58         *(int *) CMSG_DATA(cmp) = fd_to_send; 59 60         cmp = CMSG_NXTHDR(&msg, cmp); 61         cmp->cmsg_level = SOL_SOCKET; 62         cmp->cmsg_type = SCM_CREDTYPE; 63         cmp->cmsg_len = CREDSLEN; 64         credp = (struct CREDSTRUCT *) CMSG_DATA(cmp); 65 66 #  if defined(SCM_CREDENTIALS)67         // only linux need to set members of this struct !68         credp->uid = getuid (); 69         credp->gid = getegid (); 70         credp->pid = getpid (); 71 #  endif72         buf[1] = 0; 73     }74 75     buf[0] = 0; 76     if (sendmsg(fd, &msg, 0) != 2) {77         free (cmptr); 78         return -1; 79     }80 81     free (cmptr); 82     return 0; 83 }

最开始的一些宏定义,是用来区分 linux 与 bsd 上一些细节,重点在 55-64 行,这两段代码分别设置了句柄与凭证。

然后控制消息的大小 CONTROLLEN 由两部分消息的长度(RIGHTSLEN 与 CREDSLEN)累加得到,分配的内存也是这么大。

再来看接收部分:

1 int recv_fd (int fd, uid_t *uidptr, ssize_t (*userfunc) (int, const void*, size_t))  2 {  3     struct cmsghdr *cmptr = NULL;   4   5     int newfd, nr, status;   6     char *ptr;   7     char buf[MAXLINE];   8     struct iovec iov[1];   9     struct msghdr msg;  10  11     status = -1;  12     newfd = -1;  13  14     const int on = -1;  15     struct cmsghdr *cmp;  16     struct CREDSTRUCT *credp;  17     if (setsockopt (fd, SOL_SOCKET, CREDOPT, &on, sizeof(int)) < 0) { 18         fprintf (stderr, "setsockopt for %d failed\n", CREDOPT);  19         return -1;  20     } 21  22     for (;;) { 23         iov[0].iov_base = buf;  24         iov[0].iov_len = sizeof (buf);  25  26         msg.msg_iov = iov;  27         msg.msg_iovlen = 1;  28         msg.msg_name = NULL;  29         msg.msg_namelen = 0;  30  31         if ((cmptr = malloc (CONTROLLEN)) == NULL) { 32             fprintf (stderr, "malloc error\n");  33             return -1;  34         } 35  36         msg.msg_control = cmptr;  37         msg.msg_controllen = CONTROLLEN;  38  39         if ((nr = recvmsg (fd, &msg, 0)) < 0) {  40             fprintf (stderr, "recvmsg error\n");  41             free (cmptr);  42             return -1;  43         } else if (nr == 0) { 44             fprintf (stderr, "connection closed by server\n");  45             free (cmptr);  46             return -1;  47         } 48  49         for (ptr = buf; ptr < &buf[nr]; ) { 50             if (*ptr ++ == 0) { 51                 if (ptr != &buf[nr-1]) { 52                     fprintf (stderr, "message format error");  53                     free (cmptr);  54                     return -1;  55                 } 56  57                 status = *ptr & 0xff;  58                 if (status == 0) { 59                     if (msg.msg_controllen != CONTROLLEN) {  60                         fprintf (stderr, "status = 0 but no fd\n");  61                         free (cmptr);  62                         return -1;  63                     } 64  65                     for (cmp = CMSG_FIRSTHDR(&msg); cmp != NULL; cmp = CMSG_NXTHDR(&msg, cmp)) {  66                         if (cmp->cmsg_level != SOL_SOCKET) { 67                             fprintf (stderr, "ignore unknown socket level %d\n", cmp->cmsg_level);  68                             continue;  69                         } 70  71                         switch (cmp->cmsg_type) { 72                             case SCM_RIGHTS: 73                                 newfd = *(int *) CMSG_DATA(cmp);  74                                 break;  75                             case SCM_CREDTYPE: 76                                 credp = (struct CREDSTRUCT *) CMSG_DATA(cmp);  77                                 *uidptr = credp->CR_UID;  78                                 break;  79                             default: 80                                 fprintf (stderr, "ignore unknown msg type %d\n", cmp->cmsg_type);  81                                 break;  82                         } 83                     } 84                 } else {  85                     newfd = -status;  86                 } 87  88                 nr -= 2;  89             } 90         } 91  92         free(cmptr);  93         if (nr > 0 && (*userfunc)(STDERR_FILENO, buf, nr) != nr) 94             return -1;  95  96         if (status >= 0) 97             return newfd;  98     } 99 100     return -1; 101 }

重点分为两个部分:

14-20 行,设置 unix domain socket 可以接收凭证信息;

65-83 行,分别读取控制消息中的句柄与凭证信息,这里我们取了发送进程的 uid 信息作为凭证返回给上层调用者;

与发送消息类似,这里使用系统提供的 CMSG_FIRSTHDR、CMSG_NXTHDR 在控制消息中遍历各个子部分。

重新编译、运行 demo,却发现出错了:

./spipe_server ./spipe_clientcreate pipe 3.43 7create temp file /tmp/outgQY1Y4 with fd 4seek to headsend fd 4 to peerrecv fd 3, uid 500, position 0create temp file /tmp/invVgKW4 with fd 4source: 3 7seek to headconnection closed by serverrecv fd from peer failed, error -1

从输出日志看,第一次从 server 发往 client 的句柄及凭证是可以的(line 7),再之后 client 处理完消息回传时,就出错了。

首先定位出错代码位置,在 client 回传这里 (send_fd),加入一些日志:

1         if ((cmptr = malloc(CONTROLLEN)) == NULL) { 2             fprintf (stderr, "malloc memory failed\n");  3             return -1;  4         } 5  6         msg.msg_control = cmptr;  7         msg.msg_controllen = CONTROLLEN;  8  9         cmp = cmptr; 10         cmp->cmsg_level = SOL_SOCKET; 11         cmp->cmsg_type = SCM_RIGHTS; 12         cmp->cmsg_len = RIGHTSLEN; 13         *(int *) CMSG_DATA(cmp) = fd_to_send; 14         fprintf (stderr, "add fd with len %d\n", RIGHTSLEN); 15 16         cmp = CMSG_NXTHDR(&msg, cmp); 17         cmp->cmsg_level = SOL_SOCKET; 18         cmp->cmsg_type = SCM_CREDTYPE; 19         cmp->cmsg_len = CREDSLEN; 20         credp = (struct CREDSTRUCT *) CMSG_DATA(cmp); 21         fprintf (stderr, "add credential with len %d\n", CREDSLEN); 22 23 #  if defined(SCM_CREDENTIALS)24         // only linux need to set members of this struct !25         credp->uid = getuid (); 26         credp->gid = getegid (); 27         credp->pid = getpid (); 28         fprintf (stderr, "set uid %d, gid %d, pid %d\n", credp->uid, credp->gid, credp->pid);29 #  endif30         buf[1] = 0;

标黄的是新加入的输出日志,再次编译运行:

./spipe_server ./spipe_clientcreate pipe 3.43 7create temp file /tmp/outivt2Og with fd 4seek to headadd fd with len 16add credential with len 24set uid 500, gid 500, pid 12071send fd 4 to peerrecv fd 3, uid 500, position 0create temp file /tmp/inHqRwMg with fd 4source: 3 7seek to headadd fd with len 16connection closed by serverrecv fd from peer failed, error -1

可以看到,第一次传递时,这三条日志全都正确输出了,而回传时,只输出了第一条日志。

所以明显是在第一条日志与第二条日志之间的代码出了问题。左看右看,看不出这块有什么问题,难道系统提供的 CMSG_NXTHDR 会出错?

这边再加两条日志:

1         if ((cmptr = malloc(CONTROLLEN)) == NULL) { 2             fprintf (stderr, "malloc memory failed\n");  3             return -1;  4         } 5  6         msg.msg_control = cmptr;  7         msg.msg_controllen = CONTROLLEN;  8  9         cmp = cmptr; 10         cmp->cmsg_level = SOL_SOCKET; 11         cmp->cmsg_type = SCM_RIGHTS; 12         cmp->cmsg_len = RIGHTSLEN; 13         *(int *) CMSG_DATA(cmp) = fd_to_send; 14         fprintf (stderr, "add fd with len %d\n", RIGHTSLEN); 15         fprintf (stderr, "cmsghdr = %d, cmsglen = %d, after align = %d, control len = %d\n", sizeof(struct cmsghdr), CREDSLEN, CMSG_ALIGN(CREDSLEN), CONTROLLEN); 16 17         cmp = CMSG_NXTHDR(&msg, cmp); 18         fprintf (stderr, "cmp = %p\n", cmp); 19         cmp->cmsg_level = SOL_SOCKET; 20         cmp->cmsg_type = SCM_CREDTYPE; 21         cmp->cmsg_len = CREDSLEN; 22         credp = (struct CREDSTRUCT *) CMSG_DATA(cmp); 23         fprintf (stderr, "add credential with len %d\n", CREDSLEN); 24 25 #  if defined(SCM_CREDENTIALS)26         // only linux need to set members of this struct !27         credp->uid = getuid (); 28         credp->gid = getegid (); 29         credp->pid = getpid (); 30         fprintf (stderr, "set uid %d, gid %d, pid %d\n", credp->uid, credp->gid, credp->pid);31 #  endif32         buf[1] = 0;

第二条日志是主要怀疑的地方,看指针是否为空;第一条日志则是怀疑块大小计算有误,导致分配的内存不够大,指针递增时出现了范围错误,所以这里打印各种长度做验证。

再次运行后,又多了一些输出:

./spipe_server ./spipe_clientcreate pipe 3.43 7create temp file /tmp/out7UgSYZ with fd 4seek to headadd fd with len 16cmsghdr = 12, cmsglen = 24, after align = 24, control len = 40cmp = 0x9ded018add credential with len 24set uid 500, gid 500, pid 12100send fd 4 to peerrecv fd 3, uid 500, position 0create temp file /tmp/inC3nyWZ with fd 4source: 3 7seek to headadd fd with len 16cmsghdr = 12, cmsglen = 24, after align = 24, control len = 40cmp = (nil)connection closed by serverrecv fd from peer failed, error -1

神奇的地方出现了,同样的代码,相同的尺寸,第一次指针正常;第二次就为空了!

崩溃点找到了,但是还是一头雾水,看起来数据块都对齐了,计算也没毛病,难道是这个系统提供的宏 (CMSG_NXTHDR) 出问题了吗?

翻看头文件,找到这一段的定义 (我所在的系统,位于 /usr/include/bits/socket.h (L311)):

1 __EXTERN_INLINE struct cmsghdr * 2 __NTH (__cmsg_nxthdr (struct msghdr *__mhdr, struct cmsghdr *__cmsg)) 3 { 4   if ((size_t) __cmsg->cmsg_len < sizeof (struct cmsghdr)) 5     /* The kernel header does this so there may be a reason.  */ 6     return 0; 7  8   __cmsg = (struct cmsghdr *) ((unsigned char *) __cmsg 9                    + CMSG_ALIGN (__cmsg->cmsg_len));10   if ((unsigned char *) (__cmsg + 1) > ((unsigned char *) __mhdr->msg_control11                     + __mhdr->msg_controllen)12       || ((unsigned char *) __cmsg + CMSG_ALIGN (__cmsg->cmsg_len)13       > ((unsigned char *) __mhdr->msg_control + __mhdr->msg_controllen)))14     /* No more entries.  */15     return 0;16   return __cmsg;17 }

这段 INLINE 函数主要包含三个判断,

1)子消息长度小于消息头长度,返回 null;

2)下一个子消息的消息头超出消息尾部,返回null;

3)下一个子消息的消息体超出消息尾部,返回null;

直接修改系统代码不方便,将这个函数拷贝到本地并重全名为 my_cmsg_nxthdr,在各个判断下面添加日志输出:

1 struct cmsghdr *my_cmsg_nxthdr (struct msghdr *__mhdr, struct cmsghdr *__cmsg) 2 { 3   if ((size_t) __cmsg->cmsg_len < sizeof (struct cmsghdr)) { 4     /* The kernel header does this so there may be a reason.  */ 5     fprintf (stderr, "in step1\n");  6     return 0; 7   } 8  9   fprintf (stderr, "%p: cmsg_len %u, cmsg_level %d, cmsg_type %d\n", __cmsg, __cmsg->cmsg_len, __cmsg->cmsg_level, __cmsg->cmsg_type); 10   __cmsg = (struct cmsghdr *) ((unsigned char *) __cmsg + CMSG_ALIGN (__cmsg->cmsg_len));11   if ((unsigned char *) (__cmsg + 1) > ((unsigned char *) __mhdr->msg_control + __mhdr->msg_controllen)) {12       fprintf (stderr, "in step2\n"); 13       return 0; 14   }15 16   fprintf (stderr, "%p: cmsg_len %u, cmsg_level %d, cmsg_type %d\n", __cmsg, __cmsg->cmsg_len, __cmsg->cmsg_level, __cmsg->cmsg_type); 17   if (((unsigned char *) __cmsg + CMSG_ALIGN (__cmsg->cmsg_len) > ((unsigned char *) __mhdr->msg_control + __mhdr->msg_controllen))) {18     /* No more entries.  */19       fprintf (stderr, "in step3\n"); 20       fprintf (stderr, "msg len %d, after align %d, msg control %d\n", __cmsg->cmsg_len, CMSG_ALIGN(__cmsg->cmsg_len), __mhdr->msg_controllen); 21     return 0;22   }23 24   fprintf (stderr, "in final step\n"); 25   return __cmsg;26 }

为了便于根据不同的判断条件输出日志,这里对判断条件进行了拆分。

再次运行 demo,输出如下:

./spipe_server ./spipe_clientcreate pipe 3.43 7create temp file /tmp/outh7NhIs with fd 4seek to headadd fd with len 16cmsghdr = 12, cmsglen = 24, after align = 24, control len = 400x9336008: cmsg_len 16, cmsg_level 1, cmsg_type 10x9336018: cmsg_len 0, cmsg_level 0, cmsg_type 0in final stepcmp = 0x9336018add credential with len 24set uid 500, gid 500, pid 12171send fd 4 to peerrecv fd 3, uid 500, position 0create temp file /tmp/inoJMmKs with fd 4source: 3 7seek to headadd fd with len 16cmsghdr = 12, cmsglen = 24, after align = 24, control len = 400x904d008: cmsg_len 16, cmsg_level 1, cmsg_type 10x904d018: cmsg_len 500, cmsg_level 500, cmsg_type 16in step3msg len 500, after align 500, msg control 40cmp = (nil)connection closed by serverrecv fd from peer failed, error -1

原来是第三个判断出现了问题(Line 24)!

消息总长度是 16 + 24 = 40,而这里的第二个子消息单个的长度达到 500,明显越界了。

但是第二个子消息的长度明明是 24 呀,哪里跑出来的 500 呢?

而且它的其它字段也明显不对,例如消息 level 也是 500,消息类型是 16 !

初步可以确定是这块内存被弄乱了,而从前面打印的消息指针(0x904d008 与 0x904d018)看,分配的大小是没问题的,因此内存越界问题先排除掉;

其次是我们设置好的内容……等等……我们好像还没有设置第二个子消息的内容!!

……

垃圾数据!!

……

malloc 之后没有清空的垃圾数据!!

……

这也是第一次调用没问题而第二次掉坑里的原因,随着系统内存的分配回收而存在一定的随机性!

找到原因之后,修改就简单了,可以将 malloc 替换为 calloc,或者简单的加一句 memset 来清空内存:

1         if ((cmptr = malloc(CONTROLLEN)) == NULL) { 2             fprintf (stderr, "malloc memory failed\n");  3             return -1;  4         } 5  6         // important on linux, garbage data may mess cmsg_len fields,  7         // and cause CMSG_NXTHDR return null on protection. 8         memset (cmptr, 0, CONTROLLEN);  9         msg.msg_control = cmptr; 10         msg.msg_controllen = CONTROLLEN;

再次运行 demo,一切正常:

./spipe_server ./spipe_clientcreate pipe 3.43 7create temp file /tmp/outqsTYkp with fd 4seek to headadd fd with len 16cmsghdr = 12, cmsglen = 24, after align = 24, control len = 400x814c008: cmsg_len 16, cmsg_level 1, cmsg_type 10x814c018: cmsg_len 0, cmsg_level 0, cmsg_type 0in final stepcmp = 0x814c018add credential with len 24set uid 500, gid 500, pid 12207send fd 4 to peerrecv fd 3, uid 500, position 0create temp file /tmp/in3ntkip with fd 4source: 3 7seek to headadd fd with len 16cmsghdr = 12, cmsglen = 24, after align = 24, control len = 400x8389008: cmsg_len 16, cmsg_level 1, cmsg_type 10x8389018: cmsg_len 0, cmsg_level 0, cmsg_type 0in final stepcmp = 0x8389018add credential with len 24set uid 500, gid 500, pid 12208send fd 4recv fd 5, uid 500 from peer, position 010

通过这次 debug,找到了经典的 APUE 例子中的一个瑕疵 (随机性比较大,大师刚好没有遇到而已,可能你的机器也不复现)。

不过回过头来看这个场景,也不能全算在 coder 身上,我感觉系统提供的这个 CMSG_NXTHDR 宏也颇成问题:

如果我调用这个之前还没有设置下一个子消息,难道还不准我使用了么? 过多的检查反而弄巧成拙,总之一句话:差评! 哈哈~

(0)

相关推荐