基于linux下实现的ping程序(C语言)

linux下实现的ping程序

一、设计目的

PING程序是我们使用的比较多的用于测试网络连通性的程序。PING程序基于ICMP,使用ICMP的回送请求和回送应答来工作。由计算机网络课程知道,ICMP是基于IP的一个协议,ICMP包通过IP的封装之后传递。

课程设计中选取PING程序的设计,其目的是通过PING程序的设计,能初步掌握TCP/IP网络协议的基本实现方法,对网络的实现机制有进一步的认识。

熟悉SOCKET的编程,包括基本的系统调用如SOCKET、BIND等。

二、设计内容

2.1 RAW模式的SOCKET编程

PING程序是面向用户的应用程序,该程序使用ICMP的封装机制,通过IP协议来工作。为了实现直接对IP和ICMP包进行操作,实验中使用RAW模式的SOCKET编程。

2.2 具体内容

2.2.1 定义数据结构

定义IP数据报、ICMP包等相关的数据结构。

ICMP数据头结构

typedef struct Icmp
{
	unsigned char type;       		//类型
	unsigned char code;				//代码
	unsigned short check_sum;		//检验和
	unsigned short id;				//标识符
	unsigned short seq;				//序列号
}IcmpHeader;

IP数据包头结构

typedef struct iphdr   
{  
    unsigned int headLen:4;   		//首部长度
    unsigned int version:4;  		//版本
    unsigned char tos;  			//区分服务
    unsigned short totalLen;  		//总长度
    unsigned short ident;  			//标识
    unsigned short fragAndFlags;  	//标志与片偏移
    unsigned char ttl;  			//生存时间
    unsigned char proto;  			//协议
    unsigned short checkSum;  		//检验和
    unsigned int sourceIP;  		//源地址
    unsigned int destIP;  			//目的地址
}IpHeader;

2.2.2 程序实现

在LINUX环境下实现PING程序

2.2.3 程序功能

  • ping ip 地址
    如ping 192.168.1.1
  • ping 域名(进行DNS解析)
    如ping www.baidu.com
  • 参数“ -n 数字”进行设置ping 的次数
    如ping www.baidu.com –n 10
  • 参数 -t 无限循环
    如ping 192.168.1.140 -t
  • 分析ping到的数据报
    如最短时间,最长时间,平均时间和丢包率
  • ping ?
    提供帮助提示

三、实验平台与语言

  • 平台:linux
  • 语言:C语言

四、功能模块实现

4.1 总体设计方案

流程图

主要代码

