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](
http://events.linuxfoundation.jp/sites/events/files/slides/LinuxConJapan2014_makita_0.pdf) の 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 でも良い)

```console: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
```

```console:別ノードから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 に変更しないと通信できなかった。

```console
# 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 ドライバの挙動

```none:module_init()からのおおまかな流れ
module_init()
  macvlan_init_module()
    register_netdevice_notifier(&macvlan_notifier_block)
    macvlan_link_register(&macvlan_link_ops)
```

```c: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,
};
```

## 受信ハンドラの設定

```none:おおまかな流れ
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 アドレスを登録する

```none:おおまかな流れ
macvlan_open()
  dev_uc_add()
    __hw_addr_add()
    __dev_set_rx_mode()
```

* macvlan な NIC が up した時に macvlan_open() が呼ばれる？
* 実 NIC が unicast filtering をサポートしている場合はその NIC のドライバ固有の処理を呼んで追加の MAC アドレスで受信できるようにする<BR>(IFF_UNICAST_FLT がオンかどうかで判定)
* サポートしていない場合は実 NIC を promiscous mode にする



```c: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,
};
```


```c: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;
}
```

```c: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 が登録されているはず。