0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

探究Go语言如何实现简易版netstat命令

阿铭linux 来源:杰哥的IT之旅 作者: 杰哥的IT之旅 2021-07-27 10:35 次阅读

netstat 使用 go 语言实现是什么操作?本文从 netstat 原理出发详细解读了这一实践。

netstat 工作原理

netstat 命令是 linux 系统中查看网络情况的一个命令。比如我们可以通过netstat -ntlp | grep 8080查看监听 8080 端口的进程。

17f98660-e3ff-11eb-a97a-12bb97331649.jpg

netstat 工作原理如下:

通过读取/proc/net/tcp 、/proc/net/tcp6 文件,获取 socket 本地地址,本地端口,远程地址,远程端口,状态,inode 等信息

接着扫描所有/proc/[pid]/fd 目录下的的 socket 文件描述符,建立 inode 到进程 pid 映射

根据 pid 读取/proc/[pid]/cmdline 文件,获取进程命令和启动参数

根据 2,3 步骤,即可以获得 1 中对应 socket 的相关进程信息

我们可以做个测试验证整个流程。先使用 nc 命令监听 8090 端口:

nc -l 8090

找到上面 nc 进程的 pid,查看该进程所有打开的文件描述符:

vagrant@vagrant:/proc/25556/fd$ ls -alh

total 0

dr-x------ 2 vagrant vagrant 0 Nov 18 12:21 。

dr-xr-xr-x 9 vagrant vagrant 0 Nov 18 12:20 。。

lrwx------ 1 vagrant vagrant 64 Nov 18 12:21 0 -》 /dev/pts/1

lrwx------ 1 vagrant vagrant 64 Nov 18 12:21 1 -》 /dev/pts/1

lrwx------ 1 vagrant vagrant 64 Nov 18 12:21 2 -》 /dev/pts/1

lrwx------ 1 vagrant vagrant 64 Nov 18 12:21 3 -》 socket:[2226056]

上面列出的所有文件描述中,socket:[2226056]为 nc 命令监听 8090 端口所创建的 socket。其中2226056为该 socket 的 inode。

根据该 inode 号,我们查看/proc/net/tcp对应的记录信息,其中1F9A为本地端口号,转换成十进制恰好为 8090:

vagrant@vagrant:/proc/25556/fd$ cat /proc/net/tcp | grep 2226056

1: 00000000:1F9A 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 2226056 1 0000000000000000 100 0 0 10 0

根据进程 id,我们查看进程名称和启动参数:

vagrant@vagrant:/proc/25556/fd$ cat /proc/25556/cmdline

nc-l8090

下面我们看下/proc/net/tcp文件格式。

/proc/net/tcp 文件格式

/proc/net/tcp文件首先会列出所有监听状态的 TCP 套接字,然后列出所有已建立的 TCP 套接字。我们通过head -n 5 /proc/net/tcp命令查看该文件头五行:

sl local_address rem_address st tx_queue rx_queue tr tm-》when retrnsmt uid timeout inode

0: 0100007F:0019 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 22279 1 0000000000000000 100 0 0 10 0

1: 00000000:1FBB 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21205 1 0000000000000000 100 0 0 10 0

2: 00000000:26FB 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21203 1 0000000000000000 100 0 0 10 0

3: 00000000:26FD 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21201 1 0000000000000000 100 0 0 10 0

每一行各个字段解释说明如下,由于太长分为三部分说明:

第一部分:

46: 010310AC:9C4C 030310AC:1770 01

| | | | | |--》 连接状态,16 进制表示,具体值见下面说明

| | | | |------》 远程 TCP 端口号,主机字节序,16 进制表示

| | | |-------------》 远程 IPv4 地址,网络字节序,16 进制表示

| | |--------------------》 本地 TCP 端口号,主机字节序,16 进制表示

| |---------------------------》 本地 IPv4 地址,网络字节序,16 进制表示

|----------------------------------》 条目编号,从 0 开始

上面连接状态所有值如下,具体参见 linux 源码 tcp\_states.h[1]:

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, /* Now a valid state */

TCP_NEW_SYN_RECV,

TCP_MAX_STATES /* Leave at the end! */

};

第二部分:

00000150:00000000 01:00000019 00000000

| | | | |--》 number of unrecovered RTO timeouts

| | | |----------》 number of jiffies until timer expires

| | |----------------》 timer_active,具体值见下面说明

| |----------------------》 receive-queue,当状态是 ESTABLISHED,表示接收队列中数据长度;状态是 LISTEN,表示已经完成连接队列的长度

|-------------------------------》 transmit-queue,发送队列中数据长度

timer_active 所有值与说明如下:

0 no timer is pending

1 retransmit-timer is pending

2 another timer (e.g. delayed ack or keepalive) is pending

3 this is a socket in TIME_WAIT state. Not all fields will contain data (or even exist)

