Raw_Socket原始套接字

对于raw socket,只有root权限才能够创建.
raw socket的作用主要有三个方面:
1.通过raw socket来接收发向本机的ICMP,IGMP协议包,或者用来发送这些协议包.
2.接收发向本机但TCP/IP栈不能够处理的IP包:现在许多操作系统在实现网络部分的时候,通常只实现了常用的几种协议,
如tcp,udp,icmp等,但象其它的如ospf,ggp等协议,操作系统往往没有实现,如果
自己有必要编写位于其上的应用,就必须借助raw socket来实现,这是因为操作
系统遇到自己不能够处理的数据包(ip头中的protocol所指定的上层协议不能处
理)就将这个包交给协议对应的raw socket.
3.用来发送一些自己制定源地址等特殊作用的IP包(自己写IP头,TCP头等等)

raw socket的建立是通过如下方式的:
   
    sockfd = socket(PF_INET, SOCK_RAW, protocol);

第一个参数PF_INET和AF_INET的区别 :指定address family时一般设置为AF_INET,即使用IP;指定protocal family时一般设置PF_INET。
#define AF_INET 0
#define PF_INET AF_INET
所以AF_INET与PF_INET完全一样
第二个参数说明建立的是一个raw socket
第三个参数分三种情况:
    1.参数protocol用来指明所要接收的协议号,如果是象IPPROTO_TCP(6)这
    种非0,非255号的协议,则内核碰到ip头中protocol域和创建socket所使用参
    数protocol相同的IP包,就会交给这个rawsocket来处理.因此,一般说来,
    要想接收什么样的数据包,就应该用参数protocol指定相应的协议.当
    内核向此raw socket交付数据包的时候,是包括整个IP头的,并且已经是重
    组好的IP包. 如下:
    ---------------------------------------------------------------
    |ip header|tcp header(or x header)|             data          |
    ---------------------------------------------------------------        
    用recvfrom收到的数据包括一个IP头,一个相应的协议头,然后是数据(数
    据也可以为空,就看实际情况了). 但当我们发送IP包的时候,却不用亲自
    处理IP包头,只需要填 充参数protocol所指定的相应的协议头即可.也就
    是说,用sendto的时候,我们提供给它的缓冲区数据是从IP包头的第一个字
    节开始,如下,只需要构造这么一个缓冲区就可以了.
    --------------------------------------------------------------
    |tcp header(or udp header or x header)|           data       |
    --------------------------------------------------------------
    如果想自己也想亲自处理IP头,则需要IP_HDRINCL的socket选项.如下:
    int flag = 1;
    setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &flag, sizeof(int));

这样,发送时所要提供的缓冲区有成如下形式:
    ---------------------------------------------------------------
    |ip header|tcp header(or x header)|             data          |
    ---------------------------------------------------------------        
    但是,即使是这种情况,在我们发送IP包的时候.也不是填充ip头的所有字
    段,而是应该将ip头的id(identification)字段设置为0,表示让内核来处
    理这个字段.同时,内核还帮你完成ip头的校验和的计算,并随后填充check
    字段.

2.如果protocol是IPPROTO_RAW(255),这个socket只能用来发送IP包,而不能接收任何数据.发送的数据需要自己填充IP包头,并且自己计算校验和.
    3.如果protocol是IPPROTO_IP(0).在linux和sco unix上是不允许建立的.

