Jaa


[ADSI] セッションの張り方に伴う一時ポートの使われ方 ~ 同じ関数の組み合わせでも順序などで使われ方が変わる ~

皆さんごきげんよう。ういこです。

まだ病んだまま、調子が微妙に良くならないのですが体力低下って恐ろしいですね。わたしったらこのまま病み続けちゃうんじゃないかしら。うう、体がだるい。

さて、今日はなんとなくわかったようなそれでいて説明してって言われると六歳児に「特殊法人と特殊部隊ってどうちがうの?」と聞かれたときのようにわかってる様で説明が難しい、ADSI のセッションの張り方に伴う一時ポートの使われ方についてちょこっとだけ。

先日、大量の ADSI リクエストを行うとポート枯渇が起きる可能性があるという記事を up させていただきました。そこで不思議~に思うのは、「どういった条件で 1 セッションを張るの (ポートを使うの)」というところでしょうか。

実は、これこそがわかりづらい理由ではあるのですが、「認証、呼び出しプロトコル、サーバの設定、関数の実装などさまざまな条件によってセッションの動きが異なり、ポートの使用のされかたが異なる」というところがポイントなのです。認証の施行については、この記事をごらんください

本日は、この中でも、同じ関数であっても実装の仕方 (関数の呼び出し順序) によってポートの使用状況が変わるというところをお見せしたいとおもいます。

【今日のお題】

あら奥様!ADSI の処理によってセッションの張られ方、ポートの使われ方が異なるんですってよ!

今回は手っ取り早く .NET の System.DirectoryEntry クラスを使ってみましょう。

DirectoryEntry クラスは内部で ADSI にリダイレクトされ、COM オブジェクトを作成して動作します。Dispose() を呼ぶことで COM オブジェクトの参照カウンタのデクリメントが行われます。COM オブジェクトは、参照カウンタが 0 になると、破棄されます。

では、実際に確認してみましょう。Visual Studio を開いてみてください。

C# で試してみます。[ファイル(F)] - [新規作成] で新しいプロジェクトを開き、C# で Windows アプリケーションを作ってみてください。

Form が表示されるので、ツールボックスからボタンを Form 上にドラッグ アンド ドロップして、二つほど適当にはっつけてください。

はっつけたら、今度はボタンを激しくマウスでクリックしましょう。まず Button1 をクリックするとコード部分に飛んでしまいますが、戻りたいときは [Form1.cs [デザイン]] タブをクリックすると Form にボタンをおいた画面(デザイナ)に戻ります。また Button2 をクリックしてください。それぞれ、こんなコードができるはずです。

“Button1_Click” は Button1 を押したときに実行される処理、”Button2_Click” は Button2 を押したときに実行される処理です。

button1_Click に実装する処理

以下は button1_Click に実装するコードです。

 

private void button1_Click(object sender, EventArgs e)

{

// ここから

DirectoryEntry dn = new DirectoryEntry("LDAP://jpdsilm.contoso.com/RootDSE", @"jpdsilm\Administrator", "P@ssw0rd!");

this.label1.Text = dn.Properties["defaultNamingContext"].Value.ToString();

DirectoryEntry dn2 = new DirectoryEntry("LDAP://jpdsilm.contoso.com/CN=Administrator,CN=Users," + this.label1.Text.ToString(), @"jpdsilm\Administrator", "P@ssw0rd!");

this.label1.Text = dn2.Properties["sAMAccountName"].Value.ToString();

dn.Dispose();

dn = null;

dn2.Dispose();

dn2 = null;

// ここまでが増えた部分

}

 

二つの DirectoryEntry オブジェクト dn と dn2 を作成し、それぞれ認証、接続処理を行い、まとめて Dispose します。この場合、同じユーザ (jpdsilm\Administrator) でアクセスしています。

まず、dn で System.DirectoryEntry オブジェクトが作成される際に、あわせて ADSI COM オブジェクトがひとつ作成されます。

この際に作成された ADSI COM オブジェクトが 1 つのセッションを張ります。一時ポートが確保され、ESTABLISHED になります。

dn 作成後すぐに dn2 が作成されますが、同じユーザであることに加え、dn の破棄処理はされていないことからオブジェクトが生きているため dn2 は dn 作成時につくった COM オブジェクトを利用します。

よって、この処理では "使用されるポートは 1 つ" ということになります。

 

Button2_Click に実装する処理

