热门标签 | HotTags
当前位置:  开发笔记 > 后端 > 正文

skb相关路由信息

sock结构体中有两个成员缓存路由:sk_rx_dst缓存入口路由,sk_dst_cache缓存出口路由skb结构体中的_skb_refdst在特定时刻仅缓存一种路由,防止反复查找

sock结构体中有两个成员缓存路由:sk_rx_dst缓存入口路由,sk_dst_cache缓存出口路由

skb结构体中的_skb_refdst在特定时刻仅缓存一种路由,防止反复查找

skb_dst_set需要在调用前增加引用计数(dst_clone);而skb_dst_set_noref不需要,其通过标志SKB_DST_NOREF用来标识此缓存没有引用计数,并且在skb_dst_drop函数释放路由缓存时,不进行释放操作

/**
* skb_dst_set - sets skb dst
* @skb: buffer
* @dst: dst entry
*
* Sets skb dst, assuming a reference was taken on dst and should
* be released by skb_dst_drop()
*/
static inline void skb_dst_set(struct sk_buff *skb, struct dst_entry *dst)
{
skb
->_skb_refdst = (unsigned long)dst;
}
/**
* skb_dst_set_noref - sets skb dst, hopefully, without taking reference
* @skb: buffer
* @dst: dst entry
*
* Sets skb dst, assuming a reference was not taken on dst.
* If dst entry is cached, we do not take reference and dst_release
* will be avoided by refdst_drop. If dst entry is not cached, we take
* reference, so that last dst_release can destroy the dst immediately.
*/
static inline void skb_dst_set_noref(struct sk_buff *skb, struct dst_entry *dst)
{
WARN_ON(
!rcu_read_lock_held() && !rcu_read_lock_bh_held());
skb
->_skb_refdst = (unsigned long)dst | SKB_DST_NOREF;
}
/**
* skb_dst_is_noref - Test if skb dst isn't refcounted
* @skb: buffer
*/
static inline bool skb_dst_is_noref(const struct sk_buff *skb)
{
return (skb->_skb_refdst & SKB_DST_NOREF) && skb_dst(skb);
}
static inline struct rtable *skb_rtable(const struct sk_buff *skb)
{
return (struct rtable *)skb_dst(skb);
}

View Code

 


出口路由缓存

  对于本地发出的数据包(本地创建分配的skb),其缓存的为出口路由。例如,作为TCP服务端,在回复客户端SYN+ACK时,新建一个skb结构体,根据路由查询结果(inet_csk_route_req查询出口路由),设置skb路由缓存,此时缓存的为出口路由,之后在发送过程中就不需要再次查找路由了。

/*
* Send a SYN-ACK after having received a SYN.
* This still operates on a request_sock only, not on a big
* socket.
*/
static int tcp_v4_send_synack(const struct sock *sk, struct dst_entry *dst,
struct flowi *fl,
struct request_sock *req,
struct tcp_fastopen_COOKIE *foc,
bool attach_req)
{
const struct inet_request_sock *ireq = inet_rsk(req);
struct flowi4 fl4;/* First, grab a route. */
if (!dst && (dst = inet_csk_route_req(sk, &fl4, req)) == NULL)
return -1;
skb
= tcp_make_synack(sk, dst, req, foc, attach_req);
-----------------------------
}
struct sk_buff *tcp_make_synack(const struct sock *sk, struct dst_entry *dst,
struct request_sock *req,
struct tcp_fastopen_COOKIE *foc,
bool attach_req)
{
struct inet_request_sock *ireq = inet_rsk(req);
const struct tcp_sock *tp = tcp_sk(sk);
-------------------------------
skb
= alloc_skb(MAX_TCP_HEADER, GFP_ATOMIC);
------------------------
/* Reserve space for headers. */
skb_reserve(skb, MAX_TCP_HEADER);
----------------------------------------
skb_dst_set(skb, dst);
------------------------------------
}

 对于UDP协议客户端,其在connect时(UDP客户端connect不同于TCP,仅绑定通信端地址),查询路由,缓存到sock结构体的sk_dst_cache中。

int ip4_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
rt
= ip_route_connect(...);
sk_dst_set(sk,
&rt->dst);
}

 

  之后,发送UDP数据包时,检查sock结构体中的出口路由是否有效,有效的话可不用再次查询路由表,在函数ip_make_skb中直接使用rt,并且调用skb_dst_set赋值给skb的_skb_refdst结构体,以便在发送过程中使用。
对于UDP服务端,在首次发包检测到rt为空时,查询路由表得到出口路由,缓存在sock结构中,之后发包时rt有效,省去再次查询

