OVS拡張の話(とPioの話)

@shun159

shun159(@shunichigokew)

  • 自宅OpenFlowやっています
  • 自作OpenFlowコントローラを作っています。

 

  • Trema/PioにテストデータUPしまくってます。
  • Pioにはわずかながらおてつだいをしているよ。

まずtrema/pioの紹介

Pio is 何(openflow 1.3 flow-mod)

Pio::FlowMod.new(
  match: Pio::Match.new(in_port: 1),
  instructions: Pio::GotoTable.new(1)
)

Pio is 何(icmp request)

Pio::Icmp::Request.new(
  source_mac: '00:16:9d:1d:9c:c4',
  destination_mac: '00:26:82:eb:ea:d1',
  ip_source_address: '10.10.10.1',
  ip_destination_address: '8.8.8.8'
)

Pio is 何(udp)

udp = Pio::Udp.new(
 destination_mac: "ff:ff:ff:ff:ff:ff",
 source_mac: "01:01:01:01:01:01",
 ip_source_address: "1.1.1.1",
 ip_destination_address: "255.255.255.255",
 udp_source_port: 10,
 udp_destination_port: 20,
 udp_payload: "hello, pio:udp"
)
udp.to_binary_s

Pio is 何(port number)

Pio::SendOutPort.new(
  port_number: :flood,
  max_len: 0
)

Pio is 何(features_reply.ports)

features_reply.ports      
=> [{:port_no=>2,
  :hardware_address=>#<Pio::Mac:19449980 "16:7d:a4:37:ba:10">,
  :name=>"trema0-0",
  :config=>[],
  :state=>[],
  :curr=>[:port_10gb_fd, :port_copper],
  :advertised=>[],
  :supported=>[],
  :peer=>[]},
 {:port_no=>65534,
  :hardware_address=>#<Pio::Mac:19437760 "2a:b4:d6:3c:66:ba">,
  :name=>"vsw_0x1",
  :config=>[:port_down],
  :state=>[:link_down],
  :curr=>[:port_10mb_fd, :port_copper],
  :advertised=>[],
  :supported=>[],
  :peer=>[]}]

Pio is 何

  • Rubyのパケットパーサ実装

  • 「使いやすい・わかりやすい」をコンセプトを基に続けられています。

  • LLDP, ARP, ICMPなどよく使われるものが実装されています。

  • 最近はOpenFlow(1.0, 1.3.2)が実装され始めています。

  • Tremaではversion 0.4.1 から組み込まれました(2013/9/27~)

  • trema/pioのopenflowプロトコル実装がtrema version 0.4.8から利用されています。(2015/6/29~)

Trema version 0.4.8以降は遅くなりました

  • 単純にいうと1/5の速度くらいまで落ちました(cbench計測)

    • パケットパーサの性能の差も多分要因の一つかも…

別にcbenchの結果はどーでもよいのです

この際事前定義的にプログラミングできるような仕組みづくりが始められればいいなと思った次第です。

どうやって?

事前定義的にフローテーブルをプログラミングしやすくできる、"Nicira拡張"というものがあります。これを利用しましょう。

Nicira拡張?

その前に初めての方のためにおさらい

openvswitchのvendor拡張

NXAST_LEARNは有名

simple-minded MAC learining switch

root@shun159:~# ovs-ofctl dump-flows brfou
NXST_FLOW reply (xid=0x4):
 cookie=0x0, duration=1.297s, table=0, n_packets=0, n_bytes=0, idle_age=1, priority=1 
 actions=learn(table=1,priority=2,NXM_OF_VLAN_TCI[0..11],
               NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[]),resubmit(,1)
 cookie=0x0, duration=1.307s, table=1, n_packets=0, n_bytes=0, idle_age=1, priority=1 actions=FLOOD

今日はNicira拡張入れたい話をゆるく話していきます。初心者向けです。

openflow のキツいところ

「controllerに転送処理を書くんです?」

「そうだよ。面白いでしょ」

「…」