こちらは、dn 作成後、dn を dispose() し、その後に dn2 を作成しています。

この場合、dn の参照している ADSI COM オブジェクトが削除された後 dn2 が ADSI COM オブジェクトを作成しているので、dn と dn2 はそれぞれ別の ADSI COM オブジェクトを使用します。よって、セッション数は 2 、使用ポート数 2 となります。

        private void button2_Click(object sender, EventArgs e)

        {

            // ここから

            DirectoryEntry dn = new DirectoryEntry("LDAP://jpdsilm.contoso.com/RootDSE", @"jpdsilm\Administrator", "P@ssw0rd!");

            this.label1.Text = dn.Properties["defaultNamingContext"].Value.ToString();

            dn.Dispose(); // ← dn オブジェクトの破棄処理

            dn = null;

            DirectoryEntry dn2 = new DirectoryEntry("LDAP://jpdsilm.contoso.com/CN=Administrator,CN=Users," + this.label1.Text.ToString(), @"jpdsilm01\Administrator", "P@ssw0rd!");

            this.label1.Text = dn2.Properties["sAMAccountName"].Value.ToString();

            dn2.Dispose(); // ← dn2 オブジェクトの破棄処理

            dn2 = null;

            // ここまでが増えた部分

        }

この処理を行うとこんな感じになります。まず、dn が作られます。ここまでは上の処理と一緒ですね。図も一緒です。手抜きじゃないですよ。

 

// ここから

DirectoryEntry dn = new DirectoryEntry("LDAP://jpdsilm.contoso.com/RootDSE", @"jpdsilm\Administrator", <P@ssw0rd!>);

 

次の処理を見てみましょう。 ラベルに貼る処理はともかく、dn.Dispose() あたりがキモでございますのよ奥様。

 

this.label1.Text = dn.Properties["defaultNamingContext"].Value.ToString();

dn.Dispose(); // ← dn オブジェクトの破棄処理

dn = null;

この処理が内部的にはどうなるかというと、CLR 上のオブジェクト破棄処理がよばれると、参照されていた ADSI COM オブジェクトの参照カウンタが 1 減らされます。これが 0 になると、オブジェクトが破棄されます。この場合、ひとつの DirectoryEntry からしか参照されていないので、参照カウンタは 1 - 1 = 0 になり、削除されます。これに伴い、使用されていたセッションはクロージングに入り、一時ポートのステータスは ESTABLISHED から CLOSE_WAIT に移行します。

 

 

 

 次は dn2 を作成します。

 

DirectoryEntry dn2 = new DirectoryEntry("LDAP://jpdsilm.contoso.com/CN=Administrator,CN=Users," + this.label1.Text.ToString(), @"jpdsilm01\Administrator", <P@ssw0rd!>);

 

これで dn2 ができました。この時点の状況を見てみるとこんな感じです。dn はすでに破棄されていますが、CLOSE_WAIT 状態のポートは確保されたままです。 (上のグレー部分) dn2 はそのあと作成され、別途 ADSI COM オブジェクトを作成し、それを介して AD に接続しに行きます。この際にもうひとつポートを確保します。よって、ポート数は 2 になります。

 

 

実際に確認してみます。プログラムの引数を環境に合わせて変更ください。まず、button1 を一度押してみて、その時点で netstat -n を実行します。

以下の例では、一時ポート 2051 が 157.60.23.144 ポート 389 (LDAP) にセッションを張っているのがわかります。

 >netstat -n Proto Local Address     Foreign Address    State(省略) TCP  157.60.23.113:2051   157.60.23.144:389   TIME_WAIT

今度は、button2 の処理を一度実行し、netstat -n を実行します。すると、セッションが 2 個張られているのが見えます。

(一時ポート 2054 と 2055)

 >netstat -n Proto Local Address     Foreign Address    State(省略) TCP  157.60.23.113:2051   157.60.23.144:389   TIME_WAIT TCP  157.60.23.113:2054   157.60.23.144:389   TIME_WAIT TCP  157.60.23.113:2055   157.60.23.144:389   TIME_WAIT(省略)

このように、同じ内容の処理を記述したつもりでも、呼び出し順序などを変えるだけで挙動が変わるということになるのがわかるかと思います。

ぜひ、いじくり倒してみてください。

 

~ ういこう@どうも ILM2 のつくりがデンドロビウムみたいに思えてしょうがない ~