Hatena::ブログ(Diary)

SIN@SAPPOROWORKSの覚書 このページをアンテナに追加 RSSフィード Twitter

2012-01-25

ACアダプタを分解して、変圧・整流・平滑回路を見てみる

f:id:spw0022:20120125181741j:image:w360:left
何処ででもよく見るACアダプタです。
暇つぶしに・・・・・
分解して基本的な回路(変圧・整流・平滑)の動作を見て見ました。
(ほとんどのACアダプタがそうですが)ねじ止めなどで蓋が開くタイプではないので、プラスチックカッターでつなぎ目を切って開けました。
元には戻せません。(再起不能です)



f:id:spw0022:20120125181740j:image:w360:left
表記は、
INPUT:120VAC 60Hz 19W
OUTPUT:12VDC 1000mA
となっています。






f:id:spw0022:20120125181739j:image:w360:left

トランスと下の板は両面テープで張ってあるだけでした。
基板は、はんだ付けされていたものを外してみました。








f:id:spw0022:20120125181737j:image:w360:left
中に入っていたのは、トランスと小さな基板。
基板は全波整流回路だけです。









f:id:spw0022:20120125181736j:image:w360:left
接続をしなおして、各所の測定をして見ました。
※負荷は100KΩ(0.12mA)

この時の出力電圧はテスターで13.09V。トランス型のアダプタの場合、負荷によって電圧が変化しますが、
これぐらいの小さな負荷だと規格(12V)よりも高い電圧が出てます。




f:id:spw0022:20120125181735j:image:w360:left
次にオシロスコープで波形を確認しました。
トランスの出口と出力部分。そして最後に平滑コンデンサを外して出力を確認しました。








f:id:spw0022:20120125181733j:image:w360:left結果は当たり前というか、教科書どおりというか・・・
トランスの出口では、14.30V 28.60Vp-p 50Hz(交流)の波形が見られますが、出力では、13.20Vの直流になっています。
出力に現れているわずかな揺れの20mVp-pが、出力リップルという事なのでしょうか。
そして、出力手前の平滑コンデンサ2200μFを外すと、交流の負の部分が全部、正の側に変化している波形(全波整流)を見ることが出来ます。
なお、この時点でピーク電圧は、出力とほぼ同じで13.32Vになっていました。

2012-01-14

64ビット対応のDLLインジェクション(CreateRemoteThread+LoadLibrary)

久々にDLLインジェクションを書いていたのですが、64ビットに関する情報が意外に少なく少し苦労しました。せっかくですので、今回把握できた事項を可能な限りまとめたいと思います。突っ込み所満載だと思います、ぜひ、色々教えてください。

【サンプルプログラム
ソース DllInjection-src.zip
バイナリ DllInjection-bin.zip

Windowsで、他のプロセス上でコードを実行する方法の1つにDLLインジェクションという手法があります。これを利用すると、通常のプログラムの枠を超えた機能を実現することが可能になります。
DLLインジェクションは、クラッキング等にも悪用され「危険なもの」というイメージもあるようですが、上手く使用すれば、非常に有用プログラムを作成することができると思います。

[使用例]
・修正ができないプログラムを実行時に修正(メッセージ・タイトル・挙動など)
・他のプロセスの動作(メッセージ監視、トラップなど)
・他のプロセスの権限で動作(SYSTEM権限での動作など)


1 DLLインジェクションの動作イメージ
f:id:spw0022:20120108064921p:image:w360:right
Winddows上で動作する各プログラムは、それぞれ別の(仮想)メモリ空間の中で動作し、通常、相互に干渉することはありません。
プロセスのメモリ空間内には、当該プロセスの独自コードのほか、カーネルコードや必要なDLLがロードされています。そして、このDLLのロードの方法には、次の2種類があります。
(1) プログラム作成時にスタティックにリンク
(2) プログラム実行時にダイナミックにロード

DLLを「プログラム実行時にダイナミックにロード」する場合、Win32APIのLoadLibrary()及びFreeLibrary()を使用して行いますが、この動作を他のプロセスから行うのが、CreateRemoteThread+LoadLibraryによるDLLインジェクションです。図で表現すれば、プロセスAがプロセスBのメモリ空間にtest.dllを挿入するイメージです。
いったん挿入されたtest.dllのコードは、プロセスBのメモリ空間で、プロセスBの権限で動作します。
挿入しただけでは、何も起こりませんので、通常は、プロセスA(挿入元)から意図した動作ができるように、何らかの仕組み(トリガ)を組み込む事になります。

2 自プロセス内でのDLLのダイナミックロード
比較のため、最初に、一般的なDLLのダイナミックロードのコードを示します。これは自メモリ空間にDLLを読み込んで使用する普通のコードです。

HMODULE hDll = ::LoadLibrary( "test.dll" ); //test.dllをロードする
if ( hDll != NULL ){
    //DLLを使用するコードをここに記述する
    ::FreeLibrary( hDll );//test.dllをアンロードする
}

LoadLibrary()が成功した時点で、test.dllは、メモリ空間に展開され、DLL内のコードであるDllMain()が呼び出されます。
先の図で言えば、プロセスBが自分のメモリ空間にtest.dllをロードするコードになります。

3 他プロセスでのDLLのダイナミックロード
他のプロセス上でコードを実行するAPIとして、CreateRemoteThread()があります。このAPIは、ターゲットのプロセス空間で指定したスレッドを実行するもです。

HANDLE CreateRemoteThread(
    HANDLE hProcess,        // 新しいスレッドを稼働させるプロセスを識別するハンドル
    LPSECURITY_ATTRIBUTES lpThreadAttributes,// スレッドのセキュリティ属性へのポインタ
    DWORD dwStackSize,     // 初期のスタックサイズ (バイト数)
    LPTHREAD_START_ROUTINE lpStartAddress,// 新しいスレッドに実行させる関数へのポインタ
    LPVOID lpParameter,   // 新しいスレッドの引数へのポインタ
    DWORD dwCreationFlags,  // 作成フラグ
    LPDWORD lpThreadId      // 取得したスレッド識別子へのポインタ
);

このAPIでは、第4パラメータに「新しいスレッドに実行させる関数へのポインタ」を指定しますが、ここには、
LPTHREAD_START_ROUTINE 型関数ポインタが必要です。

typedef DWORD (__stdcall *LPTHREAD_START_ROUTINE) (
    [in] LPVOID lpThreadParameter
);

しかし、LPTHREAD_START_ROUTINE型は、1つのポインタ型のパラメータを受け取るという意味で、LoadLibrary()やFreeLibrary()と同じですので、代わりにLoadLibrary()等の関数ポインタを指定できるという事になります。