原始套接字有3种方式创建socket:
  1.socket(AF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP)发送接收ip数据包
  2.socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))发送接收以太网数据帧
  3.socket(AF_INET, SOCK_PACKET, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))过时了,不要用啊
  理解一下SOCK_RAW的原理, 比如网卡收到了一个 14+20+8+100+4 的udp的以太网数据帧.
  首先,网卡对该数据帧进行硬过滤(根据网卡的模式不同会有不同的动作,
如果设置了promisc混杂模式的话,则不做任何过滤直接交给上一层输入例程,
否则非本机mac或者广播mac会被直接丢弃).按照上面的例子,如果成功的话,
会进入ip输入例程.但是在进入ip输入例程之前,系统会检查系统中是否有通过
socket(AF_PACKET, SOCK_RAW, ..)创建的套接字.如果有的话并且协议相符,系统就给每个这样的socket
接收缓冲区发送一个数据帧拷贝. 然后,进入了ip输入例程,ip层会对该数据包
进行软过滤,就是检查校验或者丢弃非本机ip或者广播ip的数据包等,如果成功的
话会进入udp输入例程.但是在交给udp输入例程之前,系统会检查系统中是否有
通过socket(AF_INET, SOCK_RAW, ..)创建的套接字.如果有的话并且协议相符,
在这个例子中就是需要IPPROTO_UDP类型.系统就给每个这样的socket接收缓冲
区发送一个数据帧拷贝. 最后,进入udp输入例程。

  1. socket(AF_INET, SOCK_RAW, IPPROTO_UDP); -处理网络层的数据
  能:该套接字可以接收协议类型为(tcp udp icmp等)发往本机的ip数据包,从上面看到就是20+8+100.
  不能:不能收到非发往本地ip的数据包(ip软过滤会丢弃这些不是发往本机ip的数据包).
  不能:不能收到从本机发送出去的数据包.
  发送的话需要自己组织tcp udp icmp等头部.可以setsockopt来自己包装ip头部

  2. socket(PF_PACKET, SOCK_RAW, htons(x)); -处理数据链路层的数据
  能: 接收发往本地mac的数据帧
  能: 接收从本机发送出去的数据帧(第3个参数需要设置为ETH_P_ALL)
  能: 接收非发往本地mac的数据帧(网卡需要设置为promisc混杂模式)

  协议类型一共有四个
  ETH_P_IP 0x800 只接收发往本机mac的ip类型的数据帧
  ETH_P_ARP 0x806 只接受发往本机mac的arp类型的数据帧
  ETH_P_ARP 0x8035 只接受发往本机mac的rarp类型的数据帧
  ETH_P_ALL 0x3 接收发往本机mac的所有类型ip arp rarp的数据帧, 接收从本机发出的所有类型的数据帧.(混杂模式打开的情况下,会接收到非发往本地mac的数据帧)
  3. socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL)),这个一般用于抓包程序。
总结使用方法:
  1.只想收到发往本机某种协议的ip数据包则用第一种就足够了
  2.更多的详细的内容请使用第二种.包括ETH_P_ALL参数和混杂模式都可以使它的能力不断的加强.

二. 把网卡置为混杂模式
  在正常的情况下,一个网络接口在数据链路层应该只响应两种数据帧:
  一种是与自己硬件地址相匹配的数据帧
  一种是发向所有机器的广播数据帧
  如果要网卡接收所有通过它的数据, 而不管是不是发给它的, 那么必须把网卡置于混杂模式.
 用 Raw Socket 实现代码如下:

setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag); //设置 IP 头操作选项
    bind(sockRaw, (PSOCKADDR)&addrLocal, sizeof(addrLocal); //把 sockRaw 绑定到本地网卡上
    ioctlsocket(sockRaw, SIO_RCVALL, &dwValue);       //让 sockRaw 接受所有的数据

flag 标志是用来设置 IP 头操作的, 也就是说要亲自处理 IP 头: bool flag = ture;
  addrLocal 为本地地址: SOCKADDR_IN addrLocal;
  dwValue 为输入输出参数, 为 1 时执行, 0 时取消: DWORD dwValue = 1;

下面的程序利用Raw Socket发送TCP报文,并完全手工建立报头:
int sendTcp(unsigned short desPort, unsigned long desIP)
{
 WSADATA WSAData;
 SOCKET sock;
 SOCKADDR_IN addr_in;
 IPHEADER ipHeader;
 TCPHEADER tcpHeader;
 PSDHEADER psdHeader;
 char szSendBuf[MAX_LEN] = { 0 };
 BOOL flag;
 int rect, nTimeOver;
 if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0)
 {
  printf("WSAStartup Error!\n");
  return false;
 }
 if ((sock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_RAW, NULL, 0,
WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
 {
  printf("Socket Setup Error!\n");
  return false;
 }
 flag = true;
 if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*) &flag, sizeof(flag))
==SOCKET_ERROR)
 {
  printf("setsockopt IP_HDRINCL error!\n");
  return false;
 }
 nTimeOver = 1000;
 if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*) &nTimeOver, sizeof
