標籤:des style class code http tar
在進入Raw Socket多種強大的應用之前,我們先講解怎樣建立一個Raw Socket及怎樣用建立的Raw Socket發送和接收IP包。
建立Raw Socket
在Windows平台上,為了使用Raw Socket,需先初始化WINSOCK:
// 啟動 Winsock WSAData wsaData; if (WSAStartup(MAKEWORD(2, 1), &wsaData) != 0) { cerr << "Failed to find Winsock 2.1 or better." << endl; return 1; } |
MAKEWORD(2, 1)組成一個版本欄位,2.1版,同樣的,MAKEWORD(2, 2)意味著2.2版。MAKEWORD本身定義為:
inline word MakeWord(const byte wHigh, const byte wLow) { return ((word)wHigh) << 8 | wLow; } |
因此MAKEWORD(2, 1)實際等同於0x0201。同樣地,0x0101可等同於MAKEWORD(1, 1)。
與WSAStartup()的函數為WSACleanup(),在所有的socket都使用完後調用,如:
void sock_cleanup() { #ifdef WIN32 sockcount--; if (sockcount == 0) WSACleanup(); #endif } |
接下來,定義一個Socket控制代碼:
SOCKET sd; // RAW Socket控制代碼 |
建立Socket並將控制代碼賦值給定義的sd,可以使用WSASocket()函數來完成,其原型為:
SOCKET WSASocket(int af, int type, int protocol, LPWSAPROTOCOL_INFO lpProtocolInfo, GROUP g, DWORD dwFlags); |
其中的參數定義為:
af:地址家族,一般為AF_INET,指代IPv4(The Internet Protocol version 4)地址家族。
type:通訊端類型,如果建立原始通訊端,應該使用SOCK_RAW;
Protocol:協議類型,如IPPROTO_TCP、IPPROTO_UDP等;
lpProtocolInfo :WSAPROTOCOL_INFO結構體指標;
dwFlags:通訊端屬性標誌。
例如,下面的代碼定義ICMP協議類型的原始通訊端:
sd = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, 0, 0, 0); |
建立Socket也可以使用socket()函數:
SOCKET WSAAPI socket( int af, int type, int protocol); |
參數的定義與WSASocket()函數相同。
為了使用socket()函數建立的Socket,還需要將這個Socket與sockaddr綁定:
SOCKADDR_IN addr_in;
addr_in.sin_family = AF_INET; addr_in.sin_port = INADDR_ANY; addr_in.sin_addr.S_un.S_addr = GetLocalIP();
nRetCode = bind(sd, (struct sockaddr*) &addr_in, sizeof(addr_in)); if (SOCKET_ERROR == nRetCode) { printf("BIND Error!%d\n", WSAGetLastError()); } |
其中使用的struct sockaddr_in(即SOCKADDR_IN)為:
struct sockaddr_in { unsigned short sin_family; unsigned short int sin_port; struct in_addr sin_addr; unsigned char sin_zero[8]; } |
而bind()函數第二個參數的struct sockaddr類型定義為:
struct sockaddr { unisgned short as_family; char sa_data[14]; }; |
實際上,bind()函數採用struct sockaddr是為了考慮相容性,最終struct sockaddr和struct sockaddr_in的記憶體佔用是等同的。struct sockaddr_in中的struct in_addr成員佔用4個位元組,為32位的IP地址,定義為:
typedef struct in_addr { union { struct { u_char s_b1, s_b2, s_b3, s_b4; } S_un_b; struct { u_short s_w1, s_w2; } S_un_w; u_long S_addr; } S_un; } IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR; |
把32位的IP地址定義為上述聯合體將使使用者可以以位元組、半字或字方式讀寫同一個IP地址。同志們,注意了,這個技巧在許多軟體開發中定義資料結構時被廣泛採用。
為了控制包的發送方式,我們可能會用到如下的這個十分重要的函數來設定通訊端選項:
int setsockopt( SOCKET s, //通訊端控制代碼 int level, //選項level,如SOL_SOCKET int optname, //選項名,如SO_BROADCAST const char* optval, //選項值buffer指標 int optlen //選項buffer長度 ); |
例如,當level為SOL_SOCKET時,我們可以設定布爾型選項SO_BROADCAST從而控制通訊端是否傳送和接收廣播訊息。
下面的代碼通過設定IPPROTO_IP level的IP_HDRINCL選項為TRUE從而使能程式員親自處理IP包前序:
//設定 IP 頭操作選項 BOOL flag = TRUE; setsockopt(sd, IPPROTO_IP, IP_HDRINCL, (char*) &flag, sizeof(flag); |
下面的函數用於控制通訊端:
int ioctlsocket( SOCKET s, long cmd, //命令 u_long* argp //命令參數指標 ); |
如下面的代碼讓socket接收所有報文(sniffer模式):
u_long iMode = 1; ioctlsocket(sd, SIO_RCVALL, & iMode); //讓 sockRaw 接受所有的資料
|
Raw Socket發送報文
發送報文的函數為:
int sendto( SOCKET s, //通訊端控制代碼 const char* buf, //發送緩衝區 int len, //要發送的位元組數 int flags, //方式標誌 const struct sockaddr* to, //目標地址 int tolen //目標地址長度 ); |
或
int send( SOCKET s, //已經建立串連的通訊端控制代碼 const char* buf, int len, int flags ); |
send()函數的第1個參數只能是一個已經建立串連的通訊端控制代碼,所以這個函數就不再需要目標地址參數輸入。
函數的傳回值為實際發送的位元組數,如果返回SOCKET_ERROR,可以通過WSAGetLastError()獲得錯誤原因。請看下面的樣本:
Raw Socket接收報文
接收報文的函數為:
int recvfrom( SOCKET s, //通訊端控制代碼 char* buf, //接收緩衝區 int len, //緩衝區位元組數 int flags, //方式標誌 struct sockaddr* from, //源地址 int* fromlen ); |
或
int recv( SOCKET s, //已經建立串連的通訊端控制代碼 char* buf, int len, int flags ); |
recv()函數的第1個參數只能是一個已經建立串連的通訊端控制代碼,所以這個函數就不再需要源地址參數輸入。
函數的傳回值為實際接收的位元組數,如果返回SOCKET_ERROR,我們可以通過WSAGetLastError()函數獲得錯誤原因。請看下面的樣本:
int bread = recvfrom(sd, (char*)recv_buf, packet_size + sizeof(IPHeader), 0, (sockaddr*) &source, &fromlen); if (bread == SOCKET_ERROR) { //…讀失敗 if(WSAGetLastError()==WSAEMSGSIZE) { //…接收buffer太小 } return - 1; } |
原始通訊端按如下規則接收報文:若接收的報文中協議類型和定義的原始通訊端匹配,那麼,接收的所有資料拷貝入通訊端中;如果通訊端綁定了本地地址,那麼只有接收資料IP頭中對應的目的地址等於本地地址,接收到的資料才拷貝到通訊端中;如果通訊端定義了遠端地址,那麼,只有接收資料IP頭中對應的源地址與遠端地址匹配,接收的資料才拷貝到通訊端中。
建立報文
在利用Raw Socket發送報文時,報文的IP頭、TCP頭、UDP頭等需要程式員親自賦值,從而達到極大的靈活性。下面的程式利用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; }
|
int bwrote = sendto(sd, (char*)send_buf, packet_size, 0, (sockaddr*) &dest, sizeof(dest)); if (bwrote == SOCKET_ERROR) { //…發送失敗 if(WSAGetLastError()==…) { //… } return - 1; } else if (bwrote < packet_size) { //…發送位元組 < 欲發送位元組 }
|