なお、「関数へのポインタ」(メモリ上のアドレス)は、コードを実行するメモリ空間でのアドレス(挿入先のアドレス)である必要がありますが、LoadLibrary()及びFreeLibrary()は、kernel.dllに含まれるAPIであり、Windowsではkernel.dllが、すべてのプロセス空間で同一アドレスにロードされている(※1)ため、「挿入元でアドレスを取得」しても、その値をそのまま「挿入先のプロセス空間でのアドレス」として使用することが可能です。
関数へのポインタの取得は、GetProcAddress()を使用しています。

プロセスDLLをロードする一連のコードは、下記のとおりです。

//事前に挿入先のプロセスのハンドル(hProcess)が取得済みとする

//LoadLibrary()のアドレス取得(他プロセス上でも同一のアドレスとなる)
UIntPtr pAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
//挿入先でLoadLibraryを呼び出す pMemはパラメータのアドレス(後述)

IntPtr bytesout;
IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, pAddr, pMem, 0, out bytesout);
if (hThread != IntPtr.Zero) {
    //挿入したスレッドが終了するまで待機(DllMailから返るまで待つ)
    UInt32 INFINITE = 0xFFFFFFFF;//シグナル状態になるまで待機
    WaitForSingleObject(hThread,INFINITE);

    //スレッドの戻り値(LoadLibrary()の戻り値)を取得する
    if (GetExitCodeThread(hThread, out hDll)) {
        result = true;//成功
    }
    //スレッドハンドルのクローズ
    CloseHandle(hThread);
}

CreateRemoteThread()のあと、挿入したDLLのDllMain()が実行され、そこから返るのを、WaitForSingleObject()で待っています。また、GetExitCodeThread()を使用して、LoadLibrary()の戻り値を取得しています。これは、ロードしたDLLのハンドルであり、FreeLibrary()でDLLをアンロードする時のパラメータとして使用します。

4 LoadLibrary()のパラメータ
LoadLibrary()には、ロードするDLLのパス(文字列)のポインタが必要ですが、これも、挿入先のメモリ空間のアドレスである必要があります。自プロセス内の文字列ポインタをそのまま与えても動作できません。
そこで、このパス文字列を挿入先のプロセスのメモリ空間内に展開して、そのアドレスを取得する作業が必要になります。

この方法は、概ね下記の手順になります。
(1)挿入先のプロセス空間にメモリを確保する VirtualAllocEx() 
(2)挿入先のメモリ空間に書き込む WriteProcessMemory() 
(3)書き込んだメモリを使用する (Loadribrary()のパラメータbに使用)
(4)確保したメモリの開放 VirtualFreeEx()

また、コードは下記のようなものになります。

//事前に挿入先のプロセスのハンドル(hProcess)が取得済みとする

//パラメータ用の文字列を置くための領域を挿入先に確保する
UInt32 MEM_COMMIT = 0x1000;
UInt32 PAGE_EXECUTE_READWRITE = 0x40;
pMem = (IntPtr)VirtualAllocEx(hProcess, IntPtr.Zero, (uint)len, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (pMem != IntPtr.Zero) {
    IntPtr bytesout;
    //確保した挿入先の領域にDLL名を書き込む
    WriteProcessMemory(hProcess, pMem, dllName, (UIntPtr)len, out bytesout);
    
    //確保したメモリをここで使用する(挿入先でLoadLibraryを呼び出す)

    //確保したメモリの開放
    VirtualFreeEx(hProcess, pMem, 0, 0x8000);
}

5 64ビットと32ビットの混在
64ビットOSでは、従来の32ビットプログラムもWOW64によりそのまま使用することができるため、両者が混在する状況になっています。(※2)しかし、ここで注意が必要なのは、ロードする側のEXEとロードされる側のDLLは、同一である必要があるということです。32ビットのEXEは、32ビットのDLLしか使用できないのです。
32ビットと64ビットのプログラムでLoadLibrary()を使用して、2種類のDLLをロードした試験結果は下記のとおりです。
f:id:spw0022:20111227232617p:image:w450

Dllインジェクションを行う場合も、挿入先のプロセスに合わせたDLLを用意する必要があるという事になります。(※3)

6 CreateRemoteThread()でERROR_ACCESS_DENIEDが発生する
64ビットOS上では、32ビットプログラムも使用できますが、32ビットプログラムからは、CreateRemoteThread()は、ERROR_ACCESS_DENIEDが発生して使用できません
したがって、CreateRemoteThread()を使用したDLLインジェクションを行う場合、64ビットプログラムでしか実装できないことになります。

また、64ビットプログラムと32ビットプログラムでは、kernel32.dllが同一アドレスにロードされていないため(※4)、自プロセス内で取得したアドレスをそのまま使用することは、できないことになります。

各種の組み合わせの成否を表にしてみました。
f:id:spw0022:20111227235157p:image:w650

64ビットプログラムから32ビットプログラムのメモリ空間アドレスを取得する方法が、まったく無いわけではありませんが、少しややこしくなるため、ここでは割愛させて頂きます。

7 .NETによる両対応EXEの作成
f:id:spw0022:20120101111911p:image:w360:left
.NETプログラム中間言語であるため、32ビットと64ビットの両方で環境に応じて動作するプログラム(EXE)を作成できます。(プラットフォームターゲットでAnyCPUを選択する)
そして、実行時に64ビットで動作しているか32ビットで動作しているかを確認して、適切なDLLをインジェクションすることで両対応のプログラムとすることができます。
動作状態を確認してDLLを選択するコードは次のようになります。

//64ビットで動作している場合、ポインタは8バイトになる
var dName = (IntPtr.Size == 8)?"64bit.dll":"32bit.dll";
dllInjection.Load(dllName, exeName);//DLLのインジェクション

8 64ビット環境でDLLハンドルが取得できない
LoadLibrary()でロードしたDLLをアンロードするにはFreeLibrary()を使用しますが、この時、DLLハンドルが必要になります。先の例ではGetExitCodeThread()を使用してDLLハンドルを取得しましたが、このコードは64ビットでは問題となります。
64ビット環境では、DLLハンドルが32ビット幅を超えた位置になるのに、GetExitCodeThread()で終了ステータスを受け取るパラメータがDWORD(32ビット)のポインタになっているため、下位32ビットのアドレスしか取得できないためです。
64ビット環境でのDLLハンドルが必要な場合は、アドレスを再計算が必要です。

9 挿入するDLLの作成(動作確認用)
挿入されるコードは、ターゲットからLoadLibraryで読み込むことになりますが、これはネイティブDLLである必要があります。C#でこれを作る事ができないので、この作業はC++となります。

動作確認用にDllMail()が呼び出された時、そのメッセージの種類をポップアップするだけのものになっています。ポップアップしたウインドウをクローズしないとDllMail()を抜けませんので、ロードする側のコードもブロックします。動作確認の為、あえてそういう仕様にしました。

//【試験用に作成した test.dll DllMailが呼び出されるたびにMessageBoxを表示する】
#include "stdafx.h"
void popup(char *caption){
	char tmp[128];
	wsprintf(tmp,"pid=%d",GetCurrentProcessId());
	MessageBox(NULL,caption,tmp,0);
}
BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved){
    switch (ul_reason_for_call){
        case DLL_PROCESS_ATTACH:
	   popup("DLL_PROCESS_ATTACH");
	   break;
        case DLL_THREAD_ATTACH:
	   popup("DLL_THREAD_ATTACH");
	   break;
        case DLL_THREAD_DETACH:
	   popup("DLL_THREAD_DETACH");
	   break;
        case DLL_PROCESS_DETACH:
	   popup("DLL_PROCESS_DETACH");
	   break;
    }
    return TRUE;
}