struct sk_buff *__ip_make_skb(...)
{
skb_dst_set(skb,
&rt->dst);
}

int udp_sendmsg(...)
{
if (connected)
rt
= (struct rtable *)sk_dst_check(sk, 0);
if (rt == NULL) {
rt
= ip_route_output_flow(net, fl4, sk);
if (connected)
sk_dst_set(sk, dst_clone(
&rt->dst));
}

skb
= ip_make_skb(sk, fl4, getfrag, msg->msg_iov, ulen,
sizeof(struct udphdr), &ipc, &rt,
msg
->msg_flags);
}

对于TCP调用IP层发送数据包时(调用ip_queue_xmit),检测sock结构中出口路由缓存,如果有效,设置到skb结构体中。否则重新进行出口路由查找。

/* Note: skb->sk can be different from sk, in case of tunnels */
int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl)
{
struct inet_sock *inet = inet_sk(sk);
struct net *net = sock_net(sk);
--------------------------------------
/* Skip all of this if the packet is already routed,
* f.e. by something like SCTP.
*/
rcu_read_lock();
inet_opt
= rcu_dereference(inet->inet_opt);
fl4
= &fl->u.ip4;
rt
= skb_rtable(skb);
if (rt)
goto packet_routed;
/* Make sure we can route this packet. */
rt
= (struct rtable *)__sk_dst_check(sk, 0);
if (!rt) {// sk is tproxy no needed routing
__be32 daddr;
/* Use correct destination address if we have options. */
daddr
= inet->inet_daddr;
if (inet_opt && inet_opt->opt.srr)
daddr
= inet_opt->opt.faddr;
/* If this fails, retransmit mechanism of transport layer will
* keep trying until route appears or the connection times
* itself out.
*/
rt
= ip_route_output_ports(net, fl4, sk, daddr, inet->inet_saddr,inet->inet_dport,inet->inet_sport,
sk
->sk_protocol, RT_CONN_FLAGS(sk),sk->sk_bound_dev_if);
-----------------------------------------
sk_setup_caps(sk,
&rt->dst);
}
skb_dst_set_noref(skb,
&rt->dst);
}

 


入口路由缓存

  对于接收到的数据包,一种情况是通过early_demux获取缓存路由,例如,在函数tcp_v4_early_demux中,通过sock结构体成员sk_rx_dst中的路由缓存初始化skb的dst,顾名思义,此时缓存的为入口路由。使用设置函数skb_dst_set_noref,不增加dst的引用计数。使用关联的sock成员sk_rx_dst的引用计数,可保障在sock存续期间,skb的dst可安全释放;当sock释放时,关联的skb会一并释放。另一种情况直接查询入口路由(ip_route_input_noref),缓存到skb中。

static int ip_rcv_finish(struct sock *sk, struct sk_buff *skb)
{
if (sysctl_ip_early_demux && !skb_dst(skb) && skb->sk == NULL) {
ipprot
->early_demux(skb);
}

if (!skb_valid_dst(skb)) {
int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
iph
->tos, skb->dev);
}
}

对于:sysctl_ip_early_demux

  当内核接收到一个TCP数据包来说,首先需要查找skb对应的路由,然后查找skb对应的socket。David Miller 发现这样做是一种浪费,对于属于同一个socket(只考虑ESTABLISHED情况)的路由是相同的,那么如果能将skb的路由缓存到socket(skb->sk)中,就可以只查找查找一次skb所属的socket,就可以顺便把路由找到了,于是David Miller提交了一个patch ipv4: Early TCP socket demux;然而Davem添加的这个patch是有局限的,因为这个处理对于转发的数据包,增加了一个在查找路由之前查找socket的逻辑,可能导致转发效率的降低。
Alexander Duyck提出增加一个ip_early_demux参数来控制是否启动这个特性。


SOCK入口路由与SKB路由缓存

  内核在接收流程中,调用early_demux函数提前在IP层做established状态的sock查找,并负责将sock结构体成员sk_rx_dst的路由缓存赋值给skb成员_skb_refdst,

对于UDP协议,对于先查找 对应目标的 sk,先关联skb 和sk,在判断DST_NOCACHE标志,如果成立,增加dst引用计数,设置skb的dst;否则,调用skb_dst_set_noref直接进行设置。

