HTTPリダイレクトについて調査した

HTTPのリダイレクトには,大きく分けて次の二つ存在する.

  • 一時的なリダイレクト
  • 恒久的なリダイレクト

一般的に用いられるリダイレクトのステータスコードは 301302 である(個人的な考えですが).
しかし,他にもいくつかリダイレクトのステータスコードは存在する.

また,似たような意味を持つステータスコードもあるので,整理するために少し調査した.

今回対象とするリダイレクトのステータスコード

今回は,次の4つを対象とする.

  • 301 Moved Permanently
  • 302 Found
  • 307 Temporary Redirect
  • 308 Permanent Redirect

それぞれのステータスコードについて,RFCを参考に概要を以下に示す.
308以外はRFC 7231,308はRFC 7238で定義されている.

301 Moved Permanently

  • 恒久的なリダイレクトを表す
  • 歴史的な経緯から,301を受け取ったクライアントはリクエストメソッドをPOSTからGETに変更する可能性がある
    • これを望まない場合,代わりに307を用いることができる
    • しかし,307とは意味が異なる(恒久的↔一時的)
      • 308が新しく定義された

302 Found

  • 一時的なリダイレクトを表す
  • 歴史的な経緯から,301を受け取ったクライアントはリクエストメソッドをPOSTからGETに変更する可能性がある
    • これを望まない場合,代わりに307を用いることができる

307 Temporary Redirect

  • 一時的なリダイレクトを表す
  • これを受け取ったクライアントはリクエストメソッドを変更してはいけない

308 Permanent Redirect

  • 恒久的なリダイレクトを表す
  • これを受け取ったクライアントはリクエストメソッドを変更してはいけない

挙動の確認

それぞれのステータスコードに対する挙動を確認するため,簡単なサーバプログラムをGoで作成し,そのサーバに4種類のクライアントで接続した.
作成したサーバプログラムは mas9612/http-redirect-test に置いている.

用いたクライアント
* curl 7.43.0
* Safari 11.1.2
* Google Chrome 67.0.3396.99
* Firefox 59.0.2

上記それぞれのクライアントから,GETとPOST2つのメソッドで作成したサーバに接続した.
結果を次の表に示す.

Method GET

client 301 302 307 308
curl GET GET GET GET
Safari GET GET GET GET
Chrome GET GET GET GET
Firefox GET GET GET GET

Method POST

client 301 302 307 308
curl POST POST POST POST
Safari GET GET POST POST
Chrome GET GET POST POST
Firefox GET GET POST POST

実験結果より,curl以外のクライアントは,POSTリクエストの応答結果が301と302の時,リダイレクト先へのリクエストメソッドをGETに変更していることがわかった.
また,RFCの定義どおり,307と308はきちんとリクエストメソッドが維持されていることが確認できた.

References

HTTP Keep-Aliveについて

HTTP Keep-Aliveについて調査した.
とりあえず調査しただけなので,次回にでも実際に挙動確認をしたい.

  • 1つのTCPコネクションで複数リクエストを処理できるしくみ
  • HTTP/1.1ではKeep-Aliveがデフォルトでオン
    • オフにするにはそれを明示的に指定する必要がある
      • Connection: close ヘッダを指定

Apache

Keep-Aliveの設定

KeepAlive ディレクティブで,Keep-Aliveを有効にするかどうかを設定できる.
Keep-Aliveによるコネクション持続時間は, KeepAliveTimeout ディレクティブで設定する.

例) コネクション持続時間を10秒に設定する

KeepAlive On
KeepAliveTimeout 10

また,1つのKeep-Aliveによるコネクションで処理できるコネクション数を制限するには, MaxKeepAliveRequests ディレクトティブを使用する.
例えば, MaxKeepAliveRequests 10 と設定すると,1つのコネクションで10個までのコネクションを処理することができる.

Nginx

Keep-Aliveの設定

Nginxでは,Keep-AliveのOn/Offは keepalive_timeout の値によって決まる.
* keepalive_timeout0 : Keep-Alive Off
* keepalive_timeout0 以外: Keep-Alive On

