首页 > 其他分享 >go实现ping

go实现ping

时间:2023-09-04 17:55:28浏览次数:51  
标签:icmp nil err 实现 ping time go ICMP

 

ping 是一个经常被用来检查主机间连通性的工具, 它基于 ICMP 协议实现, 基本原理很简单: 本机给远程机器发送 ICMP 报文, 远程主机接收到 ICMP 报文后便会回复一个类似的 ICMP 报文; 当本机接收到回复后变认为远程主机是可连接的, 否则便认为这个主机是不可达的.

为了了解 golang 的网络编程, 我用 go 实现了一个 ping 命令, 本文会介绍如何实现 ping 命令.

链接:https://juejin.cn/post/6844903574833479688
代码

 

package main import ( "bytes" "encoding/binary" "fmt" "net" "os" "time" ) type ICMP struct { Type uint8 Code uint8 CheckSum uint16 Identifier uint16 SequenceNum uint16 } func usage() { msg := ` Need to run as root! Usage: goping host Example: ./goping www.baidu.com` fmt.Println(msg) os.Exit(0) } func getICMP(seq uint16) ICMP { icmp := ICMP{ Type: 8, Code: 0, CheckSum: 0, Identifier: 0, SequenceNum: seq, } var buffer bytes.Buffer binary.Write(&buffer, binary.BigEndian, icmp) icmp.CheckSum = CheckSum(buffer.Bytes()) buffer.Reset() return icmp } func sendICMPRequest(icmp ICMP, destAddr *net.IPAddr) error { conn, err := net.DialIP("ip4:icmp", nil, destAddr) if err != nil { fmt.Printf("Fail to connect to remote host: %s\n", err) return err } defer conn.Close() var buffer bytes.Buffer binary.Write(&buffer, binary.BigEndian, icmp) if _, err := conn.Write(buffer.Bytes()); err != nil { return err } tStart := time.Now() conn.SetReadDeadline((time.Now().Add(time.Second * 2))) recv := make([]byte, 1024) receiveCnt, err := conn.Read(recv) if err != nil { return err } tEnd := time.Now() duration := tEnd.Sub(tStart).Nanoseconds() / 1e6 fmt.Printf("%d bytes from %s: seq=%d time=%dms\n", receiveCnt, destAddr.String(), icmp.SequenceNum, duration) return err } func CheckSum(data []byte) uint16 { var ( sum uint32 length int = len(data) index int ) for length > 1 { sum += uint32(data[index])<<8 + uint32(data[index+1]) index += 2 length -= 2 } if length > 0 { sum += uint32(data[index]) } sum += (sum >> 16) return uint16(^sum) } func main() { if len(os.Args) < 2 { usage() } host := os.Args[1] raddr, err := net.ResolveIPAddr("ip", host) if err != nil { fmt.Printf("Fail to resolve %s, %s\n", host, err) return } fmt.Printf("Ping %s (%s):\n\n", raddr.String(), host) for i := 1; i < 6; i++ { if err = sendICMPRequest(getICMP(uint16(i)), raddr); err != nil { fmt.Printf("Error: %s\n", err) } time.Sleep(2 * time.Second) } }    

package main
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"os"
"time"
)
type ICMP struct {
Type uint8
Code uint8
CheckSum uint16
Identifier uint16
SequenceNum uint16
}
func usage() {
msg := `
Need to run as root!
Usage:
goping host
Example: ./goping www.baidu.com`
fmt.Println(msg)
os.Exit(0)
}
func getICMP(seq uint16) ICMP {
icmp := ICMP{
Type: 8,
Code: 0,
CheckSum: 0,
Identifier: 0,
SequenceNum: seq,
}
var buffer bytes.Buffer
binary.Write(&buffer, binary.BigEndian, icmp)
icmp.CheckSum = CheckSum(buffer.Bytes())
buffer.Reset()
return icmp
}
func sendICMPRequest(icmp ICMP, destAddr *net.IPAddr) error {
conn, err := net.DialIP("ip4:icmp", nil, destAddr)
if err != nil {
fmt.Printf("Fail to connect to remote host: %s\n", err)
return err
}
defer conn.Close()
var buffer bytes.Buffer
binary.Write(&buffer, binary.BigEndian, icmp)
if _, err := conn.Write(buffer.Bytes()); err != nil {
return err
}
tStart := time.Now()
conn.SetReadDeadline((time.Now().Add(time.Second * 2)))
recv := make([]byte, 1024)
receiveCnt, err := conn.Read(recv)
if err != nil {
return err
}
tEnd := time.Now()
duration := tEnd.Sub(tStart).Nanoseconds() / 1e6
fmt.Printf("%d bytes from %s: seq=%d time=%dms\n", receiveCnt, destAddr.String(), icmp.SequenceNum, duration)
return err
}
func CheckSum(data []byte) uint16 {
var (
sum uint32
length int = len(data)
index int
)
for length > 1 {
sum += uint32(data[index])<<8 + uint32(data[index+1])
index += 2
length -= 2
}
if length > 0 {
sum += uint32(data[index])
}
sum += (sum >> 16)
return uint16(^sum)
}
func main() {
if len(os.Args) < 2 {
usage()
}
host := os.Args[1]
raddr, err := net.ResolveIPAddr("ip", host)
if err != nil {
fmt.Printf("Fail to resolve %s, %s\n", host, err)
return
}
fmt.Printf("Ping %s (%s):\n\n", raddr.String(), host)
for i := 1; i < 6; i++ {
if err = sendICMPRequest(getICMP(uint16(i)), raddr); err != nil {
fmt.Printf("Error: %s\n", err)
}
time.Sleep(2 * time.Second)
}
}


