DIコンテナ使ってみて思った雑文:その2

アプリケーションにテーブルゲートウェイがあったとして、もちろんDB接続に依存している。

class UserTable
{
    private $connection;

    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    // select/insert/update/delete/etc...
}

テーブルゲートウェイはテーブルの数だけ存在するので、全部に同じ実装を書くのはDRYじゃないので抽象クラスを作る。

abstract class AbstractTableGateway
{
    private $connection;

    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    // select/insert/update/delete/etc...
}

class UserTable extends AbstractTableGateway
{
    // UserTable spesific method...
}

特定のテーブルで Connection 以外のなにかに依存する場合、コンストラクタを拡張して DI する。

class HogeTable extends AbstractTableGateway
{
    private $hoge;

    public function __construct(Connection $connection, Hoge $hoge)
    {
        parent::__construct($connection);
        $this->hoge = $hoge;
    }

    // HogeTable spesific method...
}

おっと、ここでテーブルのメタデータを保存するために AbstractTableGateway でキャッシュを使いたくなった。

abstract class AbstractTableGateway
{
    private $connection;
    private $cache;

    public function __construct(Connection $connection, Cache $cache)
    {
        $this->connection = $connection;
        $this->cache = $cache;
    }

    // select/insert/update/delete/etc...
}

すると HogeTable のコンストラクタも弄らなければならない。

class HogeTable extends AbstractTableGateway
{
    private $hoge;

    public function __construct(Connection $connection, Cache $cache, Hoge $hoge)
    {
        parent::__construct($connection, $cache);
        $this->hoge = $hoge;
    }

    // HogeTable spesific method...
}

HogeTable 的には「キャッシュとか知らんがなそっちで勝手にやってくれ」なので HogeTable のコンストラクタにまで影響するのは違和感がある。

これはまあ Connection と Cache を併せたなにかを作るか(ConnectionFacade とか)、あるいは、Connection に Cache のインスタンスを持たせればスッキリする。

あるいは AbstractTableGateway のような抽象クラスを作って継承するのはやめて、委譲とかトレイトだけでどうにかするべきだろうか。

trait TableGatewayTrait
{
    abstract protected function getTableGateway();

    // select/insert/update/delete/etc...
}

class UserTable
{
    use TableGatewayTrait;

    private $tableGateway;

    public function __construct(TableGateway $tableGateway)
    {
        $this->tableGateway = $tableGateway;
    }

    protected function getTableGateway()
    {
        return $this->tableGateway;
    }

    // UserTable spesific method...
}

class HogeTable
{
    use TableGatewayTrait;

    private $tableGateway;
    private $hoge;

    public function __construct(TableGateway $tableGateway, Hoge $hoge)
    {
        $this->tableGateway = $tableGateway;
        $this->hoge = $hoge;
    }

    protected function getTableGateway()
    {
        return $this->tableGateway;
    }

    // HogeTable spesific method...
}

コンストラクタとかで定形パターンをなんども書くのが辛ければそれらもトレイトに含めて、必要に応じてコンストラクタをオーバーライドするとか。

trait TableGatewayTrait
{
    private $tableGateway;

    public function __construct(TableGateway $tableGateway)
    {
        $this->tableGateway = $tableGateway;
    }

    protected function getTableGateway()
    {
        return $this->tableGateway;
    }

    // select/insert/update/delete/etc...
}

class UserTable
{
    use TableGatewayTrait;

    // UserTable spesific method...
}

class HogeTable
{
    use TableGatewayTrait { __construct as constructTableGateway; }

    private $hoge;

    public function __construct(TableGateway $tableGateway, Hoge $hoge)
    {
        $this->constructTableGateway($tableGateway);
        $this->hoge = $hoge;
    }

    // HogeTable spesific method...
}

コンストラクタを as で別名にするのはさすがに変か。。。


コンストラクタインジェクション前提で考えると、継承元のコンストラクタを弄ったときに派生先のすべてに影響してしまうので継承させにくいなーと思ったけどよく考えたら DI とか関係なく継承してれば当たり前のことだった。

Terraform v0.10 でいろいろ変わってた

久しぶりに Terraform を使ってたらちょこちょこ変わってたのでメモ。多分前は v0.8 とかを使ってたと思う。

Initialization

以前は terraform をインストールすると AWS とかのプロバイダのプラグインも一緒に入っていて、すぐに使えていたと思うんですが、いつからか terraform init コマンドでプロバイダのプラグインがインストールされるようになったようです。