GUIを持たないサービスなどに、このDLLを挿入すると、ポップアップウインドウのOKボタンが押せないためDllMain()から戻ることができません。同プロセスはロックし再起動が必要になりますのでご注意ください。

10 動作確認
サンプルに含まれるDllImjectionTest.exeを起動すると、「Load」と「Unload」のボタンがあるウインドウが表示されます。notepad.exe「メモ帳」を起動した状態で「Load」ボタンを押すと、notepadのプロセスIDと「DLL_PROCESS_ATTACH」が表示されたメッセージボックスがポップアップします。また「Unload」ボタンを押すと、「DLL_PROCESS_DETTACH」が表示されます。
「Load」だけ押して、dllがロードされた状態で「メモ帳」を閉じると、自動的にdllが開放されるので「DLL_PROCESS_DETACH」がポップアップします。

f:id:spw0022:20120102183141p:image:w360:left

Windows7及びVistaで実行する場合、ユーザーアカウント制御 (UAC; User Account Control)が有効になっているとOpenProcess()でACCESS_DENYEDになります。なお、UACの設定変更を反映するには、Windows再起動が必要ですので、動作確認の場合はご注意ください。





(※1)kernal.dllは、すべてのプロセスで同一アドレスにロードされている
下記は、ListDlls.exeを使用して、現在ロードされているDLLを列挙した結果です。
winlogon.exe及びnotepad.exeは、共にkernel.dllを0x00000000771c0000からロードしています。(64bit Vistaで確認)

C:\>ListDlls winlogon
ListDLLs v3.1 - List loaded DLLs
Copyright (C) 1997-2011 Mark Russinovich
Sysinternals - www.sysinternals.com

------------------------------------------------------------------------------
winlogon.exe pid: 680
Command line: winlogon.exe

Base Size Path
0x00000000ffb30000 0x67000 C:\Windows\system32\winlogon.exe
0x0000000077650000 0x186000 C:\Windows\system32\ntdll.dll
0x00000000771c0000 0x12d000 C:\Windows\system32\kernel32.dll
0x00000000fe9b0000 0x108000 C:\Windows\system32\ADVAPI32.dll
0x00000000fe7f0000 0x143000 C:\Windows\system32\RPCRT4.dll

>ListDlls notepad
ListDLLs v3.1 - List loaded DLLs
Copyright (C) 1997-2011 Mark Russinovich
Sysinternals - www.sysinternals.com

------------------------------------------------------------------------------
notepad.exe pid: 4264
Command line: "C:\Windows\system32\NOTEPAD.EXE" C:\work\32.txt

Base Size Path
0x00000000ffe10000 0x2f000 C:\Windows\system32\NOTEPAD.EXE
0x0000000077650000 0x186000 C:\Windows\system32\ntdll.dll
0x00000000771c0000 0x12d000 C:\Windows\system32\kernel32.dll
0x00000000fe9b0000 0x108000 C:\Windows\system32\ADVAPI32.dll
0x00000000fe7f0000 0x143000 C:\Windows\system32\RPCRT4.dll


(※2)64ビットと32ビットで動作しているプロセスを確認する
f:id:spw0022:20111228084043p:image:w360:left

タスクマネージャを使用すると、32ビットで動作中のプロセスには、「*32」が表示されるため、どちらで動作しているかを簡単に確認することができます。
図では、notepad.exe winlogon.exeなどが64ビットで、POWERPNT.EXE、TrueCrypt.exeなどが32ビットで動作していることが分かります。




(※3)DLLの32ビットと64ビットの違い
DLLが32ビットで作成されているか、64ビットで確認されているかは、ヘッダ情報を見ることで確認できます。
64ビットでコンパイルしたDLL
c:\>dumpbin /HEADERS 64.dll
Microsoft (R) COFF/PE Dumper Version 10.00.40219.01
Copyright (C) Microsoft Corporation. All rights reserved.

Dump of file 64.dll
PE signature found
File Type: DLL

FILE HEADER VALUES
8664 machine (x64)
7 number of sections
4EF94A96 time date stamp Tue Dec 27 13:33:26 2011
0 file pointer to symbol table
0 number of symbols
F0 size of optional header
2022 characteristics
Executable
Application can handle large (>2GB) addresses
DLL

32ビットでコンパイルしたDLL
c:\>dumpbin /HEADERS 32.dll
Microsoft (R) COFF/PE Dumper Version 10.00.40219.01
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file 32.dll

PE signature found
File Type: DLL
FILE HEADER VALUES
14C machine (x86)
7 number of sections
4EF947E4 time date stamp Tue Dec 27 13:21:56 2011
0 file pointer to symbol table
0 number of symbols
E0 size of optional header
2102 characteristics
Executable
32 bit word machine
DLL



(※4) 64ビットと32ビットでは、kernal.dllがロードされているアドレスが違う
32ビットで動作しているPOWERPNT.EXEやTrueCrypt.exeは、共に、0x0000000075230000 からkernel.dllをロードしているが、これは、先の64ビットのnotepad.exe等とは違うアドレスでとなっている。
c:\>listdlls POWERPNT.EXE
ListDLLs v3.1 - List loaded DLLs
Copyright (C) 1997-2011 Mark Russinovich
Sysinternals - www.sysinternals.com

------------------------------------------------------------------------------
POWERPNT.EXE pid: 5072
Command line:

Base Size Path
0x000000002dc30000 0x212000 C:\Program Files (x86)\Microsoft Office\Office14\POWERPNT.EXE
0x00000000774f0000 0x186000 C:\Windows\system32\ntdll.dll
0x0000000075140000 0x45000 C:\Windows\system32\wow64.dll
0x0000000074ae0000 0x4e000 C:\Windows\system32\wow64win.dll
0x0000000075130000 0x9000 C:\Windows\system32\wow64cpu.dll
0x000000002dc30000 0x212000 C:\Program Files (x86)\Microsoft Office\Office14\POWERPNT.EXE
0x00000000776b0000 0x160000 C:\Windows\SysWOW64\ntdll.dll
0x0000000075230000 0x110000 C:\Windows\syswow64\kernel32.dll
0x000000005a160000 0x924000 C:\Program Files (x86)\Microsoft Office\Office14\ppcore.dll

