【C语言】linux内核packet_setsockopt

185 篇文章 1 订阅
67 篇文章 0 订阅

一、中文注释

// 发送数据包函数。它尝试通过特定的网络设备队列直接传输一个skb(socket缓冲区)。
static int packet_direct_xmit(struct sk_buff *skb)
{
    return dev_direct_xmit(skb, packet_pick_tx_queue(skb)); // 调用dev_direct_xmit函数,并传入skb和通过packet_pick_tx_queue选择的发送队列。
}

// 设置socket选项的函数。这个函数负责处理网络层的各种选项设置。
static int
packet_setsockopt(struct socket *sock, int level, int optname, char __user *optval, unsigned int optlen)
{
    struct sock *sk = sock->sk; // 从socket结构中获取sock结构。
    struct packet_sock *po = pkt_sk(sk); // 将sock结构转换为packet_sock结构体。
    int ret;

    if (level != SOL_PACKET)
        return -ENOPROTOOPT; // 如果层级不是SOL_PACKET,返回错误。

    // 根据选项名称执行相应的操作。
    switch (optname) {
    case PACKET_ADD_MEMBERSHIP:
    case PACKET_DROP_MEMBERSHIP:
    {
        struct packet_mreq_max mreq; // 定义多播请求结构。
        int len = optlen; // 获取选项长度。
        memset(&mreq, 0, sizeof(mreq)); // 初始化多播请求结构。
        if (len < sizeof(struct packet_mreq))
            return -EINVAL; // 如果传入的长度小于packet_mreq结构大小,返回无效参数错误。
        if (len > sizeof(mreq))
            len = sizeof(mreq); // 如果传入的长度大于packet_mreq_max结构大小,调整为packet_mreq_max结构大小。
        if (copy_from_user(&mreq, optval, len))
            return -EFAULT; // 从用户空间拷贝数据失败,则返回错误。
        if (len < (mreq.mr_alen + offsetof(struct packet_mreq, mr_address)))
            return -EINVAL; // 如果长度小于mreq结构中mr_address成员起始位置加上地址长度,则返回无效参数错误。
        if (optname == PACKET_ADD_MEMBERSHIP)
            ret = packet_mc_add(sk, &mreq); // 添加多播组成员。
        else
            ret = packet_mc_drop(sk, &mreq); // 删除多播组成员。
        return ret; // 返回操作结果。
    }

    case PACKET_RX_RING:
    case PACKET_TX_RING:
    {
        union tpacket_req_u req_u;
        int len;

        lock_sock(sk); // 锁定sock结构。
        switch (po->tp_version) {
        case TPACKET_V1:
        case TPACKET_V2:
            len = sizeof(req_u.req); // 设置请求结构大小。
            break;
        case TPACKET_V3:
        default:
            len = sizeof(req_u.req3); // 设置请求结构大小。
            break;
        }
        if (optlen < len) {
            ret = -EINVAL; // 如果传入长度小于请求结构大小,返回无效参数错误。
        } else {
            if (copy_from_user(&req_u.req, optval, len))
                ret = -EFAULT; // 从用户空间拷贝数据失败,则返回错误。
            else
                ret = packet_set_ring(sk, &req_u, 0, 
                            optname == PACKET_TX_RING); // 设置环形缓冲区。
        }
        release_sock(sk); // 释放sock结构锁。
        return ret; // 返回操作结果。
    }

// 设置数据包复制阈值,当skb长度小于此值时复制数据。
case PACKET_COPY_THRESH:
{
    int val;

    if (optlen != sizeof(val))
        return -EINVAL; // 如果选项长度不合法,返回错误。
    if (copy_from_user(&val, optval, sizeof(val)))
        return -EFAULT; // 如果用户空间数据拷贝失败,返回错误。

    pkt_sk(sk)->copy_thresh = val; // 设置数据包复制阈值。
    return 0;
}
// ...
// 设置packet socket的版本号。
case PACKET_VERSION:
{
    int val;

    if (optlen != sizeof(val))
        return -EINVAL; // 如果选项长度不合法,返回错误。
    if (copy_from_user(&val, optval, sizeof(val)))
        return -EFAULT; // 如果用户空间数据拷贝失败,返回错误。
    switch (val) {
    case TPACKET_V1:
    case TPACKET_V2:
    case TPACKET_V3:
        break; // 如果为有效版本,继续执行。
    default:
        return -EINVAL; // 如果为无效版本,返回错误。
    }
    lock_sock(sk); // 锁定sock结构。
    if (po->rx_ring.pg_vec || po->tx_ring.pg_vec) {
        ret = -EBUSY; // 如果已经设置了环形缓冲区,则返回忙。
    } else {
        po->tp_version = val; // 设置协议版本。
        ret = 0;
    }
    release_sock(sk); // 释放sock结构锁。
    return ret; // 返回操作结果。
}
// ...
// 设置packet socket是否绕过队列规则处理。
case PACKET_QDISC_BYPASS:
{
    int val;

    if (optlen != sizeof(val))
        return -EINVAL; // 如果选项长度不合法,返回错误。
    if (copy_from_user(&val, optval, sizeof(val)))
        return -EFAULT; // 如果用户空间数据拷贝失败,返回错误。

    po->xmit = val ? packet_direct_xmit : dev_queue_xmit; // 根据val的值选择是直接传输还是进入队列规则处理。
    return 0;
}
// ...
// 默认case,如果没有匹配到任何选项,代表不支持该协议选项。
default:
    return -ENOPROTOOPT; // 返回不支持的协议选项错误。
// 所有case分支的目的都是处理socket层的不同选项设置,如添加/删除多播组,设置缓冲区大小和环形配置,调整性能选项等。
// 每个case分支以处理特定选项名(optname)为基础,对socket结构的特定成员进行赋值或执行相关操作函数。
    }
}