4 zero window probe timer is pending

第三部分:

1000 0 54165785 4 cd1e6040 25 4 27 3 -1

| | | | | | | | | |--》 slow start size threshold,

| | | | | | | | | or -1 if the threshold

| | | | | | | | | is 》= 0xFFFF

| | | | | | | | |----》 sending congestion window

| | | | | | | |-------》 (ack.quick《《1)|ack.pingpong

| | | | | | |---------》 Predicted tick of soft clock

| | | | | | (delayed ACK control data)

| | | | | |------------》 retransmit timeout

| | | | |------------------》 location of socket in memory

| | | |-----------------------》 socket reference count

| | |-----------------------------》 socket 的 inode 号

| |----------------------------------》 unanswered 0-window probes

|---------------------------------------------》 socket 所属用户的 uid

Go 实现简易版本 netstat 命令

netstat 工作原理和/proc/net/tcp文件结构,我们都已经了解了,现在可以使用据此使用 Go 实现一个简单版本的 netstat 命令。

18041148-e3ff-11eb-a97a-12bb97331649.jpg

核心代码如下,完整代码参加 go-netstat[2]:

// 状态码值const (

TCP_ESTABLISHED = iota + 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

// 状态码var states = 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_WAIT: “CLOSE_WAIT”,

TCP_LAST_ACK: “LAST_ACK”,

TCP_LISTEN: “LISTEN”,

TCP_CLOSING: “CLOSING”,

//TCP_NEW_SYN_RECV: “NEW_SYN_RECV”,

//TCP_MAX_STATES: “MAX_STATES”,

}

// socketEntry 结构体,用来存储/proc/net/tcp 每一行解析后数据信息type socketEntry struct {

id int

srcIP net.IP

srcPort int

dstIP net.IP

dstPort int

state string

txQueue int

rxQueue int

timer int8

timerDuration time.Duration

rto time.Duration // retransmission timeout

uid int

uname string

timeout time.Duration

inode string

}

// 解析/proc/net/tcp 行记录func parseRawSocketEntry(entry string) (*socketEntry, error) {

se := &socketEntry{}

entrys := strings.Split(strings.TrimSpace(entry), “ ”)

entryItems := make([]string, 0, 17)

for _, ent := range entrys {

if ent == “” {

continue

}

entryItems = append(entryItems, ent)

}

id, err := strconv.Atoi(string(entryItems[0][:len(entryItems[0])-1]))

if err != nil {

return nil, err

}

se.id = id // sockect entry id

localAddr := strings.Split(entryItems[1], “:”) // 本地 ip

se.srcIP = parseHexBigEndianIPStr(localAddr[0])

port, err := strconv.ParseInt(localAddr[1], 16, 32) // 本地 port

if err != nil {

return nil, err

}

se.srcPort = int(port)

remoteAddr := strings.Split(entryItems[2], “:”) // 远程 ip

se.dstIP = parseHexBigEndianIPStr(remoteAddr[0])

port, err = strconv.ParseInt(remoteAddr[1], 16, 32) // 远程 port

if err != nil {

return nil, err

}

se.dstPort = int(port)

state, _ := strconv.ParseInt(entryItems[3], 16, 32) // socket 状态

se.state = states[int(state)]

tcpQueue := strings.Split(entryItems[4], “:”)

tQueue, err := strconv.ParseInt(tcpQueue[0], 16, 32) // 发送队列数据长度

if err != nil {

return nil, err

}

se.txQueue = int(tQueue)

sQueue, err := strconv.ParseInt(tcpQueue[1], 16, 32) // 接收队列数据长度

if err != nil {

return nil, err

}

se.rxQueue = int(sQueue)

se.uid, err = strconv.Atoi(entryItems[7]) // socket uid

if err != nil {

return nil, err

}

se.uname = systemUsers[entryItems[7]] // socket user name

se.inode = entryItems[9] // socket inode

return se, nil

}

// hexIP 是网络字节序/大端法转换成的 16 进制的字符串func parseHexBigEndianIPStr(hexIP string) net.IP {

b := []byte(hexIP)

for i, j := 1, len(b)-2; i 《 j; i, j = i+2, j-2 { // 反转字节,转换成小端法

b[i], b[i-1], b[j], b[j+1] = b[j+1], b[j], b[i-1], b[i]

}

l, _ := strconv.ParseInt(string(b), 16, 64)

return net.IPv4(byte(l》》24), byte(l》》16), byte(l》》8), byte(l))

}

编辑:jq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11229

    浏览量

    208927

原文标题:Go 语言实现简易版 netstat 命令