controllerってもっとこう…

  • 宣言的な転送ルールの定義を行えるといいな(個人的に
    • controllerができる範囲で転送処理に関与しない。FDBやARPテーブル、ルーティングテーブルなどを持つ必要がないように。
    • BUMの処理も同様。BUMを例外として扱うべきかどうか。

packet_inはさせるな?

  • このままの意味をとると、利用範囲はかなり狭まってしまう。
    • packet_inをデータパスからの例外として扱う。
    • なぜ起こる?データパスが扱えない処理があるためにコントローラに例外が上がるから?(mac_learning、arp_respond、routing table lookup, egress interface lookup, BUM_handlingなどなど)
  • しかし、L2やL3の処理を扱いたいというニーズやそういう環境もあるはず。

 

"例外"になる転送処理(一部)

  • MAC Learning
    • 学習と転送ルールが必要になるため。
  • ARP Respond
    • 実はOpenFlow 1.3で対処できる。
  • Routing & Egress Interface Lookup
    • NetworkアドレスとNextHopアドレスに基づいた出力IFの決定とARPテーブルからeth_dstを検索し書き換えて出力する必要があるため。=> nexthopを扱えれば万事OK?
  • BUM handling
    • あるドメインに基づいた転送範囲の分離が必要 => ドメインをフィールドに加え、in_portとout_port(s)を記録できればよい?

前述の機能をOVSに実装するのため拡張もろもろについて

NXM_NX_REG(idx)

Packet register fields

  • OFPXMC_NXM_1
  • ovsの場合は、packet_regsの数は32bitの場合、8つまで。
  • maskable and 32 bit wide.
  • pipelineフィールドです。packet_inのmatchesにも入る。
  • set-field, copy-field(of1.5より), reg_move, reg_loadで使う。

NXAST_REG_LOAD

即値を書き込む

  • set-fieldに似ていますが、offsetを指定できる点が異なる
  • set-fieldに似ていますが、valueは64bit整数で与え、matchフィールドにはheaderのみを与える点が異なる。
  • かけないフィールドもあるよ!

NXAST_REG_MOVE

フィールドからフィールドへ値をコピー

  • openflow 1.5のcopy-fieldと同じ。
  • 私が一番好きなアクションです。

NXAST_OUTPUT_REG

src[ofs:ofs+nbits]に書かれたopenflow port に出力

  • reg_loadにフォーマットが似ているかも?
  • フローエントリ数を減らしたりまとめたりするのに地味に効く
  • 出力される前にin_portと同値でないかどうかが評価されるみたい

NXAST_LEARN

フロー追加するアクション

  • MODIFY_STRICT コマンドを指定したFLOW_MODに似たことができるアクションです。超強力なアクションです。
  • ただし、matchやactionは指定する代わりに、flow_mod_specという形式のフィールドを並べておく必要があります。*後述
  • 個人的にイマイチ不便だなぁと思う。resubmit || goto-table かければいいのに…。とないものねだり(でもdst 0b11は空いてませんかwwwデュフフフ

NXAST_LEARN(cont'd)

flow_mod_spec

  • flow_mod_specには、src:1bit, dst:2bit, n_bits:11bit, rest/bytesでそのlearnで学習するフローの動作を定義します。難しいようですが、きまりさえ抑えておけば実装は非常に簡単です。

NXAST_LEARN(cont'd)

flow_mod_spec(match field)

flow_mod_spec(<<_:2, 0:1, 0:2, Nbits:11, Data/bytes>>) ->
    <<Src:4/bytes, SrcOfs:16, Dst:4/bytes, DstOfs:16, Next/bytes>> = Data,
    SrcMatch = ofproto_oxm:codec(Src),
    DstMatch = ofproto_oxm:codec(Dst),
    Action = #apply_copy_field{ n_bits = Nbits,
                                src = SrcMatch,
                                src_offset = SrcOfs,
                                dst = DstMatch,
                                dst_offset = DstOfs },
    { Action, Next };
flow_mod_spec(<<_:2, 1:1, 0:2, Nbits:11, Data/bytes>>) ->
    <<Value:Nbits/bits, Dst:4/bytes, Ofs:16, Next/bytes>> = Data,
    DstMatch = ofproto_oxm:codec(Dst),
    Action = #apply_set_field{ n_bits = Nbits,
                               value = Value,
                               dst = DstMatch,
                               offset = Ofs },
    { Action, Next };

NXAST_LEARN(cont'd)

flow_mod_spec(load & move)

flow_mod_spec(<<_:2, 0:1, 1:2, Nbits:11, Data/bytes>>) ->
    <<Src:4/bytes, SrcOfs:16, Dst:4/bytes, DstOfs:16, Next/bytes>> = Data,
    SrcMatch = ofproto_oxm:codec(Src),
    DstMatch = ofproto_oxm:codec(Dst),
    Action = #write_copy_field{ n_bits = Nbits,
                                src = SrcMatch,
                                src_offset = SrcOfs,
                                dst = DstMatch,
                                dst_offset = DstOfs },
    { Action, Next };
flow_mod_spec(<<_:2, 1:1, 1:2, Nbits:11, Data/bytes>>) ->
    <<Value:Nbits/bits, Dst:4/bytes, Ofs:2/bytes, Next/bytes>> = Data,
    DstMatch = ofproto_oxm:codec(Dst),
    Action = #write_set_field{ n_bits = Nbits,
                               value = Value,
                               dst = DstMatch,
                               offset = Ofs },
    { Action, Next };

resubmit/resubmit_table

goto-tableみたいなもので、resubmitの方はcurrent-tableに再送信させるためのアクション

1.3ならRESUBMIT/

RESUBMIT_TABLEはgoto-tableがあるからいらないとおもう。

いくつか簡単な例を

 

  • Simple minded-learning switch
  • simple-L2VLAN
  • simple-router

learning-switch

mac_learning(table=0)

 table=0, priority=1 actions=learn(table=1,
                                   priority=2,
                                   NXM_OF_VLAN_TCI[0..11],
                                   NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],
                                   output:NXM_OF_IN_PORT[]),
                             resubmit(,1) ①