例えば次のように AWS のリソースを tf ファイルに書いておくと。

resource "aws_vpc" "test" {
    cidr_block = "10.1.0.0/16"
    tags { Name = "test" }
}

terraform init.terraform/plugins/darwin_amd64/terraform-provider-aws_v0.1.4_x4 のような位置に AWS プロバイダのためのプラグインがインストールされます。

$ terraform init

... snip ...

* provider.aws: version = "~> 0.1"

... snip ...

$ ls .terraform/plugins/darwin_amd64/terraform-provider-aws_v0.1.4_x4
.terraform/plugins/darwin_amd64/terraform-provider-aws_v0.1.4_x4

後はこれまで通り plan とか apply とかできます。

ちなみにこのファイルはいわゆる実行ファイルなので、実行属性を落とすと動かなくなります。

Mac とかなら大丈夫なんですけど・・・普段 Windows を cifs マウントしている Linux 上で作業している自分にはその仕様はちょっとツライ・・・ mound --bind とかでどうにか乗り切ってますけど。

Remote Backends

Remote Backend の設定方法も以前から変わっているようで、以前のまま使うと「なんか古いから新しくしいや」みたいなことを言われた気がします。

以前は terrarofm remote みたいなコマンドでバックエンドを設定する必要があった気がするのですが、今は次のように tf ファイルにバックエンドの情報を書いて、

terraform {
    backend "s3" {
        bucket = "oreore-tfstate"
        key    = "oreore.tfstate"
    }
}

terraform init で設定出来るようです。

複数の作業者で共有すること考えたらこの方が良いですね。

バックエンドを変更したりローカルに戻したりも tf ファイルを修正して terraform init で良いようです。楽ちん。

DIコンテナ使ってみて思った雑文

アプリケーションの流れとして、

  1. コンテナを構築
  2. アプリを実行するなにか(パイプラインとかディスパッチャとか)をコンテナから取り出す
  3. リクエストオブジェクトを作成してアプリを実行

みたいな流れかなと思う。順番的にDIコンテナが先に作られるのでリクエストオブジェクトやリクエストに含まれる情報はDIコンテナには入らない。そもそもDIコンテナはグローバルなコンテキストのものなので、リクエストのコンテキストに属するオブジェクトを入れるべきではない。

(PHPでは出来ないけれども)もし可能なのならDIコンテナはリクエストをまたがって共有されても良い。なのでコンテナに入るオブジェクトはステートレスでなければならないし、再入可能でスレッドセーフであるべき(PHPでは関係ないけれども)。

・・・みたいなイメージ。


例えば Logger にリクエストに基づく情報が自動的に付加されるようにしてみる(IPアドレスとか)。

$logger = $logger->withContext(['ipaddr' => $ipaddr]);

// $context に 'ipaddr' が追加される
$logger->info('ほげほげ');

こうして作られた Logger はリクエストのコンテキストに属するので、グローバルなコンテキストであるところのコンテナには入らない。

サービスクラスがコンテナに入っているのだとすると、サービスクラスのコンストラクタにインジェクションできるものはコンテナに入っているものだけなので、↑のよう部分的に適用された Logger をインジェクションすることはできない。

大本の Logger がインジェクションされて、そしてサービスのメソッドにリクエストオブジェクトが渡されるようにする?(コードはイメージです)

class HogeService
{
    public function __construct($logger)
    {
        $this->logger = $logger
    }

    public function func($request)
    {
        $logger = $this->logger->withContext(['ipaddr' => $request->getRemoteAddr()]);

        // $logger を使ってログを出す
    }
}

ちがう、サービスがリクエストに依存しているわけではないので ipaddr が適用された Logger を渡すべき。

class HogeService
{
    public function func($logger)
    {
        // $logger には 'ipaddr' が適用されているけどサービスはそんなこと知らねえ
    }
}

ただ $logger をずっと引数として持ち回さなければならない。引数が Logger しかないなら良いけれども、他にもリクエストのコンテキストに属するオブジェクトをたくさん持ち回したくなったときにツラミが増してくる(Logger 以外なにがあるかぱっと思いつかないけど)。

ので、それを見越して、リクエストのコンテキスト、というオブジェクトが渡されるようにしてみる。

class HogeService
{
    public function func($requestContext)
    {
        $logger = $requestContext->getLogger();
    }
}

うーん悪手? サービスが RequestContext というよくわからないものに依存している。