c:\>listdlls truecrypt.exe
ListDLLs v3.1 - List loaded DLLs
Copyright (C) 1997-2011 Mark Russinovich
Sysinternals - www.sysinternals.com

------------------------------------------------------------------------------
TrueCrypt.exe pid: 3796
Command line: "C:\Program Files\TrueCrypt\TrueCrypt.exe"

Base Size Path
0x0000000000400000 0x19d000 C:\Program Files\TrueCrypt\TrueCrypt.exe
0x00000000774f0000 0x186000 C:\Windows\system32\ntdll.dll
0x0000000075140000 0x45000 C:\Windows\system32\wow64.dll
0x0000000074ae0000 0x4e000 C:\Windows\system32\wow64win.dll
0x0000000075130000 0x9000 C:\Windows\system32\wow64cpu.dll
0x0000000000400000 0x19d000 C:\Program Files\TrueCrypt\TrueCrypt.exe
0x00000000776b0000 0x160000 C:\Windows\SysWOW64\ntdll.dll
0x0000000075230000 0x110000 C:\Windows\syswow64\kernel32.dll
0x0000000074a50000 0x85000 C:\Windows\WinSxS\x86_microsoft.windows.common-con
trols_6595b64144ccf1df_5.82.6002.18305_none_88f3a38569c2c436\COMCTL32.dll



参考資料
別のプロセスにコードを割り込ませる3つの方法
DLL Injection and function interception tutorial
Advanced Windows 第5版 上 (マイクロソフト公式解説書)
ListDLLs 作成者: Mark Russinovich

2011-12-11

F#によるパケットモニタの作成(WinPcap)

本記事は、「F# Advent Calendar 2011 」の12月12日分です。

F#の街さっぽろ」に住む、札幌ワークスSINです。F#を始めたばかりの超初心者です。近くの方々に色々やさしく教えて頂きながら楽しく勉強中です。

「F#のここがよくわからない」とかってエントリを書くと、やさしいお兄さんたちがやさしく教えて・・・の甘い言葉に乗せられて、ついついエントリーしてしまいましたが、連日、皆さんのハイレベルな記事を拝見して、完璧に失敗したなと猛反省してます。まーハードルをガッツリ下げる担当という事で、どうかお許しください。

1 WindowsでのF#によるパケットモニタ
本日の記事は、「WindowsでのF#によるパケットモニタ」です。
WindowsAPIでは、IP層以下のパケットを処理する事ができないため、通常、NICをモニタするためのデバイスドライバを作成します。そして、その代表的なものが、フリーで公開されているWinPcapです。
WinPcapは、もともとC,C++から使用するライブラリDLL形式)となっていますが、C#から使用するサンプルも多数公開されています。しかし、現時点で、F#から使用する例がまだ検索できないようなので(ええっ世界初?@o@まじ)、今回、作成してみる事にしました。

全部のコード及び、実行画面は下記のとおりです。
WinPcap.fs
Program.fs

デバイス選択中
f:id:spw0022:20111210194124p:image:w360

実行中
f:id:spw0022:20111210194123p:image:w360

※サンプルは、WinPcapインストールされていないと実行できません。
WinPcap http://www.winpcap.org/

2 WinPcap定義

.NET Framework上で動作するプログラムから、.NETアセンブリ以外のDLLエクスポートしている関数を呼び出すには、DllImport属性を使用して、あらかじめ宣言するようになっています。通常、C#プログラムでよく見かけるこの手法は、F#でも大きな問題はなく利用できました。

手順としては、WinPcapのDeveloperResourcesのページからWinPcap version4.1.2のソースコードダウンロードし、winpcap\wpcap\libpcap\pcapに含まれる下記のヘッダファイルのうち、通常利用される関数と構造体をDllImport及びStructで宣言しました。
pcap.h、pcap1.h、bpf.h、Win32-Extensions.h、remote-ext.h
指定した定義は、まとめてmodule WinPcapとし、WinPcap.fsにまとめました。

f:id:spw0022:20111211195939p:image

3 メイン処理
プログラムの主要な部分は、下記のとおりです。

f:id:spw0022:20111211201117g:image

主要な流れは、以下のとおりで、強制終了されるまで(4)を繰り返し実行しています。
(1) デバイスの一覧取得 pcap_findalldevs_ex()
(2)デバイスの選択
(3)デバースのオープン pcap_open()
(4)パケット受信 pcap_next_ex()

(3)及び(4)の繰り返し部分は、次のようになっています。
f:id:spw0022:20111211201346g:image

4 パケットの解析
受信パケットWinPcapからbyte[]データとして取得できます。パケットモニタでは、このバイト列を先頭から読みすすめ、プロトコルに当てはめて解析します。
通常この処理は、プロトコルを構造体で表現し、取得したデータに構造体のポインタを当てて解析するのが普通なのですが、F#では、fixedやunsafeが使用できないため、マーシャリングを必要とするやや煩雑な処理になってしまいました。

それでは、解析の一例としてIPv6ヘッダを処理しているコードを例に処理パターンを紹介します。
f:id:spw0022:20111211201851g:image
処理の流れは、以下のとおりです。
(1) プロトコルヘッダを表現する構造体を定義
(2) 構造体サイズ分のメモリ確保
(3) 構造体へのマーシャリング
(4) プロトコル項目の取得
(5) 表示
(6) メモリ解放

他のプロトコルも概ね同様の処理です。なお、全てのプロトコルの解析をサポートすると膨大な量になりますが、本記事では、Ether ARP IPv4 IPv6のヘッダ解析のみを行っています。

5 PInvokeStackImbalanceの警告が発生する
f:id:spw0022:20111210200433p:image:w360:right
DllImportで宣言した関数を使用すると「PInvokeStackImbalance」の警告が発生しました。
C#では、同じ定義でも問題ないのですが・・・F#では問題と認識するようです。
最終的に、原因・回避方法は、よく分かってないのですが、呼び出し規約をcdeclと明記することで環境によっては警告が出なくなるようです。(手元のVista64bitでは、cdecl指定で警告が出なくなりました)
※64bitOSと32bitOSの違いでも、警告の出方が違うようです

6 最後に
F#としてのTipsが全くない、また、関数型が理解できていない、つまらない記事で本当に申し訳ありません。
突っ込みどころ満載だと思います。ぜひ、色々教えてやって下さい。

2011-11-29