(nTimeOver)) == SOCKET_ERROR)
 {
  printf("setsockopt SO_SNDTIMEO error!\n");
  return false;
 }
 addr_in.sin_family = AF_INET;
 addr_in.sin_port = htons(desPort);
 addr_in.sin_addr.S_un.S_addr = inet_addr(desIP);
 //填充IP报头
 ipHeader.h_verlen = (4 << 4 | sizeof(ipHeader) / sizeof(unsigned long));
 // ipHeader.tos=0;
 ipHeader.total_len = htons(sizeof(ipHeader) + sizeof(tcpHeader));
 ipHeader.ident = 1;
 ipHeader.frag_and_flags = 0;
 ipHeader.ttl = 128;
 ipHeader.proto = IPPROTO_TCP;
 ipHeader.checksum = 0;
 ipHeader.sourceIP = inet_addr("localhost");
 ipHeader.destIP = desIP;
 //填充TCP报头
 tcpHeader.th_dport = htons(desPort);
 tcpHeader.th_sport = htons(SOURCE_PORT); //源端口号
 tcpHeader.th_seq = htonl(0x12345678);
 tcpHeader.th_ack = 0;
 tcpHeader.th_lenres = (sizeof(tcpHeader) / 4 << 4 | 0);
 tcpHeader.th_flag = 2; //标志位探测,2是SYN
 tcpHeader.th_win = htons(512);
 tcpHeader.th_urp = 0;
 tcpHeader.th_sum = 0;
 psdHeader.saddr = ipHeader.sourceIP;
 psdHeader.daddr = ipHeader.destIP;
 psdHeader.mbz = 0;
 psdHeader.ptcl = IPPROTO_TCP;
 psdHeader.tcpl = htons(sizeof(tcpHeader));
 //计算校验和
 memcpy(szSendBuf, &psdHeader, sizeof(psdHeader));
 memcpy(szSendBuf + sizeof(psdHeader), &tcpHeader, sizeof(tcpHeader));
 tcpHeader.th_sum = checksum((unsigned short*)szSendBuf, sizeof(psdHeader) + sizeof
(tcpHeader));
 
 memcpy(szSendBuf, &ipHeader, sizeof(ipHeader));
 memcpy(szSendBuf + sizeof(ipHeader), &tcpHeader, sizeof(tcpHeader));
 memset(szSendBuf + sizeof(ipHeader) + sizeof(tcpHeader), 0, 4);
 ipHeader.checksum = checksum((unsigned short*)szSendBuf, sizeof(ipHeader) + sizeof
(tcpHeader));
 memcpy(szSendBuf, &ipHeader, sizeof(ipHeader));
 rect = sendto(sock, szSendBuf, sizeof(ipHeader) + sizeof(tcpHeader), 0,
(struct sockaddr*) &addr_in, sizeof(addr_in));
 if (rect == SOCKET_ERROR)
 {
  printf("send error!:%d\n", WSAGetLastError());
  return false;
 }
 else
  printf("send ok!\n");
 closesocket(sock);
 WSACleanup();
 return rect;
}

2、内核接收网络数据后在rawsocket上处理原则:
(1):对于UDP/TCP产生的IP数据包,内核不将它传递给任何原始套接字,而只是将这些数据交给对应的UDP/TCP数据处理句柄(所以,如果你想要通过原始套接字来访问TCP/UDP或者其它类型的数据,调用socket函数创建原始套接字第三个参数应该指定为htons(ETH_P_IP),也就是通过直接访问数据链路层来实现
(2):对于ICMP和EGP等使用IP数据包承载数据但又在传输层之下的协议类型的IP数据包,内核不管是否已经有注册了的句柄来处理这些数据,都会将这些IP数据包复制一份传递给协议类型匹配的原始套接字.
(3):对于不能识别协议类型的数据包,内核进行必要的校验,然后会查看是否有类型匹配的原始套接字负责处理这些数据,如果有的话,就会将这些IP数据包复制一份传递给匹配的原始套接字,否则,内核将会丢弃这个IP数据包,并返回一个ICMP主机不可达的消息给源主机.
(4):如果原始套接字bind绑定了一个地址,核心只将目的地址为本机IP地址的数包传递给原始套接字,如果某个原始套接字没有bind地址,核心就会把收到的所有IP数据包发给这个原始套接字.
(5):如果原始套接字调用了connect函数,则核心只将源地址为connect连接的IP地址的IP数据包传递给这个原始套接字.
(6):如果原始套接字没有调用bind和connect函数,则核心会将所有协议匹配的IP数据包传递给这个原始套接字.

时间: 2024-09-28 21:33:57

Raw_Socket原始套接字的相关文章

python使用原始套接字 解析原始ip头数据

使用底层套接字解码底层流量,是这次做的重点工作. 首先来捕获第一个包 # coding:utf-8import socket # 监听的主机IP host = "192.168.1.100" socket_protocol = socket.IPPROTO_ICMP sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) sniffer.bind((host, 0)) sniffer.setso

linux原始套接字(3)-构造IP_TCP发送与接收

一.概述                                                    tcp报文封装在ip报文中,创建tcp的原始套接字如下: 1 sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_TCP); 此时只能构造tcp报文,如果想进一步构造ip首部,那么就要开启sockfd的IP_HDRINCL选项: 1 int on = 1; 2 setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on

