当前位置: 首页 > 科技观察

netstat命令简易版

时间:2023-03-12 01:01:02 科技观察

netstatGo语言实现netstat用go语言实现什么操作?本文从netstat原理出发,详细讲解这种做法。netstat的工作原理netstat命令是linux系统中检查网络状态的命令。比如我们可以通过netstat\-ntlp|查看监听8080端口的进程grep8080.netstat的工作原理如下:通过读取/proc/net/tcp和/proc/net/tcp6文件,获取socket本地地址、本地端口、远程地址、远程端口、状态、inode等信息并然后扫描所有/proc/[pid]/fd目录socket文件描述符,建立inode到processpid的映射,根据pid读取/proc/[pid]/cmdline文件,获取processcommand和启动参数,按照步骤2和3进行获取对应我们可以做一个测试来验证整个过程中socket相关的进程信息。首先使用nc命令监听8090端口:nc-l8090找到上面nc进程的pid,查看该进程所有打开的文件描述符:vagrant@vagrant:/proc/25556/fd$ls-alhtotal0dr-x------2vagrantvagrant0Nov1812:21.dr-xr-xr-x9vagrantvagrant0Nov1812:20..lrwx-----1vagrantvagrant64Nov1812:210->/dev/pts/1lrwx-----1vagrantvagrant64Nov1812:211->/dev/pts/1lrwx------1vagrantvagrant64Nov1812:212->/dev/pts/1lrwx------1vagrantvagrant64Nov1812:213->socket:[2226056]在上面列出的所有文件描述中,socket:[2226056]是nc命令监听在8090端口创建的socket,其中2226056是socket的inode。Accordingtotheinodenumber,wechecktherecordinformationcorrespondingto/proc/net/tcp,where1F9Aisthelocalportnumber,whichisexactly8090whenconvertedtodecimal:vagrant@vagrant:/proc/25556/fd$cat/proc/net/tcp|grep22260561:00000000:1F9A00000000:00000A00000000:0000000000:00000000000000001000022260561000000000000000010000100根据进程id,我们查看进程名称和启动参数:vagrant@vagrant:/proc/25556/fd$cat/proc/25556/cmdlinenc-l8090下面我们看下/proc/net/tcpfileformat./proc/net/tcpfileformatThe/proc/net/tcpfilewillfirstlistalllisteningTCPsockets,andthenlistallestablishedTCPsockets.我们通过head\-n5/proc/net/tcp命令查看该文件头五行:sllocal_addressrem_addresssttx_queuerx_queuetrtm->whenretrnsmtuidtimeoutinode0:0100007F:001900000000:00000A00000000:0000000000:0000000000000000002227910000000000000000100001001:00000000:1FBB00000000:00000A00000000:0000000000:0000000000000000002120510000000000000000100001002:00000000:26FB00000000:00000A00000000:0000000000:0000000000000000002120310000000000000000100001003:00000000:26FD00000000:00000A00000000:0000000000:000000000000000000212011000000000000000010000100每一行各个字段解释说明如下,由于太长分为三部分说明:第一部分:46:010310AC:9C4C030310AC:177001||||||-->Connectionstatus,expressedinhexadecimal,seethefollowingdescriptionforspecificvalues|||||------>RemoteTCPportnumber,hostbyteorder,expressedinhexadecimal||||------------->RemoteIPv4address,networkbyteorder,hexadecimalrepresentation|||------------------->LocalTCPportnumber,hostByteorder,hexadecimalrepresentation||--------------------------->localIPv4address,networkbyteorder,hexadecimalIndicates|---------------------------------->entrynumber,startingfrom0,allvalues??oftheaboveconnectionstatusareasfollows,Seelinuxsourcecodetcp\_states.h[1]fordetails:enum{TCP_ESTABLISHED=1,TCP_SYN_SENT,TCP_SYN_RECV,TCP_FIN_WAIT1,TCP_FIN_WAIT2,TCP_TIME_WAIT,TCP_CLOSE,TCP_CLOSE_WAIT,TCP_LAST_ACK,TCP_LISTEN,TCP_CLOSING,/*现在有效状态*/TCP_NEW_SYN_RECV,TCP_MAX_STATES/*留在最后!*/};第2部分:00000150:0000000001:000000TO190000|||of|outtimerec-->-------->numberofjiffiesuntiltimerexpires|||---------------->timer_active,具体取值见下面说明||---------------------->receive-queue,当状态为ESTABLISHED时,表示数据的长度在接收队列中;状态为LISTEN,表示已经连接的队列长度|------------------------------>transmit-queue,发送队列中数据的长度timer_active的所有取值和说明如下:0没有定时器挂起1retransmit-timer挂起2另一个定时器(例如delayedack或keepalive)挂起3这是TIME_WAIT中的socket状态。并非所有字段都将包含数据(甚至存在)4零窗口探测计时器正在等待第三部分:10000541657854cd1e6040254|273-1||||||||-->slowstartsizethreshold,|||||||||or-1ifthethreshold||||||||||is>=0xFFFF|||||||||---->发送拥塞窗口|||||||------>(ack.quick<<1)|ack.pingpong|||||||---------->Predictedtickofsoftclock||||||(delayedACKcontroldata)||||||------------>retransmittimeout|||||------------------>locationofsocketinmemory||||-------------------->socketreferencecount|||--------------------------->套接字inode编号||---------------------------------->未回答0-windowprobes|---------------------------------------------->socket所属用户的uidGo实现了一个简单版的netstatnetstat命令的工作原理和/proc/net/tcp文件的结构,我们已经了解了,现在我们可以使用它来使用Go实现一个简单版本的netstat命令核心代码如下,完整代码参加go-netstat[2]://状态码值const(TCP_ESTABLISHED=iota+1TCP_SYN_SENTTCP_SYN_RECVTCP_FIN_WAIT1TCP_FIN_WAIT2TCP_TIME_WAITTCP_CLOSETCP_CLOSE_WAITTCP_LAST_ACKTCP_LISTENTCP_CLOSING//TCP_NEW_SYN_RECV//TCP_MAX_STATES)//状态码varstates=map[int]string{TCP_ESTABLISHED:"ESTABLISHED",TCP_SYN_SENT:"SYN_SENT",TCP_SYN_RECV:"SYN_RECV",TCP_FIN_WAIT1:"FIN_WAIT1",TCP_FIN_WAIT2:"FIN_WAIT2",TCP_TIME_WAIT:"TIME_WAIT",TCP_CLOSE:"CLOSE",TCP_CLOSE_WALIAST:"CLOSE_WAIT"ACKLIST","TCP:LAST",TCP_CLOSING:"CLOSING",//TCP_NEW_SYN_RECV:"NEW_SYN_RECV",//TCP_MAX_STATES:"MAX_STATES",}//socketEntry结构体,用于存放/proc/net/tcptypesocketEntrystruct{idintsrcIPnet.IPsrcPortintdstIPnet.IPdstPortintstatestringtxQueueintrxQueueinttimerint8timerDurationtime.Durationrtotime.Duration//重传超时时间outuidintunamestringtimeouttime.Durationinodestring}//解析/proc/net/tcp行记录funcparseRawSocketEntry(entrystring)(*socketEntry,error){se:=&socketEntry{}entrys:=strings.Split(strings.TrimSpace(entry),"")entryItems:=make([]string,0,17)for_,ent:=rangeentrys{ifent==""{continue}entryItems=append(entryItems,ent)}id,err:=strconv.Atoi(string(entryItems[0][:len(entryItems[0])-1]))iferr!=nil{returnnil,err}se.id=id//sockectentryidlocalAddr:=strings.Split(entryItems[1],":")//本地ipse。srcIP=parseHexBigEndianIPStr(localAddr[0])port,err:=strconv.ParseInt(localAddr[1],16,32)//本地portiferr!=nil{returnnil,err}se.srcPort=int(port)remoteAddr:=strings.Split(entryItems[2],":")//远程ipse.dstIP=parseHexBigEndianIPStr(remoteAddr[0])port,err=strconv.ParseInt(remoteAddr[1],16,32)//远程portiferr!=nil{returnil,err}se.dstPort=int(port)state,_:=strconv.ParseInt(entryItems[3],16,32)//socket状态se.state=states[int(state)]tcpQueue:=strings.Split(entryItems[4],“:”)tQueue,错误:=strconv.ParseInt(tcpQueue[0],16,32)//发送队列数据长度iferr!=nil{returnnil,err}se.txQueue=int(tQueue)sQueue,err:=strconv.ParseInt(tcpQueue[1],16,32)//接收队列数据长度iferr!=nil{returnnil,err}se.rxQueue=int(sQueue)se.uid,err=strconv.Atoi(entryItems[7])//socketuidiferr!=nil{returnnil,err}se.uname=systemUsers[entryItems[7]]//socketusernamese.inode=entryItems[9]//socketinodereturnse,nil}//hexIP由网络字节序/bigendianStringfuncparseHexBigEndianIPStr(hexIPstring)net转成十六进制.IP{b:=[]byte(hexIP)fori,j:=1,len(b)-2;i>24),byte(l>>16),byte(l>>8),byte(l))}