/* * TAP-Win32 の実験 sample3.c : ICMP エコー要求に応答してみる * * MinGW でビルドする場合は -lws2_32 を指定して下さい */ #include #include #include #include "sample3.h" #pragma warning(disable: 4996) #pragma comment(lib, "Advapi32.lib") #pragma comment(lib, "ws2_32.lib") #define DEVICE_PATH_FMT "\\\\.\\Global\\%s.tap" #define TAP_CONTROL_CODE(request,method) \ CTL_CODE (FILE_DEVICE_UNKNOWN, request, method, FILE_ANY_ACCESS) #define TAP_IOCTL_GET_MAC \ TAP_CONTROL_CODE (1, METHOD_BUFFERED) #define TAP_IOCTL_SET_MEDIA_STATUS \ TAP_CONTROL_CODE (6, METHOD_BUFFERED) #define BUFMAX 2048 static HANDLE hTap = NULL; static HANDLE hEvent = NULL; // 仮想ホストの IP アドレスと MAC アドレス #define MYIPSTR "192.168.0.2" static ULONG MyIpAddress; static UCHAR MyMacAddress[ETH_ALEN]; int HandlePacket(HANDLE hTap, UCHAR *pRecvBuf, DWORD dwRecvLen); VOID dump(UCHAR *, int); BOOL ExitHandler(DWORD); CHAR *GetNetWorkDeviceGuid(CONST CHAR *, CHAR *, DWORD); int main(int ac, char *av[]) { DWORD dwLen; ULONG status = TRUE; OVERLAPPED ovl; HANDLE hEvent; UCHAR Buf[BUFMAX]; CHAR szDevicePath[256]; if (ac < 2) { printf("Usage: %s \n", av[0]); return 0; } // 指定された表示名から TAP の GUID を得る if (!GetNetWorkDeviceGuid(av[1], Buf, BUFMAX)) { printf("TAP-Win32: [%s] GUID is not found\n", av[1]); return -1; } printf("TAP-Win32: [%s] GUID = %s\n", av[1], Buf); sprintf(szDevicePath, DEVICE_PATH_FMT, Buf); // TAP デバイスを開く hTap = CreateFile (szDevicePath, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0); if (hTap == INVALID_HANDLE_VALUE) { printf("TAP-Win32: Failed to open [%s]", szDevicePath); return -1; } memset(&ovl, 0, sizeof(OVERLAPPED)); ovl.hEvent = hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); ovl.Offset = 0; ovl.OffsetHigh = 0; // Ctrl+C ハンドラを設定 SetConsoleCtrlHandler((PHANDLER_ROUTINE)ExitHandler, TRUE); // TAP の MAC アドレスを利用して // 仮想ホスト(自分)の仮想 MAC アドレスを用意 if (!DeviceIoControl(hTap,TAP_IOCTL_GET_MAC, NULL, 0, MyMacAddress, ETH_ALEN, &dwLen, NULL)) { printf("TAP-Win32: TAP_IOCTL_GET_MAC err\n"); CloseHandle(hTap); return -1; } MyMacAddress[5] += 0x01; // 仮想ホスト(自分)の IP アドレス MyIpAddress = inet_addr(MYIPSTR); // TAP デバイスをアクティブに status = TRUE; if (!DeviceIoControl(hTap,TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &dwLen, NULL)) { printf("TAP-Win32: TAP_IOCTL_SET_MEDIA_STATUS err\n"); CloseHandle(hTap); return -1; } printf("[Press CTRL+C to exit]\n"); // パケット受信ループ while (1) { if (!ReadFile(hTap, Buf, sizeof(Buf), &dwLen, &ovl)) { DWORD err = GetLastError(); if (err == ERROR_IO_PENDING) { WaitForSingleObject(hEvent, INFINITE); // 受信完了待ち GetOverlappedResult(hTap, &ovl, &dwLen, FALSE); //dump(Buf, dwLen); printf("recv %u bytes\n", dwLen); HandlePacket(hTap, Buf, dwLen); // 受信パケットの処理 } else { printf("TAP-Win32: ReadFile err=0x%08X\n", err); CloseHandle(hTap); return -1; } } else { //dump(Buf, dwLen); printf("recv %u bytes\n", dwLen); HandlePacket(hTap, Buf, dwLen); // 受信パケットの処理 } } return 0; } //------------------------------------------------- // チェックサムの再計算 USHORT RecalcCheckSum(USHORT HC, USHORT m, USHORT m_) { // 旧 checksum の 1の補数が旧データの補数和 // RFC 1624 : HC' = ~(C + (-m) + m') return ~(~HC - m + m_); } // パケットデータを TAP へ書き出す int doWriteTap(HANDLE hTap, UCHAR *pSendBuf, DWORD len) { #define ETHERDATALEN_MIN 46 DWORD dwWriteLen; HANDLE hEvent; OVERLAPPED ovl; memset(&ovl, 0, sizeof(OVERLAPPED)); ovl.hEvent = hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); ovl.Offset = 0; ovl.OffsetHigh = 0; // イーサネットデータが最小サイズに満たなければパディング if (len - sizeof(EthHdr) < ETHERDATALEN_MIN) { int padlen = ETHERDATALEN_MIN - (len - sizeof(EthHdr)); memset(pSendBuf+len, '\0', padlen); len += padlen; } if (!WriteFile(hTap, pSendBuf, len, &dwWriteLen, &ovl)) { DWORD err = GetLastError(); if (err == ERROR_IO_PENDING) { WaitForSingleObject(hEvent, INFINITE); // 完了待ち GetOverlappedResult(hTap, &ovl, &dwWriteLen, FALSE); } else { printf("TAP-Win32: WriteFile err=0x%08X\n", err); return -1; } } printf("send %u bytes\n", dwWriteLen); return 0; } // Ethernet ヘッダ編集 BOOL doEther(EthHdr *pEthRecv, EthHdr *pEthSend) { // 受信済 Ethernet ヘッダから返信用同ヘッダを編集 memcpy(pEthSend->h_dest, pEthRecv->h_source, ETH_ALEN); memcpy(pEthSend->h_source, MyMacAddress, ETH_ALEN); pEthSend->h_proto = pEthRecv->h_proto; return TRUE; } // IP ヘッダ編集 BOOL doIpHeader(IpHdr *pIpRecv, IpHdr *pIpSend) { // 受信済 IP ヘッダから返信用同ヘッダを編集 // アドレスの逆転のみを行い checksum 再計算を省略 memcpy(pIpSend, pIpRecv, pIpRecv->ihl*4); pIpSend->saddr = pIpRecv->daddr; pIpSend->daddr = pIpRecv->saddr; return TRUE; } // IP パケット処理 BOOL doIp(UCHAR *pRecvBuf, DWORD dwRecvLen, UCHAR *pSendBuf, DWORD *pdwSendLen) { EthHdr *pEth = (EthHdr*)pRecvBuf; // 受信済 Ethernet ヘッダ EthHdr *pEthx = (EthHdr*)pSendBuf; // 返信用 Ethernet ヘッダ IpHdr *pIp = (IpHdr*)((UCHAR*)pEth + sizeof(EthHdr)); // 受信済 IP ヘッダ IpHdr *pIpx = (IpHdr*)((UCHAR*)pEthx + sizeof(EthHdr)); // 返信用 IP ヘッダ IcmpHdr *pIcmp, *pIcmpx; int IpHeaderLen = pIp->ihl*4; // 自分宛てのパケットでなければ抜ける if (pIp->daddr != MyIpAddress) { return FALSE; } doEther(pEth, pEthx); // 返信用 Ethernet ヘッダを編集 doIpHeader(pIp, pIpx); // 返信用 IP ヘッダを編集 // IP ヘッダ上のプロトコル情報が ICMP の場合のみ対応 if (pIp->protocol == IPPROTO_ICMP) { // ICMP ブロックのサイズを計算 int IcmpPacketLen = dwRecvLen - sizeof(EthHdr) - IpHeaderLen; // IP パケットのフラグメント情報 -> RFC 791: P.12 USHORT f = pIp->frag_off & htons(0xBFFF); // 評価のために DF ビットをマスク BOOL MF = (pIp->frag_off & htons(0x2000)) ? TRUE : FALSE; USHORT Oft = pIp->frag_off & htons(0x1FFF); pIcmp = (IcmpHdr*)((UCHAR*)pIp + IpHeaderLen); // 受信済 ICMP ブロック位置 pIcmpx = (IcmpHdr*)((UCHAR*)pIpx + IpHeaderLen); // 返信用 ICMP ブロック位置 memcpy(pIcmpx, pIcmp, IcmpPacketLen); // フラグメントが発生していないか、または // フラグメントパケットの先頭であれば ICMP ヘッダが存在する if (f == 0 || (MF && Oft == 0)) { // ICMP エコー要求でなければ抜ける if (pIcmp->type != ICMP_ECHO) { return FALSE; } // ICMP パケットのタイプをエコー応答に pIcmpx->type = ICMP_ECHOREPLY; // ICMP チェックサムを再計算 pIcmpx->checksum = RecalcCheckSum(pIcmpx->checksum, ICMP_ECHO, ICMP_ECHOREPLY); } *pdwSendLen = dwRecvLen; return TRUE; } return FALSE; } // ARP パケット処理 BOOL doArp(UCHAR *pRecvBuf, DWORD dwRecvLen, UCHAR *pSendBuf, DWORD *pdwSendLen) { EthHdr *pEth = (EthHdr*)pRecvBuf; // 受信済 Ethernet ヘッダ EthHdr *pEthx = (EthHdr*)pSendBuf; // 返信用 Ethernet ヘッダ ArpHdr *pArp = (ArpHdr*)((UCHAR*)pEth + sizeof(EthHdr)); // 受信済 Arp ヘッダ ArpHdr *pArpx = (ArpHdr*)((UCHAR*)pEthx + sizeof(EthHdr)); // 返信用 Arp ヘッダ // 自分の MAC アドレスへの問い合わせでなければ抜ける if (pArp->ar_tip != MyIpAddress) { return FALSE; } // 返信用 Ethernet ヘッダを編集 doEther(pEth, pEthx); // 返信用 ARP ヘッダを編集 memcpy(pArpx, pArp, sizeof(ArpHdr)); pArpx->ar_op = htons(ARPOP_REPLY); // "Response" memcpy(pArpx->ar_tha, pArp->ar_sha, ETH_ALEN); pArpx->ar_tip = pArp->ar_sip; memcpy(pArpx->ar_sha, MyMacAddress, ETH_ALEN); pArpx->ar_sip = MyIpAddress; *pdwSendLen = dwRecvLen; return TRUE; } // 受信パケットの処理 int HandlePacket(HANDLE hTap, UCHAR *pRecvBuf, DWORD dwRecvLen) { EthHdr *pEth = (EthHdr*)pRecvBuf; UCHAR SendBuf[BUFMAX]; DWORD dwSendLen = 0; BOOL bRet; USHORT nEthProto = ntohs(pEth->h_proto); // Ethernet ヘッダのプロトコル情報を判定し必要なら返信パケットを生成 switch (nEthProto) { case ETH_P_ARP: // ARP bRet = doArp(pRecvBuf, dwRecvLen, SendBuf, &dwSendLen); break; case ETH_P_IP: // IP bRet = doIp(pRecvBuf, dwRecvLen, SendBuf, &dwSendLen); break; default: bRet = FALSE; break; } // 返信パケットが生成されていれば TAP へ出力 if (bRet == TRUE && dwSendLen > sizeof(EthHdr)) { if (doWriteTap(hTap, SendBuf, dwSendLen) != 0) { printf("doResponse: Write error\n"); return -1; } } return 0; } //------------------------------------------------- // パケットダンプ void dump(UCHAR *buffer, int len) { UCHAR buf[80] = "\0"; int i, cnt = 0; printf("<< dump: %d bytes >>\n", len); for (i = 0; i < len; i++) { sprintf(&buf[cnt], "%02X ", buffer[i]); cnt += 3; if ((i+1) % 8 == 0) { strcat(buf, " "); cnt++; } if ((i+1) % 16 == 0) { printf("%s\n", buf); cnt = 0; buf[0] = '\0'; } } if (buf[0] != '\0') { printf("%s\n", buf); } } // 終了時ハンドラ BOOL ExitHandler(DWORD dwCtrlType) { if (dwCtrlType == CTRL_C_EVENT) { // CTRL+C 押下 if (hTap != NULL) { CloseHandle(hTap); CloseHandle(hEvent); printf("TAP-Win32 is closed\n"); } ExitProcess(0); } return FALSE; } CHAR *GetNetWorkDeviceGuid(CONST CHAR *pDisplayName, CHAR *pszBuf, DWORD cbBuf) { #define BUFSZ 256 CONST CHAR *SUBKEY = "SYSTEM\\CurrentControlSet\\Control\\Network"; // HKLM\SYSTEM\\CurrentControlSet\\Control\\Network\{id1]\{id2}\Connection\Name が // ネットワークデバイス名(ユニーク)の格納されたエントリであり、 // {id2} がこのデバイスの GUID である HKEY hKey1, hKey2, hKey3; LONG nResult; DWORD dwIdx1, dwIdx2; CHAR szData[64], *pKeyName1, *pKeyName2, *pKeyName3, *pKeyName4; DWORD dwSize, dwType = REG_SZ; BOOL bDone = FALSE; FILETIME ft; hKey1 = hKey2 = hKey3 = NULL; pKeyName1 = pKeyName2 = pKeyName3 = pKeyName4 = NULL; // 主キーのオープン nResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SUBKEY, 0, KEY_ALL_ACCESS, &hKey1); if (nResult != ERROR_SUCCESS) { printf("GetNetWorkDeviceGuid: open key err HKLM%s\n", SUBKEY); return NULL; } pKeyName1 = (CHAR*)malloc(BUFSZ); pKeyName2 = (CHAR*)malloc(BUFSZ); pKeyName3 = (CHAR*)malloc(BUFSZ); pKeyName4 = (CHAR*)malloc(BUFSZ); dwIdx1 = 0; while (bDone != TRUE) { // {id1} を列挙するループ dwSize = BUFSZ; nResult = RegEnumKeyEx(hKey1, dwIdx1++, pKeyName1, &dwSize, NULL, NULL, NULL, &ft); if (nResult == ERROR_NO_MORE_ITEMS) { break; } // SUBKEY\{id1} キーをオープン sprintf(pKeyName2, "%s\\%s", SUBKEY, pKeyName1); nResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, pKeyName2, 0, KEY_ALL_ACCESS, &hKey2); if (nResult != ERROR_SUCCESS) { continue; } dwIdx2 = 0; while (1) { // {id2} を列挙するループ dwSize = BUFSZ; nResult = RegEnumKeyEx(hKey2, dwIdx2++, pKeyName3, &dwSize, NULL, NULL, NULL, &ft); if (nResult == ERROR_NO_MORE_ITEMS) { break; } if (nResult != ERROR_SUCCESS) { continue; } // SUBKEY\{id1}\{id2]\Connection キーをオープン sprintf(pKeyName4, "%s\\%s\\%s", pKeyName2, pKeyName3, "Connection"); nResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, pKeyName4, 0, KEY_ALL_ACCESS, &hKey3); if (nResult != ERROR_SUCCESS) { continue; } // SUBKEY\{id1}\{id2]\Connection\Name 値を取得 dwSize = sizeof(szData); nResult = RegQueryValueEx(hKey3, "Name", 0, &dwType, (LPBYTE)szData, &dwSize); if (nResult == ERROR_SUCCESS) { if (stricmp(szData, pDisplayName) == 0) { strcpy(pszBuf, pKeyName3); bDone = TRUE; break; } } RegCloseKey(hKey3); hKey3 = NULL; } RegCloseKey(hKey2); hKey2 = NULL; } if (hKey1) { RegCloseKey(hKey1); } if (hKey2) { RegCloseKey(hKey2); } if (hKey3) { RegCloseKey(hKey3); } if (pKeyName1) { free(pKeyName1); } if (pKeyName2) { free(pKeyName2); } if (pKeyName3) { free(pKeyName3); } if (pKeyName4) { free(pKeyName4); } // GUID を発見できず if (bDone != TRUE) { return NULL; } return pszBuf; }