learning-switch

packetがtable0に入力されるとtable1にvlan_id(12bit), eth_srcがeth_dstに対応するマッチとin_portがout_portに対応するフローがインストールされます。

egress(table=1)

 table=1, priority=2,vlan_tci=0x0000/0x0fff,
                     dl_dst=00:16:3e:2c:e3:d1
                     actions=output:1
 table=1, priority=2,vlan_tci=0x0000/0x0fff,
                     dl_dst=00:16:3e:4b:de:9f
                     actions=output:2
 table=1, priority=1 actions=FLOOD

learning-switch

簡単ですね

使った拡張アクション:

  • learn
  • resubmit table

isolated learning-switch

  • 先ほどのlearning_switchに「ブロードキャストドメインの分離」という機能を取り入れます。
  • 考えかた:
    • ブロードキャストはofpp_floodのような仮想ポートが利用できなくなった代わりに、floodingするテーブルをunicast-tableとは別に利用します
    • また、ブロードキャストパケットの逆流を防ぐため出力ポート番号と入力ポート番号を同値であるかどうかを評価する必要あるかも
    • どのブロードキャストの範囲に属しているかは、入力ポート番号を元にIDを付与します。

isolated learning-switch

isolated learning-switch

ingress port(table=0)

table=0, priority=1 actions=resubmit(,1)

isolated learning-switch

ingress vid(table=1)

NXST_FLOW reply (xid=0x4):
 table=1, priority=2,in_port=1 actions=load:0x1->NXM_NX_REG2[],
                                       learn(table=3,
                                             priority=100,
                                             NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],
                                             NXM_NX_REG2[],
                                             output:NXM_OF_IN_PORT[]),
                                       resubmit(,2)
 table=1, priority=2,in_port=2 actions=load:0x1->NXM_NX_REG2[],
                                       learn(table=3,
                                             priority=100,
                                             NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],
                                             NXM_NX_REG2[],
                                             output:NXM_OF_IN_PORT[]),
                                       resubmit(,2)
 table=1, actions=drop

