高速通信計算研究所

slankdevの報告

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 アドレス解決プロトコル