void tcp_v4_early_demux(struct sk_buff *skb)
{
const struct iphdr *iph;
const struct tcphdr *th;
struct sock *sk;
if (skb->pkt_type != PACKET_HOST)
return;
if (!pskb_may_pull(skb, skb_transport_offset(skb) + sizeof(struct tcphdr)))
return;
iph
= ip_hdr(skb);
th
= tcp_hdr(skb);
if (th->doff <sizeof(struct tcphdr) / 4)
return;
sk
= __inet_lookup_established(dev_net(skb->dev), &tcp_hashinfo,
iph
->saddr, th->source,
iph
->daddr, ntohs(th->dest),
skb
->skb_iif);
if (sk) {
skb
->sk = sk;
skb
->destructor = sock_edemux;
if (sk_fullsock(sk)) {
struct dst_entry *dst = READ_ONCE(sk->sk_rx_dst);
if (dst)
dst
= dst_check(dst, 0);
if (dst &&
inet_sk(sk)
->rx_dst_ifindex == skb->skb_iif)
skb_dst_set_noref(skb, dst);
}
}
}

void udp_v4_early_demux(struct sk_buff *skb)
{
struct net *net = dev_net(skb->dev);
const struct iphdr *iph;
const struct udphdr *uh;
struct sock *sk;
struct dst_entry *dst;
int dif = skb->dev->ifindex;
int ours;
/* validate the packet */
if (!pskb_may_pull(skb, skb_transport_offset(skb) + sizeof(struct udphdr)))
return;
iph
= ip_hdr(skb);
uh
= udp_hdr(skb);
if (skb->pkt_type == PACKET_BROADCAST ||
skb
->pkt_type == PACKET_MULTICAST) {
struct in_device *in_dev = __in_dev_get_rcu(skb->dev);
if (!in_dev)
return;
/* we are supposed to accept bcast packets */
if (skb->pkt_type == PACKET_MULTICAST) {
ours
= ip_check_mc_rcu(in_dev, iph->daddr, iph->saddr,
iph
->protocol);
if (!ours)
return;
}
sk
= __udp4_lib_mcast_demux_lookup(net, uh->dest, iph->daddr,
uh
->source, iph->saddr, dif);
}
else if (skb->pkt_type == PACKET_HOST) {
sk
= __udp4_lib_demux_lookup(net, uh->dest, iph->daddr,
uh
->source, iph->saddr, dif);
}
else {
return;
}
if (!sk)
return;
skb
->sk = sk;
skb
->destructor = sock_efree;
dst
= READ_ONCE(sk->sk_rx_dst);
if (dst)
dst
= dst_check(dst, 0);
if (dst) {
/* DST_NOCACHE can not be used without taking a reference */
if (dst->flags & DST_NOCACHE) {
if (likely(atomic_inc_not_zero(&dst->__refcnt)))
skb_dst_set(skb, dst);
}
else {
skb_dst_set_noref(skb, dst);
}
}
}

View Code


入口路由缓存之TCP

a)作为服务端,三次握手完成时,在函数tcp_v4_syn_recv_sock中创建子sock时赋值;
b)作为客户端在函数tcp_finish_connect;
c)在函数tcp_rcv_established中sock处于established状态时,更新其值;

struct sock *tcp_v4_syn_recv_sock()
{
newsk
= tcp_create_openreq_child(sk, req, skb);
inet_sk_rx_dst_set(newsk, skb);
}
void tcp_finish_connect()
{
//IPv4v6两个回调函数inet_sk_rx_dst_set与inet6_sk_rx_dst_set
if (skb)
icsk
->icsk_af_ops->sk_rx_dst_set(sk, skb);
}
void tcp_rcv_established()
{
if (unlikely(!sk->sk_rx_dst))
inet_csk(sk)
->icsk_af_ops->sk_rx_dst_set(sk, skb);
}

 


sk_rx_dst合法判断

  在函数tcp_v4_early_demux与tcp_v4_do_rcv中判断sk_rx_dst的合法性,此判断仅在sock状态为TCP_ESTABLISHED时进行。由于作为服务端,会接收到来自于多个接口的客户端请求,所以除需判断缓存路由是否过期外(dst->ops->check(dst, 0)),还需要判断其接口索引(rx_dst_ifindex)是否与此时报文的入接口相同(skb_iif)。两个条件有一个不满足,就释放缓存的路由项。其后会在tcp_rcv_established函数中更新。

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
struct sock *rsk;
if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
struct dst_entry *dst = sk->sk_rx_dst;
sock_rps_save_rxhash(sk, skb);
sk_mark_napi_id(sk, skb);
if (dst) {
if (inet_sk(sk)->rx_dst_ifindex != skb->skb_iif ||
!dst->ops->check(dst, 0)) {
dst_release(dst);
sk
->sk_rx_dst = NULL;
}
}
tcp_rcv_established(sk, skb, tcp_hdr(skb), skb
->len);
return 0;
}
}

 

  UDP不区分客户端或服务端,都于接收报文时,初始化sk_rx_dst缓存,与TCP不同,udp不记录此路由缓存的入接口索引(rx_dst_ifindex),在合法性检查时仅看是否过期(dst_check)

