Packets are too Fast

slankdevの報告

NFVガイドラインのようなものを少しまとめた

以下に置いた(画像部分をhatebuに変換するのが大変だったのでリンクにした) もしリンクがきれていたら, Twitter: @slankdevまで連絡をください. すぐ直します。

github.com

「次世代の通信インフラストラクチャーに対応する効率的な仮想ネットワーク機能(VNF)アーキテクチャー」を読んだのでようやく

  • NFV Whitepaper by Intel HPE
  • Hiroki SHIROKURA @slankdev
  • 2018.07.30

本レポートは以下のLINKのWhitepaperをよんで筆者なりにまとめたものである LINK


はじめに

  • 世界のIPトラフィックは今後の5年間で3倍になると予測
  • 独自規格HWは高額であり, 拡張が容易ではない
  • ITインフラ仮想化
    • 利点: 複数APPでコンピュータ資源を共有しHWコストをへらすことができる
  • NFV==サービスプロバイダが真の仮想化インフラに移行するための技術
  • AT&T, China TelecomなどによりNFVは勢いづいている

本Whitepaperでは以下を行う

  • サービスプロバイダが直面している主な問題明示
  • 相対コスト問題に対処するVNFフレームの拡張構造について説明

NWとDCの結合

China Telecom調べ

  • バックボーンNW帯域消費率が過去3年で42%増加
  • 大都市圏のNWトラフィックはバックボーントラフィックの1.5-2倍の割合で増加
  • 加入者の需要も急激に増加: 4K/8KTV,仮想通貨,拡張現実,online strage,etc…
  • モビリティ: データや接続のsrc dstはリアルタイムに変化しより複雑に
  • IoTによりインターネット上のデータ量, デバイス数が増加

クラウド/仮想化の意義: IT機能の急拡大に効率良く対応する

現状

  • クラウドとネットワークは柔軟に連携できていない
  • ネットワークの仮想化は遅れている
  • ネットワークの仕事量は, アプリケーションの仕事量とは全く異なる
  • 帯域増加によりCAPEX/OPEXが大幅に増加

以下の二つは同時に成長していくべきである

  • クラウドテクノロジーが高度に洗練され動的になる
  • SDNを用いたネットワーク構成の自動化

近年使われるサービス

  • QoS/トラフィック・シェーピング
  • 仮想プライベート・ネットワーク (VPN),
  • ファイアウォール,
  • ネットワーク・アドレス変換
  • カプセル化/カ プセル化解除,
  • ディープ・パケット・インスペクション(DPI),
  • リアルタイム・ モニタリング

効率的なVNFの実装が不可欠

VNFとは

  • 標準サーバ上で動作する仮装ネットワーク機能 (Router, BRAS, etc…)
  • 独自規格で値段も高額なHWのニーズを減らす
  • NFV/SDN/オーケストレーションを利用すれば運用上の複雑性は軽減する
  • NFV導入の課題は全体的なCAPEX/OPEXを減らすこと
  • VNFの必要性は広く認識されている

VNFの要件

現状の問題点

  • 仮想化環境に向け十分に最適化されていない
  • 専用HWと比べパフォーマンスが低い
  • パフォーマンス不足を克服するために必要なインフラコストの高さは変わらない
  • NFVモデルはまだ新しく, コスト削減を達成するための最適化要素がたくさんある
  • VNFの開発方法やアーキテクチャに関する明確なガイドライン不足は認識されていない

CUPS (Control and User Plane Separation)

  • 個別の拡張が可能になる
  • 帯域幅要求の急増によりDplaneのリソース効果と拡張性を高める必要がある

汎用サーバ上で動く最適化された再構成可能なDplane

  • 汎用PCの使用はNFVの核となるテーマの一つ
  • コントロールプレーンの拡張は簡単
  • データプレーンの拡張は困難
    • 再構成可能なパケットパイプライン
    • 複雑なパケット処理に非常に高いスループットで対応できるようなシステム設計
  • Dplaneは暗号化/復号処理などの高演算負荷のタスクもwirerateで実行する必要ある

VNFの隔離を最大限に高める 

  • VNFのチェイニングが複雑化すると複数PCが連携する必要もある
  • SFCが拡大するとNFVインフラの管理,拡張,運用はさらに難しくなる

VNFの拡大/隔離の必要性

  • VMやコンテナでは1つの機能を複数のCPUで処理できる
  • チェイニングが最適でなく, VNFが無秩序に拡大するとインフラの不可増大になる

VNFの拡大と隔離の必要性
以下のような構成で, 「VNFが拡大」してしまうと, インフラのファブリックの 合計帯域が増加する.

f:id:slankdev:20170903200407p:plain


CTNet2025: 2025までのChinaTelecomネットワークの変革

China Telecomは次の技術が新しいNWアーキテクチャに重要だと判断した.

これらに不可欠なVNFはvBRAS, vPE, vEPC, vIMS, vOLTである.


拡張性と効率性の高いvBRASアーキテクチャ

China Telecom, HPE, Intelの設計目標

  • Architecture
    • CPUSを第一段階
    • クラウドネイティブ設計原則をサポート
  • Efficient&performance
    • 電力消費量は4KW/Tbps
    • 各ノードは100Gbps以上
  • Relative cost
    • SmartNICを用いた50%おコスト削減
    • 将来の世代に対応できる拡張性を実現する

BRASからvBRASへの進化

f:id:slankdev:20170903200422p:plain

前述の©を実現する,効率的なvBRAS設計(図4)

f:id:slankdev:20170903200433p:plain

vBRAS-c Node

  • コンテナ実装に適している
  • 必要に応じて新規ユーザを追加
  • vBRAS-cインスタンスがロケーションに依存しなくなる
  • vBRAS-cを集中管理したり分散管理したりどちらも可能