linux原始套接字(2)-icmp请求与接收

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

linux原始套接字(1)-arp请求与接收

一.概述                                                   以太网的arp数据包结构: arp结构op操作参数:1为请求,2为应答. 常用的数据结构如下: 1.物理地址结构位于netpacket/packet.h 1 struct sockaddr_ll 2 { 3 unsigned short int sll_family; 4 unsigned short int sll_protocol; 5 int sll_ifindex; 6 unsi

004.原始套接字,拼接UDP数据包,通信

大致流程: 建立一个client端,一个server端,自己构建IP头和UDP头,写入数据(hello,world!)后通过原始套接字(SOCK_RAW)将包发出去. server端收到数据后,打印UDP数据并发送确认消息(yes),client收到yes后将其打印. 其中: client端IP:192.168.11.104 端口:8600 server端IP:192.168.11.105 端口:8686 注意事项: 1.运行原始套接字socket需要有root权限. 2.注意主机字节序和网络字

关于linux 原始套接字编程

关于linux 网络编程最权威的书是<<unix网络编程>>,但是看这本书时有些内容你可能理解的不是很深刻,或者说只知其然而不知其所以然,那么如果你想搞懂的话那么我建议你可以看看网络协议栈的实现. 函数原型是 int socket(int domain, int type, int protocol); 其中domain 中AF_INET , AF_UNIT 较为常用,分别创建inet 域套接字和unix域套接字,unix套接字与文件相关.平时80%用的套接字都是AF_INET.这

原始套接字(SOCK_RAW)

本文转载:http://www.cnblogs.com/duzouzhe/archive/2009/06/19/1506699.html,在此感谢 原始套接字(SOCK_RAW). 应用原始套接字,我们可以编写出由TCP和UDP套接字不能够实现的功能. 注意原始套接字只能够由有 root权限的人创建. 10.1 原始套接字的创建 int sockfd(AF_INET,SOCK_RAW,protocol) 可以创建一个原始套接字.根据协议的类型不同我们可以创建不同类型的原始套接字 比如:IPPRO

原始套接字SOCK_RAW

原始套接字SOCK_RAW 实际上,我们常用的网络编程都是在应用层的报文的收发操作,也就是大多数程序员接触到的流式套接字(SOCK_STREAM)和数据包式套接字(SOCK_DGRAM).而这些数据包都是由系统提供的协议栈实现,用户只需要填充应用层报文即可,由系统完成底层报文头的填充并发送.然而在某些情况下需要执行更底层的操作,比如修改报文头.避开系统协议栈等.这个时候就需要使用其他的方式来实现. 一 原始套接字 原始套接字(SOCK_RAW)是一种不同于SOCK_STREAM.SOCK_DGR

ICMP拒绝服务攻击(原始套接字系列四)

拒绝服务攻击(DoS)企图通过使被攻击的计算机资源消耗殆尽从而不能再提供服务,拒绝服务攻击是最容易实施的攻击行为.中美黑客大战中的中国黑客一般对美进行的就是拒绝服务攻击,其技术手段大多不够高明. ICMP实现拒绝服务攻击的途径有二:一者"单刀直入",一者"借刀杀人".具体过程分析如下:   ICMPFLOOD攻击 大量的 ICMP消息发送给目标系统,使得它不能够对合法的服务请求做出响应.中美黑客大战中的多数中国黑客采用的正是此项技术.ICMP FLOOD攻击实际上是