isolated learning-switch

mac-filter(table=2)

NXST_FLOW reply (xid=0x4):
 table=2, priority=2,dl_dst=01:00:00:00:00:00/ff:00:00:00:00:00 
                     actions=load:0xfe->NXM_NX_REG0[],resubmit(,4)
 table=2, priority=2,dl_dst=ff:ff:ff:ff:ff:ff 
                     actions=load:0xfe->NXM_NX_REG0[],resubmit(,4)
 table=2, priority=2 actions=resubmit(,3)

isolated learning-switch

unicast-l2(table=3)

NXST_FLOW reply (xid=0x4):
 table=3, priority=100,reg2=0x1,
                       dl_dst=00:16:3e:aa:ef:c7
                       actions=output:1
 table=3, priority=100,reg2=0x1,
                       dl_dst=00:16:3e:c1:c9:a6
                       actions=output:2
 table=3, priority=1 actions=load:0xfe->NXM_NX_REG0[],
                             resubmit(,4)

isolated learning-switch

bum-handling(table=4)

NXST_FLOW reply (xid=0x4):
 table=4, priority=3,reg0=0xff,reg1=0x1,in_port=1 actions=drop
 table=4, priority=3,reg0=0xff,reg1=0x2,in_port=2 actions=drop
 table=4, priority=2,reg0=0xfe,reg2=0x1 actions=load:0x1->NXM_NX_REG1[0..15],
                                                load:0xff->NXM_NX_REG0[],
                                                resubmit(,),
                                                load:0x2->NXM_NX_REG1[0..15],
                                                load:0xff->NXM_NX_REG0[],
                                                resubmit(,)
 table=4, priority=1,reg0=0xff actions=output:NXM_NX_REG1[0..15]

簡単ですね。

使った拡張アクション:

  • learn
  • load
  • resubmit table
  • resubmit

simple-router

フローチャート省略

代わりにフローテーブルの内容で話を進めます

simple-router

NXST_FLOW reply (xid=0x4):
 # ControllerからのARP をARP Handler(table=11)にとばす
 table=0, priority=2,arp,metadata=0x7 actions=resubmit(,11)
 # 何もせず、protocol filterへ飛ばす
 table=0, priority=1,metadata=0 actions=load:0x1->OXM_OF_METADATA[0..31],
                                        resubmit(,10)

ingress port (table=0)

simple-router

protocol filter (table=10)

NXST_FLOW reply (xid=0x4):
 table=10,priority=65535,arp,metadata=0x1 actions=load:0x2->OXM_OF_METADATA[0..31],
                                                  resubmit(,11)
 table=10,priority=65535,ip,metadata=0x1 actions=load:0x3->OXM_OF_METADATA[0..31],
                                                 resubmit(,12)

simple-router

arp handler (table=11) arp respond

NXST_FLOW reply (xid=0x4):
 # ARP Responder
 table=11, priority=65535,arp,metadata=0x2,in_port=2,arp_tpa=20.0.0.254,arp_op=1 
 actions=load:0xff->OXM_OF_METADATA[0..31],
         CONTROLLER:65535,
         push:NXM_OF_ARP_TPA[],
         push:NXM_NX_ARP_SHA[],
         push:NXM_OF_ARP_SPA[],
         push:NXM_OF_ETH_SRC[],
         pop:NXM_OF_ETH_DST[],
         pop:NXM_OF_ARP_TPA[],
         pop:NXM_NX_ARP_THA[],
         pop:NXM_OF_ARP_SPA[],
         set_field:2->arp_op,
         set_field:02:02:02:02:02:02->eth_src
         set_field:02:02:02:02:02:02->arp_sha,
         load:0x6->OXM_OF_METADATA[0..31],
         load:0xffff->NXM_OF_IN_PORT[],
         load:0x2->NXM_NX_REG7[0..15],
         resubmit(,64)