vBRAS-d Node

  • パケット処理専用のノード
  • 一部のコアにパケット転送のみを割り当て
  • 残りのコアに複雑なパケット処理を行わせる
  • Intel Arria10 FPGA based SmartNICが使われる
  • FPGAは以下の複雑なパケット処理機能を実行する
  • FPGAには利点がある
    • 40%をQoSとシェービングに費やした場合
    • 60%を別のパケット処理に適用できる
    • デプロイ環境に合わせて機能を変えることができる

SDNコントローラ

  • vBRAS-dのみを制御する
  • vBRAS-cがSDNコントローラになることもできる

Intel Arria10 FPGA based SmartNIC

  • 複数の並列パケット処理パイプライン(40/100GbE以上)に対応するように構成可能
  • レイテンシはASIC/ASSPで発生するレイテンシと同様
  • 機能アップデートに対しハードウェアアップデートが必要ない
  • 製品のアップグレードサイクルを速めることができる

f:id:slankdev:20170903200449p:plain

ベンダに依存しないオープンエコシステム

  • CUPSがこのアーキテクチャの肝
  • 複数ベンダが寄与する駅システムで促進される
  • 本WhitePapaerではintel HPEだが,他にも様々なベンダで構成可能
  • 多数ベンダでAPI定義の協調的議論

vBRASのパフォーマンス結果

  • 最初にやることはvBRAS-d, vBRAS-cの分離
  • SWアーキテクチャは図3に示す設計に基づいている.
  • 表1に使用したHW情報を載せる

f:id:slankdev:20170903200510p:plain

コントローラーのパフォーマンス 図5に示すセットアップでは, vBRAS-c のパフォーマンスを個別に測定 するために, ソフトウェアベースのvBRAS-dを使用しています.

コントローラの性能

  • サーバの分離
    • Spirent PPPoEテストジェネレータがPPPoEセッションを10000回実行する
    • vBRAS-dを介してvBRAS-cサーバにリダイレクトされる
    • OpenFlowのpacket-inなどのトンネリングメカニズムを必要としない
    • セッションコントロールプロトコルのネイティブ機能を保持しながら
    • cplane, dplaneの分離が可能
  • コントロールサーバの拡張
    • 2socket 14coreのういtの1coreの11%, RAMは1.5G
    • vBRAS-cが1万回セッションを処理可能
    • RAMを284Gにすると250万回以上のセッションを処理可能

f:id:slankdev:20170903200533p:plain

vBRASデータプレーンの性能

スペック

  • Arria10 FPGA based SmartNIC 3枚
  • HPE DL380サーバ Xeon E-2660v3 2way RAM=256GB
  • シェーピング・パラメーターの値: 8Mbps, 8.5Mbps, 10Mbps
  • 目標はQoS120Gbps linerate
  • 12000回の加入者セッションと2つのトラフィッククラス(優先度:高/低)
  • 1加入者あたり5Mpbpsの高/低優先度トラフィックが生成される (合計60Gbpsずつ)
  • サイズに関係なくシェーピングされたトラフィックが 8Mbps×12,000=96Gbpsで送信されると同時に, シェーピングされないトラフィックが120Gbps(損失なし)で返信される.
  • 図7は, 3 つのインテルArria10 FPGA SmartNICカードを搭載した dplaneの予備パフォーマンスを示す.
  • トラフィックをシェーピングすると, 高優先度トラフィック帯域幅が 完全に割り当てられ, 低優先度トラフィックにはシェーピングが適用される.

データプレーンの転送性能結果

  • 4,000 回のセッションの合計スループットが全セッションの最大値 (それぞ れ 96 Gbps, 102 Gbps, 120 Gbps)内に収まることが分かった.

SmartNICの利点

  • SmartNICは世代交代でより協力な物をに取り替え可能
  • SmartNIC上で動く回路はそのまま新しいSmartNIC上で動作可能
  • 2018年にはDL380が240Gbpsまで倍増すると見込む

f:id:slankdev:20170903200545p:plain f:id:slankdev:20170903200552p:plain

これらの結果は, 分割アーキテクチャーによってコントローラーの拡張性を 向上できることを示している.

  • FPGAベースのSmartNICは,QoSの高速化手段としても有望視されている.
  • 今後は,ソフトウェア・ベースのvBRAS-dをFPGAと統合されて, QoSで 完全vBRASのトラフィック・スループットを実現する取り組みが中心になるだろう

効果的なvBRASで相対コストを削減

ここでは, 以下のようなシステム上のメリットについて説明する

  • 価格, パフォーマンス, 消費電力, etc…

モデルの目標とアプローチ

  • 標準NICとSmartNICを比較します.
  • コストに影響を与えるのは消費電力, サーバーコストである
  • 同じ合計帯域を達成する同等のシステムを用意する
  • 全体的なコスト, 消費電力を比較する

方法

  • vBRASサーバーのキャパシティーを変え(50G~200G), サーバー・パフォーマンスは一定を保つよう試み, SmartNICを使用する場合と使用しない場合でコストをモデル化する.
  • 分析の結果,パフォーマンスの向上によりCPUサイクルが節約され, コア数が減ることが分かりました.
  • 図8,9,10,11は,標準NICとSmartNICで以下を比較したもの
    • 消費電力, サーバーコスト, システムコスト, 総コスト
  • 結果は,50Gbpsの基準帯域幅に対する割合で示されています.

