2014年1月12日日曜日

How to drop some tcp packets in a http response to force retransmission with iptables

I heard that a problem that one of our web server sometime has timeouts. We get timeouts more often when I access from EC2 instance like 3 out of 10 tries. I checked the result of tcpdump on the web server side and the client side and it seemed that retransmitted packet cannot reach to the client.

In order to check if it is EC2 specific problem or not, I decided to try to drop one of packet in response from server and if retransmission works.

This is how to drop one of packet.

iptables -N LOGDROP
iptables -A LOGDROP -j LOG
iptables -A LOGDROP -j DROP
iptables -A INPUT --source ${webserver_ip} -p tcp --sport 80 -m state --state ESTABLISHED -m limit --limit 1/s --limit-burst ${n-1} -j ACCEPT
iptables -A INPUT --source ${webserver_ip} -p tcp --sport 80 -m state --state ESTABLISHED -m limit --limit 1/s --limit-burst ${j} -j LOGDROP
iptables -A INPUT --source ${webserver_ip} -p tcp --sport 80 -j ACCEPT

it will accept before $nth packet and after that $j packets will be logged and dropped, all packets after that will be accepted again.
This rules should be applied on the client side. if it is executed on web server, it causes huge problems, so do not do that.

With this rule, I have checked the problem that is related to retransmit may happen from other environment.
I'm still investigating the exact reason of the problem.

Normally, even if you apply this filter, you won't have any problem because you will get retransmitted packet.
It should be quite rare problem, though, If you have any problem with retransmission, you can try to use this for investigation.

iptablesでn番目のTCPパケットをドロップして再送させる方法

とあるwebサーバでhttpリクエストが時々タイムアウトするという問題を調査することになった。
特にEC2から接続の場合によく起こるということだったので試してみると10回中3回ほどの発生率。
htmlのデータが途中まで届く。

サーバ側、クライアント側の両方でtcpdumpを見てみると、再送パケットがクライアントに届かなくなっていた。
クライアントは途中のパケットがいつまでも来ないのでタイムアウトする。

これがEC2特有の現象でないことを確認するため、サーバからのレスポンスの中から特定のパケットを落としてみて
問題が再現するかどうかを試してみることにした。

その方法がこちら。
iptables -N LOGDROP
iptables -A LOGDROP -j LOG
iptables -A LOGDROP -j DROP
iptables -A INPUT --source ${webserver_ip} -p tcp --sport 80 -m state --state ESTABLISHED -m limit --limit 1/s --limit-burst ${n-1} -j ACCEPT
iptables -A INPUT --source ${webserver_ip} -p tcp --sport 80 -m state --state ESTABLISHED -m limit --limit 1/s --limit-burst ${j} -j LOGDROP
iptables -A INPUT --source ${webserver_ip} -p tcp --sport 80 -j ACCEPT
n番目までのパケットを通過させて、その後、j個のパケットをログにとってからdropする。その後のパケットも通過。
これをクライアント側で実行する。
※もしサーバ上でこれを実行すると大変なことになるのでやってはいけない。

これにより、再送パケットのみ届かない件は別の環境からの接続でも発生することが確認できた。
残念なことに実際の問題はまだ調査中。

通常であればこのフィルタがあっても再送パケットが届くので問題なく通信できる。
滅多にないと思うがTCPでのパケット再送を任意に発生させる必要があったら試してみて欲しい。

2013年2月12日火曜日

ドキュメント作成機能付きREST API フレームワークを試す

公開用のAPIを開発するにあたって必須の物といえばドキュメントとテスターだ。先日、そういった用途にとても合う、面白いツールを見つけたので紹介したい。Swaggerという。
もともとScala, Java, Javascript, Ruby, PHPと多言語対応しているため、API作成用部分とUI部分が別々に開発されており、APIの仕様公開用JSONで仕様の情報をやり取りするように作られている。
Node.js+Expressで動作させるまでに手間取ったので以下に単純化した準備用の手順をまとめてみた。

任意のディレクトリを作成し、そこで作業を開始する。
npm install express swagger-node-express
mkdir tmp
cd tmp
git clone https://github.com/wordnik/swagger-ui.git
cd swagger-ui
npm install
npm install -g handlebars
./node_modules/.bin/cake dist
cp -a dist ../../public/docs
sed -i 's/http:\/\/petstore.swagger.wordnik.com//'  public/docs/index.html
cd ../../
そして以下の内容でapp.jsを作成する
var express = require("express");
var swagger = require("swagger-node-express");
var param = require("swagger-node-express/Common/node/paramTypes.js");

var app = express();
app.use(express.bodyParser());
app.use(express.static(__dirname + '/public'));

swagger.setAppHandler(app);
swagger.addModels({
  models: {
    User: {
      id: 'User',
      properties: {
        id: { type: 'long' },
        name: { type: 'string' }
      }
    }
  }
});

var idParam = param.path('id', 'ID of user that needs to be fetched', 'string');
idParam.defaultValue = '1';

swagger.addGet({
  spec: {
    description: 'get user',
    path: '/user.{format}/{id}',
    notes: 'Returns a user based on ID',
    summary: 'Find user by ID',
    method: 'GET',
    params: [ idParam ],
    errorResponses: [swagger.errors.invalid('id')],
    nickname: 'getUserById'
  },
  action: function (req,res) {
    if (!req.params.id) { throw swagger.errors.invalid('id'); }
    res.send({ id: parseInt(req.params.id) });
  }
});

swagger.configure("", "0.1");
app.listen(8080);
node appを実行してhttp://localhost:8080/docs/にアクセスすると 一つだけだが、APIのドキュメントが表示され、テストできる状態になっていることが確認できると思う。

POSTの際のボディの形式がJSONのみだったのでURLエンコード形式でリクエストボディを送ろうと思ったらswagger-uiを修正する必要があったり、 responseClassの情報を定義した際に動作がおかしくなったり(リクエストのContent-Typeがapplication/xmlになった。)まだまだ不完全な部分が多いように見える。
だが、頻繁に複数のAPIを開発、整備するという状況ならばこういったプロジェクトは助けになる可能性が高い。

過去別のブログに技術系の話題を書いていたが、継続していくというのはとても大変だと思う。今回このSwaggerについてぜひ紹介したいと思ったので新しく始めてみた。これを機になにかしらの記事を提供できれば、と思っている。