1.知识点
1.1套接字的分类
流套接字:TCP;数据报套接字:UDP;原始套接字:可以读写内核没有处理的IP数据包。
1.2 IP数据报格式
(1)4位版本,IPV4或者IPV6
(2)4位首部长度,单位32字长,4字节。最大首部长度60字节。当IP首部长度不是4字节的整数倍,要填充,数据部分以4的整数倍开始。
(3)16位总长度,65535字节
(4)16位标识,作用是将属于同一数据报的不同分片组装起来
(5)3位标志,只有两位有意义。最低位MF,MF=1表示后面还有分片。中间位DF,DF=0表示允许分片。
(6)13位片偏移,片偏移的作用是指出某片在原分组中的相对位置,片偏移以8个字节为偏移单位,每个分片的长度一定是8字节(64位)的整数倍。
(7)8位生存时间TTL,每经过一个路由器,TTL减去数据报在路由器中的消耗时间。当消耗时间小于1S,将TTL减1,TTL为0,丢弃数据报。
(8)8位协议,标志承载的是什么类型的报文。(TCP是6,UDP是17)
(9)16位首部检验和,一直变化(随着数据报的路由改变)。
(10)32位源,目的IP地址。
typedef struct IpHeader
{
unsigned char Version_Hlen;
unsigned char TOS;
unsigned short Length;
unsigned short Ident;
unsigned short Flags_Offset;
unsigned char TTL;
unsigned char Protocol;
unsigned short Checksum;
unsigned int SourceAddr;
unsigned int DestinationAddr;
} IpHeader;
1.3 TCP数据报格式
(1)源端口和目的端口,各占两个字节
(2)序号,4个字节,指的是本报文段所发送的数据的第一个字节的序号
(3)确认号,4个字节,确认号为N,表示前面的N-1都收到
(4)检验和,2字节,检验首部和数据,计算时应该加上12字节的伪首部
(5)紧急指针,2字节,仅在URG=1时有意义,指出本报文段中紧急数据的字节数,紧急数据的末尾在报文段的位置。
(6)选项,最长达到40字节,当没有选项,首部长度20字节。
# define URG 0x20
# define ACK 0x10
# define PSH 0x80
# define RST 0x40
# define SYN 0x02
# define FIN 0x01
typedef struct TcpHeader {
USHORT SrcPort;
USHORT DstPort;
unsigned int SequenceNum;
unsigned int Ackowledgement;
unsigned char HdrLen;
unsigned char Flags;
USHORT AdvertisedWindow;
USHORT Checksum;
USHORT UrgPtr;
} TcpHeader;
typedef struct PsdTcpHeader {
unsigned long SourceAddr;
unsigned long DestinationAddr;
char Zero;
char Protcol;
unsigned short TcpLen;
} ; PsdTcpHeader
1.4校验和的计算
对需要检验的数据每16bit进行二进制求和,高16bit不为0时需要将高16bit和低16bit反复相加,从而获得一个16bit的值,将该16bit值取反
实例:简化成4bit
发送端:
数据:1000 0100 校验和0000
反码 0111 1011 1111
叠加0111+1011+1111=0010 0001
高于4bit叠加到低4位 0001+0010=0011得到校验和
接收端:
数据:1000 0100 校验和0011
反码 0111 1011 1100
叠加0111+1011+1100=0001 1110高低叠加1111 正确
USHORT checksum ( USHORT * buffer, int size) {
unsigned long cksum = 0 ;
while ( size > 1 )
{
cksum += * buffer++ ;
size -= sizeof ( USHORT) ;
}
if ( size)
{
cksum += * ( UCHAR* ) buffer;
}
cksum = ( cksum >> 16 ) + ( cksum & 0xffff ) ;
cksum += ( cksum >> 16 ) ;
return ( USHORT) ( ~ cksum) ;
}
1.5 原始套接字编程
(1)Winsock头文件:Winsock2.h来使用Winsock的API,Ws2tcpip头文件包含了
在Windock2协议兼容文档中为TCP、IP用于检索IP地址的新函数和数据结构
(2)初始化Winsock环境
int WSAStartup (
WORD wVersionRequested,
LPWSADATA lpWSAData
) ;
struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[ WSADESCRIPTION_LEN+ 1 ] ;
char szSystemStatus[ WSASYSSTATUS_LEN+ 1 ] ;
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char * lpVendorInfo;
} ;
(3)Winsock库的释放,用WSACleanup()
if ( WSACleanup ( ) == SOCKET_ERROR)
{
printf ( "WSACleanup failed with error %d\n" , WSAGetLastError ( ) ) ;
return 0 ;
}
return 1 ;
2.实例 (有些结构系统已经定义,为了便于理解都写上了)
(1)套接字的创建和关闭。使用套接字之前,必须使用Socket函数创建一个套接字对象,此函数调用成功将返回套接字句柄。注意用完之后调用closesocket将之关闭。
int closesocket(SOCKET s) ;
SOCKET socket (
int af,
int type,
int protocol
) ;
(2)创建接收套接字的socket
SOCKET RecSocket;
RecSocket = socket ( AF_INET, SOCK_RAW, IPPROTO_IP) ;
(3)绑定套接字,使用Bind()函数
int bind (
Socket s,
const struct sockaddr FAR* name,
int namelen
) ;
Winsock中使用sockaddr_in结构指定IP地址和端口信息
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_sero[ 8 ] ;
}
绑定代码
Result= bind ( RecSocket, ( PSOCKADDR) & sock, sizeof ( sock) ) ;
if ( Result2 == SOCKET_ERROR)
{
printf ( "bind failed with error%d\n" , WSAGetLastError ( ) ) ;
closesocket ( RecSocket) ;
return 0 ;
}
获取本机ip地址的程序
char Name[ 255 ] ;
Result = gethostname ( Name, 255 ) ;
if ( Result == SOCKET_ERROR)
{
printf ( "gethostname failed with error%d\n" , WSAGetLastError ( ) ) ;
return 0 ;
}
struct hostent * pHostent;
pHostent = ( struct hostent * ) malloc ( sizeof ( struct hostent ) ) ;
pHostent = gethostbyname ( Name) ;
sock. sin_family = AF_INET;
sock. sin_port = htons ( 5555 ) ;
memcpy ( & sock. sin_addr. S_un. S_addr, pHostent-> h_addr_list[ 0 ] , pHostent-> h_length) ;
hostent的定义
struct hostent {
char * h_name;
char * * h_aliases;
int h_addrtype;
int h_length;
char * * h_addr_list;
# define h_addr h_addr_list[ 0 ]
(4)设置套接字利用函数setsockopt()实现
int setsockopt ( SOCKET s, int level, int optname, const char FAR * optval, int optlen) ;
s:
level:
optname:
optval:
optlen:
设置手工填充ip数据包首部
BOOL flag;
flag = 1 ;
int nTimeOver = 100 ;
if ( setsockopt ( SendSocket, SOL_SOCKET, SO_SNDTIMEO, ( char * ) & nTimeOver, sizeof ( nTimeOver) ) == SOCKET_ERROR)
{
printf ( "setsockopt faied with error%d\n\n" , WSAGetLastError ( ) ) ;
return 0 ;
}
设置SOCK_RAW为SIO_RCVALL,接收所有数据包
int WSAloctl (
SOCKET s,
DWORD dwloControlCode,
LPVOID lpInBuffer,
DWORD cbInBuffer,
LPVOID IpvOutBuffer,
DWORD cbOutBuffer,
LPDWORD IpcbBytesReturned,
LPWSAOVERLAPPED IpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE IpCompletionRoutline
Result = WSAIoctl ( RecSocket, SIO_RCVALL, & dwBufferInLen, sizeof ( dwBufferInLen) ,
& dwBufferLen, sizeof ( dwBufferLen) , & dwBytesReturned, NULL , NULL ) ;
if ( Result == SOCKET_ERROR)
{
printf ( "WSAIoctl failed with error %d\n" , WSAGetLastError ( ) ) ;
closesocket ( RecSocket) ;
return 0 ;
}
(5)用send()函数在已经建立的套接口上发送数据
int send (
SOCKET s,
33
const char FAR * buf,
int len,
int flags
) ;
对于无连接的套接口使用sendto()函数
int sendto (
SOCKET s,
const char FAR * buf,
int len,
int flags,
const struct sockaddr * to,
地址
int tolento
) ;
(6)套接字用recv()函数接收数据包
int recv (
SOCKET s,
char FAR * buf,
int len,
int flags
) ;
对于无连接的套接字要用recvfrom()函数
int recvfrom (
SOCKET s,
char FAR * buf,
int len,
int flags,
struct sockaddr FAR * from,
int FAR * fromlen
) ;
3.练习
练习1
编写一个基本的原始套接字程序,屏幕输出wsaData中的各项参数。
主函数如下:
void main ( )
{
int Result;
struct WSAData wsaData;
Result = WSAStartup ( MAKEWORD ( 2 , 2 ) , & wsaData) ;
if ( Result == SOCKET_ERROR)
{
printf ( "WSAStartup failed with error %d\n" , Result) ;
}
else {
printf ( "%x\n" , wsaData. wVersion) ;
printf ( "%x\n" , wsaData. wHighVersion) ;
printf ( "%s\n" , wsaData. szDescription) ;
printf ( "%s\n" , wsaData. szSystemStatus) ;
printf ( "%ld\n" , wsaData. iMaxSockets) ;
printf ( "%ld\n" , wsaData. iMaxUdpDg) ;
}
WSACleanup ( ) ;
}
练习2
利用原始套接字构造并发送 TCP 包,输出显示所构造的 IP 头和 TCP 头字段内容。
# include "Winsock2.h"
# include <iostream>
# include <winsock.h>
# include <WS2TCPIP.h>
# pragma comment ( lib, "Ws2_32.lib" )
# define MAX 100
# define SOURCE_PORT 7234
# define MAX_RECEIVEBYTE 255
using namespace std;
typedef struct ip_hdr
{
unsigned char h_verlen;
unsigned char tos;
unsigned short total_len;
unsigned short ident;
unsigned short frag_and_flags;
unsigned char ttl;
unsigned char proto;
unsigned short checksum;
unsigned int sourceIP;
unsigned int destIP;
} IPHEADER;
typedef struct tsd_hdr
{
unsigned long saddr;
unsigned long daddr;
char mbz;
char ptcl;
unsigned short tcpl;
} PSDHEADER;
typedef struct tcp_hdr
{
USHORT th_sport;
USHORT th_dport;
unsigned int th_seq;
unsigned int th_ack;
unsigned char th_lenres;
unsigned char th_flag;
USHORT th_win;
USHORT th_sum;
USHORT th_urp;
} TCPHEADER;
USHORT checksum ( USHORT* buffer, int size)
{
unsigned long cksum = 0 ;
while ( size > 1 )
{
cksum += * buffer++ ;
size -= sizeof ( USHORT) ;
}
if ( size)
{
cksum += * ( UCHAR* ) buffer;
}
cksum = ( cksum >> 16 ) + ( cksum & 0xffff ) ;
cksum += ( cksum >> 16 ) ;
return ( USHORT) ( ~ cksum) ;
}
int main ( )
{
int a;
int Result;
WSADATA wsaData;
Result = WSAStartup ( MAKEWORD ( 2 , 2 ) , & wsaData) ;
if ( Result == SOCKET_ERROR)
{
printf ( "WSAStartup failed with error %d\n" , Result) ;
return 0 ;
}
SOCKET RecSocket = socket ( AF_INET, SOCK_RAW, IPPROTO_IP) ;
if ( RecSocket == INVALID_SOCKET)
{
printf ( "socket failed with error %d\n" , WSAGetLastError ( ) ) ;
closesocket ( RecSocket) ;
return 0 ;
}
char Name[ 255 ] ;
Result = gethostname ( Name, 255 ) ;
if ( Result == SOCKET_ERROR)
{
printf ( "gethostname failed with error %d\n" , WSAGetLastError ( ) ) ;
return 0 ;
}
struct hostent * pHostent;
pHostent = ( struct hostent * ) malloc ( sizeof ( struct hostent ) ) ;
pHostent = gethostbyname ( Name) ;
SOCKADDR_IN sock;
sock. sin_family = AF_INET;
sock. sin_port = htons ( 5555 ) ;
memcpy ( & sock. sin_addr. S_un. S_addr, pHostent-> h_addr_list[ 0 ] , pHostent-> h_length) ;
int nTimeOver = 1000 ;
if ( setsockopt ( RecSocket, SOL_SOCKET, SO_SNDTIMEO, ( char * ) & nTimeOver, sizeof ( nTimeOver) ) == SOCKET_ERROR)
{
printf ( "setsockopt failed with error2 %d\n\n" , WSAGetLastError ( ) ) ;
return false;
}
struct sockaddr_in * Rsock;
Rsock = ( struct sockaddr_in * ) malloc ( sizeof ( struct sockaddr_in ) ) ;
Rsock-> sin_family = AF_INET;
Rsock-> sin_port = htons ( 8083 ) ;
Rsock-> sin_addr. s_addr = inet_addr ( "127.0.0.1" ) ;
int lenth = 0 ;
lenth = sizeof ( Rsock-> sin_port) + sizeof ( Rsock-> sin_addr) ;
IPHEADER ipHeader;
TCPHEADER tcpHeader;
PSDHEADER psdHeader;
char szSendBuf[ 60 ] = { 0 } ;
int rect;
SOCKADDR_IN addr_in;
addr_in. sin_family = AF_INET;
addr_in. sin_port = htons ( 8086 ) ;
addr_in. sin_addr. S_un. S_addr = inet_addr ( "127.0.0.1" ) ;
ipHeader. h_verlen = ( 4 << 4 | sizeof ( ipHeader) / sizeof ( unsigned long ) ) ;
ipHeader. total_len = htons ( sizeof ( ipHeader) + sizeof ( tcpHeader) ) ;
ipHeader. ident = 1 ;
ipHeader. frag_and_flags = 0 ;
ipHeader. ttl = 128 ;
tcpHeader. th_flag = 2 ;
ipHeader. proto = IPPROTO_TCP;
ipHeader. checksum = 0 ;
ipHeader. sourceIP = inet_addr ( "127.0.0.1" ) ;
ipHeader. destIP = inet_addr ( "127.0.0.1" ) ;
tcpHeader. th_dport = htons ( 8085 ) ;
tcpHeader. th_sport = htons ( 8086 ) ;
tcpHeader. th_seq = htonl ( 0x12345678 ) ;
tcpHeader. th_ack = 0 ;
tcpHeader. th_lenres = ( sizeof ( tcpHeader) / 4 << 4 | 0 ) ;
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 ( ( USHORT* ) 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 ( ( USHORT* ) szSendBuf, sizeof ( ipHeader) + sizeof ( tcpHeader) ) ;
memcpy ( szSendBuf, & ipHeader, sizeof ( ipHeader) ) ;
cout << endl << "TCP头部 " << endl;
cout << "目标端口:" << tcpHeader. th_dport << endl;
cout << "源端口:" << tcpHeader. th_sport << endl;
cout << "序列号:" << tcpHeader. th_seq << endl;
cout << "证书号:" << tcpHeader. th_ack << endl;
cout << "标志位:" << tcpHeader. th_flag << endl;
cout << "窗口尺寸:" << tcpHeader. th_win << endl;
cout << "校验和:" << tcpHeader. th_sum << endl;
cout << endl << "IP头部" << endl;
cout << "头长度:" << ipHeader. h_verlen << endl;
cout << "总长度:" << ipHeader. total_len << endl;
cout << "特征值:" << ipHeader. ident << endl;
cout << "标志位:" << ipHeader. frag_and_flags << endl;
cout << "存活时长:" << ipHeader. ttl << endl;
cout << "源IP:" << ipHeader. sourceIP << endl;
cout << "目标IP:" << ipHeader. destIP << endl;
rect = sendto ( RecSocket, szSendBuf, sizeof ( ipHeader) + sizeof ( tcpHeader) , 0 , ( struct sockaddr * ) & addr_in, sizeof ( addr_in) ) ;
if ( rect == SOCKET_ERROR)
{
printf ( "Falied!蠢驴" ) ;
}
if ( WSACleanup ( ) == SOCKET_ERROR)
{
printf ( "WSACleanup failed with error %d\n" , WSAGetLastError ( ) ) ;
return 0 ;
}
cin >> a;
return 0 ;
}
练习3
利用原始套接字接收网络接口数据包,统计接收的数据包的个数,并分析 IP 头字段,将分析结果输出。