結果の要約:

  • SmartNICソリューションは,標準NICサーバーと比べて総消費電力が最大50%低下
  • 絶対値では,このソリューションは4KW/Tbpsの設計目標内に十分収る
  • Intel Arria10FPGA based SmartNICと市販のサーバーCPUを組み合わせると, データプレーンのパフォーマンスが最適化され, 高い柔軟性を維持しながらコストを削減できます.
  • SmartNICを使用すると,各サーバーのパフォーマンス向上が3倍を超えます. スループットを向上させるには,汎用サーバのみでは無理.
  • SmartNICを使用することで,パフォーマンスが向上し, 消費電力とコストの増加分を極力抑えつつ,高帯域がサポートされます.

f:id:slankdev:20170903200608p:plain f:id:slankdev:20170903200615p:plain f:id:slankdev:20170903200623p:plain f:id:slankdev:20170903200630p:plain

「NFVを利用することで,デバイスの機能は今後,高額な専用ハードウェアに
  頼る必要はなくなります.安価なx86ベースのシステムで,機器投資コストを
  削減し,新しいアプリケーションを短期間で開発・導入できるようになります」

    -—ChinaTelecom

ほんとうか?…(slankdevの意見)


他のCUPS実装との比較

欠点がたくさんある

  • 図12に,vBRASソリューションの代替となる別のCUPS実装を示す
  • vBRAS-cは市販の標準サーバーを使用する.
  • データプレーンは独自規格のシャーシを使用します.
  • 既存の製品やビジネスへの投資を維持するために
  • このソリューションはオープンでもプログラム可能でもありません.
  • サードパーティーのベンダーはデータプレーンを利用できない
  • プロバイダーは標準ハードウェアを使用して帯域幅を拡張することができない
  • さらに,独自規格のシャーシ/ラインカードを使用している
  • ベンダーが固定されるリスクもある

一方で本Whitepaperで提案するアーキテクチャは…

  • 純粋なサーバーベースのプラットフォーム.
  • vBRAS-cとvBRAS-dを個別に拡張できます.
  • ベンダーに依存しないc/d-Nodeの両方が標準ハードウェアをベースにしている
  • 図12に示すような制約がありません.
  • 演算負荷の高いパケット処理用にデータプレーンのスループットを さらに高めるため,新しいSmartNICを採用しています.

f:id:slankdev:20170903200647p:plain


まとめ

  • サービスの集中に伴い, ネットワーク機器はより複雑になった
  • 新規ビジネスの導入サイクルは以下によりかつてないほど延長する
    • 機器が高度に特殊化
    • HW/SWの結び付きが強まり
    • 拡張が難しくなり
    • アップグレードサイクルが長くなり,
  • 仮想NWの再設計によって,サービス導入コストは削減され,効率性は向上します.
  • ここで紹介した 新しいアーキテクチャーは, SDN/NFVを融合したもの
  • クラウドとネットワークが結合され, 未来のDCがネットワークの核となる
  • 完全に仮想化されたインフラストラクチャーに移行すると以下が可能に
    • 発展する市場の需要に容易に対応できる
    • 設備投資と運用コス トを減らして全体的なコストを抑える

DPDKアプリをビルドする自前Makefile

DPDKどドキュメントによると、DPDKを使用したアプリケーションでは用意された雛形のMakefileを 一部変更することでビルド環境を提供しているが、自分で何かをつくりたいときにそんなことを やってられない。

ここでは自分で作成したMakefileでDPDKのアプリケーションをビルドする方法をまとめていく。

答えを見つけるまでのアプローチ方法

以下のアプローチで進めていけば一応絶対わかる。

  • オリジナルのビルド手順をすべて確認する
  • makeにnオプションを与えて実行されるコマンドを確認する
  • そのコマンドから少しずついらないオプションを減らしていく
  • どんなDPDKアプリでもビルドできる状態で限界まで減らす
  • 完成

模範解答的Makefile?

一応以上のことからこんなMakefileが一般的だとわかった。

DPDKPATH=$(RTE_SDK)/$(RTE_TARGET)
CXXFLAGS = -I$(DPDKPATH)/include -include $(DPDKPATH)/include/rte_config.h
LIBS = \
    -m64 -pthread -march=native \
    -Wl,--no-as-needed \
    -Wl,-export-dynamic \
    -L$(DPDKPATH)/lib \
    -lpthread -ldl -lrt -lm -lpcap \
    -Wl,--whole-archive \
    -Wl,--start-group \
    -ldpdk \
    -Wl,--end-group \
    -Wl,--no-whole-archive

all:
    g++ $(CXXFLAGS) main.cc $(LIBS)

実行用のサンプルアプリケーション

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>

#include <rte_config.h>
#include <rte_version.h>
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_ether.h>
#include <rte_cycles.h>
#include <rte_lcore.h>
#include <rte_mbuf.h>
#include <rte_hexdump.h>

#define RX_RING_SIZE 128
#define TX_RING_SIZE 512
#define NUM_MBUFS       8191
#define MBUF_CACHE_SIZE 250
#define BURST_SIZE      32