IcmpSendEcho()によるpingの送受信(#C)(#F)

IPHLPAPI.DLLのIcmpSendEcho()によりpingの送受信が行えます。
以前に紹介した、System.Net.NetworkInformation.Pingより、ややきめ細かい指定が可能です。
C#サンプル

using System;
using System.Runtime.InteropServices;

namespace Example {
    class Program {

        [DllImport("icmp.dll", SetLastError=true)]
        static extern IntPtr IcmpCreateFile();
        [DllImport("icmp.dll", SetLastError=true)]
        static extern bool IcmpCloseHandle(IntPtr handle);
        [DllImport("icmp.dll", SetLastError=true)]
        static extern Int32 IcmpSendEcho(IntPtr icmpHandle, Int32 destinationAddress, String requestData,
             Int16 requestSize, ref ICMP_OPTIONS requestOptions, ref ICMP_ECHO_REPLY replyBuffer,
             Int32 replySize, Int32 timeout);
        [DllImport("ws2_32.dll", ExactSpelling = true)]
        static extern Int32 inet_addr(string cp);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        private struct ICMP_OPTIONS {
            public Byte Ttl;
            public Byte Tos;
            public Byte Flags;
            public Byte OptionsSize;
            public IntPtr OptionsData;
        };

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        private struct ICMP_ECHO_REPLY {
            public Int32 Address;
            public Int32 Status;
            public Int32 RoundTripTime;
            public Int16 DataSize;
            public Int16 Reserved;
            public IntPtr DataPtr;
            public ICMP_OPTIONS Options;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 250)]
            public String Data;
        }

        static void Main(string[] args){
            ICMP_ECHO_REPLY reply = new ICMP_ECHO_REPLY();
            ICMP_OPTIONS option = new ICMP_OPTIONS();
            option.Ttl = 255;
            var data = "ABCDEF";
            var timeout = 1000;
            var ipStr = "192.168.0.254";

            var handle = IcmpCreateFile();
            if (0 != IcmpSendEcho(handle, inet_addr(ipStr), data, (Int16)data.Length
                    , ref option, ref reply, Marshal.SizeOf(reply), timeout)) {
                if (reply.Status == 0) {
                    Console.WriteLine(string.Format("{0} からの応答: バイト数 ={1} 時間 {2}ms TTL={3}"
                              , ipStr, reply.DataSize, reply.RoundTripTime, reply.Options.Ttl));
                } else {
                    Console.WriteLine("ERROR Status={0}",reply.Status);
                }
            } else {
                Console.WriteLine("ERROR Status={0}", reply.Status);
            }
            IcmpCloseHandle(handle);
            Console.WriteLine("\n何かのキーを押してください。");
            Console.ReadKey();
        }
    }
}

F#サンプル

やはり、下記のように定義することはできません。
[<DllImport("icmp.dll")>]
extern Int32 IcmpSendEcho( , , ICMP_ECHO_REPLY *replay , , , )

ジェネリックコンストラクターの型'ICMP_ECHO_REPLY'はアンマネージ型にする必要があります。
構造体の中が要素がすべてアンマネージ型の場合は、上記のような指定が可能なのですがICMP_ECHO_REPLYにはstringが含まれているためエラーとなるようです。

ここに構造体のポインタで使用できないため、いちいちMarshalでIntPtrへ変換する手順が非常にうるさくなってしまいます。解決策では無いのですが、今回は、ラッパ関数_IcmpSendEcho()を定義してみました。

#nowarn "9" "51"

open System
open System.Runtime.InteropServices

[<Struct;StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)>]
type ICMP_OPTIONS =
    val mutable Ttl:SByte
    val Tos:Byte
    val Flags:Byte
    val OptionsSize:Byte
    val OptionsData:IntPtr

[<Struct;StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)>]
type ICMP_ECHO_REPLY =  
    val Address:IntPtr
    val Status:Int32
    val RoundTripTime:Int32
    val DataSize:Int16
    val Reserved:Int16
    val DataPtr:IntPtr
    val Options:ICMP_OPTIONS
    [<MarshalAs(UnmanagedType.ByValTStr, SizeConst = 250)>]
    val Data:String

[<DllImport("icmp.dll", SetLastError=true)>]
extern IntPtr IcmpCreateFile()
[<DllImport("icmp.dll", SetLastError=true)>]
extern bool IcmpCloseHandle(IntPtr handle)
[<DllImport("icmp.dll", SetLastError=true)>]
extern Int32 IcmpSendEcho(IntPtr icmpHandle, Int32 destinationAddress, String requestData,
               Int16 requestSize,ICMP_OPTIONS *requestOptions,IntPtr replay,
               Int32 replySize, Int32 timeout)
[<DllImport("ws2_32.dll", ExactSpelling = true)>]
extern Int32 inet_addr(string cp)

//IcmpSendEcho()をラッピング
let _IcmpSendEcho(handle,addr,data,ttl,timeout) =
    let TYPE = typeof<ICMP_ECHO_REPLY>
    let mutable option = new ICMP_OPTIONS()
    option.Ttl <- ttl
    let size = Marshal.SizeOf(TYPE)
    let p = Marshal.AllocCoTaskMem(size)
    let result = IcmpSendEcho(handle,addr,data,int16(data.Length),&&option,p,size,timeout);
    let reply = Marshal.PtrToStructure(p,TYPE):?>ICMP_ECHO_REPLY
    Marshal.FreeCoTaskMem(p)
    result,reply

//main()
let data = "ABCDEF" //データ
let timeout = 1000 //タイムアウト
let ttl = 125y //TTL
let ipStr = "192.168.0.254" //宛先
let handle = IcmpCreateFile() 
let result,reply = _IcmpSendEcho(handle,inet_addr(ipStr),data,ttl,timeout)
if result <> 0 then
    if reply.Status = 0 then
        printfn "%s からの応答: バイト数 =%d 時間 %dms TTL=%d" 
       ipStr reply.DataSize reply.RoundTripTime reply.Options.Ttl
    else
        printfn "ERROR Status=%d" reply.Status
else
        printfn "ERROR Status=%d" reply.Status

IcmpCloseHandle(handle)|>ignore
printfn "\n何かのキーを押してください。"
Console.ReadKey() |> ignore

2011-11-28

