在進入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();
/////////page2
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;
////////////page3
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()獲得錯誤原因。請看下面的示例:
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)
{
//…發送字節 < 欲發送字節
} 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頭中對應的源地址與遠端地址匹配,接收的數據才拷貝到套接字中。 建立報文
////////////page4
在利用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報頭
/////////////page5
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;
}