Logger がコンストラクタからシュッと入ってきてそれをそのまま使えられれば良いのだけど、コンテナにリクエストに基づくモノが入っていないとすると引数で持ち回すしか無い?

あるいはコンテナにリクエストオブジェクトをぶっこんでも良いことにする?

// bootstrap.php っぽいなにか
$builder = new ContainerBuilder();
$builder->addDefinitions($definitions + [
    ServerRequestInterface::class => ServerRequestFactory::fromGlobals(),
]);

$container = $builder->build();

// コンテナにリクエストオブジェクトが入っているので logger に ipaddr が適用されている
$logger = $container->get(Logger::class);

でも PSR-7 だとミドルウェアパイプラインの中でリクエストオブジェクトがクローンされて次々に生成されるわけで、↑だとコンテナに入ったリクエストオブジェクトとコントローラーのアクションに渡ってくるリクエストオブジェクトが異なるものになってしまって違和感。

例えば X-Forwarded-For を見てリクエストが持つ RemoteAddr を本来のものに書き換えるようなミドルウェアを考えると問題がわかりやすい。

それか Logger をミュータブルにしてそういうミドルウェアが Logger に IP アドレスをセットする?

    public function process(ServerRequestInterface $request, DelegateInterface $delegate)
    {
        $this->logger->addContext(['ipaddr' => $request->getRemoteAddr()]);
        return $delegate->process($request);
    }

うーん、どんどんゆるふわになっていく・・


コントローラーをディスパッチするときに元のコンテナを拡張した新たなコンテナを作成して、コントローラーの依存はその新たなコンテナによって解決する?

    public function process(ServerRequestInterface $request, DelegateInterface $delegate)
    {
        $container = $this->extend($this->container, [
            LoggerInterface::class => function () use ($request) {
                return (new Logger())->withContext([
                    'ipaddr' => $request->getRemoteAddr(),
                ]);
            }
        ]);

        // コントローラーとコントローラーが依存するオブジェクトは新たなコンテナで依存解決される
        $result = $request->getAttribute(RouteResult::class);
        $instance = $container->get($result->getController());
    }

リクエストのアトリビュートを全部ぶっこむとかでも。

    public function process(ServerRequestInterface $request, DelegateInterface $delegate)
    {
        $request = $request->withAttribute(
            LoggerInterface::class, (new Logger())->withContext([
                'ipaddr' => $request->getRemoteAddr(),
            ])
        );

        $container = $this->extend($this->container, $request->getAttributes());

        // コントローラーとコントローラーが依存するオブジェクトは新たなコンテナで依存解決される
        $result = $request->getAttribute(RouteResult::class);
        $instance = $container->get($result->getController());
    }

とくに結論はない。

PHPUnit の Functions.php を自動でロードするやつ

下記↓の記事のコメントの通り、

PHPUnit には Functions.php というファイルが含まれていて、これを require するとアサーションなどがグローバル関数に登録されるので、次のようにテストを書くことができます。

<?php
// 普通こう書くけど、
$this->assertThat($actual, $this->equalTo($expect));

// Functions.php を読んどけばこうも書ける。
assertThat($actual, equalTo($expect));

とても便利なんですけど Functions.php は明示的に読む必要があります。

<?php
require_once __DIR__ . '/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php';

しかもこれ、PHPUnit のバージョンによって違う位置にあります。

さらに、最近なら PHPUnit を Phar で実行することもあると思います。その場合は Phar ファイル名を元に読む必要があります。

<?php
require_once 'phar://phpunit-6.2.phar/phpunit/Framework/Assert/Functions.php';

こういうのが辛かったので、次のようにリフレクションでファイル名を特定するようにしていたのですが・・・PHPUnit 6 でクラス名が PEAR から PSR-4 のスタイルに変わったので、これも動かなくなりました。

<?php
require __DIR__ . '/../vendor/autoload.php';
$reflection = new ReflectionClass('PHPUnit_Framework_Assert');
require_once dirname($reflection->getFileName()) . '/Assert/Functions.php';

そもそも↑のようなスニペットをいちいちコピペするのも面倒臭くなってきたので Functions.php を自動的に読み込むやつを作りました。

次のように composer で追加しておくと、テストの実行時に自動的に Functions.php が読まれます。

composer require --dev ngyuki/phpunit-functions

これは便利。


MySQL で date_add/sub で年が 0000 になると 0000-00-00 が返る