static __attribute((noreturn)) void lcore_main(void)
{
    class test {
        public:
        int a;
        int b;
        void print()
        {
            printf("HELLO DPDK with C++!!!\n");
        }
    };
    test a;
    a.print();
    const uint8_t num_ports = rte_eth_dev_count();

    uint8_t port;
    for (port = 0; port < num_ports; port++) {
        if (rte_eth_dev_socket_id(port) > 0 && rte_eth_dev_socket_id(port) != (int)rte_socket_id())
            printf("WARNING: port %u is on remote NUMA node to "
                    "polling thread. \n\tPerformance will "
                    "not be optimal. \n ", port);
    }
    printf("\nCore %u forwarding packets. [Ctrl+C to quit]\n", rte_lcore_id());

    for (;;) {
        for (port=0; port<num_ports; port++) {
            struct rte_mbuf* bufs[BURST_SIZE];
            const uint16_t num_rx = rte_eth_rx_burst(port, 0, bufs, BURST_SIZE);

            if (unlikely(num_rx == 0))
                continue;

            uint16_t i;
            for (i=0; i<num_rx; i++) {
                rte_pktmbuf_dump(stdout, bufs[i], sizeof(struct rte_mbuf));
                /* rte_hexdump(stdout, "recv packet", */
                /*         rte_pktmbuf_mtod(bufs[i], void*) , */
                /*         rte_pktmbuf_data_len(bufs[i])     ); */
                uint8_t* head = rte_pktmbuf_mtod(bufs[i], uint8_t*);
                memset(head , 0xee, 6);
            }

            const uint16_t num_tx = 0;
            /* const uint16_t num_tx = rte_eth_tx_burst(port, 0, bufs, num_rx); */
            /* printf("Reflect %d packet !! \n", num_rx); */
            if (unlikely(num_tx < num_rx)) {
                uint16_t buf;
                for (buf=num_tx; buf<num_rx; buf++)
                    rte_pktmbuf_free(bufs[buf]);
            }
            printf("\n\n");
        }
    }
}

static int port_init(uint8_t port, struct rte_mempool* mbuf_pool)
{
    struct rte_eth_rxmode rxmode;
    memset(&rxmode, 0, sizeof rxmode);
    rxmode.max_rx_pkt_len = ETHER_MAX_LEN;

    struct rte_eth_conf port_conf_default;
    memset(&port_conf_default, 0, sizeof(port_conf_default));
    port_conf_default.rxmode = rxmode;

    struct rte_eth_conf port_conf = port_conf_default;
    int ret;
    uint16_t q;

    if (port >= rte_eth_dev_count())
        return -1;

    const uint16_t rx_rings = 1;
    const uint16_t tx_rings = 1;
    ret = rte_eth_dev_configure(port, rx_rings, tx_rings, &port_conf);
    if (ret != 0)
        return ret;

    for (q=0; q < rx_rings; q++) {
        ret = rte_eth_rx_queue_setup(port, q, RX_RING_SIZE,
                rte_eth_dev_socket_id(port), NULL, mbuf_pool);
        if (ret < 0)
            return ret;
    }

    for (q=0; q < tx_rings; q++) {
        ret = rte_eth_tx_queue_setup(port, q, TX_RING_SIZE,
                rte_eth_dev_socket_id(port), NULL);
        if (ret < 0)
            return ret;
    }

    ret = rte_eth_dev_start(port);
    if (ret < 0)
        return ret;

    struct ether_addr addr;
    rte_eth_macaddr_get(port, &addr);
    printf("Port %u MAC: %02" PRIx8 " %02" PRIx8 " %02" PRIx8
            " %02" PRIx8 " %02" PRIx8 " %02" PRIx8 "\n",
            (unsigned)port,
            addr.addr_bytes[0], addr.addr_bytes[1],
            addr.addr_bytes[2], addr.addr_bytes[2],
            addr.addr_bytes[3], addr.addr_bytes[4]);
    rte_eth_promiscuous_enable(port);
    return 0;
}

int main(int argc, char** argv)
{
    int ret = rte_eal_init(argc, argv);
    if (ret < 0)
        rte_exit(EXIT_FAILURE, "rte_eal_init() failed\n");

    uint32_t num_ports = rte_eth_dev_count();
    printf("%d ports found  \n", num_ports);
    if (num_ports < 1)
        rte_exit(EXIT_FAILURE, "rte_eth_dev_count()");

    struct rte_mempool* mbuf_pool = rte_pktmbuf_pool_create(
            "MBUF_POOL_SLANK", NUM_MBUFS * num_ports,
            MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());

    if (mbuf_pool == NULL)
        rte_exit(EXIT_FAILURE, "rtembuf_pool_create()");

    uint8_t portid;
    for (portid=0; portid < num_ports; portid++) {
        if (port_init(portid, mbuf_pool) != 0)
            rte_exit(EXIT_FAILURE, "Cannot init port %"PRIu8 "\n", portid);
    }

    if (rte_lcore_count() > 1)
        printf("WARNING: Too many lcores enabled. Only 1 used. \n");

    lcore_main();
}

BSDのNW処理部分の設計実装

自分用雑記. まだ書きかけです.

FreeBSDの黄色い本を読んで自分なりに要約, またSTCPの設計実装のためのメモ ここで出てくる典型的例とはBSDカーネル内での実装のことである.

ソースは以下から入手した - ftp://ftp.freebsd.org/pub/FreeBSD/releases/

12 ネットワーク通信

12.1 内部構造

ネットワークサブシステムは以下で構成される.

  • プロセス間データトランスポート
  • NW間のアドレス管理とメッセージルーティング
  • 電装メディアサポート

12.1.2 通信プロトコル

プロトコルモジュールはプロトコルスイッチ構造体によって表現される. プロトコルスイッチ構造体は以下を提供する.

  • 外部からのエントリポイント
  • いくつかの属性情報

プロトコルスイッチ構造体の典型的例を以下に示す.

struct protosw {
    short   pr_type;        /* socket type used for */
    struct  domain *pr_domain;  /* domain protocol a member of */
    short   pr_protocol;        /* protocol number */
    short   pr_flags;       /* see below */
/* protocol-protocol hooks */
    pr_input_t *pr_input;       /* input to protocol (from below) */
    pr_output_t *pr_output;     /* output to protocol (from above) */
    pr_ctlinput_t *pr_ctlinput; /* control input (from below) */
    pr_ctloutput_t *pr_ctloutput;   /* control output (from above) */
/* utility hooks */
    pr_init_t *pr_init;
    pr_destroy_t *pr_destroy;
    pr_fasttimo_t *pr_fasttimo; /* fast timeout (200ms) */
    pr_slowtimo_t *pr_slowtimo; /* slow timeout (500ms) */
    pr_drain_t *pr_drain;       /* flush any excess space possible */

