Chinaunix首页 | 论坛 | 博客
  • 博客访问: 851269
  • 博文数量: 65
  • 博客积分: 534
  • 博客等级: 中士
  • 技术积分: 885
  • 用 户 组: 普通用户
  • 注册时间: 2011-09-23 14:42
个人简介

世上没有东西可以取代坚毅的地位,才干不能,有才能而失败者比比皆是;天才不能,才华横溢又毫无进取者不胜枚举;单靠教育不能,受过教育但潦倒终生者充斥世间;惟有坚毅与果断者能够无所不能,得到成功。雷?克罗克

文章分类

全部博文(65)

文章存档

2017年(1)

2015年(4)

2014年(1)

2013年(19)

2012年(32)

2011年(8)

我的朋友

分类: 系统运维

2014-08-11 17:16:29

问题描述

之前有个同学反馈说现网有机器netstat看不了监听端口的进程与pid,当时让他改用lsof -i:PORT方式规避,没去深究。最近在核对外网监听端口时又碰到了:


定位分析

为啥有的端口ok有的不能显示?是否是监听端口范围问题?
用python -m SimpleHTTPServer测试监听不同端口,netstat都不能查出进程名pid
说明这台机器有问题,strace跟踪比较6200、24849这2个进程pid有什么不同,netstat到底做了什么?
进程pid: 6200

进程pid:24849

对比发现,netstat在处理6200这个pid时,没有读取/proc/PID/cmdline。为啥没读取呢?估计得看netstat源码了。偷懒google了一把。发现之前有人遇到过,原因是:

       netstat低版本有bug,当Socket id 大于 2^31 时,会造成无法显示进程信息。


源码解惑

有点奇怪为啥是大于2^31就不行,这个博客也没讲清楚。下载了当前系统netstat 1.6.0版源码, grep -r -i "cmdline"狂扫。在netstat.c文件找到处理cmdline的函数代码:

  1. static void prg_cache_load(void)
  2. {
  3.     char line[LINE_MAX],eacces=0;
  4.     int procfdlen,fd,cmdllen,lnamelen;
  5.     char lname[30],cmdlbuf[512],finbuf[PROGNAME_WIDTH];
  6.     long inode;
  7.     const char *cs,*cmdlp;
  8.     DIR *dirproc=NULL,*dirfd=NULL;
  9.     struct dirent *direproc,*direfd;

  10.     if (prg_cache_loaded || !flag_prg) return;
  11.     prg_cache_loaded=1;
  12.     cmdlbuf[sizeof(cmdlbuf)-1]='\0';
  13.     if (!(dirproc=opendir(PATH_PROC))) goto fail;
  14.     while (errno=0,direproc=readdir(dirproc)) {
  15. #ifdef DIRENT_HAVE_D_TYPE_WORKS
  16.     if (direproc->d_type!=DT_DIR) continue;
  17. #endif
  18.     for (cs=direproc->d_name;*cs;cs++)
  19.      if (!isdigit(*cs))
  20.         break;
  21.     if (*cs)
  22.      continue;
  23.     procfdlen=snprintf(line,sizeof(line),PATH_PROC_X_FD,direproc->d_name);
  24.     if (procfdlen<=0 || procfdlen>=sizeof(line)-5)
  25.      continue;
  26.     errno=0;
  27.     dirfd=opendir(line);
  28.     if (! dirfd) {
  29.      if (errno==EACCES)
  30.         eacces=1;
  31.      continue;
  32.     }
  33.     line[procfdlen] = '/';
  34.     cmdlp = NULL;
  35.     while ((direfd = readdir(dirfd))) {
  36. #ifdef DIRENT_HAVE_D_TYPE_WORKS
  37.      if (direfd->d_type!=DT_LNK)
  38.         continue;
  39. #endif
  40.      if (procfdlen+1+strlen(direfd->d_name)+1>sizeof(line))
  41.         continue;
  42.      memcpy(line + procfdlen - PATH_FD_SUFFl, PATH_FD_SUFF "/",
  43.          PATH_FD_SUFFl+1);
  44.      strcpy(line + procfdlen + 1, direfd->d_name);
  45.      lnamelen=readlink(line,lname,sizeof(lname)-1);
  46.             lname[lnamelen] = '\0'; /*make it a null-terminated string*/

  47.             extract_type_1_socket_inode(lname, &inode);

  48.             if (inode < 0) extract_type_2_socket_inode(lname, &inode);

  49.             if (inode < 0) continue;

  50.      if (!cmdlp) {
  51.         if (procfdlen - PATH_FD_SUFFl + PATH_CMDLINEl >=
  52.          sizeof(line) - 5)
  53.          continue;
  54.         strcpy(line + procfdlen-PATH_FD_SUFFl, PATH_CMDLINE);
  55.         fd = open(line, O_RDONLY);
  56.         if (fd < 0)
  57.          continue;
  58.         cmdllen = read(fd, cmdlbuf, sizeof(cmdlbuf) - 1);   #读取/proc/PID/cmdline
  59.         if (close(fd))
  60.          continue;
  61.         if (cmdllen == -1)
  62.          continue;
  63.         if (cmdllen < sizeof(cmdlbuf) - 1)
  64.          cmdlbuf[cmdllen]='\0';
  65.         if ((cmdlp = strrchr(cmdlbuf, '/')))
  66.          cmdlp++;
  67.         else
  68.          cmdlp = cmdlbuf;
  69.      }

  70.      snprintf(finbuf, sizeof(finbuf), "%s/%s", direproc->d_name, cmdlp);
  71.      prg_cache_add(inode, finbuf);
  72.     }
  73.     closedir(dirfd);
  74.     dirfd = NULL;
  75.     }
  76.     if (dirproc)
  77.     closedir(dirproc);
  78.     if (dirfd)
  79.     closedir(dirfd);
  80.     if (!eacces)
  81.     return;
  82.     if (prg_cache_loaded == 1) {
  83.     fail:
  84.     fprintf(stderr,_("(No info could be read for \"-p\": geteuid()=%d but you should be root.)\n"),
  85.         geteuid());
  86.     }
  87.     else
  88.     fprintf(stderr, _("(Not all processes could be identified, non-owned process info\n"
  89.              " will not be shown, you would have to be root to see it all.)\n"));
  90. }
