netfilter和iptables--netfilter源码篇
netfilter和iptables–netfilter源码篇
防火墙是保护服务器和基础设施的重要工具,在Linux系统下,目前广泛使用的防火墙工具是iptables,但实际进行规则实施并产生实际作用的是Netfilter,iptables与内核中的netfilter框架中Hook协同工作,实现IP数据包的控制与过滤,本次将基于Linux6.5从源码视角分析。
总视角下网络通信协议栈模式如下:
图中各个HOOK点触发情况:
(1)PREROUTING:NF_IP_PRE_ROUTING
(2)INPUT:NF_IP_LOCAL_IN
(3)FORWARD:NF_IP_FORWARD
(4)OUTPUT:NF_IP_LOCAL_OUT
(5)POSTROUTING:NF_IP_POST_ROUTING
显而易见,数据包在netfiler框架中的流向取决于路由判定,第一次路由判定是通过检查输入的数据包的IP头部,根据目的地址判定请求的是否是本地主机IP地址,如果请求的是本机IP,数据包发给本机,否则说明请求的IP并非本机,进行路由转发;第二次次路由判定是根据输出的数据包IP头部,根据路由表中路由信息获取下一跳主机/网关IP,进行转发,其数据包流向路径分为三部分。
1、发往本地
数据包经过Hook点情况:NF_INET_PRE_ROUTING–>NF_INET_LOCAL_IN
,当数据包从网卡进入Linux网络协议栈首先触发NF_INET_PRE_ROUTING
,经过路由判决,请求目的IP为本机,触发NF_INET_LOCAL_IN
。
2、转发数据
数据包经过Hook点情况:NF_INET_PRE_ROUTING–>NF_INET_FORWARD–>NF_INET_POST_ROUTING
,接收到的数据包经过路由判决,发现请求的目的IP为其他机器,触发NF_INET_FORWARD
,完成处理后触发NF_INET_POST_ROUTING
。
3、本地发出
数据包经过Hook点情况:NF_INET_LOCAL_OUT–>NF_INET_POST_ROUTING
,本机产生的数据包进入Linux协议栈立即触发NF_INET_LOCAL_OUT
,对于三种路径的数据包最终均会触发NF_INET_POST_ROUTING
。
源码视角下函数调用源码分析:
(1)发往本地
ip_rcv()
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
struct net_device *orig_dev)
{
struct net *net = dev_net(dev);
skb = ip_rcv_core(skb, net);
if (skb == NULL)
return NET_RX_DROP;
/*
1、调用 Netfilter 钩子 NF_INET_PRE_ROUTING,对数据包进行预路由阶段处理
2、skb 传递给 ip_rcv_finish
*/
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
net, NULL, skb, dev, NULL,
ip_rcv_finish);
}
ip_rcv_finish()
static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
// 获取当前数据包关联的网络设备
struct net_device *dev = skb->dev;
int ret;
/* if ingress device is enslaved to an L3 master device pass the
\* skb to its handler for processing
*/
skb = l3mdev_ip_rcv(skb);
if (!skb)
return NET_RX_SUCCESS;
// 调用 ip_rcv_finish_core 进行进一步的核心 IP 处理
ret = ip_rcv_finish_core(net, sk, skb, dev, NULL);
// 如果数据包未被丢弃,则继续调用 dst_input 处理路由和传递
if (ret != NET_RX_DROP)
ret = dst_input(skb);
return ret;
}
dst_input()
static inline int dst_input(struct sk_buff *skb)
{
// 调用 skb 中 dst_entry 结构体的 input 函数指针,使用 INDIRECT_CALL_INET 进行间接调用
// 如果是 IPv6 数据包,则调用 ip6_input
// 如果是 IPv4 数据包,则调用 ip_local_deliver
return INDIRECT_CALL_INET(skb_dst(skb)->input,
ip6_input, ip_local_deliver, skb);
}
ip_local_deliver()
int ip_local_deliver(struct sk_buff *skb)
{
/*
\* Reassemble IP fragments.
*/
struct net *net = dev_net(skb->dev);
// 检查数据包是否分片
if (ip_is_fragment(ip_hdr(skb))) {
//分片调用ip_defrag重组
if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER))
return 0;
}
/*
1、调用 Netfilter 的 NF_INET_LOCAL_IN 钩子,允许 Netfilter 在本地传输前进行处理
2、成功后调用 ip_local_deliver_finish 处理
*/
return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,
net, NULL, skb, skb->dev, NULL,
ip_local_deliver_finish);
}
数据包在发往本地路径上,当执行iptables的input规则时,经过PREROUTING
链和INPUT
链,触发NF_INET_PRE_ROUTING
,用于目的地址转换/数据包修改(nat表和mangle表);触发NF_INET_LOCAL_IN
Hook点,控制数据包是否可以传递到本地协议栈。
(2)转发数据
ip_route_input_noref()
ip_rcv_finish()函数中调用ip_rcv_finish_core
()进行核心IP处理,进行路由提示检查、根据IP协议选择TCP解复用以及UDP解复用、检查skb是否是有效的路由缓存,没有缓存进入ip_route_input_noref
进入路由查找,存在路由缓存则从设备dev获取in_device结构,设置IPSKB_NOPOLICY
。
int ip_route_input_noref(struct sk_buff *skb, __be32 daddr, __be32 saddr,
u8 tos, struct net_device *dev)
{
struct fib_result res;
int err;
//屏蔽掉 tos 字段中不需要的位,通常只保留服务类型的高两位,表示优先级。
tos &= IPTOS_RT_MASK;
rcu_read_lock();
//RCU锁机制保护下进行实际的路由查找
err = ip_route_input_rcu(skb, daddr, saddr, tos, dev, &res);
rcu_read_unlock();
return err;
}
EXPORT_SYMBOL(ip_route_input_noref);
ip_route_input_rcu()
该函数中进行实际的路由查找,将路由查找的结果存储在fib_result
。
-
/* called with rcu_read_lock held */ static int ip_route_input_rcu(struct sk_buff *skb, __be32 daddr, __be32 saddr, u8 tos, struct net_device *dev, struct fib_result *res) { //目的ip是多播地址 if (ipv4_is_multicast(daddr)) { //获取数据包接收设备的in_device结构 struct in_device *in_dev = __in_dev_get_rcu(dev); int our = 0; int err = -EINVAL; if (!in_dev) return err; //检查目标多播地址和源地址是否与设备 in_dev 上配置的多播组匹配。返回值布尔值 our,表示该设备是否参与了该多播组。 our = ip_check_mc_rcu(in_dev, daddr, saddr, ip_hdr(skb)->protocol); /* check l3 master if no match yet */ if (!our && netif_is_l3_slave(dev)) { struct in_device *l3_in_dev; l3_in_dev = __in_dev_get_rcu(skb->dev); if (l3_in_dev) our = ip_check_mc_rcu(l3_in_dev, daddr, saddr, ip_hdr(skb)->protocol); } //多播路由输入处理 //匹配成功且启用IP多播路由转发CONFIG_IP_MROUTE,设备配置为多播转发器IN_DEV_MFORWARD进行多播路由处理 if (our #ifdef CONFIG_IP_MROUTE || (!ipv4_is_local_multicast(daddr) && IN_DEV_MFORWARD(in_dev)) #endif ) { //多播IP路由查找 err = ip_route_input_mc(skb, daddr, saddr, tos, dev, our); } return err; } //daddr是非多播地址/不匹配,数据包回退置慢路径,进入常规路由查找 return ip_route_input_slow(skb, daddr, saddr, tos, dev, res); }
ip_route_input_slow()
处理网络数据包的慢速路径路由查找,特别是在路由缓存不可用或者需要更复杂处理时使用。
static int ip_route_input_slow(struct sk_buff *skb, __be32 daddr, __be32 saddr,
u8 tos, struct net_device *dev,
struct fib_result *res)
{
struct in_device *in_dev = __in_dev_get_rcu(dev);
struct flow_keys *flkeys = NULL, _flkeys;
struct net *net = dev_net(dev);
struct ip_tunnel_info *tun_info;
int err = -EINVAL;
unsigned int flags = 0;
u32 itag = 0;
struct rtable *rth;
struct flowi4 fl4;
bool do_cache = true;
/* IP on this device is disabled. */
if (!in_dev)
goto out;
/* Check for the most weird martians, which can be not detected
\* by fib_lookup.
*/
//tun_info获取数据包隧道信息,数据包如果通过隧道技术传输且传输模式不是TX,获取tun_id标识隧道连接
tun_info = skb_tunnel_info(skb);
if (tun_info && !(tun_info->mode & IP_TUNNEL_INFO_TX))
fl4.flowi4_tun_key.tun_id = tun_info->key.tun_id;
else
fl4.flowi4_tun_key.tun_id = 0;
//删除数据包路由缓存目标,每次进入慢速路径时,进行清除
skb_dst_drop(skb);
//检查saddr是否是多播地址/链路层广播地址
if (ipv4_is_multicast(saddr) || ipv4_is_lbcast(saddr))
goto martian_source;
res->fi = NULL;
res->table = NULL;
//daadr
if (ipv4_is_lbcast(daddr) || (saddr == 0 && daddr == 0))
goto brd_input;
/* Accept zero addresses only to limited broadcast;
\* I even do not know to fix it or not. Waiting for complains :-)
*/
if (ipv4_is_zeronet(saddr))
goto martian_source;
if (ipv4_is_zeronet(daddr))
goto martian_destination;
/* Following code try to avoid calling IN_DEV_NET_ROUTE_LOCALNET(),
\* and call it once if daddr or/and saddr are loopback addresses
*/
if (ipv4_is_loopback(daddr)) {
if (!IN_DEV_NET_ROUTE_LOCALNET(in_dev, net))
goto martian_destination;
} else if (ipv4_is_loopback(saddr)) {
if (!IN_DEV_NET_ROUTE_LOCALNET(in_dev, net))
goto martian_source;
......
}
ip_mkroute_input()
处理多路径路由选择和调用低层函数创建路由缓存条目。内核配置中启用CONFIG_IP_ROUTE_MULTIPATH
进行多路径路由选择,根据路由的查找结果fib_result
中的fib_info
是否存在,fib_multipath_hash
检查路由条目是否大于1,选择进入多路径路由选择处理,fib_select_multipath
根据哈希值选择合适的路径,并将选定的路径更新到 fib_result
中。其中哈希值的计算使用网络数据包的一些特征(如源地址、目的地址、端口号等确保这样相同流量会选择相同的路径,以保持数据包的顺序性
static int ip_mkroute_input(struct sk_buff *skb,
struct fib_result *res,
struct in_device *in_dev,
__be32 daddr, __be32 saddr, u32 tos,
struct flow_keys *hkeys)
{
\#ifdef CONFIG_IP_ROUTE_MULTIPATH
if (res->fi && fib_info_num_path(res->fi) > 1) {
int h = fib_multipath_hash(res->fi->fib_net, NULL, skb, hkeys);
fib_select_multipath(res, h);
}
\#endif
/* create a routing cache entry */
return __mkroute_input(skb, res, in_dev, daddr, saddr, tos);
}
__mkroute_input()
创建路由缓存条目,设置路由转发的处理函数。
/* called in rcu_read_lock() section */
static int __mkroute_input(struct sk_buff *skb,
const struct fib_result *res,
struct in_device *in_dev,
__be32 daddr, __be32 saddr, u32 tos)
{
struct fib_nh_common *nhc = FIB_RES_NHC(*res);
struct net_device *dev = nhc->nhc_dev;
struct fib_nh_exception *fnhe;
struct rtable *rth;
int err;
struct in_device *out_dev;
bool do_cache;
u32 itag = 0;
/* get a working reference to the output device */
out_dev = __in_dev_get_rcu(dev);
if (!out_dev) {
net_crit_ratelimited("Bug in ip_route_input_slow(). Please report.\n");
return -EINVAL;
}
//地址是否有效
err = fib_validate_source(skb, saddr, daddr, tos, FIB_RES_OIF(*res),
in_dev->dev, in_dev, &itag);
if (err < 0) {
ip_handle_martian_source(in_dev->dev, in_dev, skb, daddr,
saddr);
goto cleanup;
}
//处理重定向和代理ARP
//do_cache标识是否可以缓存路由,存在有效路由信息res->fi并且未标记itag
do_cache = res->fi && !itag;
//如果输出设备是输入设备且err且设备允许重定向且数据包时IP类型,设置标志位IPSKB_DOREDIRECT
if (out_dev == in_dev && err && IN_DEV_TX_REDIRECTS(out_dev) &&
skb->protocol == htons(ETH_P_IP)) {
__be32 gw;
gw = nhc->nhc_gw_family == AF_INET ? nhc->nhc_gw.ipv4 : 0;
if (IN_DEV_SHARED_MEDIA(out_dev) ||
inet_addr_onlink(out_dev, saddr, gw))
IPCB(skb)->flags |= IPSKB_DOREDIRECT;
}
//不是IP类型在设备是在l3从属设备时检查代理ARP相关配置
if (skb->protocol != htons(ETH_P_IP)) {
/* Not IP (i.e. ARP). Do not create route, if it is
* invalid for proxy arp. DNAT routes are always valid.
* Proxy arp feature have been extended to allow, ARP
lies back to the same interface, to support
* Private VLAN switch technologies. See arp.c.
if (out_dev == in_dev &&
IN_DEV_PROXY_ARP_PVLAN(in_dev) == 0) {
err = -EINVAL;
goto cleanup;
}
}
//检查设备是否配置了NOPOLICY,是则在数据包控制块中设置IPSKB_NOPOLICY,数据包不受IPsec/其他安全限制
if (IN_DEV_ORCONF(in_dev, NOPOLICY))
IPCB(skb)->flags |= IPSKB_NOPOLICY;
//查找异常路由
fnhe = find_exception(nhc, daddr);
if (do_cache) {
if (fnhe)
rth = rcu_dereference(fnhe->fnhe_rth_input);
else
rth = rcu_dereference(nhc->nhc_rth_input);
if (rt_cache_valid(rth)) {
skb_dst_set_noref(skb, &rth->dst);
goto out;
}
}
//分配新的路由缓存
rth = rt_dst_alloc(out_dev->dev, 0, res->type,
IN_DEV_ORCONF(out_dev, NOXFRM));
if (!rth) {
err = -ENOBUFS;
goto cleanup;
}
//设置路由属性
rth->rt_is_input = 1;//输入路由
RT_CACHE_STAT_INC(in_slow_tot);//更新路由缓存统计信息
rth->dst.input = ip_forward;//路由处理函数进行路由转发
rt_set_nexthop(rth, daddr, res, fnhe, res->fi, res->type, itag,
do_cache);//设置下一跳相关信息
lwtunnel_set_redirect(&rth->dst);//设置与隧道相关的重定向信息
skb_dst_set(skb, &rth->dst);//将数据包的目标设置为新分配的路由缓存
out:
err = 0;
cleanup:
return err;
}
ip_forward()
主要用于处理 IPv4 数据包的转发逻辑,包括 TTL 处理、路径选择、MTU 检查、ICMP 响应等。
int ip_forward(struct sk_buff *skb)
{
u32 mtu;
struct iphdr *iph; /* Our header */
struct rtable *rt; /* Route we use */
struct ip_options *opt = &(IPCB(skb)->opt);
struct net *net;
SKB_DR(reason);
/* that should never happen */
//主机发送的数据包丢弃
if (skb->pkt_type != PACKET_HOST)
goto drop;
if (unlikely(skb->sk))
goto drop;
if (skb_warn_if_lro(skb))
goto drop;
if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb)) {
SKB_DR_SET(reason, XFRM_POLICY);
goto drop;
}
if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))
return NET_RX_SUCCESS;
//计算数据包校验和
skb_forward_csum(skb);
net = dev_net(skb->dev);
/*
* According to the RFC, we must first decrease the TTL field. If
that reaches zero, we must reply an ICMP control message telling
* that the packet's lifetime expired.
/
//获取ttl并检查生存时间是否已过
if (ip_hdr(skb)->ttl <= 1)
goto too_many_hops;
//数据包是否可以被转发
if (!xfrm4_route_forward(skb)) {
SKB_DR_SET(reason, XFRM_POLICY);
goto drop;
}
//通过skb获取路由表
rt = skb_rtable(skb);
if (opt->is_strictroute && rt->rt_uses_gateway)
goto sr_failed;
//标记已转发
IPCB(skb)->flags |= IPSKB_FORWARDED;
//获取目的地的最大传输单元(MTU)
mtu = ip_dst_mtu_maybe_forward(&rt->dst, true);
//检查数据包是否超过MTU
if (ip_exceeds_mtu(skb, mtu)) {
IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS);
//超过发送ICMP不可达消息,设置类型ICMP_FRAG_NEEDED
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
htonl(mtu));
SKB_DR_SET(reason, PKT_TOO_BIG);
goto drop;
}
/* We are about to mangle packet. Copy it! */
if (skb_cow(skb, LL_RESERVED_SPACE(rt->dst.dev)+rt->dst.header_len))
goto drop;
iph = ip_hdr(skb);
/* Decrease ttl after skb cow done */
ip_decrease_ttl(iph);
/*
* We now generate an ICMP HOST REDIRECT giving the route
we calculated.
*/
if (IPCB(skb)->flags & IPSKB_DOREDIRECT && !opt->srr &&
!skb_sec_path(skb))
ip_rt_send_redirect(skb);
if (READ_ONCE(net->ipv4.sysctl_ip_fwd_update_priority))
skb->priority = rt_tos2priority(iph->tos);
//使用 NF_HOOK 调用 IPv4 的转发钩子,执行数据包的转发逻辑,最终交给 ip_forward_finish 函数处理
return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD,
net, NULL, skb, skb->dev, rt->dst.dev,
ip_forward_finish);
......
}
ip_forward_finish()
ipv4数据包转发
static int ip_forward_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
struct ip_options *opt = &(IPCB(skb)->opt);
//更新转发数据包统计,IPSTATS_MIB_OUTFORWDATAGRAMS成功转发的数据包数量
__IP_INC_STATS(net, IPSTATS_MIB_OUTFORWDATAGRAMS);
//将当前数据包长度skb->len添加到总输出字节统计中,IPSTATS_MIB_OUTOCTETS成功转发的数据包总字节数
__IP_ADD_STATS(net, IPSTATS_MIB_OUTOCTETS, skb->len);
\#ifdef CONFIG_NET_SWITCHDEV
if (skb->offload_l3_fwd_mark) {
consume_skb(skb);
return 0;
}
\#endif
if (unlikely(opt->optlen))
ip_forward_options(skb);
skb_clear_tstamp(skb);
//数据包发送到路由表中指定的下一跳,最终将数据包交付给相应的网络设备
return dst_output(net, sk, skb);
}
dst_output()
static inline int dst_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
return INDIRECT_CALL_INET(skb_dst(skb)->output,
ip6_output, ip_output,
net, sk, skb);
}
(3)本地发出
ip_output()
处理 IPv4 数据包的发送,将数据包从网络协议栈传递到下层传输层。
int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
struct net_device *dev = skb_dst(skb)->dev, *indev = skb->dev;
//IPSTATS_MIB_OUT成功发送的数据包总字节数,IP_UPD_PO_STATS更新IPv4输出统计信息
IP_UPD_PO_STATS(net, IPSTATS_MIB_OUT, skb->len);
skb->dev = dev;
skb->protocol = htons(ETH_P_IP);
//调用 Netfilter 钩子NF_INET_POST_ROUTING,处理数据包的转发过程,调用ip_finish_output完成数据包的发送
return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING,
net, sk, skb, indev, dev,
ip_finish_output,
!(IPCB(skb)->flags & IPSKB_REROUTED));
}
EXPORT_SYMBOL(ip_output);
ip_finish_output()
发送 IPv4 数据包之前,结合 BPF 和 cgroup 机制对数据包进行处理,并根据处理结果决定是否继续发送数据包。
static int ip_finish_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
int ret;
//通过BPF_CGROUP_RUN_PROG_INET_EGRESS调用与cgroup相关的BPF程序
ret = BPF_CGROUP_RUN_PROG_INET_EGRESS(sk, skb);
switch (ret) {
case NET_XMIT_SUCCESS:
//成功,发送数据包
return __ip_finish_output(net, sk, skb);
case NET_XMIT_CN:
//数据包在拥塞情况下也允许发送数据包
return __ip_finish_output(net, sk, skb) ? : ret;
default:
//不允许发送,释放数据包资源,标记为被BPF CGROUP 规则丢弃SKB_DROP_REASON_BPF_CGROUP_EGRESS
kfree_skb_reason(skb, SKB_DROP_REASON_BPF_CGROUP_EGRESS);
return ret;
}
}
__ip_finish_output()
处理 IPv4 数据包的实际发送逻辑,负责根据数据包的属性(如 MTU 和分片需求)决定是否进行分片、是否使用大包分割 (GSO),以及是否调用适当的下层函数来完成数据包的发送。
static int __ip_finish_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
unsigned int mtu;
//定义CONFIG_NETFILTER以及CONFIG_XFRM且目的ip的xfrm字段存在
\#if defined(CONFIG_NETFILTER) && defined(CONFIG_XFRM)
/* Policy lookup after SNAT yielded a new policy */
if (skb_dst(skb)->xfrm) {
//标记IPSKB_REROUTED重新路由
IPCB(skb)->flags |= IPSKB_REROUTED;
//输出到下一个目的地
return dst_output(net, sk, skb);
}
\#endif
//获取当前目标路径的最大传输单元MTU,可以发送的最大数据包大小
mtu = ip_skb_dst_mtu(sk, skb);
//如果该数据包是GSO(大分组分段),GSO允许数据包在网络协议栈中尽可能大,提升速率,在实际传输前进行分段
if (skb_is_gso(skb))
return ip_finish_output_gso(net, sk, skb, mtu);
//数据包长度大于MTU/IP数据包选项要求进行分片
if (skb->len > mtu || IPCB(skb)->frag_max_size)
//分片处理
return ip_fragment(net, sk, skb, mtu, ip_finish_output2);
//不进行分片
return ip_finish_output2(net, sk, skb);
}
ip_finish_output2()
IP数据包发送流程的最后一步,负责将数据包通过相应的网络接口传输出去。涉及到多播、广播、邻居缓存、隧道重定向等处理。
static int ip_finish_output2(struct net *net, struct sock *sk, struct sk_buff *skb)
{
//从skb中提取目标条目
struct dst_entry *dst = skb_dst(skb);
//将目标路径条目转换成路由表条目
struct rtable *rt = (struct rtable *)dst;
//获取数据包目标网络设备
struct net_device *dev = dst->dev;
//计算链路层预留空间长度,hh_len链路层header信息
unsigned int hh_len = LL_RESERVED_SPACE(dev);
struct neighbour *neigh;
//ipv6网关
bool is_v6gw = false;
//多播
if (rt->rt_type == RTN_MULTICAST) {
IP_UPD_PO_STATS(net, IPSTATS_MIB_OUTMCAST, skb->len);
} //广播
else if (rt->rt_type == RTN_BROADCAST)
IP_UPD_PO_STATS(net, IPSTATS_MIB_OUTBCAST, skb->len);
//skb的头部可用空间是否足以存放链路层头部信息
if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {
//扩展空间
skb = skb_expand_head(skb, hh_len);
if (!skb)
return -ENOMEM;
}
//是否需要通过隧道传输
if (lwtunnel_xmit_redirect(dst->lwtstate)) {
//发送数据包
int res = lwtunnel_xmit(skb);
if (res < 0 || res == LWTUNNEL_XMIT_DONE)
return res;
}
rcu_read_lock_bh();
//获取下一跳邻居信息,并判断是否是ipv6网关
neigh = ip_neigh_for_gw(rt, skb, &is_v6gw);
if (!IS_ERR(neigh)) {
int res;
//确认邻居信息
sock_confirm_neigh(skb, neigh);
/* if crossing protocols, can not use the cached header */
res = neigh_output(neigh, skb, is_v6gw);
rcu_read_unlock_bh();
return res;
}
rcu_read_unlock_bh();
//没有找到邻居条目
net_dbg_ratelimited("%s: No header cache and no neighbour!\n",
__func__);
//丢弃数据包
kfree_skb_reason(skb, SKB_DROP_REASON_NEIGH_CREATEFAIL);
return -EINVAL;
}
以上是关于IP数据包经过netfilter框架收包、发包、转发路径源码分析,当我们在用户态执行iptables命令时,以INPUT
链为例,iptables -A INPUT -s 192.168.1.1 -j DROP
,经过一系列步骤到达netfilter框架,netfilter将新的规则添加到 INPUT 链,当新的数据包到达网络协议栈时会与该链中新增的规则进行逐条匹配,根据匹配的规则定义其动作,如ACCEPT
、DROP
、REJECT
、DROP
,进行处理,默认策略ACCEPT
。
以上见解如有错误,欢迎指正~~,未完待续。