Linux-cp创建接口对
为了能够将数据报文由VPP送到Linux中,Linux-cp的如下命令为VPP中的接口创建对应的linux中映射接口(host-if),host-if默认为tap类型接口,可通过关键字tun改变接口类型,创建tun类型的映射接口。
VLIB_CLI_COMMAND (lcp_itf_pair_create_command, static) = {
.path = "lcp create",
.short_help = "lcp create <sw_if_index>|<if-name> host-if <host-if-name> "
"netns <namespace> [tun]",
.function = lcp_itf_pair_create_command_fn,
};
解析lcp命令行参数,交由函数lcp_itf_pair_create处理。
static clib_error_t *
lcp_itf_pair_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
vlib_cli_command_t *cmd)
{
unformat_input_t _line_input, *line_input = &_line_input;
vnet_main_t *vnm = vnet_get_main ();
while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
{
if (unformat (line_input, "%d", &sw_if_index));
else if (unformat (line_input, "%U", unformat_vnet_sw_interface, vnm,
&sw_if_index)) ;
else if (unformat (line_input, "host-if %s", &host_if_name)) ;
else if (unformat (line_input, "netns %s", &ns)) ;
else if (unformat (line_input, "tun"))
host_if_type = LCP_ITF_HOST_TUN;
else
{
unformat_free (line_input);
vec_free (host_if_name);
vec_free (ns);
return clib_error_return (0, "unknown input `%U'", format_unformat_error, input);
}
}
r = lcp_itf_pair_create (sw_if_index, host_if_name, host_if_type, ns, NULL);
首先,检查指定的VPP接口是否有效,以及要创建的linux接口的名称是否合法。
int
lcp_itf_pair_create (u32 phy_sw_if_index, u8 *host_if_name,
lip_host_type_t host_if_type, u8 *ns,
u32 *host_sw_if_indexp)
{
if (!vnet_sw_if_index_is_api_valid (phy_sw_if_index))
{
LCP_ITF_PAIR_ERR ("pair_create: invalid phy index %u", phy_sw_if_index);
return VNET_API_ERROR_INVALID_SW_IF_INDEX;
}
if (!lcp_validate_if_name (host_if_name))
{
LCP_ITF_PAIR_ERR ("pair_create: invalid host-if-name '%s'",
host_if_name);
return VNET_API_ERROR_INVALID_ARGUMENT;
}
其次,检查VPP接口是否存在。对于未指定命名空间的情况,使用默认的命名空间,默认命名空间也可能为空。
vnm = vnet_get_main ();
sw = vnet_get_sw_interface (vnm, phy_sw_if_index);
hw = vnet_get_sup_hw_interface (vnm, phy_sw_if_index);
if (!sw || !hw)
{
LCP_ITF_PAIR_ERR ("pair_create: invalid interface");
return VNET_API_ERROR_INVALID_SW_IF_INDEX;
}
/*
* Use interface-specific netns if supplied.
* Otherwise, use netns if defined, otherwise use the OS default.
*/
if (ns == 0 || ns[0] == 0)
ns = lcp_get_default_ns ();
VLAN接口
对于VLAN接口,如果其不支持IP地址配置,表明其为L2层接口,不进行处理,返回错误。
/* sub interfaces do not need a tap created */
if (vnet_sw_interface_is_sub (vnm, phy_sw_if_index))
{
err = vnet_sw_interface_supports_addressing (vnm, phy_sw_if_index);
if (err)
{
LCP_ITF_PAIR_ERR ("pair_create: can't create LCP for a "
"sub-interface without exact-match set");
return VNET_API_ERROR_INVALID_ARGUMENT;
}
对于VLAN接口,查找其父接口是否已经创建Linux映射接口,如果父接口没有linux映射接口,就不能为其vlan子接口创建linux映射接口。
outer_vlan = sw->sub.eth.outer_vlan_id;
inner_vlan = sw->sub.eth.inner_vlan_id;
outer_proto = inner_proto = ETH_P_8021Q;
if (1 == sw->sub.eth.flags.dot1ad)
outer_proto = ETH_P_8021AD;
LCP_ITF_PAIR_INFO ("pair_create: subif: dot1%s outer %d inner %d on %U",
sw->sub.eth.flags.dot1ad ? "ad" : "q", outer_vlan,
inner_vlan, format_vnet_sw_if_index_name, vnm,
hw->sw_if_index);
parent_if_index = lcp_itf_pair_find_by_phy (sw->sup_sw_if_index);
if (INDEX_INVALID == parent_if_index)
{
LCP_ITF_PAIR_ERR ("pair_create: can't find LCP for %U",
format_vnet_sw_if_index_name, vnet_get_main (),
sw->sup_sw_if_index);
return VNET_API_ERROR_INVALID_SW_IF_INDEX;
}
lip = lcp_itf_pair_get (parent_if_index);
if (!lip)
{
LCP_ITF_PAIR_ERR ("pair_create: can't create LCP for a "
"sub-interface without an LCP on the parent");
return VNET_API_ERROR_INVALID_ARGUMENT;
}
如果指定了命名空间,切换到指定命名空间,检查命令行指定的linux接口名称是否存在,如下,检查其名称对应的linux接口索引值是否有效。
LCP_ITF_PAIR_DBG ("pair_create: parent %U", format_lcp_itf_pair, lip);
parent_vif_index = lip->lip_vif_index;
/*
* see if the requested host interface has already been created
*/
orig_ns_fd = ns_fd = -1;
err = NULL;
if (ns && ns[0] != 0)
{
orig_ns_fd = clib_netns_open (NULL /* self */);
ns_fd = clib_netns_open (ns);
if (orig_ns_fd == -1 || ns_fd == -1)
goto socket_close;
clib_setns (ns_fd);
}
vif_index = if_nametoindex ((const char *) host_if_name);
对于指定的linux接口不存在的情况,如果VPP接口的内层VLAN有值,表明此接口有两层VLAN标签,查找外出VLAN对应的接口是否已经创建linux映射接口,如果不存在返回错误。否则,记录外层VLAN对应的linux映射接口,作为要新创建接口的父接口。
if (!vif_index)
{
if (inner_vlan)
{
vlan = inner_vlan;
proto = inner_proto;
linux_parent_if_index = lcp_itf_pair_find_by_outer_vlan (
hw->sw_if_index, sw->sub.eth.outer_vlan_id, sw->sub.eth.flags.dot1ad);
if (INDEX_INVALID == linux_parent_if_index ||
!(llip = lcp_itf_pair_get (linux_parent_if_index)))
{
LCP_ITF_PAIR_ERR (
"pair_create: can't find LCP for outer vlan %d proto %s on %U",
outer_vlan, outer_proto == ETH_P_8021AD ? "dot1ad" : "dot1q",
format_vnet_sw_if_index_name, vnm, hw->sw_if_index);
err = clib_error_return (0, "parent pair not found");
goto socket_close;
}
LCP_ITF_PAIR_DBG ("pair_create: linux parent %U", format_lcp_itf_pair, llip);
parent_vif_index = llip->lip_vif_index;
}
对于VPP的vlan接口只有一层vlan的情况,父接口为物理接口的linux映射接口。函数lcp_netlink_add_link_vlan为linux接口parent_vif_index,创建vlan子接口host_if_name。
else
{
vlan = outer_vlan;
proto = outer_proto;
}
err = lcp_netlink_add_link_vlan (parent_vif_index, vlan, proto,
(const char *) host_if_name);
if (err != 0)
{
LCP_ITF_PAIR_ERR ("pair_create: cannot create link "
"outer(proto:0x%04x,vlan:%u).inner(proto:0x%"
"04x,vlan:%u) name:'%s'",
outer_proto, outer_vlan, inner_proto,
inner_vlan, host_if_name);
}
if (!err)
vif_index = if_nametoindex ((char *) host_if_name);
}
在VPP中,为父接口对应的tap接口,创建vlan接口。
/* create a sub-interface on the tap
*/
if (!err &&
vnet_create_sub_interface (lip->lip_host_sw_if_index, sw->sub.id,
sw->sub.eth.raw_flags, inner_vlan,
outer_vlan, &host_sw_if_index))
{
LCP_ITF_PAIR_ERR (
"pair_create: failed to create tap subint: %d.%d on %U",
outer_vlan, inner_vlan, format_vnet_sw_if_index_name, vnm,
lip->lip_host_sw_if_index);
err = clib_error_return (
0, "failed to create tap subint: %d.%d. on %U", outer_vlan,
inner_vlan, format_vnet_sw_if_index_name, vnm,
lip->lip_host_sw_if_index);
}
切换为原有的命名空间,关闭套接口。
socket_close:
if (orig_ns_fd != -1)
{
clib_setns (orig_ns_fd);
close (orig_ns_fd);
}
if (ns_fd != -1)
close (ns_fd);
if (err)
return VNET_API_ERROR_INVALID_ARGUMENT;
}
物理接口
对于物理接口,初始化创建tap接口所需参数,接收队列设置为工作线程worker的数量,发送队列设置为1。linux tap/tun接口名称设置为命令行参数host_if_name。其物理地址设置为与VPP接口的物理地址相同。
else
{
tap_create_if_args_t args = {
.num_rx_queues = clib_max (1, vlib_num_workers ()),
.num_tx_queues = 1,
.id = hw->hw_if_index,
.host_if_name = host_if_name,
};
ethernet_interface_t *ei;
if (host_if_type == LCP_ITF_HOST_TUN)
args.tap_flags |= TAP_FLAG_TUN;
else
{
ei = pool_elt_at_index (ethernet_main.interfaces, hw->hw_instance);
mac_address_copy (&args.host_mac_addr, &ei->address.mac);
}
linux映射tap接口的mtu设置为vpp相应接口的L3层MTU值,在其为零的情况下,设置为ETHERNET_MAX_PACKET_BYTES(9216)。
/* The TAP interface does copy forward the host MTU based on the VPP
* interface's L3 MTU, but it should also ensure that the VPP tap
* interface has an MTU that is greater-or-equal to those. Considering
* users can set the interfaces at runtime (set interface mtu packet ...)
* ensure that the tap MTU is large enough, taking the VPP interface L3
* if it's set, and otherwise a sensible default.
*/
host_sw_mtu_size = sw->mtu[VNET_MTU_L3];
if (host_sw_mtu_size)
{
args.host_mtu_set = 1;
args.host_mtu_size = host_sw_mtu_size;
}
else
host_sw_mtu_size = ETHERNET_MAX_PACKET_BYTES;
通过函数tap_create_if创建指定的linux tap/tun接口和VPP中对应的virtio tap/tun接口。
if (ns && ns[0] != 0)
args.host_namespace = ns;
vm = vlib_get_main ();
tap_create_if (vm, &args);
if (args.rv < 0)
{
LCP_ITF_PAIR_ERR ("pair_create: could not create tap, retval:%d", args.rv);
return args.rv;
}
vnet_sw_interface_set_mtu (vnm, args.sw_if_index, host_sw_mtu_size);
根据virtio接口索引得到其父接口,继而得到virtio_if_t结构。对于tap类型,设置ETHERNET_INTERFACE_FLAG_STATUS_L3层标志,vif_index为virtio接口对应的linux中的接口索引,host_sw_if_index为vpp中virtio接口的索引。
/* get the hw and ethernet of the tap
*/
hw = vnet_get_sup_hw_interface (vnm, args.sw_if_index);
virtio_main_t *mm = &virtio_main;
virtio_if_t *vif = pool_elt_at_index (mm->interfaces, hw->dev_instance);
/* Leave the TAP permanently up on the VPP side.
* This TAP will be shared by many sub-interface.
* Therefore we can't use it to manage admin state.
* force the tap in promiscuous mode.
*/
if (host_if_type == LCP_ITF_HOST_TAP)
{
ei = pool_elt_at_index (ethernet_main.interfaces, hw->hw_instance);
ei->flags |= ETHERNET_INTERFACE_FLAG_STATUS_L3;
}
vif_index = vif->ifindex;
host_sw_if_index = args.sw_if_index;
}
将vpp物理接口的链路状态,设置到linux中映射tap接口的链路状态。
/* Copy the link state from VPP into the host side.
* The TAP is shared by many interfaces, always keep it up.
* This controls whether the host can RX/TX.
*/
sw = vnet_get_sw_interface (vnm, phy_sw_if_index);
lip = lcp_itf_pair_get (lcp_itf_pair_find_by_vif (vif_index));
LCP_ITF_PAIR_INFO ("pair create: %U sw-flags %u hw-flags %u",
format_lcp_itf_pair, lip, sw->flags, hw->flags);
vnet_sw_interface_admin_up (vnm, host_sw_if_index);
lcp_itf_set_link_state (lip, sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP);
根据物理接口的链路状态,设置virtio tap/tun接口的链路状态和速率。
/* Reflect current link state and link speed of the hardware interface on the
* TAP interface.
*/
if (host_if_type == LCP_ITF_HOST_TAP &&
!vnet_sw_interface_is_sub (vnm, phy_sw_if_index))
{
hw = vnet_get_sup_hw_interface (vnm, phy_sw_if_index);
lcp_itf_pair_link_up_down (vnm, hw->hw_if_index, hw->flags);
}
if (host_sw_if_indexp)
*host_sw_if_indexp = host_sw_if_index;
return 0;