DHCPで取得したアドレスのreleaseとrenew (C#)(F#) その2

先日の同タイトルの記事のうち、F#サンプルを書き直してみたので再投です。

DHCPで取得した動的アドレスは、iphelapi.dllのIpReleaseAddressとIpRenewAddressで開放と再取得が可能です。同APIパラメータ(IP_ADAPTER_INDEX_MAP )は、GetInterfaceInfoで取得したインターフェース情報をそのまま渡します。

C#サンプル

using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;

namespace Examlpe {
    class Program {

        const int MAX_ADAPTER_NAME = 128;
        const int ERROR_INSUFFICIENT_BUFFER = 122;
        const int ERROR_SUCCESS = 0;
        
        [DllImport("Iphlpapi.dll", CharSet = CharSet.Auto)]
        static extern int IpReleaseAddress(ref IP_ADAPTER_INDEX_MAP AdapterInfo);

        [DllImport("Iphlpapi.dll", CharSet = CharSet.Auto)]
        static extern int IpRenewAddress(ref IP_ADAPTER_INDEX_MAP AdapterInfo);

        [DllImport("Iphlpapi.dll", CharSet = CharSet.Auto)]
        static extern int GetInterfaceInfo(IntPtr PIfTableBuffer, ref int size);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        struct IP_ADAPTER_INDEX_MAP {
            public int Index;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ADAPTER_NAME)]
            public String Name;
        }

        static void Main(string[] args) {
            var ar = new List<IP_ADAPTER_INDEX_MAP>();
            int size = 0;
            if (GetInterfaceInfo(IntPtr.Zero, ref size) == ERROR_INSUFFICIENT_BUFFER) {//nullを設定して必要サイズを得る
                var p = Marshal.AllocHGlobal(size);//メモリ割当て
                if (GetInterfaceInfo(p, ref size) == ERROR_SUCCESS) {//バッファをセットして2回目の呼び出し
                    var num = Marshal.ReadInt32(p);//IP_ADAPTER_INDEX_MAP配列の数を取得
                    var ptr = IntPtr.Add(p, 4);
                    for (int i = 0; i < num; i++) {//インターフェース数分だけ処理する
                        var adapter = (IP_ADAPTER_INDEX_MAP)Marshal.PtrToStructure(ptr, typeof(IP_ADAPTER_INDEX_MAP));
                        Console.WriteLine(string.Format("[{0}]{1}", adapter.Index,adapter.Name));
                        ar.Add(adapter);
                        ptr = IntPtr.Add(ptr, Marshal.SizeOf(typeof(IP_ADAPTER_INDEX_MAP)));//次のデータ
                    }
                }
                Marshal.Release(p);
            }
            //release
            Console.WriteLine("\n何かのキーを押すと、IPアドレスをreleaseします。");
            Console.ReadKey();
            ar.ForEach(n => IpReleaseAddress(ref n));
            Console.WriteLine("releaseしました。");

            //renew
            Console.WriteLine("\n何かの)押すと、IPアドレスをrenewします。");
            Console.ReadKey();
            ar.ForEach(n => IpRenewAddress(ref n));
            Console.WriteLine("renewしました。");

            Console.WriteLine("\n何かのキーを押してください。");
            Console.ReadKey();
        }
    }
}

F#サンプル
GCを全面的に信用して?インターフェースの一覧をポインタで保持してみました。

#nowarn "9" "51"

open System
open System.Runtime.InteropServices

[<Literal>]
let MAX_ADAPTER_NAME=128
let ERROR_INSUFFICIENT_BUFFER = 122
let ERROR_SUCCESS = 0

[<Struct; StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)>]
type IP_ADAPTER_INDEX_MAP = 
    val Index:int
    [<MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ADAPTER_NAME)>]
    val Name:String

[<DllImport("Iphlpapi.dll", CharSet = CharSet.Auto)>]
extern int IpReleaseAddress(IntPtr AdapterInfo)

[<DllImport("Iphlpapi.dll", CharSet = CharSet.Auto)>]
extern int IpRenewAddress(IntPtr AdapterInfo)

[<DllImport("Iphlpapi.dll", CharSet = CharSet.Auto)>]
extern int GetInterfaceInfo(IntPtr PIfTableBuffer,int *size)

let TYPE = typeof<IP_ADAPTER_INDEX_MAP>

//インターフェースの一覧取得
let ar =
    [
    let mutable size = 0
    if GetInterfaceInfo(IntPtr.Zero,&&size) = ERROR_INSUFFICIENT_BUFFER then
        let p = Marshal.AllocHGlobal(size)//メモリ割当て
        if GetInterfaceInfo(p,&&size) = ERROR_SUCCESS then
            let step = Marshal.SizeOf(TYPE)
            let num = Marshal.ReadInt32(p)//IP_INTERFACE_INFO.NumAdapters(データ数)
            let ptr = IntPtr.Add(p, 4)
            for i in [0..num-1] do
                let p = IntPtr.Add(ptr, step*i) //データへのポインタ
                yield p
         Marshal.Release(p)|>ignore
    ]

//インターフェースの一覧表示
ar
|>Seq.map(fun p -> Marshal.PtrToStructure(p,TYPE):?>IP_ADAPTER_INDEX_MAP)
|>Seq.iter(fun n -> printfn "%d %s" n.Index n.Name) 

//release
printfn "\n何かのキーを押すと、IPアドレスをreleaseします。"
Console.ReadKey() |> ignore
ar|>Seq.iter(fun p -> IpReleaseAddress(p)|>ignore)
printfn "releaseしました。"

//renew
printfn "\n何かのキーを押すと、IPアドレスをrenewします。"
Console.ReadKey() |> ignore
ar|>Seq.iter(fun p -> IpRenewAddress(p)|>ignore)
printfn "renewしました。"


printfn ""
printfn "何かのキーを押してください。"
Console.ReadKey() |> ignore

2011-11-25

あ〜Ping 「ICMPがファイアウォールでブロックされている端末の疎通確認」

HokkaidoCap#8でLTのお時間頂きました。

最近のPCは、パーソナルファイアウォールにより、ほとんどの場合デフォルトpingに返事しないようになっています。LAN内のPCをpingで確認しようとしても、返事が返ってこないのでちょっと面倒くさい。
今回は、ICMPをブロックされた端末にも、pingのように軽易に生存確認できるプログラムを作成してみました。


バイナリ arping.zip
ソースコード arping-src.zip

2011-11-24

DHCPで取得したアドレスのreleaseとrenew (C#)(F#)

2011.11.28 追記
本記事のF#サンプルを書き直しました。

DHCPで取得した動的アドレスは、iphelapi.dllのIpReleaseAddressとIpRenewAddressで開放と再取得が可能です。同APIパラメータ(IP_ADAPTER_INDEX_MAP )は、GetInterfaceInfoで取得したインターフェース情報をそのまま渡します。

C#サンプル

using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;

namespace Examlpe {
    class Program {

        const int MAX_ADAPTER_NAME = 128;
        const int ERROR_INSUFFICIENT_BUFFER = 122;
        const int ERROR_SUCCESS = 0;
        
        [DllImport("Iphlpapi.dll", CharSet = CharSet.Auto)]
        static extern int IpReleaseAddress(ref IP_ADAPTER_INDEX_MAP AdapterInfo);

        [DllImport("Iphlpapi.dll", CharSet = CharSet.Auto)]
        static extern int IpRenewAddress(ref IP_ADAPTER_INDEX_MAP AdapterInfo);

        [DllImport("Iphlpapi.dll", CharSet = CharSet.Auto)]
        static extern int GetInterfaceInfo(IntPtr PIfTableBuffer, ref int size);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        struct IP_ADAPTER_INDEX_MAP {
            public int Index;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ADAPTER_NAME)]
            public String Name;
        }

        static void Main(string[] args) {
            var ar = new List<IP_ADAPTER_INDEX_MAP>();
            int size = 0;
            if (GetInterfaceInfo(IntPtr.Zero, ref size) == ERROR_INSUFFICIENT_BUFFER) {//nullを設定して必要サイズを得る
                var p = Marshal.AllocHGlobal(size);//メモリ割当て
                if (GetInterfaceInfo(p, ref size) == ERROR_SUCCESS) {//バッファをセットして2回目の呼び出し
                    var num = Marshal.ReadInt32(p);//IP_ADAPTER_INDEX_MAP配列の数を取得
                    var ptr = IntPtr.Add(p, 4);
                    for (int i = 0; i < num; i++) {//インターフェース数分だけ処理する
                        var adapter = (IP_ADAPTER_INDEX_MAP)Marshal.PtrToStructure(ptr, typeof(IP_ADAPTER_INDEX_MAP));
                        Console.WriteLine(string.Format("[{0}]{1}", adapter.Index,adapter.Name));
                        ar.Add(adapter);
                        ptr = IntPtr.Add(ptr, Marshal.SizeOf(typeof(IP_ADAPTER_INDEX_MAP)));//次のデータ
                    }
                }
                Marshal.Release(p);
            }
            //release
            Console.WriteLine("\n何かのキーを押すと、IPアドレスをreleaseします。");
            Console.ReadKey();
            ar.ForEach(n => IpReleaseAddress(ref n));
            Console.WriteLine("releaseしました。");

            //renew
            Console.WriteLine("\n何かの)押すと、IPアドレスをrenewします。");
            Console.ReadKey();
            ar.ForEach(n => IpRenewAddress(ref n));
            Console.WriteLine("renewしました。");

            Console.WriteLine("\n何かのキーを押してください。");
            Console.ReadKey();
        }
    }
}