    struct  pr_usrreqs *pr_usrreqs; /* user-protocol hook */
};

12.1.3 ネットワークインタフェース

インタフェースはOSの起動時にioctlを使用して動作確認を行う. それぞれ以下のような管理構造体がある.

  • ifnet構造体 -> インタフェース
  • ifaddr構造体 -> インタフェースに付加されるアドレス

これらはそれぞれ線形リストとして管理される可変長データリストである. これも典型的な例を示す.

struct ifnet {
    void    *if_softc;      /* pointer to driver state */
    void    *if_l2com;      /* pointer to protocol bits */
    struct vnet *if_vnet;       /* pointer to network stack instance */
    TAILQ_ENTRY(ifnet) if_link;     /* all struct ifnets are chained */
    char    if_xname[IFNAMSIZ]; /* external name (name + unit) */
    const char *if_dname;       /* driver name */
    int if_dunit;       /* unit or IF_DUNIT_NONE */
    u_int   if_refcount;        /* reference count */
    struct  ifaddrhead if_addrhead; /* linked list of addresses per if */
        /*
         * if_addrhead is the list of all addresses associated to
         * an interface.
         * Some code in the kernel assumes that first element
         * of the list has type AF_LINK, and contains sockaddr_dl
         * addresses which store the link-level address and the name
         * of the interface.
         * However, access to the AF_LINK address through this
         * field is deprecated. Use if_addr or ifaddr_byindex() instead.
         */
    int if_pcount;      /* number of promiscuous listeners */
    struct  carp_if *if_carp;   /* carp interface structure */
    struct  bpf_if *if_bpf;     /* packet filter structure */
    u_short if_index;       /* numeric abbreviation for this if  */
    short   if_index_reserved;  /* spare space to grow if_index */
    struct  ifvlantrunk *if_vlantrunk; /* pointer to 802.1q data */
    int if_flags;       /* up/down, broadcast, etc. */
    int if_capabilities;    /* interface features & capabilities */
    int if_capenable;       /* enabled features & capabilities */
    void    *if_linkmib;        /* link-type-specific MIB data */
    size_t  if_linkmiblen;      /* length of above data */
    struct  if_data if_data;
    struct  ifmultihead if_multiaddrs; /* multicast addresses configured */
    int if_amcount;     /* number of all-multicast requests */

/* procedure handles */
    int (*if_output)        /* output routine (enqueue) */
        (struct ifnet *, struct mbuf *, const struct sockaddr *,
             struct route *);
    void    (*if_input)     /* input routine (from h/w driver) */
        (struct ifnet *, struct mbuf *);
    void    (*if_start)     /* initiate output routine */
        (struct ifnet *);
    int (*if_ioctl)     /* ioctl routine */
        (struct ifnet *, u_long, caddr_t);
    void    (*if_init)      /* Init routine */
        (void *);
    int (*if_resolvemulti)  /* validate/resolve multicast */
        (struct ifnet *, struct sockaddr **, struct sockaddr *);
    void    (*if_qflush)        /* flush any queues */
        (struct ifnet *);
    int (*if_transmit)      /* initiate output routine */
        (struct ifnet *, struct mbuf *);
    void    (*if_reassign)      /* reassign to vnet routine */
        (struct ifnet *, struct vnet *, char *);
    struct  vnet *if_home_vnet; /* where this ifnet originates from */
    struct  ifaddr  *if_addr;   /* pointer to link-level address */
    void    *if_llsoftc;        /* link layer softc */
    int if_drv_flags;       /* driver-managed status flags */
    struct  ifaltq if_snd;      /* output queue (includes altq) */
    const u_int8_t *if_broadcastaddr; /* linklevel broadcast bytestring */

    void    *if_bridge;     /* bridge glue */

    struct  label *if_label;    /* interface MAC label */

    /* these are only used by IPv6 */
    void    *if_unused[2];
    void    *if_afdata[AF_MAX];
    int if_afdata_initialized;
    struct  rwlock if_afdata_lock;
    struct  task if_linktask;   /* task for link change events */
    struct  rwlock if_addr_lock;    /* lock to protect address lists */

    LIST_ENTRY(ifnet) if_clones;    /* interfaces of a cloner */
    TAILQ_HEAD(, ifg_list) if_groups; /* linked list of groups per if */
                    /* protected by if_addr_lock */
    void    *if_pf_kif;
    void    *if_lagg;       /* lagg glue */
    char    *if_description;    /* interface description */
    u_int   if_fib;         /* interface FIB */
    u_char  if_alloctype;       /* if_type at time of allocation */

    u_int   if_hw_tsomax;       /* tso burst length limit, the minimum
                     * is (IP_MAXPACKET / 8).
                     * XXXAO: Have to find a better place
                     * for it eventually. */

    /*
     * Spare fields are added so that we can modify sensitive data
     * structures without changing the kernel binary interface, and must
     * be used with care where binary compatibility is required.
     */
    char    if_cspare[3];
    int if_ispare[4];
    void    *if_pspare[8];      /* 1 netmap, 7 TDB */
};