// ICMP数据头结构  
typedef struct Icmp
{
	unsigned char type;       	//类型
	unsigned char code;			//代码
	unsigned short check_sum;	//检验和
	unsigned short id;			//标识符
	unsigned short seq;			//序列号
}IcmpHeader;
//执行ping功能
int ping(const char *ip,  int send_count)
{
	int rawfd;
	struct sockaddr_in dest_adr;
	char icmp_data[1024];
	int size = sizeof(IcmpHeader)+32;
	int r, i = 0, send, recv=0, lost=0;
	char recv_buf[1024];
	int all_time[1024] = {0};
	//创建原始套接字
	rawfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if(rawfd == -1)	
	{
		perror("create socket failed!");
		return -1;
	}
	//设置目的地址与端口
	dest_adr.sin_family = AF_INET;
	dest_adr.sin_port = htons(80);
	inet_aton(ip, &dest_adr.sin_addr);
	
	//封装icmp数据包
	pack_icmp(icmp_data, size);
	printf("\n正 Ping %s 具有%d个字节的数据:\n", ip, size-sizeof(IcmpHeader));
	if(send_count == LOOP)
	{
		//无限循环
		while(1)
		{
			if(ping_one(rawfd,dest_adr, ip, icmp_data , all_time) != -1)
			{
				recv++;
			}
			send++;
		}
	}
	else
	{
		for(i = 0; i<send_count; i++) 
		{
			if(ping_one(rawfd,dest_adr, ip, icmp_data , all_time) != -1)
			{
				recv++;	
			}
			send++;
		}
	}
	printf("\n%s 的 Ping 统计信息:\n   数据包:已发送 = %d,已接收 = %d, 丢失 
= %d<%.1f%% 丢失>, \n",ip, send, recv, lost, ((float)lost/(float)send)*100);
	printf("往返行程的估计时间:\n 最短=%dms 最长=%dms 平均 = %dms \n\n"
				,min(all_time, send), max(all_time, send), average(all_time, send));
	close(rawfd);
	return 0;
}

4.2 DNS域名解析功能实现

4.2.1 DNS 服务器

  • 地址:202.96.134.133
  • 端口:53
# define DNS_PORT 53
# define DNS_IP	"202.96.134.133"
# define DNS_IP2	"8.8.8.8"

4.2.2 DNS 的实现基础:

通过UDP发送查询报文给DNS 服务器,然后从服务通过UDP 返回的回应报文中解析得到对应域名的IP。

4.2.3 DNS 报文格式

4.2.4 首部格式

其中标志字段:16位

定义首部结构体

typedef struct DNSheader
{
	unsigned short id;
	unsigned char  qr_opcode_aa_tc_rd;
	unsigned char  ra_zero_rcode;
	unsigned short qdcount;
	unsigned short ancount;
	unsigned short nscount;
	unsigned short arcount;
}DnsHeader;

4.2.5 问题记录格式

  • 查询名字:域名的可变长字段;其中计数字段指明每一节中的字符数
  • 查询类型:16位;值的意义如下表,查询ip 时为1
  • 查询类别:16位;定义使用DNS的特定协议, 一般为1

查询名字打包代码:

//把域名打包成dns数据报的数据部分 如(3www5baidu3com)
//计算每段的数量
for(i = 0; i < name_len; i++)
{
	if(netname[i] == '.')
	{
		len[flg++] = i - s;
		s += len[flg-1]+1;
	}
}
len[flg] = i - s;
i = 0;
flg = 0;
data[i++] = len[flg++];
//加入每段字节的数量
for(; i < name_len+1; i++)
{
	if(netname[i-1] == '.')
	{
		data[i] = len[flg++];
	}
	else
	{
		data[i] = netname[i-1];
	}
}

4.2.6 资源记录格式

资源数据:可变长;值内容取决于类型字段的值,可以是数值、域名、偏移指针、字符串。

4.2.7 在资源数据中提取ip和原域名

失败的解决方法

由于没有查到详细的解析资料,因此通过对整个报文每个字节进行分析,发现其格式的规律:ip地址放在报文的末尾,可以通过指针快速定位。

采样分析的域名有(www.baidu.com 和 www.sina.com),下图是对 www.baidu.com 的分析。

代码如下:

//直接定位ip地址
Ipadr *ip =(Ipadr *) (sen_buf+(r-8-4-8-2));
//如果ip地址长度不为4, 则返回
if(ip->len != 0x0400)
{
	return -1;
}
//次ip地址转为字符串
sprintf(get_ip, "%u.%u.%u.%u", ip->a, ip->b, ip->c, ip->d);

失败原因:过于投机取巧,取得两个分析对象不够特殊,对DNS回答格式完全不解

新的解决方法

通过用wireshark抓取DNS包进行分析,由于分析次数过多,此处以 www.baidu.com 为例。

由抓取的dns包分析可得到回答部分的格式如下:

answer1:
name : 不定长,c0  0c指段偏移地址
type:  16 位 0005 是别名answer
class:  16位, 0001
ttl:32位
data length: 16位
cname: 别名,长度data length

answer 2
name:c0 2b 指向别名
type:  16位 0001 是ip answer
class : 16位 0001
ttl: 32位
data length:16位
address: 4个字节
answer 3
与回应2相似 ip 不一样

进一步分析

可以发现回答部分有两个类型(只是本设计的情况,DNS有很多种类型),type 字段为5时为别名回应,为1 时为ip回应,因此通过type和上面得到的格式来进行ip和别名的获取。

其中还发现,DNS为了减小数据报文的长度,回应部份重复的字段会省略,并通过偏移指针指向重复部分,由上面的分析可知是用c0(代替字段长度)来转义下一字节为偏移指针。

获取代码设计

int parse_dns_respone(unsigned char *recv_buf,unsigned char **answer_o, int data_len ,char 
*get_ip, const char *netname)
{
	int asw_type;
	int i,j, k;
	static int fn = 0, fi = 0;
	int r;
	unsigned char cname[40];
	unsigned char *answer = *answer_o;
	answer +=2;//查询域名
	asw_type = ntohs(*((unsigned short*)answer));
	answer +=2;//type
	
	answer +=2;//class
	answer +=4;//ttl

	if(asw_type == 5) 			//域名包
	{
		bzero(cname, sizeof(cname));
		//解析域名
		parse_dns_name(recv_buf, &answer, cname);
		fn = 1; 							//标记已取得别名
	}
	else if(asw_type == 1)		//ip 回应包
	{
		//解析IP
		parse_dns_ip(&answer ,get_ip);
		if(fn == 1)
			printf("\n----  %s(%s)",cname , get_ip);
		else
			printf("\n----  %s(%s)",netname , get_ip);
		fi = 1;								//标记已取得IP
	}
	*answer_o = answer;
	if(fn != 0 && fi != 0) return 2;			//已取得别名和域名。则结果
	else if(fi != 0) return 1;
	return 0;
}

//解析IP
void parse_dns_ip(unsigned char **answer_o ,char *get_ip)
{ 
	unsigned char *answer = *answer_o;
	Ipadr *ip =(Ipadr *)answer;			//取提ip	
	sprintf(get_ip, "%u.%u.%u.%u", ip->a, ip->b, ip->c, ip->d);
	*answer_o = answer;
	
}
//解析域名
void parse_dns_name(unsigned char *recv_buf, unsigned char **answer_o, unsigned char *cname)
{ 
	int tmp_len;
	int d_length;
	int i, k;
	unsigned char *answer = *answer_o;
	d_length = ntohs(*((unsigned short*)answer));//总长度
	answer +=2;							//data length
	i=0;
	for(k=0; k<d_length; k++)
	{
		tmp_len = *answer++;			//名字段长度
		if(i != 0)
			cname[i++]='.';
		if(tmp_len == 0xc0)				//CO转义为复字段
		{
			int tmp  =  *answer++;		//获得偏移指针
			k ++;
			i--;
			while(1)
			{
				tmp_len = recv_buf[tmp++];//跳转到偏移位置
				if(tmp_len == 0) break;	  //偏移位置结果
				cname[i++]='.';
				//填充域名
				get_seg_name(&recv_buf[tmp], cname+i, tmp_len);
				tmp += tmp_len;
				i += tmp_len;
			}
			continue;
		}	
		if(tmp_len == 0) break;
		//填充域名
		get_seg_name(answer, cname+i, tmp_len);
		k += tmp_len;
		i += tmp_len;
		//移动指针
		answer += tmp_len;
		
	}
	cname[i] = '\0';
	*answer_o = answer;
}

4.2.8 遇到的问题

有多段连续的域名字段重复

原来只考虑到有一个域名字段是重复的,但是有些是有多段连续的域名字段重复的,解决的方法是(直到len字节为0 才认为没有重复字段了)。

while(1)
{
	tmp_len = recv_buf[tmp++];//跳转到偏移位置
	if(tmp_len == 0) break;	  //偏移位置结果
	cname[i++]='.';
	//填充域名
	get_seg_name(&recv_buf[tmp], cname+i, tmp_len);
	tmp += tmp_len;
	i += tmp_len;
}

有多个域名回答

原来只考虑到只有一个域名名回答,但有些(如 www.sina.com )是有多个域名的,因此解决方法是域名回答可以多次解析,只有取得IP地址才结束,而不是原来只解析两个回答就认为拿到了IP地址。

4.2.9 本实现的不足

  • 没有完全掌握DNS 回应报文的格文
  • 服务器单一,没有备用服务器
  • 还存在未知域名不能解析的,未能确保能解析所有正确域名

五、结果分析

Ping  ip 地址 如 ping 192.168.1.1

Ping 域名 如:ping www.sougou.com

Ping –n 如:ping 14.215.177.37 –n 2

Ping –t 如: ping www.baidu.com -t

Ping ?

六、心得体会

做的永远比想象中的难,修改了多次代码,刚开始想只要IP 地址,要愿意深入分析回应数据报,只是进过一些特例来定位ip 地址就好,想不到两个特例效果是一样的(www.baidu.com 和 www.sina.com ), ip 地址都是在末尾前几个字节,但是,由于DNS 的去掉重复的功能,造成只有是只有2个IP 地址的域名才能有效,多于或少于两个的都不行,因此又得花时间重新进行分析,然后才想到利用抓包软件来协助分析,又花了不少心血修正这个BUG,程序员真不好做,坐到腰酸背痛。

但是,还情事还没那么顺利,拿多几个域名来试之后又发现了问题,具有多个别名的域名没办法正确解析,在原来的基础上又很难修改,因此决定把代码封装起来,封装多几个函数,然后在封装好的基础上解决多个别名的问题,但是在调试中又出现多个连绵的域名字段重复省略导致解析也来的域名完全的问题,又进行了一番修改。真不容易。

总结

  • DNS的格式还需要找相关资料来学习
  • 以后写代码如果不是用于学习的,则应该找完成的框架进行修改,这样可以省时间,且写出来的程序也会比较稳定
  • 写程序前最好先做出详细方案,避免一些BUG
  • 网络知识的学习还有很多要学,网络编程要学的知识更加多

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/941164.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WPF 布局控件

wpf 布局控件有很多&#xff0c;常用的有&#xff1a;Grid, UniformGrid, Border, StackPanel, WrapPanel, DockPanel。 1. Grid Grid 经常作为控件的 Content 使用&#xff0c;常作为 Windows, UserControl 等 UI 元素的根节点。它用来展示一个 n 行 n 列的排版。 因此就有…

内网渗透横向移动技巧

在正常情况中&#xff0c;横向移动是在已经获取了足够的权限的情况下进行横向移动&#xff0c;下面中的方法大部分也需要高权限的操作。 https://www.freebuf.com/articles/network/251364.html 内网横向移动分为三种情况&#xff1a; 1.在VPN环境中进行横向移动&#xff1b; 2…

MONI后台管理系统-swagger3(springdoc-openapi)集成

springdoc-openapi Java 库有助于使用 Spring Boot 项目自动生成 API 文档。springdoc-openapi 通过在运行时检查应用程序来根据 Spring 配置、类结构和各种注释推断 API 语义。 该库会自动生成 JSON/YAML 和 HTML 格式的页面文档。生成的文档可以使用swagger-api注释进行补充。…

C/C++圣诞树

系列文章 序号直达链接1C/C爱心代码2C/C跳动的爱心3C/C李峋同款跳动的爱心代码4C/C满屏飘字表白代码5C/C大雪纷飞代码6C/C烟花代码7C/C黑客帝国同款字母雨8C/C樱花树代码9C/C奥特曼代码10C/C精美圣诞树11C/C俄罗斯方块12C/C贪吃蛇13C/C孤单又灿烂的神-鬼怪14C/C闪烁的爱心15C…

前端网页开发学习(HTML+CSS+JS)有这一篇就够!

目录 HTML教程 ▐ 概述 ▐ 基础语法 ▐ 文本标签 ▐ 列表标签 ▐ 表格标签 ▐ 表单标签 CSS教程 ▐ 概述 ▐ 基础语法 ▐ 选择器 ▐ 修饰文本 ▐ 修饰背景 ▐ 透明度 ▐ 伪类 ▐ 盒子模型 ▐ 浮动 ▐ 定位 JavaScript教程 ▐ 概述 ▐ 基础语法 ▐ 函数 …

vue3和element-plus笔记

对子组件直接使用v-model 子组件内定义如下 const props defineProps({modelValue: {type: String,required: true} }) const emits defineEmits(["update:modelValue"]) 父组件定义如下 <script setup> const deleteId ref(null) </script> <…

Buck开关电源闭环控制的仿真研究15V/5V[Matlab/simulink源码+Word文档]

课题设计要求 ⑴输入直流电压(VIN)&#xff1a;15V ⑵输出电压(VO)&#xff1a;5.0V ⑶负载电阻&#xff1a;R2欧 ⑷输出电压纹波峰-峰值 Vpp≤50mV &#xff0c;电感电流脉动&#xff1a;输出电流的10% ⑸开关频率(fs)&#xff1a;100kHz ⑹BUCK主电路二极管的通态压降VD0.5V…

单元测试使用记录

什么是单元测试 简单来说就是对一个类中的方法进行测试&#xff0c;对输出的结果检查判断是否符合预期结果 但是在多年的工作中&#xff0c;从来没有哪个项目中真正系统的用到了单元测试&#xff0c;因此对它还是很陌生的&#xff0c;也就造成更加不会在项目中区使用它。 如何…

麒麟操作系统服务架构保姆级教程(三)ssh远程连接

如果你想拥有你从未拥有过的东西&#xff0c;那么你必须去做你从未做过的事情 作为一名成熟运维架构师&#xff0c;我们需要管理的服务器会达到几十台&#xff0c;上百台&#xff0c;上千台&#xff0c;甚至是上万台服务器&#xff0c;而且咱们的服务器还不一定都在一个机房&am…

2024年图像处理、多媒体技术与机器学习

重要信息 官网&#xff1a;www.ipmml.org 时间&#xff1a;2024年12月27-29日 地点&#xff1a;中国-大理 简介 2024年图像处理、多媒体技术与机器学习&#xff08;CIPMT 2024&#xff09;将于2024年12月27-29日于中国大理召开。将围绕图像处理与多媒体技术、机器学习等在…

用Python在Excel工作表中创建、修改及删除表格区域

在数据分析和自动化处理的工作中&#xff0c;Excel作为一种强大的工具被广泛应用&#xff0c;而通过Python来操作Excel工作表中的表格&#xff0c;可以极大提高工作效率。表格&#xff08;Table&#xff09;是Excel中的一种重要结构&#xff0c;它是一个特殊的单元格区域&#…

【AI】✈️问答页面搭建-内网穿透公网可访问!

目录 &#x1f44b;前言 &#x1f440;一、后端改动 &#x1f331;二、内网穿透 &#x1f49e;️三、前端改动 &#x1f379;四、测试 &#x1f4eb;五、章末 &#x1f44b;前言 小伙伴们大家好&#xff0c;上次本地搭建了一个简单的 ai 页面&#xff0c;实现流式输出问答…

GM_T 0039《密码模块安全检测要求》题目

单项选择题 根据GM/T 0039《密码模块安全检测要求》,送检单位的密码模块应包括()密码主管角色。 A.一个 B.两个 C.至少一个 D.至少两个 正确答案:C 多项选择题 根据GM/T 0039《密码模块安全检测要求》,关于非入侵式安全,以下属于安全三级密码模块要求的是()。 …

使用生存分析进行游戏时间测量

标题&#xff1a;Playtime Measurement with Survival Analysis 作者&#xff1a;Markus Viljanen, Antti Airola, Jukka Heikkonen, Tapio Pahikkala 译者&#xff1a;游戏数据科学 1 游戏中的游戏时间 1.1 为什么游戏时间很重要 游戏分析在理解玩家行为方面变得越来越重…

Linux快速入门-兼期末快速复习使用

Linux快速入门-兼期末快速复习使用 一小时快速入门linux快速一&#xff1a;Linux操作系统概述1. Linux概述1.1 定义与特点1.2 起源与发展1.3 Linux结构1.4 版本类别1.5 应用和发展方向 2. 安装与启动2.1 Windows下VMware安装Linux2.2 安装Ubuntu 快速二&#xff1a;linux的桌面…

制造研发企业与IPD管理体系

芯片/半导体/制造研发型企业&#xff0c;大都知道华为使用过的IPD管理体系&#xff0c;但大家用到什么程度&#xff0c;那就是参差不齐了。 因为IPD管理体系它只是一个管理理念&#xff0c;是一个方法论。它需要有相应的组织架构来承载&#xff0c;它有很复杂的流程需要有IT系统…

帝国CMS自动生成标题图片并写进数据库

帝国CMS背景可自定义&#xff0c;可单独背景也可以随机背景,此插件根帝国cms官方论坛帖子改的&#xff0c;增加了生成图片后写入数据库,笔者的古诗词网 www.gushichi.com 也是这样设置的。 效果图 将下面的代码插入到/e/class/userfun.php中增加如下函数 单独背景代码 //自动…

数据分析和AI丨知识图谱,AI革命中数据集成和模型构建的关键推动者

人工智能&#xff08;AI&#xff09;已经吸引了数据科学家、技术领导者以及任何使用数据进行商业决策者的兴趣。绝大多数企业都希望利用人工智能技术来增强洞察力和生产力&#xff0c;而对于这些企业而言&#xff0c;数据集的质量差成为了最主要的障碍。 数据源需要进行清洗且明…

java小知识点:比较器

java中自主排序主要根据一个Comparator类来实现。 他内部实现用的是Timsort策略。大概思想是说将整个集合分成几个小段&#xff0c;每个小段分别排序&#xff0c;然后再拼在一起。 主要用法是传入两个数&#xff08;也可以不是Integer或int类型&#xff0c;这里只是把他们都统称…

【嵌入式开发笔记】OpenOCD到嵌入式调试

最近在把玩一块Risc-V的开发板&#xff0c;使用开发板调试时&#xff0c;需要用到专门的下载器和OpenOCD进行调试。 为了连接这个板子&#xff0c;费了九牛二虎之力。 这里简单记录一下自己的折腾经过吧。 0x00 环境准备 0x0001 调试背景 系统&#xff1a;Virtual Box Ub…