ソースは 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 でも良い)
## 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
# 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()
macvlan_init_module()
register_netdevice_notifier(&macvlan_notifier_block)
macvlan_link_register(&macvlan_link_ops)
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 にする
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,
};
/**
* 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;
}
/*
* 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 が登録されているはず。