Deprecated: The each() function is deprecated. This message will be suppressed on further calls in /home/zhenxiangba/zhenxiangba.com/public_html/phproxy-improved-master/index.php on line 456
============================== pf IMPLIMENTATION Ryoji Kanai ============================== ====================================================================== 概要 ====================================================================== ○ 概要の概要 o stateful packet filtering(stateful inspect) o NAT o Redirection(ポート転送というか変換) o フラグメントの再構築 XXX: pf_norm.c 未開拓なり〜 o IPv6対応 XXX: 対応状況調査〜 o ICMP エラーの送信元への配送 ○ static filtering 通常のフィルタリング o af, src/dest address(mask付き), src/dest port(range) o direction(in/out) o interface o TCP flags o icmp type/code o keep stateフラグでstateful finteringも可能 o dont-fragment bitの強制解除 o minimum ttlの上書き ○ NAT アドレス変換機 o simple NAT(pf上ではBINATという名前) source addrとその変換先を1対1で対応させた変換 双方向変換を行う。 ソース(もしくはディスティネーション)アドレスの書換えのみ。 o NAT source addrが多に対して変換先が1な対応の変換 一方行の変換しか行わない。 ソースアドレスとソースポートの書換え。 通常はstate機能と合せて使うのかな? ○ redirection o 単純なport変換機 ○ stateful filtering 動的なフィルタリングテーブル o いわゆる"STATEFUL INSPECTION" http://www.enteract.com/~lspitz/fwtable.html o テーブル管理はバイナリツリー。高さ平衡木の一種でAVL treeと呼ばれ るもの。AVLツリーは下記のようなルールに従うバイナリーツリー: どの子木も互いに高さが1つしか違わない。 サーチ、挿入、削除のコストはともに O(log n) になる。一方でコード が多少、複雑になっている(今後、ロックとかの問題は大丈夫だろうか) o tcpについては ipfilter でも使われているSequence numberの妥当性も チェックしている。 参考: "Real Stateful TCP Packet Filtering in IP Filter" Guido van Rooij o icmpについては icmp id(+ af, ipaddr ) でstateful tableのマッチン グを行う。 o icmpエラー系は、データ部分を参照して、エラーもととなったパケット を参照。そのエラーもとのパケットについてのstateful table参照も行 う。つまり telnet hoge 80 とかやって、hoge:80が開いてなくて、 destination port unreachableなicmpが帰って来たら、このicmpを正し く telnet したホストまで届ける。 ====================================================================== 概念図 ====================================================================== | | +----------|----------------+ |pf(IN) v | | +----------+ | | | Fragment | | | +----------+ | | | | | v | | +------------------+ | | | proto SW | | | +------------------+ | | TCP UDP ICMP OTHER | <-- v6版だとicmp6 | | | | | | がある | v | | +---------+ |----- | |Normalize| | | プロトコル依存処理 | | TCP | | | UDPはだいたいTCPと同じ | +----+----+ | | ICMPにはrdrが無かったり | | | v normalize_tcpはtcpのみ |+------|------------+ | ||STATE v | | || +-------+ | | || |dynamic|-+ | | || | rule | |MATCH | | || +-------+ v | | || NO +---------+ | | || | |address | | | || | |translate| | | || | +---------+ | | |+-----|---|---------+ | | v | | | +-----+| | | | rdr || | | +--+--+| | | | | | | v | | | +-----+| | | |biNAT|| | | +--+--+| | | | | | | v | | | +------+| | | |static|| | | | | rule || | v | +---+--+| |----- | | | | | v v | | +------------------+ | | | LOG / etc | | | +------------------+ | | | | +----------|----------------+ v KERNEL Processing | +----------|----------------+ |pf(OUT) v | | +----------+ | | | Fragment | | | +----------+ | | | | | v | | +------------------+ | | | proto SW | | | +------------------+ | | TCP UDP ICMP OTHER | | | | | | | | v | | +---------+ | | |Normalize| | | | TCP | | | +----+----+ | | | | |+------|------------+ | ||STATE v | | || +-------+ | | || |dynamic|-+ | | || | rule | |MATCH | | || +-------+ v | | || NO +---------+ | | || | |address | | | || | |translate| | | || | +---------+ | | |+-----|---|---------+ | | v | | | +-----+| | | |biNAT|| |<---IN/OUTでは | +--+--+| | NAT処理が違う | | | | | v | | | +-----+| | | | NAT || | | +--+--+| | | | | | | v | | | +------+| | | |static|| | | | rule || | | +---+--+| | | | | | | v v | | +------------------+ | | | LOG / etc | | | +------------------+ | | | | +----------|----------------+ v ====================================================================== 関数編 ====================================================================== ------------------------------ netinet/ip_input.c:ipv4_input(m) ------------------------------ ○ mはrecv packet の mbuf。 ○ pfまでのおおまかな流れ o mbuf pullup (length check) o version check o ip header length check(20以下じゃないか) o 127/8 check(RFC1122) o ip check sum o ip header length check(ip_len < header lengthじゃないか) o mbuf が ip len未満だったりしないか o でやっと pf に突入 /* * Packet filter */ if (pf_test(PF_IN, m->m_pkthdr.rcvif, &m) != PF_PASS) goto bad; ip = mtod(m, struct ip *); hlen = ip->ip_hl << 2; ------------------------------ pf_get_nat ------------------------------ ○ nat ルール検索用ルーチン ○ pf_nats_active をたどって、direction, ifp, proto, af, source/destination addr(notフラグ、dest mask付き)でマッチするものを 抜き出す。 ○ 最後にマッチしたものをかえす ------------------------------ pf_get_binat ------------------------------ ○ binat ルール検索用ルーチン。 ○ pf_binats_active をたどって、direction, ifp, proto, af, source addr, destination addr(notフラグ、dest mask付き)でマッチする ものを抜き出す ○ 最後にマッチしたものをかえす ○ bi-natだから逆方向のルールについても検索する (アドレスは remote addr / destination addr(not, mask付き)) ------------------------------ pf_test_tcp ------------------------------ ○ 処理 o PF_OUT 時は、 BINAT(pf_get_binat), NAT(pf_get_nat)の順でチェック。 PF_IN 時は、 RDR(pf_get_rdr), BINAT(pf_get_binat)の順でチェック。 BINAT: o "from addr to ip/mask -> addr"なルール o 現在のsource addr/portをバックアップして、アドレス変換。 o 新ポートは旧ポートと同じ。アドレスはルールで指定。 NAT: o "from ip/mask to ip/mask -> addr"なルール o 現在のsource addr/portをバックアップして、新 source port を pf_get_sportで取得してアドレス変換。アドレスはルールで指定。 RDR: o destinationの方を変換するルール。 o 現在のdest addr/portをバックアップして、新 dest ポートを pf_map_port_rangeで割り当てて、アドレス変換。 o アドレスはルール指定。 o それぞれマッチしたら、pf_change_apでアドレス等の変更 o 通常ルール木(pf_rules_active)を検索(if分が沢山)。 ちなみに、 "quick"フラグが立ってないとルールを最後まで評価して、 最後にマッチしたものを"マッチしたルール"とする。 o action == DROP で、RETURN RSTなフラグもしくはreturn icmpなフラグ の場合は、発信元にreply packetを返さないといけないので、 NAT/RDR の逆処理を行ない、アドレス等を元パケットに戻す。 (実際には NAT/BINAT/RDRでifして別の処理をしている。ちなみに、元の 値はあらかじめ保存してある。) o (NAT/BINAT/RDRでhit, 通常ルールでhitにしろ)keep stateだったら state tableへの反映を行う。 - PF_OUTだった場合 -- gateway addr/port = source addr/port -- external addr/port = dest addr/port (NAT/BINATの場合は) lan addr/port = 元のaddr/port (そうじゃない場合は) lan addr/port = gateway addr/port - PF_INの場合 -- lan addr/port = dest addr/port -- external addr = source addr (NAT/BINATの場合は) gateway addr/port = 元のaddr/port (そうじゃない場合は) gateway addr/port = gateway addr/port - seq no(low, hi値)関係 -- SYN, 通常ルールにhit, keep_stateがSTATE MODULATEだったらさらに seq noの再生成を行う。 - max windows size(とりあえず1)、state(src=SYN SENT, dest=closed) creation = 時間, expire(pftm_tcp_first_packet), packets(1), などを設定 - pf_insert_stateでstate tableへ追加 o (必要なら)mbufへの反映 o passでreturn(NAT/BINAT/通常のルールの、どれかでマッチした) ------------------------------ pf_test_udp ------------------------------ ○ pf_test_tcpに良く似ている。ていうか殆ど同じ。 seq noまわりの機能が無いだけか。 ------------------------------ pf_test_icmp ------------------------------ ○ icmp/icmp6なルールチェック ○ やっている事は pf_test_tcp に近い ○ 処理 o icmp type/code/idをゲット icmp/icmp6によってswitch o PF_OUT 時は、 BINAT(pf_get_binat), NAT(pf_get_nat)の順でチェック。 PF_IN 時は、 BINAT(pf_get_binat)を順でチェック。(RDRはない) それぞれマッチしたら元アドレスを保存してアドレス変換。 o あとは pf_test_tcp と似ている。 要はRDRが無いってこと。 ------------------------------ pf_test(dir, ifp, m) ------------------------------ ○ dirは方向。INとかOUTとか ifpはインターフェイス mはチェック対象のmbuf ○ pfのチェック部分 入口 ○ 処理の流れ o pf_status.runningがONじゃなかったら PASS PACKET_TAG_PF_GENERATEDなtagが付いてたらこれもPASS SEE: man mbuf_tags XXX: 何の目的で存在するtagなのかは不明... o この mbuf **m には M_PKTHDRが存在するか? 存在するとpanic。 inv4_inputではノーチェックでm_pkghdr.{len,csum,rcvif}にアクセスし ている。 o pftm_interval間隔で以下の処理 - pf_purge_expire_status tree_ext_gwy, tree_lan_extのexpire - pf_purge_expired_fragments フラグメント用データの解放 - 次回の処理用に現在時刻を保存 (今よりpftm_interval時間のうちはこの処理が実行されない) o struct ipより小さい mbuf だったら PFRES_SHORT で 処理終了 o pf_normalize_ip でフラグメントの再構成 この時点で drop される場合もある o 再構成後にまたstruct ipとの比較 o ここで protocol ごとの処理に分れる TCP: o pf_pull_hdr でTCPヘッダの取り出し ここで処理終わりの場合あり o pf_normalize_tcp ここで drop の場合あり o pf_test_state_tcp ここで pass して終わる場合あり o pf_test_tcp UDP: o pf_pull_hdr でUDPヘッダの取り出し ここで処理終わりの場合あり o pf_test_state_udp ここで pass して終わる場合あり o pf_test_udp ICMP: o pf_pull_hdr でICMPヘッダの取り出し ここで処理終わりの場合あり o pf_test_state_icmp ここで pass して終わる場合あり o pf_test_icmp その他: pf_test_other ------------------------------ pf_test_state_tcp ------------------------------ ○ dynamic tableによるtcpフィルタリング処理 ○ 処理 o 検索対象である現在のパケットから key を作成する o directionがPF_INならtree_ext_gwyを、そうでないならtree_lan_extで pf_find_stateを使い検索 o 見つからなかったら、PF_DROPで処理は終わり --- tcp な stateful rule に match した場合の処理、開始 --- o Sequence numberの妥当性チェック。 具体的なアルゴリズムは以下参照: "Real Stateful TCP Packet Filtering in IP Filter" 対応としては 1. 普通にOK src->state(SYN_SENT, TCPS_CLOSINGとか)の更新 stateに応じてrule expire timeの更新 2. 普通じゃないけどOK 宛先がSYN_SENT or どっちかがFIN_WAIT_2なSTATEで、なおかつ seq no.が予想範囲外な時。下記のような場合? 2-1. 馬鹿なTCPスタックは、(3-way handshakeの時に?)相手が応答 する前にSYNを連発するらしい??はへ?? XXX:いまいち分からず... 2-2. TCPのコネクション確立後にこのPFの動いてるマシンがリブート した。 2-3. コネクション終了直後に飛ぶ事があるらしい(Solarisとか) OKではあるけれど、expire timeは更新しない。 FIN, RSTについてのstateは更新する。 3. deny ログを取るぐらい。 o pf_change_ap を使って、NAT。アドレス変換。 もちろん IN,OUT時で反対の変換になるようになっている。 mbufへの反映も。 o 統計情報の更新 ------------------------------ pf_change_ap ------------------------------ ○ アドレス、ポートの変更。 それに伴なって、チェックサムの差分再計算 ○ NATでアドレス、ポートを変更する時に呼ばれる ------------------------------ pf_test_state_udp ------------------------------ ○ dynamic tableによるudpフィルタリング処理 ○ 処理 o 検索対象である現在のパケットから key を作成する o directionがPF_INならtree_ext_gwyを、そうでないならtree_lan_extで pf_find_stateを使い検索 o 見つからなかったら、PF_DROPで処理は終わり o expire o pf_change_ap を使って、NAT。アドレス変換。 もちろん IN,OUT時で反対の変換になるようになっている。 mbufへの反映も。 o 統計情報の更新 ------------------------------ pf_test_state_icmp ------------------------------ ○ dynamic tableによるicmp/icmp6フィルタリング処理 ○ destination unreachable等の場合はその原因となったパケットがあるはず である。その「原因パケット」をicmp dataから特定して、dynamic table との参照も行う。また、NAT変換もする。 ○ 処理 o icmp/icmp6 typeを調べて、UDP/TCPに対するエラーなのかを調べる。 icmpの場合ICMP_UNREACH, ICMP_SOURCEQUENCH, ICMP_REDIRECT等だった らそう。 ** icmp typeがTCP/UDPに対するエラーじゃなかった時 ** o 普通にstate tableを検索する icmp/icmp6の場合はaf, protocol, icmpidでの検索になる o expire時間を延長とか。 o NAT処理。NATの反映。 実際は direction, icmp/icmp6によって処理を分けている。 o PASSでreturn ** icmp typeがTCP/UDPに対するエラーだった時 ** o (下記、実際にはicmp/icmp6, protocolによってswitchしているので、 それぞれまったく違うコードが実行される。) o icmp data部分をip headerとしてpullupする。 icmp data部分について、ip header分のサイズが無かったら DROPで returnする。 ICMP error message too short(ip)とかログして。 o (ipv4のみ)ip offsetが設定されてたら DROP で終了。 o ip header lengthから次プロトコルへのoffset, proto, src ip dst ip, cksumを取得 ただしipv6の場合は ip optionを追っかけてoffsetを調べる。 その時に IPPROTO_FRAGMENT があったら DROP する。 他(AH, HOPOPTS, ROUTING, DSTOPTS)は追っかけるはず。 o ip/ip6ヘッダ情報等を調べたので 次protocol依存処理 TCP: o まず頭 8 byte以降はアクセスするなと。ackskew testはできないぞっと。 o という前提で tcp header pullup(失敗したら DROP) o stateful table検索用のkeyを作成して pf_find_state を使い、 PF_INならtree_ext_gwyを、それ以外(PF_OUT)ならtree_lan_extを検索。 o 見付からなかったらDROP。 o 見付かったら、その取り出した tcp header の seq no をpf_change_aで 修正。 o 軽く seq no チェックしてみたり。 o NATして(pf_change_icmp)反映(m_copyback)。 o tcp headerも反映。 o PASSでreturn UDP: o udp header pullup(失敗したら DROP) o stateful table検索用のkeyを作成して pf_find_state を使い、 PF_INならtree_ext_gwyを、それ以外(PF_OUT)ならtree_lan_extを検索。 o 見付からなかったらDROP。 o NATして(pf_change_icmp)反映(m_copyback)。 o PASSでreturn ICMP/ICMP6(まったくの別処理だけど同じような事をしてる): o (icmp data内にある)icmp header pullup(失敗したら DROP) o そのicmpを使って stateful table検索 PF_INならtree_ext_gwyを、それ以外(PF_OUT)ならtree_lan_extを検索。 o 見付からなかったらDROP。 o (必要なら)NATして(pf_change_icmp)反映(m_copyback)。 o PASSでreturn それ以外: o デバッグメッセージぐらいで、DROP ------------------------------ pf_purge_expire_status ------------------------------ ○ tree_ext_gwy treeをサーチし、expireしているものをremoveする ○ その際、tree_lan_extのデータも削除する ○ 処理 o tree_ext_gwy の first nodeをpf_tree_firstで取得 *:loop o nodeが最後になるまで pf_tree_nextでnodeをサーチする o tree nodeがexpireしてないなら、次のノードをサーチ o expireしてたら、tree_lan_extサーチ用のkeyを作成 keyでtree_lan_extをサーチして削除 o pf_find_stateを使って削除したはずの key が無いか確認 o STATE_TRANSLATEで NAT なキーか調べる そうだったらpf_put_sportでプールにデータを帰す o tree_ext_gwyからも消して o 次 loopに戻る ------------------------------ pf_tree_remove() ------------------------------ ○ tree構造のバランスを取りつつ keyを削除する ○ 処理 o pf_tree_node **n: 処理の対象となるツリー構造の先頭 pf_tree_node *p: 削除したノードの子供の親ノードになる部分 pf_tree_key *key: 削除対象 と共に呼ばれる o pf_tree_key_compareで n の右か左かもしくは自分が削除対象か調べる 1. 左側に削除ノードがあるらしい場合 2. 右側に削除ノードがあるらしい場合 3. 自分が削除ノードな場合 = 削除 XXX: 再帰呼び出しを、入れ子状態的に実行 ------------------------------ pf_tree_key_compare() ------------------------------ ○ pf_tree_key a,b について、 proto, addr[01], port[01] の比較 ○ 大小によって、-1,+1 もしく(proto, portの場合)は単純に差を返す ------------------------------ pf_find_state() ------------------------------ ○ pf_tree_search を使用して与えられたpf_tree_keyを持つpf_tree_nodeを捜す ○ そのpf_tree_nodeのstateを返す ------------------------------ pf_tree_search() ------------------------------ ○ pf_tree_node *nの中からpf_tree_key *keyと一致するものを返す ○ pf_tree_key_compareによって、返り値が正だったら左、 負だったら右のノードへの下っていく。 ------------------------------ pf_tree_first() ------------------------------ ○ pf_tree_nodeの最初のnodeを返す ○ 処理 o 親方向にどんどんのぼる o さらに、左方向へどんどん下る ------------------------------ pf_tree_next() ------------------------------ ○ アルゴリズム o 右にノードを持ってれば、右に下って、さらに下れるだけ左に下る o 親に登れたら登る。 (その親から見て)左の枝から親に登った場合は、単に親に登るだけ o 親に登れたら登る。 (その親から見て)右の枝から親に登った場合は、 左の枝から親に登るまで、延々と親に登る ○ つまり、順番は下記のように。 4 / \ / \ 2 8 / \ / \ 1 3 6 10 / \ / \ 5 7 9 11 ------------------------------ pf_put_sport ------------------------------ ○ pf_tcp_ports / pf_udp_ports からノードを取り出し、 プール pf_sport_pl に帰す ------------------------------ pf_normalize_ip pf_purge_expired_fragments ------------------------------ ○ フラグメント再構成処理系 ------------------------------ kern/subr_pool.c: _pool_put(pool_put) ------------------------------ ○ ロックして、プールにノードを追加。 プール管理関数系? XXX: subr_pool全体に渡るロック処理。SMPへの理解なども必要。深い。 ====================================================================== 構造体 変数編 ====================================================================== XXX:構造体の概念図的なものが欲しい ------------------------------ はじめに ------------------------------ ○ 下記ヘッダファイル、重要なり o sys/queue.h o altq/altq.h ============================== struct pf_port_node ============================== ○ int16なportデータを持つ ○ 双方向リンクリスト ○ sys/queue.h altq/altq.h ============================== pf_port_list pf_tcp_ports; pf_port_list pf_udp_ports; struct pool pf_sport_pl; ============================== ○ pf_*_ports: NAT変換の時に使う tcp/udp のポート管理用関数 使用中のポート番号についてはこのリストに繋がる pf_sport_pl: 未使用のポートをプールする ○ 構造 next --> next --> next port port port みたいな ○ リスト操作には LIST_FOREACH, LIST_REMOVE を使用。 ○ pool操作には、pool_get, pool_put を使用。 ============================== struct pf_binat ============================== ○ 1対1のNAT。 ○ 内側アドレス(saddr)とその変換先アドレス(raddr)が1対1で対応している。 なので内->外, 外->内の両方に適応、アドレス変換が適用されるルール。 ○ char ifname[IFNAMSIZ]; インターフェイス名 struct ifnet *ifp; インターフェイス管理構造体 TAILQ_ENTRY(pf_binat) entries; キュー構造に struct pf_addr saddr; ソースアドレス struct pf_addr daddr; ディスティネーションアドレス struct pf_addr dmask; ディスティネーションアドレスのマスク struct pf_addr raddr; リモートアドレス(ソースアドレスを変換するアドレス) u_int8_t af; アドレスファミリー u_int8_t proto; プロトコル u_int8_t dnot; "!"フラグ用 ○ 例 o 構成 192.168.0.10 192.168.0.20 ^ ^ | | +------+| |+------+ +------+ |host A|------|pf BOX|------|hostB | +------+ +------+| |+------+ | | v v 123.45.0.1/25 123.45.0.129 o アドレス変換の例 その1 ルール saddr = 192.168.0.10 daddr = 0.0.0.0 dmask = 0.0.0.0 raddr = 123.45.0.2 実際の通信は src = 192.168.0.10:1000 => src = 123.45.0.2:1000 dst = 61.1.2.3:80 => dst = 61.1.2.3:80 src = 61.1.2.3:80 <= src = 61.1.2.3:80 dst = 192.168.0.10:1000 <= dst = 123.45.0.2:1000 hostAは 192.168.0.10 <--> 123.45.0.2 として静的にIPアドレス変換さ れる。常に。 o アドレス変換の例 その2 ルール saddr = 192.168.0.10 daddr = 61.0.0.0 dmask = 255.0.0.0 raddr = 123.45.0.2 という事も可能。 61.0.0.0/8 に対してだけ 123.45.0.2 といアドレスが 192.168.0.10 に 変換される。61.0.0.0/8 以外との通信に関しては 123.45.0.2 自身(ア ドレス変換なし)との通信になる。 ============================== TAILQ_HEAD(pf_natqueue, pf_nat) pf_nats[2]; TAILQ_HEAD(pf_binatqueue, pf_binat) pf_binats[2]; TAILQ_HEAD(pf_rdrqueue, pf_rdr) pf_rdrs[2]; ============================== ○ それぞれのルールが線型リスト形式で保存されている ○ それぞれ [0]がactiveだったら [1]はinactiveというかんじ ○ キューのフラッシュ手順 o ロック o inactiveな方をまずフラッシュする(DIOCBEGINNATSとか) o active と inactive を入れ換える(DIOCCOMMITNATSとか) o ロック解除 o inactiveの方のエントリを削除(poolに解放)しておく ============================== struct pool pf_tree_pl, pf_rule_pl, pf_nat_pl, pf_sport_pl; struct pool pf_rdr_pl, pf_state_pl, pf_binat_pl; ============================== ○ pool(9) ============================== struct pf_nat ============================== ○ 多対1のNAT。 o 単に src = 192.168.0.0/255.255.0.0 => src = 123.45.0.2(指定したアドレス) dst = 61.0.0.0/255.0.0.0 => dst = そのまま などという一方行のアドレス変換をするだけ src = 61.0.0.1 dst = 123.45.0.2 という感じの応答パケットがあったらこれはこれで別途対応するルール を書かなければいけない。 ============================== struct pf_port_list ============================== ○ pf_port_node ○ sys/queue.h を使用したキュー構造 int16 portのみをデータとして持つ ============================== struct pf_tree_node ============================== ○ parent, left, right (それぞれ別のnodeを指す)を持つ tree 構造 ○ key, state, balance を持つ ====================================================================== ためになる資料 ====================================================================== ○ IP Filter http://coombs.anu.edu.au/ipfilter/ ○ STATEFUL INSPECTION http://www.enteract.com/~lspitz/fwtable.html ○ OpenBSD Packet Filter http://www.benzedrine.cx/pf.html ○ IPFilter and PF resources http://www.obfuscation.org/ipf/ ○ Real Stateful TCP Packet Filtering in Ip-filter http://www.usenix.org/events/sec01/invitedtalks/rooij.pdf