F#サンプル
まったくひどいサンプルになってます。

本当は、
[<DllImport("Iphlpapi.dll", CharSet = CharSet.Auto)>]
extern int IpReleaseAddress(IP_ADAPTER_INDEX_MAP * AdapterInfo)
としたかったのですが、ジェネリックコンストラクターの型’IP_ADAPTER_INDEX_MAP’は、アンマネージ型にする必要があります」っと言って受け付けてくれません。
IP_ADAPTER_INDEX_MAPにStringが無ければ問題無いようなのですが・・・・

結局、パラメータを(IntPtr AdapterInfo)として宣言したので、非常にひどい状態になってしまってます。

#nowarn "9" "51"

open System
open System.Runtime.InteropServices

[<Literal>]
let MAX_ADAPTER_NAME=128
let ERROR_INSUFFICIENT_BUFFER = 122
let ERROR_SUCCESS = 0

[<Struct; StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)>]
type IP_ADAPTER_INDEX_MAP = 
    val Index:int
    [<MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ADAPTER_NAME)>]
    val Name:String

[<DllImport("Iphlpapi.dll", CharSet = CharSet.Auto)>]
extern int IpReleaseAddress(IntPtr AdapterInfo)

[<DllImport("Iphlpapi.dll", CharSet = CharSet.Auto)>]
extern int IpRenewAddress(IntPtr AdapterInfo)

[<DllImport("Iphlpapi.dll", CharSet = CharSet.Auto)>]
extern int GetInterfaceInfo(IntPtr PIfTableBuffer,int *size)

let ar =
    let mutable size = 0
    if GetInterfaceInfo(IntPtr.Zero,&&size) = ERROR_INSUFFICIENT_BUFFER then
        let p = Marshal.AllocHGlobal(size)//メモリ割当て
        if GetInterfaceInfo(p,&&size) = ERROR_SUCCESS then
            let num = Marshal.ReadInt32(p)//IP_INTERFACE_INFO.NumAdapters(データ数)
            let ptr = IntPtr.Add(p, 4)
            let ar =
                let step = Marshal.SizeOf(typeof<IP_ADAPTER_INDEX_MAP>)
                [0..num-1]
                |>List.map(fun i ->IntPtr.Add(ptr, step * i)) //データへのポインタ
                |>List.map(fun p -> Marshal.PtrToStructure(p,typeof<IP_ADAPTER_INDEX_MAP>):?>IP_ADAPTER_INDEX_MAP)
            Marshal.Release(p)|>ignore
            ar
        else
            []
    else
        []

ar|>Seq.iter(fun a -> printfn "%d %s" a.Index a.Name) 

let ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof<IP_ADAPTER_INDEX_MAP>))

//release
printfn "\n何かのキーを押すと、IPアドレスをreleaseします。"
Console.ReadKey() |> ignore
ar|>Seq.iter(fun a ->
        Marshal.StructureToPtr(a, ptr, false)
        IpReleaseAddress(ptr)|>ignore
        )
printfn "releaseしました。"

//renew
printfn "\n何かのキーを押すと、IPアドレスをrenewします。"
Console.ReadKey() |> ignore
ar|>Seq.iter(fun a -> 
        Marshal.StructureToPtr(a, ptr, false)
        IpRenewAddress(ptr)|>ignore
        )
printfn "renewしました。"

Marshal.FreeHGlobal(ptr)

printfn ""
printfn "何かのキーを押してください。"
Console.ReadKey() |> ignore

2011-11-21