prg_cache_load函数的第60行打开/proc/PID/cmdline, 63行读取/proc/PID/cmdline。现在需要找到这段代码没执行的原因。
回头看,如果inode<0会绕过这段代码。我们看相关的extract_type_1_socket_inode、extract_type_2_socket_inode函数。

  1. static void extract_type_1_socket_inode(const char lname[], long * inode_p) {

  2.     /* If lname is of the form "socket:[12345]", extract the "12345"
  3.        as *inode_p. Otherwise, return -1 as *inode_p.
  4.        */

  5.     if (strlen(lname) < PRG_SOCKET_PFXl+3) *inode_p = -1;
  6.     else if (memcmp(lname, PRG_SOCKET_PFX, PRG_SOCKET_PFXl)) *inode_p = -1;
  7.     else if (lname[strlen(lname)-1] != ']') *inode_p = -1;
  8.     else {
  9.         char inode_str[strlen(lname + 1)]; /* e.g. "12345" */
  10.         const int inode_str_len = strlen(lname) - PRG_SOCKET_PFXl - 1;
  11.         char *serr;

  12.         strncpy(inode_str, lname+PRG_SOCKET_PFXl, inode_str_len);
  13.         inode_str[inode_str_len] = '\0';
  14.         *inode_p = strtol(inode_str,&serr,0);
  15.         if (!serr || *serr || *inode_p < 0 || *inode_p >= INT_MAX)
  16.             *inode_p = -1;
  17.     }
  18. }
注意到末尾,当inode_p >= INT_MAX时, *inode_p指针值为-1.检查机器的INT_MAX值: getconf  INT_MAX 结果是2147483647,而pid为6200的进程socket inode号2413086211大于2147483647所以返回-1值。

  1. static void extract_type_2_socket_inode(const char lname[], long * inode_p) {

  2.     /* If lname is of the form "[0000]:12345", extract the "12345"
  3.        as *inode_p. Otherwise, return -1 as *inode_p.
  4.        */

  5.     if (strlen(lname) < PRG_SOCKET_PFX2l+1) *inode_p = -1;
  6.     else if (memcmp(lname, PRG_SOCKET_PFX2, PRG_SOCKET_PFX2l)) *inode_p = -1;
  7.     else {
  8.         char *serr;

  9.         *inode_p=strtol(lname + PRG_SOCKET_PFX2l,&serr,0);
  10.         if (!serr || *serr || *inode_p < 0 || *inode_p >= INT_MAX)
  11.             *inode_p = -1;
  12.     }
  13. }
同样extract_type_2_socket_inode也返回-1值,导致prg_cache_load函数未读取/proc/PID/cmdline文件内容。
现在可以确认: 原因是socket inode超过INT_MAX,引起netstat不识别进程名pid.

ref: %E6%9F%90%E4%BA%9B-linux-%E5%8F%91%E8%A1%8C%E7%89%88%E4%B8%AD-netstat-%E7%9A%84-bug.html
阅读(4922) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~