
本教程详细阐述了在go语言中如何通过发送udp探测包并监听icmp“端口不可达”消息来检测远程udp端口的可达性。文章解释了udp协议的无连接特性,以及icmp type 3 code 3消息的原理,并提供了使用`golang.org/x/net/icmp`库实现这一机制的专业指南和示例代码,同时强调了相关的注意事项。
UDP(用户数据报协议)是一种无连接协议,它不提供像TCP那样的握手机制来确认连接的建立或端口的监听状态。因此,传统的“ping”工具(基于ICMP Echo Request/Reply)无法直接用于检测特定UDP端口的开放状态。然而,当一个UDP数据包被发送到一个目标主机的特定端口,而该端口上没有应用程序在监听时,操作系统的网络栈通常会生成一个ICMP(互联网控制消息协议)“目标不可达”消息,并将其发送回源主机。
具体来说,这种情况下产生的ICMP消息类型为3(Destination Unreachable),代码为3(Port Unreachable)。这个机制可以被利用来间接判断一个远程UDP端口是否处于非监听状态。
根据RFC792的定义,ICMP“目标不可达”消息的结构如下:
Destination Unreachable Message
0 1 2 3
0 1 2 3 4 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unused |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Internet Header + 64 bits of Original Data Datagram |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
IP Fields:
Destination Address
The source network and address from the original datagram's data.
ICMP Fields:
Type
3
Code
0 = net unreachable;
1 = host unreachable;
2 = protocol unreachable;
3 = port unreachable;
4 = fragmentation needed and DF set;
5 = source route failed.其中,Type字段为3表示“目标不可达”,Code字段为3表示“端口不可达”。通过解析接收到的ICMP消息的这两个字段,我们可以判断UDP探测包是否遇到了一个未监听的端口。
在Go语言中,标准库net提供的net.UDPConn.ReadFromUDP方法主要用于读取UDP套接字接收到的UDP数据包。当发送的UDP探测包触发了ICMP“端口不可达”错误时,这个ICMP错误消息通常不会直接通过ReadFromUDP返回给应用程序的UDP套接字。这是因为ICMP错误消息是在IP层由操作系统内核处理和生成的,而不是作为UDP数据包传递给应用程序。因此,尝试通过ReadFromUDP来捕获ICMP错误通常会失败,表现为ReadFromUDP返回0字节和nil错误(如果设置了超时,则可能返回超时错误),因为它没有收到任何UDP数据。
芝士饼
芝士饼是一个一站式AI原生应用开发平台,简单几步即可完成应用的创建与发布。
84
查看详情
为了捕获ICMP错误消息,我们需要使用更底层的网络接口,即原始套接字(Raw Socket),它允许应用程序直接发送和接收IP层的数据包,包括ICMP消息。
在Go语言中,我们可以借助golang.org/x/net/icmp库来创建和管理ICMP原始套接字,从而实现UDP端口可达性的检测。其核心思路是:
以下是一个Go语言示例,演示了如何实现UDP端口可达性检测:
package main
import (
"errors"
"fmt"
"log"
"net"
"os"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
// UDPPortCheckResult 定义端口检测结果
type UDPPortCheckResult struct {
Reachable bool
Error error
}
// CheckUDPPortReachability 发送UDP探测包并监听ICMP回复以检测端口可达性
func CheckUDPPortReachability(targetAddr string, timeout time.Duration) UDPPortCheckResult {
// 1. 解析目标地址
addr, err := net.ResolveUDPAddr("udp4", targetAddr)
if err != nil {
return UDPPortCheckResult{false, fmt.Errorf("解析目标地址失败: %w", err)}
}
// 2. 创建UDP连接用于发送探测包
// 选择一个随机的本地端口
udpConn, err := net.ListenUDP("udp4", nil)
if err != nil {
return UDPPortCheckResult{false, fmt.Errorf("创建UDP发送连接失败: %w", err)}
}
defer udpConn.Close()
// 3. 创建ICMP原始套接字用于监听回复
// "ip4:icmp" 表示监听IPv4的ICMP协议
icmpConn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return UDPPortCheckResult{false, fmt.Errorf("创建ICMP监听连接失败: %w", err)}
}
defer icmpConn.Close()
// 设置ICMP连接的读取超时
if err := icmpConn.SetReadDeadline(time.Now().Add(timeout)); err != nil {
return UDPPortCheckResult{false, fmt.Errorf("设置ICMP读取超时失败: %w", err)}
}
// 4. 发送UDP探测包
message := []byte("UDP Port Probe")
if _, err := udpConn.WriteTo(message, addr); err != nil {
return UDPPortCheckResult{false, fmt.Errorf("发送UDP探测包失败: %w", err)}
}
// 5. 从ICMP套接字读取并解析回复
buffer := make([]byte, 1500) // 通常ICMP报文不会太大
for {
n, peer, err := icmpConn.ReadFrom(buffer)
if err != nil {
// 如果是超时错误,则认为端口可达(没有收到不可达回复)
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
return UDPPortCheckResult{true, nil} // 超时,认为端口可达
}
return UDPPortCheckResult{false, fmt.Errorf("读取ICMP回复失败: %w", err)}
}
// 确保回复来自目标主机
if peer.String() != addr.IP.String() {
continue // 忽略来自其他主机的ICMP回复
}
// 解析ICMP消息
// 注意:icmp.ParseMessage期望的是ICMP报文,而不是整个IP报文。
// 在Linux/Unix上,ListenPacket("ip4:icmp")通常直接返回ICMP报文。
// 在Windows上可能需要手动剥离IP头。这里假设是直接ICMP报文。
// 更严谨的做法是使用 ipv4.ParseHeader 来检查IP头,然后提取ICMP部分。
// 但 icmp.ListenPacket 通常会处理好这些。
msg, err := icmp.ParseMessage(ipv4.ICMPType, buffer[:n])
if err != nil {
log.Printf("解析ICMP消息失败: %v", err)
continue // 尝试读取下一个
}
switch msg.Type {
case ipv4.ICMPTypeDestinationUnreachable:
if msg.Code == icmp.DstUnreachPort {
// 收到端口不可达错误,说明端口未开放
return UDPPortCheckResult{false, errors.New("端口不可达 (ICMP Type 3, Code 3)")}
}
// 其他目标不可达错误,可能表示网络或主机问题
return UDPPortCheckResult{false, fmt.Errorf("目标不可达 (ICMP Type %d, Code %d)", msg.Type, msg.Code)}
case ipv4.ICMPTypeEchoReply:
// 收到ICMP Echo Reply,这不是我们期望的,但表示主机存活
// 这种情况下,UDP端口可能开放,也可能只是主机响应了ping
// 继续等待或视为可达
// log.Printf("收到ICMP Echo Reply,可能端口可达")
// return UDPPortCheckResult{true, nil} // 暂时认为可达
default:
// 收到其他ICMP消息,继续等待或忽略
// log.Printf("收到其他ICMP消息: Type %d, Code %d", msg.Type, msg.Code)
}
}
}
func main() {
if len(os.Args) < 3 {
fmt.Println("用法: go run main.go <目标IP> <目标UDP端口>")
fmt.Println("例如: go run main.go 127.0.0.1 8080")
return
}
targetIP := os.Args[1]
targetPort := os.Args[2]
targetAddr := net.JoinHostPort(targetIP, targetPort)
timeout := 2 * time.Second
fmt.Printf("检测UDP端口 %s 的可达性...\n", targetAddr)
result := CheckUDPPortReachability(targetAddr, timeout)
if result.Reachable {
fmt.Printf("UDP端口 %s 似乎是可达的 (未收到ICMP端口不可达错误).\n", targetAddr)
} else {
fmt.Printf("UDP端口 %s 不可达: %v\n", targetAddr, result.Error)
}
}通过利用ICMP“目标不可达”消息(Type 3, Code 3),我们可以在Go语言中实现对远程UDP端口可达性的检测。虽然标准UDP套接字无法直接接收这些ICMP错误,但golang.org/x/net/icmp库提供了一种有效的方法来创建原始ICMP套接字并监听这些消息。然而,在实现过程中必须注意权限、防火墙、网络设备行为以及超时设置等关键因素,以确保检测的准确性和可靠性。这种技术对于服务发现、健康检查或网络诊断等场景具有重要意义。
以上就是Go语言中利用ICMP检测UDP端口可达性教程的详细内容,更多请关注其它相关文章!
# 芝士
# 盖州seo推广
# 南平市网站营销推广
# 山东企业网站优化推广
# 宜昌网站建设资质公司
# 温州网站建设供应商
# 球鞋的网络营销与推广
# 漳州网站建设课件下载
# 水杯如何营销推广
# 营口seo查询技巧
# seo工具伊思诺
# 这是
# 是一个
# 的是
# 我们可以
# 创建一个
# linux
# 应用程序
# 数据包
# 可达
# 端
# 路由器
# 字节
# ipv6
# internet
# 防火墙
# go语言
# 操作系统
# golang
# windows
# go
相关栏目:
【
Google疑问12 】
【
Facebook疑问10 】
【
优化推广96088 】
【
技术知识133117 】
【
IDC资讯59369 】
【
网络运营7196 】
【
IT资讯61894 】
相关推荐:
火柴人战争网页版在线玩
12306不能订票的时间段是固定的吗? | 节假日购票时间有无变化
三角洲行动2025年9月10日摩斯密码分享
抖音评论无法发送如何修复 抖音评论功能操作指南
Go语言中方法接收器的选择:值类型还是指针类型?
Dash应用中自定义HTML页面标题与网站图标(F*icon)的实用指南
《宝可梦大集结》S4冠军之路开始时间介绍
《领英》查看屏蔽名单方法
《米姆米姆哈》米姆获取及技能攻略
WooCommerce 购物车:始终显示所有交叉销售商品
poki官网最新入口 poki小游戏大全入口
京东快递包裹信息查询入口 京东快递官方查询平台入口
cad加载的线型看不见怎么办_cad线型不可见问题解决方法
抖音号显示企业机构号是什么意思?企业机构号申请条件是什么?
如何在Python中安全地将环境变量转换为整数并满足Mypy类型检查
SQL聚合查询、联接与筛选:GROUP BY 子句的正确使用与常见陷阱
口腔诊所管理软件推荐
青橙手机语音助手怎么唤醒_青橙手机语音助手设置与唤醒方法
J*aScript桌面应用_Electron多进程架构实战
芒果TV官网登录入口 芒果TV官方网站登录入口
51漫画网实时入口 51漫画网页版官方免费漫画入口
Python对象引用与属性赋值:理解链表中的行为
yy漫画官方网站登录入口_yy漫画在线阅读页面地址
C++ virtual析构函数作用_C++基类虚析构函数防止内存泄漏
智云Q3和Q2有什么升级_智云Q3与Q2手持云台功能与性能对比分析
mysql归档数据怎么导出为csv_mysql归档数据导出为csv文件的方法
三星M34录音变声问题_Samsung M34麦克风调整
TikTok视频播放中断怎么办 TikTok播放异常修复方法
米侠浏览器插件无法启用怎么办 米侠浏览器扩展兼容性修复
lol小红书怎么|直播|?lol小红书|直播|是什么意思?
mysql怎么导入sql文件_mysql导入sql文件的方法与技巧
优化CSS动画与J*aScript定时器协同:构建稳定Toast提示
如何查询国外邮政编码_国外邮政编码查询的多种有效途径
高效调试PHP大型嵌套数组:JSON序列化与可视化工具实践
sublime如何撤销关闭的标签页_sublime重新打开已关闭文件技巧
Microsoft Edge网页字体太淡看不清怎么办_Microsoft Edge字体渲染优化技巧
使用jQuery精确检测除指定元素外任意位置的点击事件
苹果手机如何清理系统缓存数据 iPhone非越狱清理垃圾文件的技巧【系统优化】
喜茶GO更换登录账号方法
Excel宏怎么删除_Excel中删除宏的详细操作流程
Sublime怎么配置YAML文件格式化_Sublime YAML Formatter插件教程
顺丰快递怎么查物流_顺丰快递物流信息实时查询操作指南
Pandas中基于动态偏移量实现DataFrame列值位移的策略
苹果电脑如何快速截图并编辑 苹果电脑截屏标注快捷操作
在VS Code中利用AI辅助进行代码迁移
AI图层蒙版怎么用_AI图层蒙版应用技巧与设计实例
Bootstrap 5导航栏折叠功能失效:数据属性迁移指南
泰拉瑞亚网页版在线登录入口 泰拉瑞亚官方正版入口
139邮箱登录入口官网 139邮箱登录入口官网网址
XPath动态元素定位:如何精准选择文本内容变化的元素
2025-11-01
运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。