文章出处:【微信号:aming_linux,微信公众号:阿铭linux】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    在学习go语言的过程踩过的坑

    作为一个5年的phper,这两年公司和个人都在顺应技术趋势,新项目慢慢从php转向了go语言,从2021年到现在,笔者手上也先后开发了两个go项目。在学习go
    的头像 发表于 11-11 09:22 110次阅读

    go语言如何解决并发问题

    作为一个后端开发,日常工作中接触最多的两门语言就是PHP和GO了。无可否认,PHP确实是最好的语言(手动狗头哈哈),写起来真的很舒爽,没有任何心智负担,字符串和整型压根就不用区分,开发速度真的是比
    的头像 发表于 10-23 13:38 109次阅读
    <b class='flag-5'>go</b><b class='flag-5'>语言</b>如何解决并发问题

    verilog设计之基于basys3实现简易分秒数字钟

    基于basys3实现简易分秒数字钟
    发表于 09-03 14:15 0次下载

    三十分钟入门基础Go Java小子版

    前言 Go语言定义 Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态、强类型、编译型语言
    的头像 发表于 08-12 14:32 673次阅读
    三十分钟入门基础<b class='flag-5'>Go</b> Java小子版

    用ESP32,做了个siri?!开源了!

    触碰交互3.通过修改代码,可以设置AI的对话方式和回答风格4.支持用于拓展设计,例如:智能MP3、简易版siri/小爱同学、实时翻译工具…想实现这些功能,软硬件如
    的头像 发表于 06-29 08:04 328次阅读
    用ESP32,做了个siri?!开源了!

    AWTK 开源串口屏开发(18) - 用 C 语言自定义命令

    编写代码即可实现常见的应用。但是,有时候我们需要自定义一些命令,以实现一些特殊的功能。本文档介绍如何使用C语言自定义命令。1.
    的头像 发表于 05-11 08:24 416次阅读
    AWTK 开源串口屏开发(18) - 用 C <b class='flag-5'>语言</b>自定义<b class='flag-5'>命令</b>

    关于go中接口类型的表示方法

    go是一个静态性语言,每个变量都有静态的类型,因此每个变量在编译阶段中有明确的变量类型,比如像:int、float32、MyType。
    的头像 发表于 04-28 10:13 334次阅读

    Go语言中的函数、方法与接口详解

    Go 没有类,不过可以为结构体类型定义方法。方法就是一类带特殊的接收者参数的函数。方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。(非结构体类型也可以定义方法)
    的头像 发表于 04-23 16:21 752次阅读

    学习笔记|如何用Go程序采集温湿度传感器数据

    整个利用Go语言从AHT20温湿度传感器获取数据的完整步骤和实现方法。一、C程序获取AHT20温湿度1、参考ElfBoard官方例程:03-例程源码\03-1命令
    的头像 发表于 03-21 11:46 644次阅读
    学习笔记|如何用<b class='flag-5'>Go</b>程序采集温湿度传感器数据

    使用go语言实现一个grpc拦截器

    在开发grpc服务时,我们经常会遇到一些通用的需求,比如:日志、链路追踪、鉴权等。这些需求可以通过grpc拦截器来实现。本文使用go语言实现一个 grpc一元模式(Unary)拦截器
    的头像 发表于 12-18 10:13 635次阅读
    使用<b class='flag-5'>go</b><b class='flag-5'>语言实现</b>一个grpc拦截器

    Go编程语言-你应该知道的一切

    Go 编程语言的故事始于 Google,当时三位工程师 Robert Griesemer、Rob Pike 和 Ken Thompson 对 C++ 的复杂性以及缺乏提供高效编译和执行的简单语言感到厌倦。
    的头像 发表于 12-11 17:37 617次阅读

    idea如何输入命令行参数

    在许多软件开发和系统管理的任务中,我们经常需要向应用程序传递命令行参数。命令行参数是在运行时传递给程序的值,用于指定程序的行为和配置选项。本文将详细介绍如何在不同的编程语言和操作系统中输入命令
    的头像 发表于 12-06 15:01 1082次阅读

    uboot命令的执行过程是什么

    u-boot命令的执行过程。 1、u-boot命令的执行过程 在u-boot命令执行到最后时,开始进入命令循环,等待用户输入命令和处理
    的头像 发表于 12-04 17:31 763次阅读

    Uboot命令实现大致流程图

    zImage内核镜像下载到开发板之后,可以使用u-boot的go命令进行直接跳转,这个时候内核直接解压启动。 但是此时的内核无法挂载文件系统,因为go命令没有将内核需要的相关启动参数从
    的头像 发表于 12-04 17:09 391次阅读
    Uboot<b class='flag-5'>命令</b><b class='flag-5'>实现</b>大致流程图

    python调用windows命令

    Python是一种强大的编程语言,可以用于开发各种不同类型的应用程序。其中一个常见的用途是使用Python调用Windows命令来执行特定的任务。在本文中,我们将详细讨论如何使用Python调用
    的头像 发表于 11-29 14:34 998次阅读