MySQL の sql_modeNO_ZERO_IN_DATE,NO_ZERO_DATE とかを指定すると 0000-00-00 や、月や日が 00 などの日時の挿入を禁止できます。

がしかし、年が 0000 の場合の動きが謎いです。

MySQL のバージョンは次の通り。

select version();
/*-----------+
| version()  |
+------------+
| 5.7.19-log |
+-----------*/

SQL モードを指定します。

set session sql_mode = 'STRICT_ALL_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO';
select @@sql_mode;
/*--------------------------------------------------------------------------+
| @@sql_mode                                                                |
+---------------------------------------------------------------------------+
| STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO |
+--------------------------------------------------------------------------*/

データを入れます。想定通りの動きだと思います。月や日の 00 は無効ですが年の 0000 は有効です。

create table t (d date);
insert into t values ('0000-00-00'); /* ERROR 1292 (22007): Incorrect date value */
insert into t values ('0001-01-00'); /* ERROR 1292 (22007): Incorrect date value */
insert into t values ('0001-00-01'); /* ERROR 1292 (22007): Incorrect date value: */
insert into t values ('0000-01-01'); /* Query OK */
insert into t values ('0000-01-02'); /* Query OK */
insert into t values ('9999-12-30'); /* Query OK */
insert into t values ('9999-12-31'); /* Query OK */

なんということでしょう。

select d, date_add(d, interval 1 day) as `d+1` from t;
/*-----------+------------+
| d          | d+1        |
+------------+------------+
| 0000-01-01 | 0000-00-00 |
| 0000-01-02 | 0000-00-00 |
| 9999-12-30 | 9999-12-31 |
| 9999-12-31 | NULL       |
+------------+-----------*/

select d, date_sub(d, interval 1 day) as `d-1` from t;
/*-----------+------------+
| d          | d-1        |
+------------+------------+
| 0000-01-01 | 0000-00-00 |
| 0000-01-02 | 0000-00-00 |
| 9999-12-30 | 9999-12-29 |
| 9999-12-31 | 9999-12-30 |
+------------+-----------*/

9999-12-31 + 1 day が NULL なのは良いとして 0000-01-010000-01-02date_adddate_sub すると 0000-00-00 なりました。

下記の結果を見るに、比較はうまく動いているっぽい。

select d, d < '0000-01-02', d > '0000-01-01' from t;
/*-----------+------------------+------------------+
| d          | d < '0000-01-02' | d > '0000-01-01' |
+------------+------------------+------------------+
| 0000-01-01 |                1 |                0 |
| 0000-01-02 |                0 |                1 |
| 9999-12-30 |                0 |                1 |
| 9999-12-31 |                0 |                1 |
+------------+------------------+-----------------*/

どうやら date_adddate_sub による演算の結果、年が 0000 になると 0000-00-00 になってしまうっぽい。

select date_add('0000-12-30', interval 1 day) as `0000-12-30 + 1`,
       date_add('0000-12-31', interval 1 day) as `0000-12-31 + 1`,
       date_sub('0001-01-01', interval 1 day) as `0001-01-01 - 1`,
       date_sub('0001-01-02', interval 1 day) as `0001-01-02 - 1`;
/*---------------+----------------+----------------+----------------+
| 0000-12-30 + 1 | 0000-12-31 + 1 | 0001-01-01 - 1 | 0001-01-02 - 1 |
+----------------+----------------+----------------+----------------+
| 0000-00-00     | 0001-01-01     | 0000-00-00     | 0001-01-01     |
+----------------+----------------+----------------+---------------*/

西暦1年の前年は0年ではなく紀元前1年?なのなら 0000 という年は無効な日付になるので結果が 0000-00-00 とかになるのも判らなくもないけど・・それなら date_adddate_sub の入力に与えられたときも結果は 0000-00-00 になるべきな気がするし NO_ZERO_IN_DATE で弾かれてほしい気もする。

あとこれ、テーブルへの格納ではなくて date_adddate_sub による演算に対して発生しているので sql_mode は関係なく発生するようです。

KeepAlive On な Apache+mod_php で HTTP/1.0 クライアントに HTTP/1.1 を返すとタイムアウトを待ってしまう

  • PHP 7.1.7
  • Apache 2.4.10
  • ApacheBench 2.3
  • zend-expressive 2.0.3
  • zend-expressive-skeleton 2.0.3
  • zend-diactoros 1.4.0
  • zend-stratigility 2.0.1

Docker で Apache+mod_php を実行して、

