【Linux5.4】【TUN】代码学习记录(3)–tun_link_ops
在tun_init初始化函数中,由rtnl_link_register执行注册,将tun_link_ops加入链表link_ops中。
tun_link_ops包含内容
static struct rtnl_link_ops tun_link_ops __read_mostly = {
.kind = DRV_NAME,
.priv_size = sizeof(struct tun_struct),
.setup = tun_setup,
.validate = tun_validate,
.get_size = tun_get_size,
.fill_info = tun_fill_info,
};
tun_link_ops是rtnl_link_ops结构体对象,后者定义在<include/net/rtnetlink.h>中
struct rtnl_link_ops {
struct list_head list;//内部使用,实际上在rtnl_link_register中,最后调用的list_add_tail(&ops->list, &link_ops)就是它。
const char *kind;//标示符,用来检验link_ops是否已存在一样的ops
size_t priv_size;//网络设备私有空间大小
/* setup 网络设备设置功能 */
void (*setup)(struct net_device *dev);
unsigned int maxtype;//最高的设备特有netlink属性号
const struct nla_policy *policy;//用于设备特定属性验证的Netlink策略
/* validate netlink/changelink参数的可选验证功能 */
int (*validate)(struct nlattr *tb[],
struct nlattr *data[],
struct netlink_ext_ack *extack);
/* newlink 配置和注册新设备 */
int (*newlink)(struct net *src_net,
struct net_device *dev,
struct nlattr *tb[],
struct nlattr *data[],
struct netlink_ext_ack *extack);
/* changelink 修改已有设备参数 */
int (*changelink)(struct net_device *dev,
struct nlattr *tb[],
struct nlattr *data[],
struct netlink_ext_ack *extack);
/* dellink 移除设备 */
void (*dellink)(struct net_device *dev,
struct list_head *head);
/* get_size 计算转储设备特定netlink属性所需的空间 */
size_t (*get_size)(const struct net_device *dev);
/* fill_info 转储设备特定的netlink属性 */
int (*fill_info)(struct sk_buff *skb,
const struct net_device *dev);
/* get_xstats_size 计算转储设备特殊统计信息所需空间 */
size_t (*get_xstats_size)(const struct net_device *dev);
/* fill_xstats 转储设备统计信息 */
int (*fill_xstats)(struct sk_buff *skb,
const struct net_device *dev);
/* 确定要创建的传输队列的数量。 */
unsigned int (*get_num_tx_queues)(void);
/* 确定要创建的接收队列的数量。 */
unsigned int (*get_num_rx_queues)(void);
unsigned int slave_maxtype;
const struct nla_policy *slave_policy;
int (*slave_changelink)(struct net_device *dev,
struct net_device *slave_dev,
struct nlattr *tb[],
struct nlattr *data[],
struct netlink_ext_ack *extack);
size_t (*get_slave_size)(const struct net_device *dev,
const struct net_device *slave_dev);
int (*fill_slave_info)(struct sk_buff *skb,
const struct net_device *dev,
const struct net_device *slave_dev);
/* get_link_net 用于获取设备的i/o网络命名空间 */
struct net *(*get_link_net)(const struct net_device *dev);
/* get_linkxtats_size 用于计算转储特定于设备的扩展链路状态所需的空间 */
size_t (*get_linkxstats_size)(const struct net_device *dev,
int attr);
/* fill_linkxstats 转储设备特殊的扩展链路状态 */
int (*fill_linkxstats)(struct sk_buff *skb,
const struct net_device *dev,
int *prividx, int attr);
};
备注:tun_link_ops 中.priv_size = sizeof(struct tun_struct),按照priv_size定义,是网络设备私有空间大小,表明tun_struct结构体是net_device结构体私有数据结构。这在tun.c代码中有所体现
struct tun_struct *tun = netdev_priv(dev);
tun_setup
static void tun_setup(struct net_device *dev)
{
struct tun_struct *tun = netdev_priv(dev);
tun->owner = INVALID_UID;
tun->group = INVALID_GID;
tun_default_link_ksettings(dev, &tun->link_ksettings);
dev->ethtool_ops = &tun_ethtool_ops; //定义了一组ethtool工具的函数操作集
/*
* needs_free_netdev与priv_destructor 用于释放网络设备
* needs_free_netdev标志设置true
* 由netdev_run_todo执行free_netdev操作
* priv_destructor用于释放dev中私有数据所占用内存
* 这样做将net_device的释放与其私有数据的释放方法进行分割
*/
dev->needs_free_netdev = true;
dev->priv_destructor = tun_free_netdev;
/* We prefer our own queue length */
dev->tx_queue_len = TUN_READQ_SIZE;//TUN_READQ_SIZE定义500,这里当TUN设备建立后通过ifconfig可查看到该数值。
}
- 先看netdev_priv,定义在<include/linux/netdevice.h>
/**
1. netdev_priv - access network device private data
2. @dev: network device
3. 4. Get network device private data
*/
static inline void *netdev_priv(const struct net_device *dev)
{
return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN);
}
netdev_priv就是在dev首地址+32位对齐的偏移量,得到私有数据地址。
- 再看tun_default_link_ksettings
static void tun_default_link_ksettings(struct net_device *dev,
struct ethtool_link_ksettings *cmd)
{
ethtool_link_ksettings_zero_link_mode(cmd, supported);
ethtool_link_ksettings_zero_link_mode(cmd, advertising);
cmd->base.speed = SPEED_10; //带宽10M
cmd->base.duplex = DUPLEX_FULL; //全双工
cmd->base.port = PORT_TP; //双绞线
cmd->base.phy_address = 0;
cmd->base.autoneg = AUTONEG_DISABLE; //自协商无效
}
???这里只看到对ethtool_link_ksettings 结构体对象cmd进行了设置,但没看懂跟dev的关系,暂不清楚,也可能跟dev没关系只是参数必须带dev,在此留个疑问
tun_validate
简单的netlink操作集允许删除netlink或tap设备。
/* Trivial set of netlink ops to allow deleting tun or tap
* device with netlink.
*/
static int tun_validate(struct nlattr *tb[], struct nlattr *data[],
struct netlink_ext_ack *extack)
{
NL_SET_ERR_MSG(extack,
"tun/tap creation via rtnetlink is not supported.");
return -EOPNOTSUPP;
}
???这个属实是没明白,该功能到底有没有实现。。。
tun_get_size
/* get_size 计算转储设备特定netlink属性所需的空间 */
static size_t tun_get_size(const struct net_device *dev)
{
BUILD_BUG_ON(sizeof(u32) != sizeof(uid_t));
BUILD_BUG_ON(sizeof(u32) != sizeof(gid_t));
return nla_total_size(sizeof(uid_t)) + /* OWNER */
nla_total_size(sizeof(gid_t)) + /* GROUP */
nla_total_size(sizeof(u8)) + /* TYPE */
nla_total_size(sizeof(u8)) + /* PI */
nla_total_size(sizeof(u8)) + /* VNET_HDR */
nla_total_size(sizeof(u8)) + /* PERSIST */
nla_total_size(sizeof(u8)) + /* MULTI_QUEUE */
nla_total_size(sizeof(u32)) + /* NUM_QUEUES */
nla_total_size(sizeof(u32)) + /* NUM_DISABLED_QUEUES */
0;
}
tun_fill_info
tun_fill_info是要在skb中配置属性数据。
/* fill_info 转储设备特定的netlink属性 */
static int tun_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
struct tun_struct *tun = netdev_priv(dev);
if (nla_put_u8(skb, IFLA_TUN_TYPE, tun->flags & TUN_TYPE_MASK))
goto nla_put_failure;
if (uid_valid(tun->owner) &&
nla_put_u32(skb, IFLA_TUN_OWNER,
from_kuid_munged(current_user_ns(), tun->owner)))
goto nla_put_failure;
if (gid_valid(tun->group) &&
nla_put_u32(skb, IFLA_TUN_GROUP,
from_kgid_munged(current_user_ns(), tun->group)))
goto nla_put_failure;
if (nla_put_u8(skb, IFLA_TUN_PI, !(tun->flags & IFF_NO_PI)))
goto nla_put_failure;
if (nla_put_u8(skb, IFLA_TUN_VNET_HDR, !!(tun->flags & IFF_VNET_HDR)))
goto nla_put_failure;
if (nla_put_u8(skb, IFLA_TUN_PERSIST, !!(tun->flags & IFF_PERSIST)))
goto nla_put_failure;
if (nla_put_u8(skb, IFLA_TUN_MULTI_QUEUE,
!!(tun->flags & IFF_MULTI_QUEUE)))
goto nla_put_failure;
if (tun->flags & IFF_MULTI_QUEUE) {
if (nla_put_u32(skb, IFLA_TUN_NUM_QUEUES, tun->numqueues))
goto nla_put_failure;
if (nla_put_u32(skb, IFLA_TUN_NUM_DISABLED_QUEUES,
tun->numdisabled))
goto nla_put_failure;
}
return 0;
nla_put_failure:
return -EMSGSIZE;
}
其中nla_put_u8 和 nla_put_u32定义在<include/net/netlink.h>
作用是向skb中写入属性。
/**
* nla_put_u8 - Add a u8 netlink attribute to a socket buffer
* @skb: socket buffer to add attribute to
* @attrtype: attribute type
* @value: numeric value
*/
static inline int nla_put_u8(struct sk_buff *skb, int attrtype, u8 value)
{
/* temporary variables to work around GCC PR81715 with asan-stack=1 */
u8 tmp = value;
return nla_put(skb, attrtype, sizeof(u8), &tmp);
}
/**
* nla_put_u32 - Add a u32 netlink attribute to a socket buffer
* @skb: socket buffer to add attribute to
* @attrtype: attribute type
* @value: numeric value
*/
static inline int nla_put_u32(struct sk_buff *skb, int attrtype, u32 value)
{
u32 tmp = value;
return nla_put(skb, attrtype, sizeof(u32), &tmp);
}
可以看到nla_put_u8 和 nla_put_u32都是对nla_put函数的封装。
nla_put实际调用__nla_put,而__nla_put首先调用__nla_reserve用于在skb中预留出要添加属性的内存空间,之后配置属性。
nla_put、__nla_put、__nla_reserve定义在<lib/nlattr.c>
/**
* __nla_reserve - reserve room for attribute on the skb
* @skb: socket buffer to reserve room on
* @attrtype: attribute type
* @attrlen: length of attribute payload
*
* Adds a netlink attribute header to a socket buffer and reserves
* room for the payload but does not copy it.
*
* The caller is responsible to ensure that the skb provides enough
* tailroom for the attribute header and payload.
*/
struct nlattr *__nla_reserve(struct sk_buff *skb, int attrtype, int attrlen)
{
struct nlattr *nla;
/*
* 在skb内预留nla_total_size(attrlen)大小空间,
* nla_total_size(attrlen) = attrlen + NLA_HDRLEN + 4字节对齐
*/
nla = skb_put(skb, nla_total_size(attrlen));
nla->nla_type = attrtype;//属性类型
nla->nla_len = nla_attr_size(attrlen);//NLA_HDRLEN + attrlen
memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen));
return nla;
}
EXPORT_SYMBOL(__nla_reserve);
/**
* __nla_put - Add a netlink attribute to a socket buffer
* @skb: socket buffer to add attribute to
* @attrtype: attribute type
* @attrlen: length of attribute payload
* @data: head of attribute payload
*
* The caller is responsible to ensure that the skb provides enough
* tailroom for the attribute header and payload.
*/
void __nla_put(struct sk_buff *skb, int attrtype, int attrlen,
const void *data)
{
struct nlattr *nla;
nla = __nla_reserve(skb, attrtype, attrlen);
memcpy(nla_data(nla), data, attrlen);
}
EXPORT_SYMBOL(__nla_put);
/**
* nla_put - Add a netlink attribute to a socket buffer
* @skb: socket buffer to add attribute to
* @attrtype: attribute type
* @attrlen: length of attribute payload
* @data: head of attribute payload
*
* Returns -EMSGSIZE if the tailroom of the skb is insufficient to store
* the attribute header and payload.
*/
int nla_put(struct sk_buff *skb, int attrtype, int attrlen, const void *data)
{
if (unlikely(skb_tailroom(skb) < nla_total_size(attrlen)))
return -EMSGSIZE;
__nla_put(skb, attrtype, attrlen, data);
return 0;
}
EXPORT_SYMBOL(nla_put);
nla_attr_size 与nla_total_size定义在<include/net/netlink.h>
/**
* nla_attr_size - length of attribute not including padding
* @payload: length of payload
*/
static inline int nla_attr_size(int payload)
{
return NLA_HDRLEN + payload;
}
/**
* nla_total_size - total length of attribute including padding
* @payload: length of payload
*/
static inline int nla_total_size(int payload)
{
return NLA_ALIGN(nla_attr_size(payload));//payload + NLA_HDRLEN(属性头长度)+对齐内存
}
其中宏定义 NLA_ALIGN(x)表示对x进行4对齐并向上取整,即x=2返回4,x=5返回8
#define NLA_ALIGNTO 4
#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))