Keep-Aliveによるコネクション持続時間は,On/Offの設定同様 keepalive_timeout で設定する.

例) コネクション持続時間を10秒に設定する

keepalive_timeout 10

References

USENIX 2018の論文読み

軽く読んだので,雑なまとめ.
ほぼ自分用のメモ.

Elastic Scaling of Stateful Network Functions

  • https://www.usenix.org/conference/nsdi18/presentation/woo
  • NFV(Network Functions Virtualization)におけるスケーリングの弾力性は重要な要素
    • 実用レベルでの実現は難しかった
      • 多くのNFs(Network Functions)はステートフル
      • NFを構成するインスタンス同士での状態共有が必要である
      • NFでのスループットとレイテンシの要件を満たしたステート共有の実装は難しい
  • S6を提案
    • パフォーマンスの低下なしにNFにスケーリングの弾力性を提供するフレームワーク
    • ステートをDSO(distributed shared object)とする
      • 弾力性と高パフォーマンスの要件を満たすために拡張したもの
    • NFの管理者は,ステートがどのように分散・共有されているかを気にすることなくプログラミングできる
      • S6が透過的に処理をしてくれる(データの局所性や整合性等を抽象化する)
  • 実験・評価の結果
    • 現在のNFの動的スケーリング手法と比較
      • スケーリング: 100倍のパフォーマンス向上
      • 通常時: 2〜5倍のパフォーマンス向上

Stroboscope: Declarative Network Monitoring on a Budget

  • https://www.usenix.org/conference/nsdi18/presentation/tilmans
  • ISPにとって,ネットワークの動作がどうなっているのか等を正確に知ることは困難
    • エンドホストを制御するのは不可能
    • 大量にトラフィックの統計を取る,という方法に頼るしかなかった
      • 情報の粒度が粗いという問題がある
  • Stroboscopeを提案
    • どんなトラフィックフローでもきめ細かいモニタリングが可能
    • 高レベルのクエリを入力すると,自動でいろいろやってくれる
      • どのフローをミラーリングするか
      • ルールをどこに配置するか
      • カバレッジを最大化するためにはいつルールをスケジューリングすれば良いか
    • 既存のルータ上で動作する

SafeBricks: Shielding Network Functions in the Cloud

  • Network Function Virtualization (NFV) の出現により,企業等ではネットワークでの処理をクラウド側に任せることが増えた
    • セキュリティリスクもある
    • クラウドは攻撃の影響を受けやすい
  • SafeBricksを提案
    • 信頼できないクラウドからNFを守るシステム
    • 暗号化されたトラフィックのみがクラウドプロバイダの方に流れる
      • トラフィックとNFの両方の完全性を保つ
    • クライアントに最小権限を強制する
    • SafeBricks leverages a combination of hardware enclaves and language-based enforcement
    • SafeBricksによるオーバーヘッドは0〜15%

RyuによるOpenFlow入門 (2)

前回の続き.今回はMACアドレス学習機能のある,スイッチングハブを作成する.
前回同様,OpenFlowのバージョンは1.0を使用します.

スイッチングハブ

ソースコードは以下の通り.

L2のフレームをスイッチの他のポートに送信するという基本機能は前回実装したリピータハブと変わらない.
そのため,プログラムの大枠は同じである.

リピータハブとスイッチングハブの違いとして,スイッチングハブではMACアドレスの学習機能があるという点がある.
このため,一度MACアドレスの学習をした後は,そのMACアドレス宛のフレームはその機器が接続されているポートのみに送信するようになる.
(リピータハブでは,全てのフレームを全てのポート(フレームが入ってきたポート以外)に送信する)

では,プログラムを見てみる.
なお,リピータハブのプログラムと似たような部分は省略します.

L2Switch クラスのインスタンスを作成する際に,MACアドレスとポートの対応付けをするためのディクショナリ( mac_to_port )を作成しておく(19行目).
MACアドレスを学習したら,この辞書にポートとの対応付けを登録していく.