struct ifaddr {
    struct  sockaddr *ifa_addr; /* address of interface */
    struct  sockaddr *ifa_dstaddr;  /* other end of p-to-p link */
#define ifa_broadaddr   ifa_dstaddr /* broadcast address interface */
    struct  sockaddr *ifa_netmask;  /* used to determine subnet */
    struct  if_data if_data;    /* not all members are meaningful */
    struct  ifnet *ifa_ifp;     /* back-pointer to interface */
    struct  carp_softc *ifa_carp;   /* pointer to CARP data */
    TAILQ_ENTRY(ifaddr) ifa_link;   /* queue macro glue */
    void    (*ifa_rtrequest)    /* check or clean routes (+ or -)'d */
        (int, struct rtentry *, struct rt_addrinfo *);
    u_short ifa_flags;      /* mostly rt_flags for cloning */
    u_int   ifa_refcnt;     /* references to this structure */
    int ifa_metric;     /* cost of going out this interface */
    int (*ifa_claim_addr)       /* check if an addr goes to this if */
        (struct ifaddr *, struct sockaddr *);
    struct mtx ifa_mtx;
};

またソケットを確保する場合, 各ソケットはsocket構造体とsockaddr構造体を確保して システム内部で一意性を保つ. 典型的実装を示す.

struct socket {
    int so_count;       /* (b) reference count */
    short   so_type;        /* (a) generic type, see socket.h */
    short   so_options;     /* from socket call, see socket.h */
    short   so_linger;      /* time to linger while closing */
    short   so_state;       /* (b) internal state flags SS_* */
    int so_qstate;      /* (e) internal state flags SQ_* */
    void    *so_pcb;        /* protocol control block */
    struct  vnet *so_vnet;      /* network stack instance */
    struct  protosw *so_proto;  /* (a) protocol handle */
/*
 * Variables for connection queuing.
 * Socket where accepts occur is so_head in all subsidiary sockets.
 * If so_head is 0, socket is not related to an accept.
 * For head socket so_incomp queues partially completed connections,
 * while so_comp is a queue of connections ready to be accepted.
 * If a connection is aborted and it has so_head set, then
 * it has to be pulled out of either so_incomp or so_comp.
 * We allow connections to queue up based on current queue lengths
 * and limit on number of queued connections for this socket.
 */
    struct  socket *so_head;    /* (e) back pointer to listen socket */
    TAILQ_HEAD(, socket) so_incomp; /* (e) queue of partial unaccepted connections */
    TAILQ_HEAD(, socket) so_comp;   /* (e) queue of complete unaccepted connections */
    TAILQ_ENTRY(socket) so_list;    /* (e) list of unaccepted connections */
    u_short so_qlen;        /* (e) number of unaccepted connections */
    u_short so_incqlen;     /* (e) number of unaccepted incomplete
                       connections */
    u_short so_qlimit;      /* (e) max number queued connections */
    short   so_timeo;       /* (g) connection timeout */
    u_short so_error;       /* (f) error affecting connection */
    struct  sigio *so_sigio;    /* [sg] information for async I/O or
                       out of band data (SIGURG) */
    u_long  so_oobmark;     /* (c) chars to oob mark */
    TAILQ_HEAD(, aiocblist) so_aiojobq; /* AIO ops waiting on socket */

    struct sockbuf so_rcv, so_snd;


    struct  ucred *so_cred;     /* (a) user credentials */
    struct  label *so_label;    /* (b) MAC label for socket */
    struct  label *so_peerlabel;    /* (b) cached MAC label for peer */
    /* NB: generation count must not be first. */
    so_gen_t so_gencnt;     /* (h) generation count */
    void    *so_emuldata;       /* (b) private data for emulators */
    struct so_accf {
        struct  accept_filter *so_accept_filter;
        void    *so_accept_filter_arg;  /* saved filter args */
        char    *so_accept_filter_str;  /* saved user args */
    } *so_accf;
    /*
     * so_fibnum, so_user_cookie and friends can be used to attach
     * some user-specified metadata to a socket, which then can be
     * used by the kernel for various actions.
     * so_user_cookie is used by ipfw/dummynet.
     */
    int so_fibnum;      /* routing domain for this socket */
    uint32_t so_user_cookie;
};

struct sockaddr {
    unsigned char   sa_len;     /* total length */
    sa_family_t sa_family;  /* address family */
    char        sa_data[14];    /* actually longer; address value */
};

12.2 ソケットとプロトコル間のインタフェース

ここはまだstcpがsocketの実装段階でないためまだ読んでいない

12.3 プロトコル間インタフェース

すべてのプロトコル間での標準インタフェース形式を採用していれば, とても便利そうだけど, BSDではそのような設計は行なわれていない. それはプロトコルとの関わりが様々であるから. アドレス変換などいろいろ含めて一般化すると複雑になりすぎるからっぽい.

この辺の問題(問題ではないか. . )はstcpで将来的に一般化するのもアプローチとしては おもしろいかもしれない.

12.3.1 pr_output

もっとも一般的なパケットの出力関数は以下のようなプロトタイプで与えられる.

int (*pr_output)(
    struct protcol_ctrl* proto,
    struct mbuf*         msg,
    struct sockaddr*     addr,
    struct mbuf*         ctrl,
    struct thread*       td
    )

この例ではmsgに含まれるデータをプロトコル制御ブロックprotoで表現される. プロトコルモジュールに転送する.

stcpでは以下のような設計でいく予定である.

int (*pr_output)(
    struct protcol_ctrl* proto,
    struct mbuf*         msg,
    struct sockaddr*     addr,
    )

12.3.2 pr_input

もっtも一般的なパケットの入力関数は以下のようなプロトタイプで与えられる.

void (*pr_input)(
    struct mbuf* msg,
    int hlen
    )