链接:https://juejin.cn/post/6844903574833479688

 

使用 ip4:icmp 实现

即使我们想实现privileged ping,我们也不需要直接使用raw socket,还是使用icmp包。

在这种场景下,我们的network需要是ip4:icmp,能够发送ICMP包,而不是上面的udp4

package main   import ( "fmt" "log" "net" "os" "time"   "golang.org/x/net/icmp" "golang.org/x/net/ipv4" )   const ( protocolICMP = 1 )   func main() { if len(os.Args) != 2 { fmt.Fprintf(os.Stderr, "usage: %s host\n", os.Args[0]) os.Exit(1) } host := os.Args[1]   // 解析目标主机的 IP 地址 dst, err := net.ResolveIPAddr("ip", host) if err != nil { log.Fatal(err) }   // 创建 ICMP 连接 conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0") if err != nil { log.Fatal(err) } defer conn.Close()   // 构造 ICMP 报文 msg := &icmp.Message{ Type: ipv4.ICMPTypeEcho, Code: 0, Body: &icmp.Echo{ ID: os.Getpid() & 0xffff, Seq: 1, Data: []byte("Hello, are you there!"), }, } msgBytes, err := msg.Marshal(nil) if err != nil { log.Fatal(err) }   // 发送 ICMP 报文 start := time.Now() _, err = conn.WriteTo(msgBytes, dst) if err != nil { log.Fatal(err) }   // 接收 ICMP 报文 reply := make([]byte, 1500) for i := 0; i < 3; i++ { err = conn.SetReadDeadline(time.Now().Add(5 * time.Second)) if err != nil { log.Fatal(err) } n, peer, err := conn.ReadFrom(reply) if err != nil { log.Fatal(err) } duration := time.Since(start)   // 解析 ICMP 报文 msg, err = icmp.ParseMessage(protocolICMP, reply[:n]) if err != nil { log.Fatal(err) }   // 打印结果 switch msg.Type { case ipv4.ICMPTypeEchoReply: echoReply, ok := msg.Body.(*icmp.Echo) if !ok { log.Fatal("invalid ICMP Echo Reply message") return } if peer.String() == host && echoReply.ID == os.Getpid()&0xffff && echoReply.Seq == 1 { fmt.Printf("reply from %s: seq=%d time=%v\n", dst.String(), msg.Body.(*icmp.Echo).Seq, duration) return } default: fmt.Printf("unexpected ICMP message type: %v\n", msg.Type) } } }

 

//  https://colobu.com/2023/04/26/write-the-ping-tool-in-Go/

使用Go实现ping工具

ping是一个网络工具,它被广泛地用于测试网络连接的质量和稳定性。当我们想知道我们的电脑是否能够与其他设备或服务器进行通信时,ping就是我们最好的朋友。当我们想侦测网络之间的连通性和网络质量的时候,也常常使用ping工具测量,因为它是操作系统常带的一个网络诊断工具,小而强大。

ping最初是由Mike Muuss在1983年为Unix系统开发的。它的名字是来自于海军潜艇的声纳系统,声纳系统通过发送一个声波并测量其返回时间来确定目标的位置。Ping的工作原理类似,它发送一个小数据包到目标设备,然后等待该设备返回一个响应,以测量其响应时间和延迟。

当我们使用Ping测试网络连接时,它能够告诉我们两个重要的指标:延迟和丢包率。延迟是指从发送ping请求到接收到响应所需的时间,通常以毫秒为单位计算。丢包率则是指在ping请求和响应之间丢失的数据包的百分比。如果丢包率过高,说明网络连接可能存在问题,导致数据传输不稳定或者甚至无法连接。

除了基本的ping命令外,还有许多其他ping命令和选项可供使用。例如,可以使用“-c”选项指定发送ping请求的次数,使用“-i”选项指定Ping请求之间的时间间隔。此外,还可以使用“-s”选项指定发送ping请求的数据包大小。

尽管ping是一个非常有用的工具,但它也有一些限制。ping测试的结果可能会受到许多因素的影响,例如网络拥塞、防火墙、路由器丢弃等等。此外,一些设备或服务器可能已禁用对ping请求的响应,因此无法获得ping测试的结果。

尽管它有一些限制,但它仍然是网络管理员和用户必备的工具之一。

实现原理

ping工具是基于rfc 792 (ICMP协议)来实现的。它是一份名为“Internet控制消息协议(ICMP)规范”的文件,由Jon Postel和J. Reynolds在1981年9月发布。该文档定义了ICMP协议,该协议是TCP/IP网络协议套件中的一个重要组成部分。

ICMP协议是一种网络层协议,用于传输与网络控制和错误处理相关的消息。该协议通常与IP协议一起使用,用于在Internet上交换信息。RFC 792详细介绍了ICMP协议中的不同消息类型及其用途。ping就是利用发送一个Echo请求得到一个Echo Reply实现的。

 

 

 

参考:

https://juejin.cn/post/6844903574833479688

https://colobu.com/2023/04/26/write-the-ping-tool-in-Go/

 

标签:icmp,nil,err,实现,ping,time,go,ICMP
From: https://www.cnblogs.com/rebrobot/p/17677721.html

相关文章

  • Python接口编程:理解、设计和实现
    在当今的软件开发世界中,接口(API)是应用程序之间进行数据交换和通信的关键方式。这种通信可以在同一应用程序的各组件之间,也可以在完全不同的系统和平台之间进行。Python作为一种功能强大的编程语言,因其易用性和丰富的库而广泛用于开发接口。理解接口在计算机科学中,接口通常是一种定......
  • 应用可视化流程设计,实现提质增效流程化办公!
    如果想要实现提高办公效率的目的,显然采用传统的办公方式是无法实现的。如今,在低代码技术平台深入无纸化办公的当下,应用可视化流程设计软件,可以借助其灵活、易操作、可视化、轻量级等优势特点,助力广大用户实现流程化办公,进入数字化转型新时代。在众多软件服务商中,流辰信息是一支有......
  • SpringBoot+Nacos+OpenFeign实现服务的注册、发现、远程调用
    一、概述微服务的开发必定会用到SpringBoot、Nacos(服务发现、注册、配置中心)、OpenFeign(服务远程调用,也就是通过注解调用其他服务的接口)。今天写这篇是想把这三个功能集成在一起。让他们一起参与微服务的开发工作。要实现的目标:nacos要能够注册及发现服务,openfeign......
  • 规则持久化-实现push模式持久化
                        ......
  • 网站登录记忆跳转实现的一种方法 [摘]
    记忆登录跳转可以打打方便用户使用getHeader("Referer");方式简单不过不能处理post的参数本来都是用在struts,webwork里的,为了容易看明白改写成jsp方式的-------------------------------------------------------------------------------------------------------------member.js......
  • Layui-select 下拉框实现拼音全拼匹配/首字母模糊搜索
    Layui中搜索选择框搜索汉字不管用解决方案基于layui2.5.3首先贴上原创作者的文章地址,非常感谢他的思路Layui-select下拉框实现拼音全拼匹配/首字母模糊搜索最近做项目的时候引用了layui并使用了本文作者的方法解决了我得问题,但是由于需要使用layui新版的tree.js,发现不......
  • Spring事务配置笔记(实现不同Service间调用事务)
    作者:fbysss关键字:Spring,事务处理一、关键配置示例:<beanid="sessionFactory"class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"><propertyname="dataSource&qu......
  • Java中实现的栈or队列两种方式对比
    Java中实现的栈or队列两种方式对比​ 我们知道,在Java中,可以直接使用Stack来实现栈,这是一种看到名字就会自动想到栈的类,但是现代Java编程中却不推荐使用Stack来实现栈,这是为什么?首先来看一下Java中的Collection接口继承图:Stack1.线程安全,但是带来的开销大,效率低​ Stack是直接......
  • 用递归和非递归两种方式实现二叉树的先序遍历(前序遍历)
    一、分析先序遍历(前序遍历)遍历顺序为:根、左、右。二、递归实现publicclassNode{ publicintvalue;publicNodeleft;publicNoderight;publicNode(intdata){ this.value=data;}}publicvoidpreOrderRecur(Nodehead){ if(head==null){ return......
  • 分享实用工具源码--实现Windows IDE中查看Linux下编译信息
    作者:fbysss关键字:实用工具源码 Windows下查看Linux编译信息一、背景:本人写C程序不多,更不用说Linux下了。偶然一个机会,接了个这样的活,vi我用的还马马虎虎,但程序超过一千行,看起来就有些眼花了。于是只好在VC下编写代码,ftp传到Linux服务器,再用gcc编译,出错了再到VC下修改,再上传,如......