docker run --rm -p 8888:80 -v "$PWD:/var/www/html" php:apache

header 関数で HTTP/1.1 を指定するコードを配置して、

<?php
header('HTTP/1.1 200 OK');

次のように ab します。

ab -c 1 -n 1 http://localhost:8888/

すると、なんか異様なスコアになります(1回のリクエストに5秒もかかってる)。

Requests per second:    0.20 [#/sec] (mean)
Time per request:       5011.751 [ms] (mean)
Time per request:       5011.751 [ms] (mean, across all concurrent requests)

header 関数をコメントアウトしたり、

<?php
//header('HTTP/1.1 200 OK');

HTTP/1.0 を指定したり、

<?php
header('HTTP/1.0 200 OK');

Connection: close を指定したり、

<?php
header('HTTP/1.1 200 OK');
header('Connection: close');

Aapche Bench で KeepAlive を有効にすれば、

ab -k -c 1 -n 1 http://localhost:8888/

それっぽい結果になります。

Requests per second:    560.85 [#/sec] (mean)
Time per request:       1.783 [ms] (mean)
Time per request:       1.783 [ms] (mean, across all concurrent requests)

原因

Apache Bench はデフォで HTTP/1.0 なリクエストを送ります。なのでレスポンスの最後はサーバの方からクローズされることを期待します。

一方、php が header 関数で HTTP/1.1 を指定すると、Apache は元のリクエストが 1.0 だったとしても 1.1 を応答し、しかも KeepAlive On なら KeepAlive も有効になります。そのため KeepAliveTimeout で指定された秒数まで接続しっぱなしになります。

Apache Bench はサーバからのアクティブクローズを待つので KeepAliveTimeout の秒数が経過するまでリクエストが終わらなくなります。

Docker Hub の php:apache のイメージは KeepAlive ONKeepAliveTimeout 5 です。

curl だと発生しない

curl で HTTP/1.0 を指定してもこの現象は発生しません。

time curl -i -0 http://localhost:8888/
HTTP/1.1 200 OK
Date: Sun, 30 Jul 2017 08:54:12 GMT
Server: Apache/2.4.10 (Debian)
X-Powered-By: PHP/7.1.7
Content-Length: 0
Content-Type: text/html; charset=UTF-8


real    0m0.012s
user    0m0.003s
sys     0m0.004s

curl はリクエストを 1.0 で送ってもレスポンスが 1.1 なら 1.1 のクライアントとして振る舞うからだと思います。ので、Content-Length を元にレスポンスの終わりを判断できます。

なお、php のコードを次のようにすると、

<?php
header('HTTP/1.1 200 OK');
flush();

curl の応答は次のようになります。

time curl -i -0 http://localhost:8888/
HTTP/1.1 200 OK
Date: Sun, 30 Jul 2017 08:57:05 GMT
Server: Apache/2.4.10 (Debian)
X-Powered-By: PHP/7.1.7
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

real    0m0.014s
user    0m0.002s
sys     0m0.005s

この場合は Transfer-Encoding: chunked なので、チャンクサイズ 0 でレスポンスの終端が判断できます。

zend-expressive

この現象、zend-expressive を Docker Hub の php:apache で動かして ab したときのスコアがおかしなことになって気づきました。

zend-expressive で使われている zend-diactoros のレスポンスオブジェクトは普通に作るとリクエストの内容に依らず 1.1 で固定のためです。

パイプラインの最初のほうでリクエストのバージョンを元にレスポンスのバージョンを変更するミドルウェアを登録しておけば解決します。

// pipeline.php

use Psr\Http\Message\ServerRequestInterface;
use Interop\Http\ServerMiddleware\DelegateInterface;

$app->pipe(function (ServerRequestInterface $request, DelegateInterface $delegate) {
    return $delegate->process($request)->withProtocolVersion($request->getProtocolVersion());
});

nginx+php-fpm では発生しない

ちなみに nginx+php-fpm の構成では発生しませんでした。

nginx の場合は php-fpm が 1.1 を返してきたとしても、リクエストのバージョンが 1.0 なら Connection: close を付与したうえでサーバからアクティブクローズするようです。

結局どこが悪いの?

現代の PHP は http_response_code があるのでレスポンスコードを指定するためだけにプロトコルバージョンまで付けて header 関数を使うべきでは無いと思いますけれども、zend-diactoros は header 関数でステータスコードを指定しています。ただ、PSR-7 の ResponseInterfacegetProtocolVersion/withProtocolVersion を持っているので、レスポンスの送信時にプロトコルバージョンも指定するのは至って自然な気がします。

なので、zend-expressive でレスポンスのプロトコルバージョンがリクエストのプロトコルバージョンに基いていないのがダメなのだろうか・・・?

でも、HTTP/1.0 のリクエストに対して HTTP/1.1 のレスポンスを返すこと自体は合法なようです。

ただ、サーバが「おれ HTTP/1.1 でも大丈夫だよ」とクライアントに伝えるためであって、KeepAlive などの HTTP/1.1 の機能は有効にするべきではない、気がする、ので、Apache が悪いような気もする。

あるいは zend-expressive が(あるいは mod_php が) HTTP/1.0 なリクエストに対しては Connection :close でレスポンスを返すべき?

SNMP v3 の engineID とかのメモ

man snmpcmd とか man snmpd.conf とか man snmptrapd.conf とか tcpdump とかで試行錯誤で推測した内容からのメモ。全然見当違いの可能性もある。


SNMP v3 のエージェントはエージェントを一意に識別するための engineID を設定する必要がある。engineID は不変で別のエージェントの engineID と競合してはならない。

Net-SNMP では engineID は未設定だとマシンのホスト名に対して見つかる最初の IP アドレスが使用される、と日本語の man にはるけど、英語の man では最初の起動時にシステム時刻と乱数に基づいて生成されるとなっている。

生成済の engineID を削除して再生成を何度か試したところ毎回異なる値になったので、英語の man のが正しいと思われる。engineIDType の設定で日本語の man と同じように生成することもできると思う。

生成された値は /var/lib/net-snmp/snmpd.conf に記録される。

SNMPv3 で snmpwalk すると、最初はユーザーや engineID は空でリクエストが送信され、エージェントからは .1.3.6.1.6.3.15.1.1.4.0=225 のような値が応答される。この OID は SNMP-USER-BASED-SM-MIB::usmStatsUnknownEngineIDs.0 という名前で、225 という値は engineID が不明なためにドロップされたパケットの数、らしい、MIB ファイルの記述によると。確かに実行するたびに値がインクリメントされている。

この応答が返ってくるとき、一緒に engineID も付いて返ってくる。snmpwalk は(つまりマネージャーは)この engineID を使って目的の OID の値を取得するためにもう一度リクエストする。

つまり、SNMPv3 を使う場合は engineID を取得するために1往復分余分にパケットがやり取りされている。もちろん engineID は不変という前提なので、一度取得した engineID をキャッシュしておけば必要ない、ただし snmpwalk はそんなのキャッシュしてないので毎回1往復余分にパケットをやり取りする。

一方、snmptrap コマンドでトラップを送る場合、snmpd の初回起動時に生成された engineID のとは別の engineID が生成されて、その engineID を付けてトラップが送信される。

ここで生成された値は /var/lib/net-snmp/snmpapp.conf に記録される。

トラップの場合、送信元がエージェントで送信先がマネージャーなので、エージェントはマネージャーが自分の engineID を知っているという前提でトラップを投げる。つまり、snmpwalk(で送られる GetRequest)のときのように1往復余分にやりとりしたりせず engineID が違ってればパケットが無視されておしまい。

snmptrapd で v3 のトラップを受けるためには、送信元の snmptrapengineID を調べて、その値を元に送信先の snmptrapd でユーザーを作成する必要がある。

たぶん、ネットワーク機器とかの組み込みの SNMP なら GetRequest に応答する engineID とトラップの engineID は同じ?なのかな??なのでマネージャーは GetRequest したときの engineID を記憶しておいてトラップを受信したときに照合することができる?のだろうか??マネージャーが Net-SNMP だと無理そう?だけど??

Net-SNMP の snmpdsnmptrap だと別々の engineID が生成されることがあるのでそういうのはできない。

v3 snmptrap とかでググると、適当な engineID をベタ書きで snmptrap コマンドの引数にしている例が多いのだけど、本当ならエージェントの /var/lib/net-snmp/snmpapp.conf に記録された engineID を使ってマネージャーの snmptrapd を設定するのが正しい?ような気がする??


2ヶ月ぐらい前に社内に書いてたメモからのコピペ。

うーん、結局のところよく判っていない。いろいろあって実案件でこの知識使うこともなくなったので、詳しくは調べていない。

というかなんでこんなニッチなことを調べる必要に迫られたのか・・なにエンジニアなのかわからなくなるわ。