def __init__(self, *args, **kwargs):
    super(L2Switch, self).__init__(*args, **kwargs)
    self.mac_to_port = {}

MACアドレスの学習は,Packet Inでスイッチからのデータを受け取ったときに行う.
まず,スイッチから入ってきたフレームのMACアドレスとポートの対応付けを mac_to_port に登録する(63行目).

self.mac_to_port[dpid][src] = msg.in_port

その後,出力ポートを決定するために,宛先MACアドレスの対応付け情報が mac_to_port の中に存在するかどうかを確認する(66行目〜70行目).
もし存在していたら,見つかったポートを出力ポートとしてPacket Outメッセージを作成する.
もし存在していなかったら,全てのポートを出力ポートとしてPacket Outメッセージを作成する.

ofproto = dp.ofproto
if dst in self.mac_to_port[dpid]:
    out_port = self.mac_to_port[dpid][dst]
else:
    out_port = ofproto.OFPP_FLOOD

加えて,もし宛先MACアドレスの対応付け情報が存在していた場合,同じ宛先MACアドレスのフレームが再度コントローラに来るのを防ぐため,FlowModメッセージを使ってスイッチにフローエントリを書き込む(76行目〜77行目).
FlowModを使うのは,毎回コントローラにフレームが来てしまうとその分パフォーマンスが落ちてしまうため,それを避けるため.

if out_port != ofproto.OFPP_FLOOD:
    self.add_flow(dp, msg.in_port, dst, src, actions)

RyuによるOpenFlow入門 (1)

Ryuを使って簡単なL2スイッチを作ってみた.
Ryuについての理解が浅い状態でいきなりMACアドレスの学習機能を持ったL2スイッチを作成するのは難しいかと思ったので,まずMACアドレス学習機能のない単純なL2スイッチ(リピータハブ)を作成し,その後スイッチングハブを実装してみる.

リピータハブ

ソースコードは以下の通り.

OpenFlowスイッチに入ってきたパケットは,スイッチ内にあるフローテーブルを参照し,テーブル内にマッチするエントリがなければOpenFlowコントローラへPacket Inメッセージを送出する.
リピータハブでは,入ってきたパケットをそれ以外のポート全てにそのまま送信する(フラッディング)ので,Packet Inで入ってきたデータをそのまますべてのポートに向けて送ってあげれば良い.

コントローラからPacket Outメッセージを送ることで,OpenFlowスイッチからパケットを送出することができる.
これを利用して,データのフラッディングを行う.

Packet Outメッセージの作成部分は次のようになる.

actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)]
out = ofp_parser.OFPPacketOut(
    datapath=dp, buffer_id=msg.buffer_id, in_port=msg.in_port,
    actions=actions
)

Packet Outメッセージでは,アクションを指定することで,パケットの出力先を指定することができる.
今回は入力ポート以外全てに送りたいため, OFPActionOutput の引数として ofp.OFPP_FLOOD を指定する.

作成したアクション等の情報を OFPPacketOut の引数として与えてあげ,Packet Outメッセージを作成する.
指定した引数は次のような感じ.

  • datapath : データパス.OpenFlowスイッチを表す(みたい).それぞれのOpenFlowスイッチはユニークなID(Datapath ID)を持つ.
  • buffer_id : OpenFlowスイッチにバッファされているデータのID.
  • in_port : パケットの受信ポート.
  • actions : 上で作ったアクションを指定する.

最後に,作成したメッセージを send_msg() で送信してあげれば完了.

mininetを使って動作確認を行ってみる.

$ ryu-manager dumb_l2_switch.py

$ mn --switch ovs --controller remote
...
*** Starting CLI:
mininet> pingall
*** Ping: testing ping reachability
h1 -> h2
h2 -> h1
*** Results: 0% dropped (2/2 received)

ちゃんと疎通しているみたい.

スイッチングハブについては違う記事に分けて書きます.