这段代码是Linux内核网络栈中的一部分,提供对原始套接字层的操作。`packet_direct_xmit` 函数用于直接通过指定的网络队列发送数据包。`packet_setsockopt` 函数是一个针对`SOL_PACKET`套接字层级的设置选项的函数,可以添加或删除多播组成员,设置缓冲区环区域,修改某些性能参数等。这些操作通常是由网络程序进行socket编程时所使用。

每个 case 分支处理一个特定的套接字选项,例如 PACKET_RX_RING 或 PACKET_TX_RING 用于分配或释放环形缓冲区,而 PACKET_COPY_THRESH 用于设置包拷贝阈值等。每个分支内部通常会首先验证用户提供的参数长度是否合法,然后从用户空间拷贝数据到内核空间,接着进行相应的设置修改,某些情况下还需要在操作前后获取和释放锁,以保护数据结构的一致性。如果选项设置成功,通常会返回 0,否则返回错误码。

二、中文讲解

第一个函数 packet_direct_xmit:

static int packet_direct_xmit(struct sk_buff *skb)
{
    return dev_direct_xmit(skb, packet_pick_tx_queue(skb));
}

packet_direct_xmit 函数是一个非常简单的函数,它接收一个 sk_buff 结构体指针 skb(代表网络数据包),调用 dev_direct_xmit 函数直接将这个数据包发送出去。发送的队列是由 packet_pick_tx_queue(skb) 函数返回的。通常这个函数被用作数据包的直接传输,规避了一些队列管理机制。

第二个函数 packet_setsockopt:

static int packet_setsockopt(struct socket *sock, int level, int optname, char __user *optval, unsigned int optlen)
{
    // ...
}