HttpWebRequestで取得したデータをcharsetに応じてデコードする (C#)(F#)

先にも書いた通り、HttpWebRequestで取得したデータをstringに変換する際、HttpWebResponsのCharacterSetプロパティで示されるエンコード形式は正確でない事があります。

下記のサンプルでは、HttpWebRequestで取得したデータをbyte[]で受け、一旦ASCIIでstringに変換しヘッダの中にcharsetの指定が有るかどうかを検索しています。
検索の結果、エンコードの形式が判明した場合は、byte[]から改めて適切なエンコードでstringへ変換します。

C#サンプル

using System;
using System.Net;
using System.IO;
using System.Text;

namespace Examlpe {
    class Program {
        static void Main(string[] args) {

            //HTTPリクエスト
            var req = HttpWebRequest.Create("http://www.sapporoworks.ne.jp/spw/");
            req.Timeout = 15000;//タイムアウト(15秒)
            //HTPレスポンス
            var res = (HttpWebResponse)req.GetResponse();

            //byte [] で読み込む
            var buf = ReadBytes(res.GetResponseStream());
            //デフォルトをASCIIとする
            var encoding = Encoding.ASCII;
            
            //ASCIIでstringに変換する
            string str =encoding.GetString(buf);

            //1行ごとMETAヘッダにcharesetの指定がないかを検索する
            foreach (var s in str.Split('\n')) {
                var l = s.ToUpper();
                if (l.IndexOf("<META") != -1 && l.IndexOf("CHARSET")!=-1) {
                    //指定があった場合は、エンコードの種類を取得する
                    var charset = GetCharset(l);
                    encoding = Encoding.GetEncoding(charset);
                    //取得したエンコードでstringへの変換をやり直す
                    str = encoding.GetString(buf);
                    break;
                }
            }
            Console.WriteLine(str);

            Console.WriteLine();
            Console.WriteLine("何かのキーを押してください。");
            Console.ReadKey();
        }
        
        //charset=の後ろを取得する
        static string GetCharset(string str) {
            //"="の検索開始
            int start = str.IndexOf("CHARSET");
            if (start == -1)
                return "";//失敗
           
            for(int i=start;;i++){
                if(str[i]=='='){
                    start = i+1;
                    break;
                }
            }
            int end = 0;
            for (int i = start + 1; ; i++) {
                if(str[i]=='"' || str[i]=='>' || str[i]==' ' || str[i]==';'){
                    end=i;
                    break;
                }
            }
            if (end == 0)
                return "";//失敗
            return str.Substring(start,end-start);
        }
        
        //Streamからbyte[]で読み込む
        static byte[] ReadBytes(Stream stream) {
            var buf = new byte[0];
            var tmp = new byte[1024];
            while (true) {
                var len = stream.Read(tmp, 0, 1024);
                if (len <= 0)
                    break;
                buf = AppendBuffer(buf, tmp , len);
            }
            return buf;
        }
        //byte[]への追加
        static byte[] AppendBuffer(byte[] buf, byte[] tmp ,int len) {
            var res = new byte[buf.Length + len];
            Buffer.BlockCopy(buf, 0, res, 0, buf.Length);
            Buffer.BlockCopy(tmp, 0, res, buf.Length, len);
            return res;
        }
    }
}

F#サンプル
もっと、シンプルに書けないものだろうか・・・・

open System
open System.Net
open System.IO
open System.Text
open System.Linq

////charset=の後ろのエンコードの種類を取得する
let GetCharset (str:string) = 
    //charsetの後の最初の'='の位置をスタート位置(s)として取得
    let s = str.IndexOf('=',str.IndexOf("CHARSET")) + 1
    //スタート位置以降で、最初に'"'、'>'、' '若しくは';'が現れた位置を終了位置(e)として取得する
    let e = 
        [s..str.Length-1]
        |>Seq.tryFind(fun n -> str.[n] = '"' || str.[n] = '>'|| str.[n] = ' '|| str.[n] = ';')
    if e <> None then 
        str.Substring(s,e.Value-s) //スタート位置〜終了位置の部分文字列を取得
    else
        "" //失敗

//Streamからbyte[]で読み込む
let rec ReadBytes (stream:Stream,buf) = 
    let tmp = Array.create 1024 0uy
    match stream.Read(tmp, 0, 1024) with
    |n when n>0 -> ReadBytes ( stream , Array.append buf tmp.[0..n-1] )
    |_ -> buf
   

//byte[] をstringに変換する
let rec GetString (encoding:Encoding,buf:byte[]) = 
    let str = encoding.GetString(buf)
    //ASCIIでデコードした場合は、METAヘッダを確認する
    if encoding = Encoding.ASCII then
        let s = //<charsetのある行を取得する
            str.Split('\n') 
            |> Seq.map(fun s->s.ToUpper())
            |> Seq.tryFind(fun s->s.IndexOf("<META") <> -1 && s.IndexOf("CHARSET") <> -1)
        match s with
        |None -> str
        |_ ->
            match GetCharset(s.Value) with //エンコードの種類を取得
            |charset when charset <> "" -> GetString(Encoding.GetEncoding(charset),buf)
            |_ -> str
    else
        str

//HTTPリクエスト
let req = HttpWebRequest.Create("http://www.sapporoworks.ne.jp/spw/")
req.Timeout <- 15000//タイムアウト(15秒)
//HTPレスポンス
let res = req.GetResponse():?>HttpWebResponse
//Streamからbyte[]で読み込む
let buf = ReadBytes(res.GetResponseStream(),[||])
//byte[] をstringに変換する
let str = GetString(Encoding.ASCII,buf)

printfn "%s" str
printfn ""
printfn "何かのキーを押してください。"
Console.ReadKey() |> ignore

2011.11.24追記
mclh46氏による記事
http://d.hatena.ne.jp/mclh46/20111123/1322015736

2011-11-20

HttpWebRequestによる軽易なHTTPデータ受信 (C#)(F#)

System.Net.WebRequestを利用すると、軽易にHTTPでデータを取得できます。
サンプルでは、HTTPリクエストと同レスポンスのヘッダを列挙表示しています。

受信したデータの本体を取得する場合はHttpWebResponsのGetResponseStream()でストリームを生成しこれを読み取ることになりますが、この際、適切なエンコードが必要になります。

エンコードの種類は、HttpWebResponsのCharacterSetプロパティに設定されているはずですが、これが、正確でない場合があります。うまく取得できなかった場合は、受信データ(HTML)本体の中を開いてヘッダなどを解釈する必要があるかも知れません。


C#サンプル

using System;
using System.Net;

namespace Examlpe {
    class Program {
        static void Main(string[] args) {


            //HTTPリクエスト
            var req = HttpWebRequest.Create("http://www.google.co.jp/");
            req.Timeout = 15000;//タイムアウト(15秒)
            //HTPレスポンス
            var res = (HttpWebResponse)req.GetResponse();

            //ヘッダ取得
            Console.WriteLine("");
            Console.WriteLine("[SEND]");
            var h = req.Headers;
            for (int i = 0; i < h.Count;i++ ) {
                Console.WriteLine("{0}: {1}", h.GetKey(i),h[i]);
            }

            //ヘッダ取得
            Console.WriteLine("");
            Console.WriteLine("[RECV]");
            h = res.Headers;
            for (int i = 0; i < h.Count; i++) {
                Console.WriteLine("{0}: {1}", h.GetKey(i), h[i]);
            }
            Console.WriteLine();
            Console.WriteLine("何かのキーを押してください。");
            Console.ReadKey();
        }
    }
}

F#サンプル

open System
open System.Net

//ヘッダ取得
let getHeaders (h:WebHeaderCollection) =
    [0..h.Count-1]
    |>Seq.map(fun i-> h.GetKey(i),h.[i])

//HTTPリクエスト
let req = HttpWebRequest.Create("http://www.google.co.jp/")
req.Timeout <- 15000 //タイムアウト(15秒)
//HTPレスポンス
let res = req.GetResponse():?>HttpWebResponse

printfn ""
printfn "[SEND]"
getHeaders(req.Headers)|>Seq.iter(fun(k,v)->printfn "%s: %s" k v)

printfn ""
printfn "[RECV]"
getHeaders(res.Headers)|>Seq.iter(fun(k,v)->printfn "%s: %s" k v)

printfn ""
printfn "何かのキーを押してください。"
Console.ReadKey() |> ignore

2011-11-19

BLackJumboDog 運転と改造のすすめ

CLR/H64 でお話させて頂きました。

参加された方々、つまらない話に1時間もおつきあい頂き感謝です。
もし、ご利用になられる事がありましたら、気楽に声を掛けてください。
全面的にサポートさせて頂きます。