linux原始套接字(2)-icmp请求与接收
一.概述
上一篇arp请求使用的是链路层的原始套接字。icmp封装在ip数据报里面,所以icmp请求可以直接使用网络层的原始套接字,即socket()第一个参数是PF_INET。如下:
sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
icmp报文不同的类型有不同的格式,我们以icmp回显请求和会显应答报文格式(即ping程序使用的报文类型)为例:

类型为8表示请求,为0表示应答。校验和要自己计算,标识符一般为程序的进程ID号。序号自定义,一般从1开始。选项数据里面可以放时间戳,用作计算ping一次的花费时间!
icmp报文结构定义在netinet/ip_icmp.h
struct icmp
{
u_int8_t icmp_type; /* type of message, see below */
u_int8_t icmp_code; /* type sub code */
u_int16_t icmp_cksum; /* ones complement checksum of struct */
union
{
u_char ih_pptr; /* ICMP_PARAMPROB */
struct in_addr ih_gwaddr; /* gateway address */
struct ih_idseq /* echo datagram */
{
u_int16_t icd_id;
u_int16_t icd_seq;
} ih_idseq;
u_int32_t ih_void;
/* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */
struct ih_pmtu
{
u_int16_t ipm_void;
u_int16_t ipm_nextmtu;
} ih_pmtu;
struct ih_rtradv
{
u_int8_t irt_num_addrs;
u_int8_t irt_wpa;
u_int16_t irt_lifetime;
} ih_rtradv;
} icmp_hun;
#define icmp_pptr icmp_hun.ih_pptr
#define icmp_gwaddr icmp_hun.ih_gwaddr
#define icmp_id icmp_hun.ih_idseq.icd_id
#define icmp_seq icmp_hun.ih_idseq.icd_seq
#define icmp_void icmp_hun.ih_void
#define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void
#define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu
#define icmp_num_addrs icmp_hun.ih_rtradv.irt_num_addrs
#define icmp_wpa icmp_hun.ih_rtradv.irt_wpa
#define icmp_lifetime icmp_hun.ih_rtradv.irt_lifetime
union
{
struct
{
u_int32_t its_otime;
u_int32_t its_rtime;
u_int32_t its_ttime;
} id_ts;
struct
{
struct ip idi_ip;
/* options and then 64 bits of data */
} id_ip;
struct icmp_ra_addr id_radv;
u_int32_t id_mask;
u_int8_t id_data[];
} icmp_dun;
#define icmp_otime icmp_dun.id_ts.its_otime
#define icmp_rtime icmp_dun.id_ts.its_rtime
#define icmp_ttime icmp_dun.id_ts.its_ttime
#define icmp_ip icmp_dun.id_ip.idi_ip
#define icmp_radv icmp_dun.id_radv
#define icmp_mask icmp_dun.id_mask
#define icmp_data icmp_dun.id_data
};
二.icmp请求代码
/**
* @file icmp_request.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <sys/time.h>
/* icmp报文长度 */
#define ICMP_PACKET_LEN sizeof(struct icmp)
void err_exit(const char *err_msg)
{
perror(err_msg);
exit();
}
/* 校验和 */
unsigned short check_sum(unsigned short *addr, int len)
{
int nleft = len;
;
unsigned short *w = addr;
unsigned ;
)
{
sum += *w++;
nleft -= ;
}
)
{
*(unsigned char *)(&answer) = *(unsigned char *)w;
sum += answer;
}
sum = (sum >> ) + (sum & 0xffff);
sum += (sum >> );
answer = ~sum;
return answer;
}
/* 填充icmp报文 */
struct icmp *fill_icmp_packet(int icmp_type, int icmp_sequ)
{
struct icmp *icmp_packet;
icmp_packet = (struct icmp *)malloc(ICMP_PACKET_LEN);
icmp_packet->icmp_type = icmp_type;
icmp_packet->icmp_code = ;
icmp_packet->icmp_cksum = ;
icmp_packet->icmp_id = htons(getpid());
icmp_packet->icmp_seq = htons(icmp_sequ);
/* 发送时间 */
gettimeofday((struct timeval *)icmp_packet->icmp_data, NULL);
/* 校验和 */
icmp_packet->icmp_cksum = check_sum((unsigned short *)icmp_packet, ICMP_PACKET_LEN);
return icmp_packet;
}
/* 发送icmp请求 */
void icmp_request(const char *dst_ip, int icmp_type, int icmp_sequ)
{
struct sockaddr_in dst_addr;
struct icmp *icmp_packet;
int sockfd, ret_len;
char buf[ICMP_PACKET_LEN];
/* 请求的地址 */
bzero(&dst_addr, sizeof(struct sockaddr_in));
dst_addr.sin_family = AF_INET;
dst_addr.sin_addr.s_addr = inet_addr(dst_ip);
)
err_exit("sockfd()");
/* icmp包 */
icmp_packet = fill_icmp_packet(icmp_type, icmp_sequ);
memcpy(buf, icmp_packet, ICMP_PACKET_LEN);
/* 发送请求 */
ret_len = sendto(sockfd, buf, ICMP_PACKET_LEN, , (struct sockaddr *)&dst_addr, sizeof(struct sockaddr_in));
)
printf("sendto() ok!!!\n");
close(sockfd);
}
int main(int argc, const char *argv[])
{
)
{
printf(]);
exit();
}
/* 发送icmp请求 */
icmp_request(argv[], , );
;
}
流程:命令行接收icmp请求的目标IP,106行发送请求,指定icmp类型是8,序列号是1。然后通过目标IP地址创建网络地址结构,接着创建ICMP类型的原始套接字,填充icmp报文,并把发送时间填到icmp的数据结构。
三.icmp接收代码
/**
* @file icmp_recv.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
/* IP首部长度 */
#define IP_HEADER_LEN sizeof(struct ip)
/* icmp报文长度 */
#define ICMP_PACKET_LEN sizeof(struct icmp)
/* IP + ICMP长度 */
#define IP_ICMP_PACKET_LEN IP_HEADER_LEN + ICMP_PACKET_LEN
void err_exit(const char *err_msg)
{
perror(err_msg);
exit();
}
/* 计算发送时间与接收时间的毫秒差 */
float time_interval(struct timeval *recv_time, struct timeval *send_time)
{
;
/* 如果接收的时间微妙小于发送的微妙 */
if (recv_time->tv_usec < send_time->tv_usec)
{
recv_time->tv_sec -= ;
recv_time->tv_usec += ;
}
msec = (recv_time->tv_sec - send_time->tv_sec) * 1000.0 + (recv_time->tv_usec - send_time->tv_usec) / 1000.0;
return msec;
}
int main(void)
{
struct ip *ip_header;
struct icmp *icmp_packet;
char buf[IP_ICMP_PACKET_LEN];
struct timeval *recv_timeval, *send_timeval;
int sockfd, ret_len;
)
err_exit("sockfd()");
recv_timeval = malloc(sizeof(struct timeval));
)
{
ret_len = recv(sockfd, buf, IP_ICMP_PACKET_LEN, );
)
{
/* 接收时间 */
gettimeofday(recv_timeval, NULL);
/* 取出ip首部 */
/* 取出icmp报文 */
ip_header = (struct ip *)buf;
icmp_packet = (struct icmp *)(buf + IP_HEADER_LEN);
/* 取出发送时间 */
send_timeval = (struct timeval *)icmp_packet->icmp_data;
printf("===============================\n");
printf("from ip:%s\n", inet_ntoa(ip_header->ip_src));
printf("icmp_type:%d\n", icmp_packet->icmp_type);
printf("icmp_code:%d\n", icmp_packet->icmp_code);
printf("time interval:%.3fms\n", time_interval(recv_timeval, send_timeval));
}
}
free(recv_timeval);
close(sockfd);
;
}
流程:创建ICMP类型的原始套接字后直接接收。首先获取接收时间,然后依次取出ip首部,icmp报文,再取出icmp的请求时间。从ip首部获取源ip地址,从icmp报文获取该报文的类型,代码号,通过发送时间和接收时间计算毫秒差!
四.实验
.打开wireshark一起观察。以root运行icmp_recv,再运行icmp_request



