24
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

macvlan の使い方と挙動のメモ

ソースは CentOS 7.0.1406 (3.10.0-123.el7.x86_64) で private モードの流れを確認した。

macvlan とは

実 NIC の上に仮想的な NIC 作る機能。
作成した仮想 NIC には任意の MAC アドレスを設定できる。

keepalived の仮想 MAC アドレス機能の実現に使われている。
LXC でもコンテナ用の仮想 NIC として使われている。
Kernel Documentation で触れられていないようだがマイナーな存在なのだろうか?

Virtual switching technologies and Linux bridge の 6-10 ページあたりが参考になる。

4つのモードがあるが、private か bridge を使うことが多そう。

  • private
  • vepa
  • bridge
  • passthru

使い方

設定によっては macvlan でなく実 NIC の方の MAC アドレスを使って通信している場合がある。
keepalived で仮想 MAC アドレス機能を使う時は特に注意が必要。

  • 実 NIC や同じ実 NIC 上に作った他の macvlan が持つ MAC アドレスで ARP 応答しないよう、全ての NIC で arp_ignore = 1 にする
  • reverse path filter に引っかかって通信できないので rp_filter = 0 にする (実 NIC は 1 にして macvlan の 仮想 NIC だけ 0 でも良い)
macvlan作成・アドレス設定
## MAC アドレスを指定する場合は type macvlan の前に address 00:00:5e:00:01:01 のように書けば良い
# ip link add link eth0 name vmac0 type macvlan mode private
# ip link add link eth0 name vmac1 type macvlan mode private
# ip link set dev vmac0 up
# ip link set dev vmac1 up
# ip addr add dev eth0  10.1.1.1/24
# ip addr add dev vmac0 10.1.1.2/24
# ip addr add dev vmac1 10.1.1.3/24

# ip addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
    link/ether 00:0c:29:37:f3:52 brd ff:ff:ff:ff:ff:ff
    inet 10.1.1.1/24 scope global eth0
       valid_lft forever preferred_lft forever
# ip addr show dev vmac0
9: vmac0@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
    link/ether c6:96:62:87:e7:f0 brd ff:ff:ff:ff:ff:ff
    inet 10.1.1.2/24 scope global vmac0
       valid_lft forever preferred_lft forever
    inet6 fe80::c496:62ff:fe87:e7f0/64 scope link
       valid_lft forever preferred_lft forever
# ip addr show dev vmac1
10: vmac1@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
    link/ether da:e2:73:15:5f:ff brd ff:ff:ff:ff:ff:ff
    inet 10.1.1.3/24 scope global vmac1
       valid_lft forever preferred_lft forever
    inet6 fe80::d8e2:73ff:fe15:5fff/64 scope link
       valid_lft forever preferred_lft forever

# sysctl -w net.ipv4.conf.all.arp_ignore=1
# sysctl -w net.ipv4.conf.vmac0.rp_filter=0
# sysctl -w net.ipv4.conf.vmac1.rp_filter=0

## CentOS 7.2 で確認したところ、/lib/sysctl.d/50-default.conf で
## net.ipv4.conf.all.rp_filter が 1 に変更されていたので注意
# sysctl -w net.ipv4.conf.all.rp_filter=0
別ノードからpingを打った後にMACアドレスを確認
# ip neigh show | grep ^10 | sort
10.1.1.1 dev ens32 lladdr 00:0c:29:37:f3:52 REACHABLE
10.1.1.2 dev ens32 lladdr c6:96:62:87:e7:f0 REACHABLE
10.1.1.3 dev ens32 lladdr da:e2:73:15:5f:ff REACHABLE

例外的な注意点

vSphere ESXi 5.1 で試していたら e1000 ではうまく動かなかった。

ESXi の e1000 では unicast filtering が無い?
http://sourceforge.net/p/e1000/mailman/message/32952083/
手動で promiscous に変更しないと通信できなかった。

# lspci | grep Ethernet
02:00.0 Ethernet controller: Intel Corporation 82545EM Gigabit Ethernet Controller (Copper) (rev 01)

e1000 の代わりに vmxnet3 を利用すれば ESXi 5.1 でも promiscous に変更せずに通信できた。

(当然だが、unicast filtering の話題とは別に vSwitch 設定で Promiscous Mode や MAC Address Changes を Accept にする必要がある)

macvlan ドライバの挙動

module_init()からのおおまかな流れ
module_init()
  macvlan_init_module()
    register_netdevice_notifier(&macvlan_notifier_block)
    macvlan_link_register(&macvlan_link_ops)