packet_setsockopt 函数用于设置给定套接字 sock 的某些选项。函数参数 level 表示选项的级别,这里必须是 SOL_PACKET,否则返回 -ENOPROTOOPT 错误。`optname` 指定了要设置的选项的名称,`optval` 是指向包含选项值的用户空间缓冲区的指针,而 optlen 表示缓冲区的长度。
函数体内是一个 switch 语句,根据 optname 的不同,执行不同的操作:
1. PACKET_ADD_MEMBERSHIP 和 PACKET_DROP_MEMBERSHIP:用于管理数据包多播成员资格。
2. PACKET_RX_RING 和 PACKET_TX_RING:设置接收和发送环形缓冲区,这些操作涉及到性能优化,以高效处理网络数据包。
3. PACKET_COPY_THRESH:设置数据包复制阈值。
4. PACKET_VERSION:指定环形缓冲区版本(TPACKET_V1, TPACKET_V2, 或 TPACKET_V3)。
5. PACKET_RESERVE:为环形缓冲区中每个帧预留空间。
6. PACKET_LOSS:设置丢包选项,以模拟网络丢包情况。
7. PACKET_AUXDATA:启用或禁用附加数据。
8. PACKET_ORIGDEV:路由过程中获取原始设备信息的选项。
9. PACKET_VNET_HDR:用于虚拟网络设备头部的选项。
10. PACKET_TIMESTAMP:设置数据包的时间戳类型。
11. PACKET_FANOUT 和 PACKET_FANOUT_DATA:用于将数据包分发到多个程序。
12. PACKET_TX_HAS_OFF:设置是否启用硬件校验和。
13. PACKET_QDISC_BYPASS:设置是否绕过队列规则直接发送数据包。
对于每种情况,在设置选项前都有一些校验逻辑来保证输入参数的有效性。如果输入参数无效,函数会返回相应的错误代码(例如 -EINVAL 表示无效参数)。各种选项设置可能涉及到从用户空间复制数据到内核空间(使用 copy_from_user),也可能对套接字状态加锁来确保线程安全。成功执行操作后,函数通常返回 0 表示成功。

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
`skb_release` 函数是 Linux 内核中用来释放 `skb` 数据结构(网络数据包缓冲区)的函数。它的定义在文件 `net/core/skbuff.c` 中。 下面是 `skb_release` 函数的代码及注释: ```c void skb_release(struct sk_buff *skb) { struct nf_hook_state state; unsigned int i; /* Call the packet destroy function for nf_hooks */ if (unlikely(nf_hooks_needed(skb))) nf_hook_state_init(&state, NFPROTO_UNSPEC, NF_HOOK_INVALID, NULL, skb, NULL, dev_net(skb->dev), 0); for (i = 0; i < NFPROTO_NUMPROTO; i++) nf_hook(NFPROTO(i), NF_INET_PRE_ROUTING, &state, skb, skb->dev, NULL, skb_dst(skb)->dev, NULL, skb->protocol); /* Call the destructor for each extension header. */ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { if (skb_shinfo(skb)->frags[i].page) kmap_atomic(skb_shinfo(skb)->frags[i].page); if (skb_shinfo(skb)->destructor[i]) skb_shinfo(skb)->destructor[i](skb); if (skb_shinfo(skb)->frags[i].page) kunmap_atomic(skb_shinfo(skb)->frags[i].page); } if (skb_shinfo(skb)->frag_list) kfree_skb_list(skb_shinfo(skb)->frag_list); /* Release the skb itself */ kmem_cache_free(skbuff_head_cache, skb); } ``` 该函数的作用是释放 `skb` 数据结构,它的主要步骤包括: 1. 如果 `skb` 上注册了网络钩子(`nf_hook`),则依次调用每个网络钩子的销毁函数(`nf_hook` 函数的第 4 个参数)。 2. 对于 `skb` 中的每个扩展头,调用其析构函数(`destructor`)进行清理,以释放扩展头占用的内存。 3. 如果 `skb` 中存在分段数据(`frag_list`),则释放分段数据占用的内存。 4. 最后,释放 `skb` 本身占用的内存。 需要注意的是,该函数只是释放 `skb` 数据结构本身占用的内存,但并不会释放 `skb` 引用的其他内存(例如数据包的内容、关联的网络设备等)。这些内存的释放由其他函数负责。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

109702008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值