simple-router

arp handler (table=11)

NXST_FLOW reply (xid=0x4):
 # ARP Reply をControlerへおくる
 table=11, priority=65535,arp,metadata=0x2,in_port=2,arp_tpa=20.0.0.254,arp_op=2
 actions=load:0xff->OXM_OF_METADATA[0..31],
         CONTROLLER:65535
 # ControllerからのARP Requestを一部書き換えて、Egress(table=64)に飛ばす
 table=11, priority=65535,arp,reg7=0x2,metadata=0xff actions=set_field:02:02:02:02:02:02->eth_src,
                                                             set_field:02:02:02:02:02:02->arp_sha,
                                                             set_field:20.0.0.254->arp_spa,
                                                             load:0x6->OXM_OF_METADATA[0..31],
                                                             resubmit(,64)

simple-router

routing table (table=12)

NXST_FLOW reply (xid=0x4):
 # Egress Interface Lookup
 table=12, priority=65535,reg1=0x14000000/0xffffff00,metadata=0x4 actions=load:0x5->OXM_OF_METADATA[0..31],
                                                                          load:0x2->NXM_NX_REG7[0..15],
                                                                          mod_dl_src:02:02:02:02:02:02,
                                                                          resubmit(,13)
 table=12, priority=65535,reg1=0xa000000/0xffffff00,metadata=0x4 actions=load:0x5->OXM_OF_METADATA[0..31],
                                                                         load:0x1->NXM_NX_REG7[0..15],
                                                                         mod_dl_src:01:01:01:01:01:01,
                                                                         resubmit(,13)
 # Connected Route Lookup
 table=12, priority=40024,ip,metadata=0x3,nw_dst=20.0.0.0/24 actions=load:0x4->OXM_OF_METADATA[0..31],
                                                                     move:NXM_OF_IP_DST[]->NXM_NX_REG1[],
                                                                     resubmit(,)
 table=12, priority=40024,ip,metadata=0x3,nw_dst=10.0.0.0/24 actions=load:0x4->OXM_OF_METADATA[0..31],
                                                                     move:NXM_OF_IP_DST[]->NXM_NX_REG1[],
                                                                     resubmit(,)
 # Static Route Lookup
 table=12, priority=800,ip,metadata=0x3,nw_dst=8.0.0.0/8 actions=load:0x4->OXM_OF_METADATA[0..31],
                                                                 set_field:0x14000001->reg1,
                                                                 resubmit(,)

simple-router

nexthop mac (table=13)

NXST_FLOW reply (xid=0x4):
 # ARP Entry Lookup
 table=13, priority=2,ip,reg1=0xa000001,metadata=0x5 actions=load:0x6->OXM_OF_METADATA[0..31],
                                                             mod_dl_dst:00:16:3e:27:63:d2,
                                                             resubmit(,64)
 table=13, priority=2,ip,reg1=0x14000001,metadata=0x5 actions=load:0x6->OXM_OF_METADATA[0..31],
                                                              mod_dl_dst:00:16:3e:ae:e6:f0,
                                                              resubmit(,64)
 # ARP Entry miss!
 table=13, priority=1,ip,metadata=0x5 actions=load:0xff->OXM_OF_METADATA[0..31],CONTROLLER:65535

simple-router

Egress Port (table=64)

NXST_FLOW reply (xid=0x4):
 table=64, priority=65535,metadata=0x6 actions=output:NXM_NX_REG7[0..15]

簡単ですね

  • 拡張アクション:
    • learn
    • load
    • move
    • set-field(of1.0)
    • resubmit
    • resubmit table
    • push/pop
    • output_reg
  • 拡張match:
    • reg(idx)
    • metadata(of1.0)
    •  
  • このように簡単にプログラムできるようになります。

  • ので、trema/pioに拡張をいれませんか?

  • テスト用パケットデータもあります。

ちなみに…

今回は都合によりOpenFlow 1.0で話をしましたが1.3でも大体同じことです。(多分

おしまい

ご意見お待ちしています。