drivers/net/macvlan.c
static struct rtnl_link_ops macvlan_link_ops = {
        .kind           = "macvlan",
        .setup          = macvlan_setup,
        .newlink        = macvlan_newlink,
        .dellink        = macvlan_dellink,
};

/* 省略 */

static struct notifier_block macvlan_notifier_block __read_mostly = {
        .notifier_call  = macvlan_device_event,
};

受信ハンドラの設定

おおまかな流れ
macvlan_newlink()
  macvlan_common_newlink()
    macvlan_port_create()
      netdev_rx_handler_register()
  • netdev_rx_handler_register は macvlan_handle_frame() を登録
  • ハンドラである macvlan_handle_frame() は netif_receive_skb() から呼ばれる

MAC アドレスを登録する

おおまかな流れ
macvlan_open()
  dev_uc_add()
    __hw_addr_add()
    __dev_set_rx_mode()
  • macvlan な NIC が up した時に macvlan_open() が呼ばれる?
  • 実 NIC が unicast filtering をサポートしている場合はその NIC のドライバ固有の処理を呼んで追加の MAC アドレスで受信できるようにする
    (IFF_UNICAST_FLT がオンかどうかで判定)
  • サポートしていない場合は実 NIC を promiscous mode にする
drivers/net/macvlan.c
static const struct net_device_ops macvlan_netdev_ops = {
        .ndo_init               = macvlan_init,
        .ndo_uninit             = macvlan_uninit,
        .ndo_open               = macvlan_open,
        .ndo_stop               = macvlan_stop,
        .ndo_start_xmit         = macvlan_start_xmit,
        .ndo_change_mtu         = macvlan_change_mtu,
        .ndo_fix_features       = macvlan_fix_features,
        .ndo_change_rx_flags    = macvlan_change_rx_flags,
        .ndo_set_mac_address    = macvlan_set_mac_address,
        .ndo_set_rx_mode        = macvlan_set_mac_lists,
        .ndo_get_stats64        = macvlan_dev_get_stats64,
        .ndo_validate_addr      = eth_validate_addr,
        .ndo_vlan_rx_add_vid    = macvlan_vlan_rx_add_vid,
        .ndo_vlan_rx_kill_vid   = macvlan_vlan_rx_kill_vid,
        .ndo_fdb_add            = macvlan_fdb_add,
        .ndo_fdb_del            = macvlan_fdb_del,
        .ndo_fdb_dump           = ndo_dflt_fdb_dump,
};
net/core/dev_addr_lists.c
/**
 *      dev_uc_add - Add a secondary unicast address
 *      @dev: device
 *      @addr: address to add
 *
 *      Add a secondary unicast address to the device or increase
 *      the reference count if it already exists.
 */
int dev_uc_add(struct net_device *dev, const unsigned char *addr)
{
        int err;

        netif_addr_lock_bh(dev);
        err = __hw_addr_add(&dev->uc, addr, dev->addr_len,
                            NETDEV_HW_ADDR_T_UNICAST);
        if (!err)
                __dev_set_rx_mode(dev);
        netif_addr_unlock_bh(dev);
        return err;
}
net/core/dev.c
/*
 *      Upload unicast and multicast address lists to device and
 *      configure RX filtering. When the device doesn't support unicast
 *      filtering it is put in promiscuous mode while unicast addresses
 *      are present.
 */
void __dev_set_rx_mode(struct net_device *dev)
{
        const struct net_device_ops *ops = dev->netdev_ops;

        /* dev_open will call this function so the list will stay sane. */
        if (!(dev->flags&IFF_UP))
                return;

        if (!netif_device_present(dev))
                return;

        if (!(dev->priv_flags & IFF_UNICAST_FLT)) {
                /* Unicast addresses changes may only happen under the rtnl,
                 * therefore calling __dev_set_promiscuity here is safe.
                 */
                if (!netdev_uc_empty(dev) && !dev->uc_promisc) {
                        __dev_set_promiscuity(dev, 1);
                        dev->uc_promisc = true;
                } else if (netdev_uc_empty(dev) && dev->uc_promisc) {
                        __dev_set_promiscuity(dev, -1);
                        dev->uc_promisc = false;
                }
        }

        if (ops->ndo_set_rx_mode)
                ops->ndo_set_rx_mode(dev);
}

ops->ndo_set_rx_mode(dev) は e1000 なら
e1000_set_rx_mode が登録されているはず。

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
24
Help us understand the problem. What are the problem?