この例では入力パケットをmsgとしてプロトコルモジュールに取り込まれる. またヘッダの長さを 引数で指定することで簡単に受け取り元プロトコルのヘッダを消去することも可能である.

12.3.3 pr_ctlinput

この機能はまだstcpに実装しないため, 読んでいない.

12.4 プロトコルとネットワークインタフェース間のインタフェース

もっとも低いレイヤのプロトコルはネットワークインタフェースとパケットを送受信するための インタフェースが必要である.

# slankdevメモ
ネットワークインターフェースとプロトコルの動作は前節のプロトコル間のインタフェースと違い
送信と受信が対にならないことに注意して欲しい. ネットワークインタフェースのパケット送信は
ユーザ側から能動的に行なわなくてはならないが, パケットの受信はある任意のタイミングで
パケットがかってにネットワークインタフェースに届くので, 処理の形は受動的である.
受信するコードの部分には受信ごとに, 適切なプロトコルのモジュールのパケットキューに転送を
するように実装をする必要がある.

12.4.1 パケットの送信

ネットワークインタフェースにパケット送信を要求するインタフェースの典型的設計は以下のようになる.

int (*if_output)(
    struct ifnet*    ifp,
    struct mbuf*     msg,
    struct sockaddr* dst,
    struct rtentry*  rt
    )

インタフェースifpを指定して受信したパケットをmsgに, 送信元情報をdstに格納する.

stcpでは以下のように設計していく予定

int (*if_output)(
    struct ifnet*    ifp,
    struct mbuf*     msg,
    struct sockaddr* dst
    )

12.4.2 パケットの受信

ここについては現段階ではほぼ必要ないと思われる.

12.8

12.8.2 アドレス解決プロトコル

BSDのARP実装についてメモ

どこを参照したか

FreeBSDソースの以下のファイル

ファイル名 概要
sys/net/if_arp.h arphdr構造体とかその辺がある
sys/net/if_ethersubr.c Ethernetに関する処理がある
sys/netinet/if_ether.c Ether,ARPに関する大事な実装

ARP処理部分の流れ

  1. init
  2. arpresolve
    1. arprequest

ARPでの代表的関数

関数名 概要
arprequest arpリクエストをブロードキャスト
arpresolve 名前解決を行う
arpintr パケットがarpかを確認前

Glossary

  • lla(Link Lyer Address)

よく使われる構造体

struct   arphdr {
    u_short ar_hrd;     /* format of hardware address */
#define ARPHRD_ETHER    1   /* ethernet hardware format */
#define ARPHRD_IEEE802  6   /* token-ring hardware format */
#define ARPHRD_ARCNET   7   /* arcnet hardware format */
#define ARPHRD_FRELAY   15  /* frame relay hardware format */
#define ARPHRD_IEEE1394 24  /* firewire hardware format */
#define ARPHRD_INFINIBAND 32    /* infiniband hardware format */
    u_short ar_pro;     /* format of protocol address */
    u_char  ar_hln;     /* length of hardware address */
    u_char  ar_pln;     /* length of protocol address */
    u_short ar_op;      /* one of: */
#define ARPOP_REQUEST   1   /* request to resolve address */
#define ARPOP_REPLY 2   /* response to previous request */
#define ARPOP_REVREQUEST 3  /* request protocol address given hardware */
#define ARPOP_REVREPLY  4   /* response giving protocol address */
#define ARPOP_INVREQUEST 8  /* request to identify peer */
#define ARPOP_INVREPLY  9   /* response identifying peer */
/*
 * The remaining fields are variable in size,
 * according to the sizes above.
 */
#ifdef COMMENT_ONLY
    u_char  ar_sha[];   /* sender hardware address */
    u_char  ar_spa[];   /* sender protocol address */
    u_char  ar_tha[];   /* target hardware address */
    u_char  ar_tpa[];   /* target protocol address */
#endif
};

ダブルロックの有用性について

2016.08.01 サイボウズラボユースにて. ずいぶんまえに学んだ内容だが、資料を生理していたらmemoがでてきたので。 ここに吐いておく(間違っていたらすみません)

はじめに通常のロックについて

以下の手順で行われる

lock()
if (inited == false)
    init()
    inited = true
unlock()

このときif文とlock,unlockのレイテンシの重さはだいたい以下のような関係

if() <<<< lock(), unlock()

初期化をする人は以下のように実行する

lock()
if (inited == false)
init()
inited = true
unlock()

それ以降の人はこのように実行する

lock()
unlock()

このときの初期化をする人、それ以降の人の割合は以下のような関係

初期化者 <<<<< 超えられない壁 <<<<<< それ以外

なので、いちいちlock, unlockを毎回させるのは嫌だ!!

これがダブルロックのモチベーション

ダブルロックとは

以下のような手順で行われる

if (inited == false)
    lock()
    if (inited == false)
        init()
        inited = true
    unlock()

初期化者は以下のように実行

if (inited == false)
lock()
if (inited == false)
init()
inited = true
unlock()

それ以外の人は以下のように実行

if (inited == false)

見てわかるようにlock,unlockを回避できた。だがしかし、2000年くらいに ただのダブルロックがすべてのアーキテクチャで安全ではないことが示された。

Intelのアーキとかだと、大体うまく動くようだが、Javaとかだと様々なアーキで 動かすことになるので、その問題が浮き彫りになったっぽい。 その出来事以降、Intelのプロセッサのマニュアルにもその趣旨に関する文が追加されている。 詳しくは以下の4/21日を参照する。

http://homepage1.nifty.com/herumi/diary/1204.html

C++11以降はスレッドセーフなロック手法を使っている。

C++のスレッドセーフロックの確認

以下のコードをコンパイルしてasmを見る

