UNP——原始套接字
1.原始套接字的用处
使用原始套接字可以构造或读取网际层及其以上报文。
具体来说,可以构造 ICMP, IGMP 协议报文,通过开启 IP_HDRINCL 套接字选项,进而自定义 IPv4首部。
2. 创建原始套接字
2.1 使用 SOCK_RAW 创建原始套接字
sockfd = socket(AF_INET, SOCK_RAW, protocol);
protocol 定义在 <netinet/in.h>,如 IPPROTO_xxx。
2.2 初始化原始套接字的常用步骤
2.2.1 IP_HDRINCL
int on = 1;
setsockopt(sockfd, IPPOTO_IP, IP_HDRINCL, &on, sizeof(on));
2.2.2 bind, connect
原始套接字不存在端口的概念(TCP报文只是原始套接字报文的数据部分,而非首部),所以调用 bind, connect,分别设置 报文的源地址和目标地址。
对应bind而言,没有启用 IP_HDRINCL 选项,则bind会设置套接字源地址,若没有进行bind,内核会 根据数据包出口设置源地址。
对于connect,若进行connect,则设置套接字对端地址,从而可以用send/write而非sendto。
3. 原始套接字的输出
3.1 sendto 和 send
如果套接字已经设置了对端地址,则可以使用send
3.2 内核对发送数据的处理
(1)没启动 IP_HDRINCL,内核构造IPv4首部,应用进程传来的数据会被当成 IPv4数据部分,内核会将自己构造的首部和应用传来的数据并接,其中IPv4协议号按照 socket 第三个参数即 IPPROTO_XXX 设置。
(2)开启了 IP_HDRINCL,IPv4首部由应用程序构造, 内核会将应用进程传来的数据当成包含IPv4首部和IPv4数据部分的完整IPv4报文,但 IPv4首部的校验和字段总是由内核计算并存储。
即,根据IPROTO_XXX是否启用,传递给原始套接字的数据报可能不完整(不包含IP首部)。
3.3 内核会对超出 MTU 的分组进行分片。
3.4 内核总会对IPv4首部求校验并存储,而对于其他校验如ICMP,TCP,需要自己求的。
4. 原始套接字的输入
原始套接字的输入来自于内核,内核会将哪些数据传给原始套接字呢?
(1)内核不会将TCP和UDP分组传给原始套接字,如果希望获得TCP和UDP分组,必须使用链路层套接字。
(2)大多数的ICMP消息和所有的IGMP消息在内核处理完其中信息后,会传递给原始套接字
(3)内核不认识的协议字段的所有IP数据报将传给原始套接字
(4)如果数据包以分片形式到达,则在数据包完整前,内核不会传给原始套接字
当IP数据报通过上面的筛选,内核会找到匹配该数据包的原始套接字,并将数据包拷贝给该套接字,那么匹配的规则是什么呢?
(1)数据包的协议和套接字的协议匹配
(2)数据包的目的地址和套接字的源地址匹配
(3)数据包的源地址和套接字的目的地址匹配
(4)若套接字的协议和目的地址和源地址都是 void的,即协议设置为0,没有调用 connect 设置目的地址,没有调用bind设置 源地址。则该套接字会接受所有的IP数据包。
从原始套接字,收到的数据报,一定是包含IP首部的完整数据包。
5.实际测试
5.1 ping程序
实际发现 connect 没有。
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include "wrap_common.h"
#include <string.h>
#include <sys/time.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/ip_icmp.h> void proc_v4(char *ptr, ssize_t len, struct timeval *tvrecv); uint16_t in_cksum(uint16_t *addr, int len)
{
int nleft = len;
uint32_t sum = 0;
uint16_t *w = addr;
uint16_t answer = 0; while (nleft > 1) {
sum += *w++;
nleft -= 2; } if (nleft == 1) {
*(unsigned char *)(&answer) = *(unsigned char *)w ;
sum += answer; } /* 4add back carry outs from top 16 bits to low 16 bits */
sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
sum += (sum >> 16); /* add carry */
answer = ~sum; /* truncate to 16 bits */
return(answer);
} struct icmp* get_icmp(int seq)
{
static struct icmp icmp_buf;
static int datalen = 56;
int len; icmp_buf.icmp_type = ICMP_ECHO;
icmp_buf.icmp_code = 0;
icmp_buf.icmp_id = getpid();
icmp_buf.icmp_seq = seq;
memset(icmp_buf.icmp_data, 0xa5, datalen); gettimeofday((struct timeval *)icmp_buf.icmp_data, NULL);
len = 8 + datalen;
icmp_buf.icmp_cksum = 0;
icmp_buf.icmp_cksum = in_cksum((unsigned short *)&icmp_buf, len); return &icmp_buf;
} int main(int argc, char **argv)
{
int sockfd;
const struct sockaddr_in dst_addr = {0}, addr = {0}, src_addr = {0};
char *dst_ip;
struct icmp *icmp;
struct timeval tval;
int recv_len, i;
char buf[128]; dst_ip = argv[1]; sockfd = Socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); Inet_pton(AF_INET, dst_ip, (void *)&dst_addr.sin_addr); for (i = 0; ; i++) {
icmp = get_icmp(i); if (sendto(sockfd, icmp, 8+56,0, (struct sockaddr *)&dst_addr, sizeof(struct sockaddr_in)) < 0)
perror("sendto");
if ((recv_len = recv(sockfd, &buf, sizeof(buf), 0)) < 0)
perror("recv"); gettimeofday(&tval, NULL);
proc_v4(buf, recv_len, &tval); sleep(1);
} return 0;
} void tv_sub(struct timeval *out, struct timeval *in)
{
if ( (out->tv_usec -= in->tv_usec) < 0 ) { /* out -= in */
--out->tv_sec;
out->tv_usec += 1000000;
}
out->tv_sec -= in->tv_sec;
} void proc_v4(char *ptr, ssize_t len, struct timeval *tvrecv)
{
int hlen1, icmplen;
double rtt;
struct ip *ip;
struct icmp *icmp;
struct timeval *tvsend; ip = (struct ip *) ptr; /* start of IP header */
hlen1 = ip->ip_hl << 2; /* length of IP header */
if (ip->ip_p != IPPROTO_ICMP)
return; /* not ICMP */ icmp = (struct icmp *) (ptr + hlen1); /* start of ICMP header */
if ( (icmplen = len - hlen1) < 8 )
return; /* malformed packet */ if (icmp->icmp_type == ICMP_ECHOREPLY) { if (icmp->icmp_id != getpid()) {
printf("icmp id error : %d != %d \n", icmp->icmp_id, getpid());
return; /* not a response to our ECHO_REQUEST */ } if (icmplen < 16)
return; /* not enough data to use */ tvsend = (struct timeval *) icmp->icmp_data;
tv_sub(tvrecv, tvsend);
rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0; printf("from %s ", inet_ntoa(ip->ip_src));
printf("%d bytes, seq=%u, ttl=%d, rtt=%.3f ms\n", icmplen, icmp->icmp_seq, ip->ip_ttl, rtt);
}
}
5.2 使用多进程,和多线程测试
发现只要一个套接字发包,所有套接字都会收到回复,所以在读包时应该将所有包读出。
while ((recv_len = recv(sockfd, &buf, sizeof(buf), MSG_DONTWAIT)) >= 0) {
gettimeofday(&tval, NULL);
proc_v4(buf, recv_len, &tval);
}
5.3 ping程序的recv必须即使处理,才知道接受包时的准确时间。所以应该用多路转接IO,或者专门用个线程来阻塞接受。
UNP——原始套接字的更多相关文章
- 原始套接字-自定义IP首部和TCP首部
/* ===================================================================================== * * Filenam ...
- 浅谈原始套接字 SOCK_RAW 的内幕及其应用(port scan, packet sniffer, syn flood, icmp flood)
一.SOCK_RAW 内幕 首先在讲SOCK_RAW 之前,先来看创建socket 的函数: int socket(int domain, int type, int protocol); domai ...
- Linux Socket 原始套接字编程
对于linux网络编程来说,可以简单的分为标准套接字编程和原始套接字编程,标准套接字主要就是应用层数据的传输,原始套接字则是可以获得不止是应用层的其他层不同协议的数据.与标准套接字相区别的主要是要开发 ...
- 005.TCP--拼接TCP头部IP头部,实现TCP三次握手的第一步(Linux,原始套接字)
一.目的: 自己拼接IP头,TCP头,计算效验和,将生成的报文用原始套接字发送出去. 若使用tcpdump能监听有对方服务器的包回应,则证明TCP报文是正确的! 二.数据结构: TCP首部结构图: s ...
- 004.UDP--拼接UDP数据包,构造ip头和udp头通信(使用原始套接字)
一.大致流程: 建立一个client端,一个server端,自己构建IP头和UDP头,写入数据(hello,world!)后通过原始套接字(SOCK_RAW)将包发出去. server端收到数据后,打 ...
- 002.ICMP--拼接ICMP包,实现简单Ping程序(原始套接字)
一.大致流程: 将ICMP头和时间数据设置好后,通过创建好的原始套接字socket发出去.目的主机计算效验和后会将数据原样返回,用当前时间和返回的数据结算时间差,计算出rtt. 二.数据结构: ICM ...
- linux原始套接字(4)-构造IP_UDP
一.概述 同上一篇tcp一样,udp也是封装在ip报文里面.创建UDP的原始套接字如下: (soc ...
- linux原始套接字(3)-构造IP_TCP发送与接收
一.概述 tcp报文封装在ip报文中,创建tcp的原始套接字如下: sockfd = socket ...
- linux原始套接字(2)-icmp请求与接收
一.概述 上一篇arp请求使用的是链路层的原始套接字.icmp封装在ip数据报里面,所以icmp请 ...
随机推荐
- Js中Currying的应用
Js中Currying的应用 柯里化Currying是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术,是函数式编程应用. 描述 如果说函数式编程中有两 ...
- 例题4-2 刽子手游戏(Hangman Judge, UVa 489)
#include<stdio.h> #include<string.h> int ok ,no; int left ,chance; char s[20] ,s2[20]; v ...
- Jmeter请求之接口串联自动化测试(未完)
方案一:添加Cookie管理器,把用户的登录状态存在cookie管理器中,类似于浏览器 存储测试结果: 监听器->保存响应到文件,对结果进行存储 文件名前缀:保存到哪个地方前缀是什么D:\tes ...
- Java常见的一些经典面试题(附答案解析)
前言: 我想每个程序员比较头疼的事情都是:工作拧螺丝,面试造火箭吧.但是又必须经历这个过程,尤其是弄不清面试官问的问题,如果你准备的不是很充分,会导致面试的时候手足无措.今天这篇文章是从已工作5年的程 ...
- Python之format字符串格式化
1.字符串连接 >>> a = 'My name is ' + 'Suen' >>> a 'My name is Suen' >>> a = 'M ...
- 【API管理 APIM】如何查看APIM中的Request与Response详细信息,如Header,Body中的参数内容
问题描述 通过APIM门户或者是Developer门户,我们可以通过Test功能测试某一个接口,通过Trace可以获取非常详细的Request,Response的信息,包含Header,X-Forwa ...
- 5G-第五代移动通信系统(5th generation mobile/wireless/cellular system)
通信系统 有意义的信息交流被称为通讯. 自然界:狼嚎.狗叫.虫鸣.鸡叫. 人类社会:说话.眼神.写信.烽火台. 目的:信息的传递. 人类发明电以后,开始使用电来传递信息,特别快. 于是有了电报.电话以 ...
- Vue.js 3.0搭配.NET Core写一个牛B的文件上传组件
在开发Web应用程序中,文件上传是经常用到的一个功能. 在Jquery时代,做上传功能,一般找jQuery插件就够了,很少有人去探究上传文件插件到底是怎么做的. 简单列一下我们要做的技术点和功能点 使 ...
- Phoenix创建索引源码过程
date: 2020-09-27 13:50:00 updated: 2020-09-28 16:30:00 Phoenix创建索引源码过程 org.apache.phoenix.index.Inde ...
- C# 9.0 新特性预览 - init-only 属性
C# 9.0 新特性预览 - init-only 属性 前言 随着 .NET 5 发布日期的日益临近,其对应的 C# 新版本已确定为 C# 9.0,其中新增加的特性(或语法糖)也已基本锁定,本系列文章 ...