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请 ...
随机推荐
- 本地环境Django配置问题
Django本地环境出现的问题 当你的前端出现这个问题的时候 你只需要吧setting.py 中的DEBUG 改为 True,即可 我原来是是DEBUG = False 本人亲测有用!!!
- C++字符串操作小结
忽略大小写比较大小 库函数strcasecmp和_stricmp: 这两个函数都不属于C++标准库,strcasecmp由POSIX引入,windows平台则定义了功能等价的_stricmp.用法和C ...
- 【Flutter 混合开发】嵌入原生View-iOS
Flutter 混合开发系列 包含如下: 嵌入原生View-Android 嵌入原生View-iOS 与原生通信-MethodChannel 与原生通信-BasicMessageChannel 与原生 ...
- centos8安装php7.4
一,下载php7.4 1,官方网站: https://www.php.net/ 2,下载 [root@yjweb source]# wget https://www.php.net/distribut ...
- centos8平台基于iftop监控网络流量
一,iftop的作用: 基于ip统计外部机器与本机之间的网络流量, 可以方便的查看各客户端是否有非正常的到本机的访问 说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnbl ...
- python 保存登录状态 cookie
import requests from lxml import etree import faker url = "https://www.yeves.cn/admin/Articles& ...
- CentOS7下RabbitMQ服务安装配置 (亲测有效)
erlang 21.3 rabbitmq-server 3.7.14 下载地址 链接: https://pan.baidu.com/s/1g_T1Q_6zpyO3AepS0ZPgYQ 提取码: abq ...
- svn:E170001:Authorization failed解决
eclipse添加svn资源库:打开eclipse→Window→SVN资源库→空白处右键新建资源库位置→填写正确的URL→finish 错误信息: 原因一:用户名或密码错误 因svn登录验证的账号信 ...
- hbase的Java基本操作
hbase的Java基本操作 建表,建列簇操作 private static Connection connection; private static Admin admin; public sta ...
- centos下搭建Jenkins持续集成环境
1.安装JDK yum install -y java 2.安装jenkins 添加Jenkins库到yum库,Jenkins将从这里下载安装. 1 wget -O /etc/yum.repos.d/ ...