struct A {
   int a;
   A(int b) : a(b) {}
};
A& get_instance()
{
    static A a(2);
    return a;
}
$ c++ -std=c++11 -S -Ofast -o out.s main.cc
 .section    __TEXT,__text,regular,pure_instructions
    .macosx_version_min 10, 11
    .globl  __Z12get_instancev
    .align  4, 0x90
__Z12get_instancev:                     ## @_Z12get_instancev
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    movb    __ZGVZ12get_instancevE1a(%rip), %al
    testb   %al, %al
    jne LBB0_3
## BB#1:
    leaq    __ZGVZ12get_instancevE1a(%rip), %rdi
    callq   ___cxa_guard_acquire
    testl   %eax, %eax
    je  LBB0_3
## BB#2:
    movl    $2, __ZZ12get_instancevE1a(%rip)
    leaq    __ZGVZ12get_instancevE1a(%rip), %rdi
    callq   ___cxa_guard_release
LBB0_3:
    leaq    __ZZ12get_instancevE1a(%rip), %rax
    popq    %rbp
    retq
    .cfi_endproc

.zerofill __DATA,__bss,__ZZ12get_instancevE1a,4,2 ## @_ZZ12get_instancevE1a
.zerofill __DATA,__bss,__ZGVZ12get_instancevE1a,8,3 ## @_ZGVZ12get_instancevE1a

.subsections_via_symbols

これがスレッドセーフなロックのasm. __cxa_guard_acquire, __cxa_guard_releaseがそれである

こうするとスレッドセーフでない従来型なasmをはいてくれる

$ c++ -std=c++11 -S -Ofast -o out.s main.cc -fno-threadsafe-statics
 .section    __TEXT,__text,regular,pure_instructions
    .macosx_version_min 10, 11
    .globl  __Z12get_instancev
    .align  4, 0x90
__Z12get_instancev:                     ## @_Z12get_instancev
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    movb    __ZGVZ12get_instancevE1a(%rip), %al
    testb   %al, %al
    jne LBB0_2
## BB#1:
    movl    $2, __ZZ12get_instancevE1a(%rip)
    movb    $1, __ZGVZ12get_instancevE1a(%rip)
LBB0_2:
    leaq    __ZZ12get_instancevE1a(%rip), %rax
    popq    %rbp
    retq
    .cfi_endproc

.zerofill __DATA,__bss,__ZZ12get_instancevE1a,4,2 ## @_ZZ12get_instancevE1a
.zerofill __DATA,__bss,__ZGVZ12get_instancevE1a,1,0 ## @_ZGVZ12get_instancevE1a

.subsections_via_symbols

これが従来型。 以上でした。

BPFに関する情報収集

BPFについてサーベイしたので、それのまとめ. BPF for Dummiesのプロトタイプといえるよういしていけたら最高である。 現状は雑記状態なのでご了承いただきたい.

BPFとは,何をするためのものか

正直, 僕自身もよく分からない。 BPFはin kernelのパケットフィルタに最適な 仮想機械として設計実装されたが、現在はkernel内部のあらゆるデータフローを トレーシングやデバッグするために 用いられている。といった感じか。。。

構成

eBPFがattachできるポイントは現状で以下. ちなみにcBPFはsocketのみをサポートしている

  • socket (via setsockopt) (hook skb)
  • kprobe (via bcc) (func-call-infos)
  • uprobe (via bcc) (func-call-infos)
  • trace points (via bcc) (func-call-infos)
  • syscall (via seccmomp) (system-call-infos)
  • tc (no exploring)

みやすくするとこんな感じ(iovisorのスライド)

f:id:slankdev:20170508005046p:plain

引用: https://www.iovisor.org

cBPF,eBPF

eBPF(Extended BPF)の登場後、伝統的なBPFの区別のために伝統的なBPFをcBPF (Classic BPF)とよぶようになりました。現在では一般的にBPFと呼ぶとeBPFを さしていることがほとんどのようです。

違いを簡単にまとめた.

cBPF eBPF
Register 32bit 2 regs 64bit 10 regs
Memory stack stack
Instruction 4B ld/st to stack 1-8B ld/st to stack
Instruction 1-8B ld to packet 1-8B ld/st fo packet
Instruction Cond Jump forward Cond Jump forward/backward
Instruction ALU Instructions Same + Signed Shift + Bswap
Else . Helper Functions
Else . Helper Data Structure

引用: https://www.slideshare.net/PLUMgrid/ebpf-and-linux-networking

Linuxではより高度な作業を可能にするためSKBのフィールドをマクロで参照できるよう にしている。kernel docに概要がのせられているので、以下に示す。

  len                skb->len
  proto              skb->protocol
  type               skb->pkt_type
  poff               Payload start offset
  ifidx              skb->dev->ifindex
  nla                Netlink attribute of type X with offset A
  nlan               Nested Netlink attribute of type X with offset A
  mark               skb->mark
  queue              skb->queue_mapping
  hatype             skb->dev->type
  rxhash             skb->hash
  cpu                raw_smp_processor_id()
  vlan_tci           skb_vlan_tag_get(skb)
  vlan_avail         skb_vlan_tag_present(skb)
  vlan_tpid          skb->vlan_proto
  rand               prandom_u32()

引用: https://www.kernel.org/doc/Documentation/networking/filter.txt

CPUのIDまでとれるらしい。恐ろしい。。

命令セット

ここで書くと長くなってしまうので、別の記事に分割した。 そちらを参照すること。

slankdev.hatenablog.com

slankdev.hatenablog.com

活用例

  • bcc (Generic Tracing Tool)
  • seccomp
  • socket filter

その他キーワード