static void udp_sk_rx_dst_set(struct sock *sk, struct dst_entry *dst)
{
old
= xchg(&sk->sk_rx_dst, dst);
}

void udp_v4_early_demux(struct sk_buff *skb)
{
dst
= READ_ONCE(sk->sk_rx_dst);
if (dst)
dst
= dst_check(dst, 0);
}

int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,
int proto)
{
sk
= skb_steal_sock(skb);
if (sk) {
struct dst_entry *dst = skb_dst(skb);
//unlikely定义,表明udp路由缓存几乎不更新, 使用优化选项
if (unlikely(sk->sk_rx_dst != dst))
udp_sk_rx_dst_set(sk, dst);
}
}

 

在用户层应用关闭socket时,于inet层释放缓存的入口路由缓存:

void inet_sock_destruct(struct sock *sk)
{
------------------------------
dst_release(sk
->sk_rx_dst);
------------------------
}

 


转发路由缓存

  转发路由缓存与入口路由缓存查找方法相同,同是ip_rcv_finish函数中获得转发路由缓存。此时,FIB(fib_lookup)查询出的路由类型不是之前的RTN_LOCAL,路由dst的input函数指针设置为ip_forward; output函数指针设置为ip_output。在转发过程中避免重复查找路由

static int __mkroute_input(...)
{
rth
= rt_dst_alloc(out_dev->dev,
IN_DEV_CONF_GET(in_dev, NOPOLICY),
IN_DEV_CONF_GET(out_dev, NOXFRM), do_cache);

rth
->dst.input = ip_forward;
rth
->dst.output = ip_output;
}

static int ip_route_input_slow(...)
{
if (!IN_DEV_FORWARD(in_dev))
goto no_route;

// 最终调用__mkroute_input。
err = ip_mkroute_input(skb, &res, &fl4, in_dev, daddr, saddr, tos);
out: return err;
}

 

 

 

http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!!

但行好事 莫问前程

--身高体重180的胖子



推荐阅读
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了在Oracle数据库中创建序列时如何选择cache或nocache参数。cache参数可以提高序列的存取速度,但可能会导致序列丢失;nocache参数可以避免序列丢失,但在高并发访问时可能导致性能问题。文章详细解释了两者的区别和使用场景。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • Todayatworksomeonetriedtoconvincemethat:今天在工作中有人试图说服我:{$obj->getTableInfo()}isfine ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
  • Python中sys模块的功能及用法详解
    本文详细介绍了Python中sys模块的功能及用法,包括对解释器参数和功能的访问、命令行参数列表、字节顺序指示符、编译模块名称等。同时还介绍了sys模块中的新功能和call_tracing函数的用法。推荐学习《Python教程》以深入了解。 ... [详细]
  • 在编写业务代码时,常常会遇到复杂的业务逻辑导致代码冗长混乱的情况。为了解决这个问题,可以利用中间件模式来简化代码逻辑。中间件模式可以帮助我们更好地设计架构和代码,提高代码质量。本文介绍了中间件模式的基本概念和用法。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文详细介绍了git常用命令及其操作方法,包括查看、添加、提交、删除、找回等操作,以及如何重置修改文件、抛弃工作区修改、将工作文件提交到本地暂存区、从版本库中删除文件等。同时还介绍了如何从暂存区恢复到工作文件、恢复最近一次提交过的状态,以及如何合并多个操作等。 ... [详细]
  • Vagrant虚拟化工具的安装和使用教程
    本文介绍了Vagrant虚拟化工具的安装和使用教程。首先介绍了安装virtualBox和Vagrant的步骤。然后详细说明了Vagrant的安装和使用方法,包括如何检查安装是否成功。最后介绍了下载虚拟机镜像的步骤,以及Vagrant镜像网站的相关信息。 ... [详细]
  • 本文介绍了在Ubuntu下制作deb安装包及离线安装包的方法,通过备份/var/cache/apt/archives文件夹中的安装包,并建立包列表及依赖信息文件,添加本地源,更新源列表,可以在没有网络的情况下更新系统。同时提供了命令示例和资源下载链接。 ... [详细]
author-avatar
oFoUro_877
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有