可以看到icmp的类型是0,代码也是0。响应时间跟我们的程序差不多。
.现在我们请求一个不可达的ip地址



主机不可达时,返回的icmp报文类型是3,代码是1。报文结构不同,取出的发送时间是不正常的,所以这里计算的时间间隔也不正常。wireshark里面的结果是,本机自动广播了一个arp请求,但没有机器回答本机。
部分icmp类型:

linux原始套接字(2)-icmp请求与接收的更多相关文章
- linux原始套接字(1)-arp请求与接收
一.概述 以太网的arp数据包结构: arp结构op操作参数:1为请求,2为应答. 常用的数据结构如 ...
- Linux原始套接字实现分析---转
http://blog.chinaunix.net/uid-27074062-id-3388166.html 本文从IPV4协议栈原始套接字的分类入手,详细介绍了链路层和网络层原始套接字的特点及其内核 ...
- 关于linux 原始套接字编程
关于linux 网络编程最权威的书是<<unix网络编程>>,但是看这本书时有些内容你可能理解的不是很深刻,或者说只知其然而不知其所以然,那么如果你想搞懂的话那么我建议你可以看 ...
- Linux原始套接字抓取底层报文
1.原始套接字使用场景 我们平常所用到的网络编程都是在应用层收发数据,每个程序只能收到发给自己的数据,即每个程序只能收到来自该程序绑定的端口的数据.收到的数据往往只包括应用层数据,原有的头部信息在传递 ...
- linux原始套接字(4)-构造IP_UDP
一.概述 同上一篇tcp一样,udp也是封装在ip报文里面.创建UDP的原始套接字如下: (soc ...
- linux原始套接字(3)-构造IP_TCP发送与接收
一.概述 tcp报文封装在ip报文中,创建tcp的原始套接字如下: sockfd = socket ...
- Linux网络编程——原始套接字编程
原始套接字编程和之前的 UDP 编程差不多,无非就是创建一个套接字后,通过这个套接字接收数据或者发送数据.区别在于,原始套接字可以自行组装数据包(伪装本地 IP,本地 MAC),可以接收本机网卡上所有 ...
- UNIX网络编程——原始套接字的魔力【续】
如何从链路层直接发送数据帧 上一篇里面提到的是从链路层"收发"数据,该篇是从链路层发送数据帧. 上一节我们主要研究了如何从链路层直接接收数据帧,可以通过bind函数来将原始套接字绑 ...
- Linux基础(11)原始套接字
一边接收函数返回一边判断返回值时一定要把接收的优先级加()提高再去判断 例 if((sockfd = socket()) < 0) 问题: 如何实现SYN扫描器扫描端口 , 比如AB两个设备要进 ...
随机推荐
- C#开发可以可视化操作的windows服务
使用C#开发自定义windows服务是一件十分简单的事.那么什么时候,我们需要自己开发windows服务呢,就是当我们需要计算机定期或者一 直执行我们开发的某些程序的时候.我经常看到许多人开发的win ...
- Baraja演示15种不同的洗牌特效
实例演示 下载地址 实例代码 实例演示 实例代码 <div class="container"> <header class="clearfix&q ...
- jquery取消事件冒泡的三种方法(推荐)
1.通过返回false来取消默认的行为并阻止事件起泡. jQuery 代码: ? 1 2 3 4 5 6 7 8 9 10 11 $("form").bind( "s ...
- iOS UISlider的使用
UISlider是一个方便的控件,让用户能够以可视化的方式设置指定范围内的值. 和按钮一样,滑块也能响应事件,还可像文本框一样被读取.如果希望用户对滑块的调整立刻影响应用程序,则需要让他触发操作. 下 ...
- SVN源码泄露漏洞
SVN(subversion)是源代码版本管理软件,造成SVN源代码漏洞的主要原因是管理员操作不规范.“在使用SVN管理本地代码过程中,会自动生成一个名为.svn的隐藏文件夹,其中包含重要的源代码信息 ...
- 2013 Visual Studio Magazine读者选择奖界面框架类获奖情况
2013 Visual Studio Magazine读者选择奖已经正式揭晓了!据了解,截至今年此奖项已经评选了21次,非常值得.NET开发人员信赖和参考.此次评选共有400多个产品角逐28个分类的奖 ...
- Emacs学习心得之 基础配置
作者:枫雪庭 出处:http://www.cnblogs.com/FengXueTing-px/ 欢迎转载 Emacs学习心得之 基础配置 1.前言2.基础配置 一.前言 本篇博文记录了Emacs的一 ...
- Sharepoint学习笔记—习题系列--70-576习题解析 -(Q66-Q68)
Question 66 You are designing an application that will use a timer job that will run each night to s ...
- Android 抽屉效果的导航菜单实现
Android 抽屉效果的导航菜单实现 抽屉效果的导航菜单 看了很多应用,觉得这种侧滑的抽屉效果的菜单很好. 不用切换到另一个页面,也不用去按菜单的硬件按钮,直接在界面上一个按钮点击,菜单就滑出来,而 ...
- 拓展:使用终端创建、编译、链接OC…
本文介绍一下如何使用Mac OS X自带终端快速创建.编译.链接OC程序. 1.打开终端 顺序:打开Finder——应用程序——实用工具——终端 2.打开需要存放 .m 文件的路径(比如我需要放到桌面 ...