目录

1 L3、L4协议跟踪初始化 nf_conntrack_proto_init()

1.1 L3协议管理

1.1.1 struct nf_conntrack_l3proto

1.1.2 L3协议注册 nf_conntrack_l3proto_register()

1.2. L4协议的管理

1.2.1 struct nf_conntrack_l4proto

1.2.2 L4协议注册 nf_conntrack_l4proto_register()

2 自定义协议L3、L4处理注册 nf_conntrack_l3proto_ipv4_init()

2.1 L3协议注册 nf_conntrack_l3proto_ipv4

2.2 “L4”协议注册 tcp、udp、“icmp”

2.3 注册钩子 ipv4_conntrack_ops


1 L3、L4协议跟踪初始化 nf_conntrack_proto_init()

L3和L4协议管理由一个单独的模块实现,其初始化是由连接跟踪子系统的初始化函数 nf_conntrack_init() 调用的,初始化函数如下:

int nf_conntrack_proto_init(void)
{unsigned int i;int err;//为默认的L4协议注册sysctl控制参数err = nf_ct_l4proto_register_sysctl(&nf_conntrack_l4proto_generic);if (err < 0)return err;//将每个层三协议族初始化为一个默认的非法struct nf_conntrack_l3proto对象for (i = 0; i < AF_MAX; i++)rcu_assign_pointer(nf_ct_l3protos[i], &nf_conntrack_l3proto_generic);return 0;
}struct nf_conntrack_l4proto nf_conntrack_l4proto_generic __read_mostly =
{.l3proto       = PF_UNSPEC,.l4proto       = 0,.name          = "unknown",.pkt_to_tuple        = generic_pkt_to_tuple,.invert_tuple       = generic_invert_tuple,.print_tuple        = generic_print_tuple,.packet          = packet,.new          = new,...
};struct nf_conntrack_l3proto nf_conntrack_l3proto_generic __read_mostly = {.l3proto    = PF_UNSPEC,.name      = "unknown",.pkt_to_tuple    = generic_pkt_to_tuple,.invert_tuple   = generic_invert_tuple,.print_tuple    = generic_print_tuple,.get_l4proto     = generic_get_l4proto,
};

1.1 L3协议管理

每个支持连接跟踪子系统的L3协议(或者说协议族),都必须先向连接跟踪子系统注册自己,实际上就是提供一个struct nf_conntrack_l3proto对象。

1.1.1 struct nf_conntrack_l3proto

L3协议在连接跟踪子系统中的定义如下:

struct nf_conntrack_l3proto
{//L3协议号,就是用协议族表示,如PF_INETu_int16_t l3proto;//协议的名字,如AF_INET协议族为“ipv4”const char *name;//根据skb内容计算tuple,tuple可以理解为连接的地址,每一个连接有两个tuple,//分别表示数据传输的两个方向。nhoff表示L3首部在skb中的偏移,如果计算成功,//返回true,否则返回false(必选回调)int (*pkt_to_tuple)(const struct sk_buff *skb, unsigned int nhoff,struct nf_conntrack_tuple *tuple);//根据orig方向的tuple,计算其反方向的tupleint (*invert_tuple)(struct nf_conntrack_tuple *inverse,const struct nf_conntrack_tuple *orig);int (*print_tuple)(struct seq_file *s, const struct nf_conntrack_tuple *);//数据包进入连接跟踪子系统后,通过检查后,会调用该回调确定返回给Netfilter框架的值,//一般来讲,连接跟踪子系统不应该过滤数据包,所以该函数大多数情况下应该返回NF_ACCEPTint (*packet)(struct nf_conn *ct, const struct sk_buff *skb,enum ip_conntrack_info ctinfo);//如果输入的数据包属于一个新的连接,那么会调用该回调int (*new)(struct nf_conn *ct, const struct sk_buff *skb);//分析skb首部,解析出L4协议号保存在protonum中,L4报文开始位置距离skb第一个//字节的偏移量保存在dataoff中,处理成功返回大于0(必选回调)int (*get_l4proto)(const struct sk_buff *skb, unsigned int nhoff,unsigned int *dataoff, u_int8_t *protonum);//下面三个字段和Netlink相关,用于和用户空间通信,先忽略int (*tuple_to_nlattr)(struct sk_buff *skb,const struct nf_conntrack_tuple *t);int (*nlattr_to_tuple)(struct nlattr *tb[],struct nf_conntrack_tuple *t);const struct nla_policy *nla_policy;
#ifdef CONFIG_SYSCTL//L3协议可以指定一些属于自己的系统配置参数struct ctl_table_header  *ctl_table_header;struct ctl_path       *ctl_table_path;struct ctl_table    *ctl_table;
#endif /* CONFIG_SYSCTL */struct module *me;
};

连接跟踪子系统用全局数组保存各个协议族注册的struct nf_conntrack_l3proto对象,每个协议族只能注册一个该对象。

struct nf_conntrack_l3proto *nf_ct_l3protos[AF_MAX] __read_mostly;
EXPORT_SYMBOL_GPL(nf_ct_l3protos);
static DEFINE_MUTEX(nf_ct_proto_mutex);

L3协议的组织结构如下图所示:

1.1.2 L3协议注册 nf_conntrack_l3proto_register()

上面也有提到过,支持连接跟踪子系统的协议族在其初始化过程中需要向连接跟踪子系统注册自己的struct nf_conntrack_l3proto对象,注册函数如下:

int nf_conntrack_l3proto_register(struct nf_conntrack_l3proto *proto)
{int ret = 0;//当前版本支持的协议族最大值为AF_MAX=34if (proto->l3proto >= AF_MAX)return -EBUSY;mutex_lock(&nf_ct_proto_mutex);//同一协议族不允许重复注册,如下面的初始化过程,nf_ct_l3_protos[]的每个元素//在初始化时都被设置成了nf_conntrack_l3proto_generic,表示尚未注册if (nf_ct_l3protos[proto->l3proto] != &nf_conntrack_l3proto_generic) {ret = -EBUSY;goto out_unlock;}//注册L3协议可能存在的系统参数ret = nf_ct_l3proto_register_sysctl(proto);if (ret < 0)goto out_unlock;//将proto记录到全局数组的对应位置,完成注册rcu_assign_pointer(nf_ct_l3protos[proto->l3proto], proto);
out_unlock:mutex_unlock(&nf_ct_proto_mutex);return ret;
}
EXPORT_SYMBOL_GPL(nf_conntrack_l3proto_register);

nf_conntrack_l3proto_unregister()是对应的去注册函数,不再赘述。

1.2. L4协议的管理

类似于L3协议的管理,连接跟踪子系统也使用一个全局的数组管理支持的L4协议,不过这个数组是二维数组。

//nf_conntrack_proto.c
//和L3协议使用同一把互斥锁
static struct nf_conntrack_l4proto **nf_ct_protos[PF_MAX] __read_mostly;
struct nf_conntrack_l3proto *nf_ct_l3protos[AF_MAX] __read_mostly;
EXPORT_SYMBOL_GPL(nf_ct_l3protos);

其实使用二维数组的原因也很简单,因为一种L3协议可以支持多种L4协议,所以该二维数组的一维索引就是协议族,然后二维长度可变,视不同协议族的支持情况而定。

1.2.1 struct nf_conntrack_l4proto

L4协议在连接跟踪子系统中的定义如下:

struct nf_conntrack_l4proto
{/* L3 Protocol number. */u_int16_t l3proto;/* L4 Protocol number. */u_int8_t l4proto;//根据skb内容生成tuple,tuple用于标示一条连接。nhoff表示L4首部在skb中的偏移;//如果转换成功,返回true,否则返回false(必选回调)。连接跟踪子系统会优先调用//L3协议的pkt_to_tuple()生成tuple,只有L3协议生成失败时才会调用L4协议的回调int (*pkt_to_tuple)(const struct sk_buff *skb, unsigned int dataoff,struct nf_conntrack_tuple *tuple);int (*invert_tuple)(struct nf_conntrack_tuple *inverse,const struct nf_conntrack_tuple *orig);int (*packet)(struct nf_conn *ct, const struct sk_buff *skb,unsigned int dataoff, enum ip_conntrack_info ctinfo,int pf, unsigned int hooknum);int (*new)(struct nf_conn *ct, const struct sk_buff *skb,unsigned int dataoff);/* Called when a conntrack entry is destroyed */void (*destroy)(struct nf_conn *ct);//L4协议可以通过该回调校验skb,校验通过返回大于0(可选回调)int (*error)(struct sk_buff *skb, unsigned int dataoff,enum ip_conntrack_info *ctinfo,int pf, unsigned int hooknum);int (*print_tuple)(struct seq_file *s, const struct nf_conntrack_tuple *);/* Print out the private part of the conntrack. */int (*print_conntrack)(struct seq_file *s, const struct nf_conn *);//netlink相关int (*to_nlattr)(struct sk_buff *skb, struct nlattr *nla,const struct nf_conn *ct);int (*from_nlattr)(struct nlattr *tb[], struct nf_conn *ct);int (*tuple_to_nlattr)(struct sk_buff *skb,const struct nf_conntrack_tuple *t);int (*nlattr_to_tuple)(struct nlattr *tb[],struct nf_conntrack_tuple *t);const struct nla_policy *nla_policy;
#ifdef CONFIG_SYSCTLstruct ctl_table_header **ctl_table_header;struct ctl_table *ctl_table;unsigned int     *ctl_table_users;
#ifdef CONFIG_NF_CONNTRACK_PROC_COMPATstruct ctl_table_header   *ctl_compat_table_header;struct ctl_table   *ctl_compat_table;
#endif
#endif//L4协议名字const char *name;struct module *me;
};

可见,L4协议的内容和L3协议基本类似,其含义和调用场景也相同。

1.2.2 L4协议注册 nf_conntrack_l4proto_register()

int nf_conntrack_l4proto_register(struct nf_conntrack_l4proto *l4proto)
{int ret = 0;//当前版本L4协议号不能超过PF_MAX=34if (l4proto->l3proto >= PF_MAX)return -EBUSY;mutex_lock(&nf_ct_proto_mutex);//注册L3协议的第一个L4协议,在全局结构nf_ct_protos[]中分配空间if (!nf_ct_protos[l4proto->l3proto]) {/* l3proto may be loaded latter. */struct nf_conntrack_l4proto **proto_array;int i;//最多分配256个,也就是说每个L3协议在连接跟踪子系统中最多可以支持256个L4协议proto_array = kmalloc(MAX_NF_CT_PROTO *sizeof(struct nf_conntrack_l4proto *), GFP_KERNEL);if (proto_array == NULL) {ret = -ENOMEM;goto out_unlock;}//分配完毕,将该L3协议的所有L4协议初始化为nf_conntrack_l4proto_generic,//和L3协议的含义一样,这表示一种未注册状态for (i = 0; i < MAX_NF_CT_PROTO; i++)proto_array[i] = &nf_conntrack_l4proto_generic;//nf_ct_protos[]指向新分配的空间nf_ct_protos[l4proto->l3proto] = proto_array;} else if (nf_ct_protos[l4proto->l3proto][l4proto->l4proto] !=&nf_conntrack_l4proto_generic) {//不是第一次注册L4协议,检查该L4协议之前是否已经注册过了ret = -EBUSY;goto out_unlock;}//注册该L4协议可能支持的sysctl系统参数ret = nf_ct_l4proto_register_sysctl(l4proto);if (ret < 0)goto out_unlock;//注册成功,保存L4协议到全局数组nf_ct_protos[]中rcu_assign_pointer(nf_ct_protos[l4proto->l3proto][l4proto->l4proto], l4proto)
out_unlock:mutex_unlock(&nf_ct_proto_mutex);return ret;
}
EXPORT_SYMBOL_GPL(nf_conntrack_l4proto_register);

对应的去注册函数为nf_conntrack_l4proto_unregister(),不再赘述。

2 自定义协议L3、L4处理注册 nf_conntrack_l3proto_ipv4_init()

static int __init nf_conntrack_l3proto_ipv4_init(void)
{int ret = 0;need_conntrack();//向Netfilter框架注册xxx_sock_opt()接口ret = nf_register_sockopt(&so_getorigdst);if (ret < 0) {printk(KERN_ERR "Unable to register netfilter socket option\n");return ret;}//向连接跟踪子系统框架注册自己支持的L4协议,分别有tcp、udp、icmpret = nf_conntrack_l4proto_register(&nf_conntrack_l4proto_tcp4);if (ret < 0) {printk("nf_conntrack_ipv4: can't register tcp.\n");goto cleanup_sockopt;}ret = nf_conntrack_l4proto_register(&nf_conntrack_l4proto_udp4);if (ret < 0) {printk("nf_conntrack_ipv4: can't register udp.\n");goto cleanup_tcp;}ret = nf_conntrack_l4proto_register(&nf_conntrack_l4proto_icmp);if (ret < 0) {printk("nf_conntrack_ipv4: can't register icmp.\n");goto cleanup_udp;}//向连接跟踪子系统框架注册自己,即L3协议--AF_INEETret = nf_conntrack_l3proto_register(&nf_conntrack_l3proto_ipv4);if (ret < 0) {printk("nf_conntrack_ipv4: can't register ipv4\n");goto cleanup_icmp;}//向Netfilter框架注册钩子函数ret = nf_register_hooks(ipv4_conntrack_ops, ARRAY_SIZE(ipv4_conntrack_ops));if (ret < 0) {printk("nf_conntrack_ipv4: can't register hooks.\n");goto cleanup_ipv4;}return ret;...
}...
module_init(nf_conntrack_l3proto_ipv4_init);
module_exit(nf_conntrack_l3proto_ipv4_fini);

2.1 L3协议注册 nf_conntrack_l3proto_ipv4  

协议族要支持连接跟踪子系统,需要向连接跟踪子系统框架注册一个L3协议对象,AF_INET协议族的L3协议对象定义如下:

struct nf_conntrack_l3proto nf_conntrack_l3proto_ipv4 __read_mostly = {.l3proto  = PF_INET,.name        = "ipv4",.pkt_to_tuple   = ipv4_pkt_to_tuple,.invert_tuple  = ipv4_invert_tuple,.print_tuple   = ipv4_print_tuple,.get_l4proto    = ipv4_get_l4proto,
#if defined(CONFIG_NF_CT_NETLINK) || defined(CONFIG_NF_CT_NETLINK_MODULE).tuple_to_nlattr = ipv4_tuple_to_nlattr,.nlattr_to_tuple = ipv4_nlattr_to_tuple,.nla_policy   = ipv4_nla_policy,
#endif
#if defined(CONFIG_SYSCTL) && defined(CONFIG_NF_CONNTRACK_PROC_COMPAT).ctl_table_path  = nf_net_ipv4_netfilter_sysctl_path,.ctl_table   = ip_ct_sysctl_table,
#endif.me        = THIS_MODULE,
};

2.2 “L4”协议注册 tcp、udp、“icmp”


struct nf_conntrack_l3proto nf_conntrack_l3proto_ipv4 __read_mostly = {.l3proto     = PF_INET,.name        = "ipv4",.pkt_to_tuple   = ipv4_pkt_to_tuple,.invert_tuple  = ipv4_invert_tuple,.print_tuple   = ipv4_print_tuple,.get_l4proto    = ipv4_get_l4proto,....me      = THIS_MODULE,
};struct nf_conntrack_l4proto nf_conntrack_l4proto_tcp4 __read_mostly =
{.l3proto       = PF_INET,.l4proto         = IPPROTO_TCP,.name            = "tcp",.pkt_to_tuple        = tcp_pkt_to_tuple,.invert_tuple       = tcp_invert_tuple,.print_tuple        = tcp_print_tuple,.print_conntrack     = tcp_print_conntrack,.packet      = tcp_packet,.new          = tcp_new,.error           = tcp_error,...
};
EXPORT_SYMBOL_GPL(nf_conntrack_l4proto_tcp4);struct nf_conntrack_l4proto nf_conntrack_l4proto_udp4 __read_mostly =
{.l3proto       = PF_INET,.l4proto     = IPPROTO_UDP,.name            = "udp",.pkt_to_tuple        = udp_pkt_to_tuple,.invert_tuple       = udp_invert_tuple,.print_tuple        = udp_print_tuple,.packet          = udp_packet,.new          = udp_new,.error           = udp_error,...
};
EXPORT_SYMBOL_GPL(nf_conntrack_l4proto_udp4);struct nf_conntrack_l4proto nf_conntrack_l4proto_icmp __read_mostly =
{.l3proto       = PF_INET,.l4proto     = IPPROTO_ICMP,.name           = "icmp",.pkt_to_tuple       = icmp_pkt_to_tuple,.invert_tuple      = icmp_invert_tuple,.print_tuple       = icmp_print_tuple,.packet         = icmp_packet,.new         = icmp_new,.error          = icmp_error,.destroy      = NULL,.me         = NULL,...
}

2.3 注册钩子 ipv4_conntrack_ops

要想真正的让整个连接跟踪子系统动起来,当然还需要向Netfilter 框架注册钩子函数,AF_INET协议族为支持连接跟踪子系统,注册了如下钩子函数:

static struct nf_hook_ops ipv4_conntrack_ops[] __read_mostly = {{.hook      = ipv4_conntrack_in,.owner     = THIS_MODULE,.pf      = NFPROTO_IPV4,.hooknum    = NF_INET_PRE_ROUTING,.priority    = NF_IP_PRI_CONNTRACK,},{.hook     = ipv4_conntrack_local,.owner      = THIS_MODULE,.pf      = NFPROTO_IPV4,.hooknum    = NF_INET_LOCAL_OUT,.priority  = NF_IP_PRI_CONNTRACK,},{.hook     = ipv4_helper,.owner       = THIS_MODULE,.pf      = NFPROTO_IPV4,.hooknum    = NF_INET_POST_ROUTING,.priority   = NF_IP_PRI_CONNTRACK_HELPER,},{.hook      = ipv4_confirm,.owner      = THIS_MODULE,.pf      = NFPROTO_IPV4,.hooknum    = NF_INET_POST_ROUTING,.priority   = NF_IP_PRI_CONNTRACK_CONFIRM,},{.hook     = ipv4_helper,.owner       = THIS_MODULE,.pf      = NFPROTO_IPV4,.hooknum    = NF_INET_LOCAL_IN,.priority   = NF_IP_PRI_CONNTRACK_HELPER,},{.hook      = ipv4_confirm,.owner      = THIS_MODULE,.pf      = NFPROTO_IPV4,.hooknum    = NF_INET_LOCAL_IN,.priority   = NF_IP_PRI_CONNTRACK_CONFIRM,},
};static int __init nf_conntrack_l3proto_ipv4_init(void)
{int ret = 0;need_conntrack();nf_defrag_ipv4_enable();ret = nf_register_sockopt(&so_getorigdst);if (ret < 0) {printk(KERN_ERR "Unable to register netfilter socket option\n");return ret;}ret = register_pernet_subsys(&ipv4_net_ops);if (ret < 0) {pr_err("nf_conntrack_ipv4: can't register pernet ops\n");goto cleanup_sockopt;}ret = nf_register_hooks(ipv4_conntrack_ops,ARRAY_SIZE(ipv4_conntrack_ops));if (ret < 0) {pr_err("nf_conntrack_ipv4: can't register hooks.\n");goto cleanup_pernet;}ret = nf_ct_l4proto_register(&nf_conntrack_l4proto_tcp4);if (ret < 0) {pr_err("nf_conntrack_ipv4: can't register tcp4 proto.\n");goto cleanup_hooks;}ret = nf_ct_l4proto_register(&nf_conntrack_l4proto_udp4);if (ret < 0) {pr_err("nf_conntrack_ipv4: can't register udp4 proto.\n");goto cleanup_tcp4;}ret = nf_ct_l4proto_register(&nf_conntrack_l4proto_icmp);if (ret < 0) {pr_err("nf_conntrack_ipv4: can't register icmpv4 proto.\n");goto cleanup_udp4;}ret = nf_ct_l3proto_register(&nf_conntrack_l3proto_ipv4);if (ret < 0) {pr_err("nf_conntrack_ipv4: can't register ipv4 proto.\n");goto cleanup_icmpv4;}...
}

钩子较多,但是它们是有规律的。把连接跟踪子系统看做一个黑盒,进入该黑盒的点为PRE_ROUTING和LOCAL_OUT,从该黑盒出去的点为LOCAL_IN和POST_ROUTING,所以连接跟踪子系统在每个入口点和出口点个注册了2个钩子。以PRE_ROUTING点为例:优先级-400注册了钩子ipv4_conntrack_defrag(),该优先级很高,保证连接跟踪模块能够最先处理数据包,该函数完成IP报文的组装(连接跟踪模块不处理分片报文);还有一个优先级为-200的钩子ipv4_conntrack_in(),执行数据包进入连接跟踪子系统时处理。在出口处有help和confirm,它们只是操作不同,但设计思想是一样的。钩子函数的用法参见《linux内核协议栈 netfilter 之连接跟踪子系统AF_INET钩子函数》

linux内核协议栈 netfilter 之连接跟踪子系统的L3 L4协议栈模块初始化与自定义注册相关推荐

  1. 走进Linux内核之Netfilter框架

    走进Linux内核之Netfilter框架 - 掘金笔者此前对Linux内核相关模块稍有研究,实现内核级通信加密.视频流加密等.话不多说直接上才艺,现在带你走进Linux内核之Netfilter框架. ...

  2. Linux内核分析 - 网络[十七]:NetFilter之连接跟踪

    内核版本:2.6.34 转载请注明 博客:http://blog.csdn.net/qy532846454 by yoyo 前面章节介绍过Netfilter的框架,地址见:http://blog.cs ...

  3. 一文读懂 Linux 下单机实现百万并发的内核黑科技:连接跟踪(Conntrack)

    公众号关注 「奇妙的 Linux 世界」 设为「星标」,每天带你玩转 Linux ! 本文介绍连接跟踪(connection tracking,conntrack,CT)的原理,应用,及其在 Linu ...

  4. tcp/ip 协议栈Linux内核源码分析十 邻居子系统分析一 概述通用邻居框架

    内核版本:3.4.39 为什么需要邻居子系统呢?因为在网络上发送报文的时候除了需要知道目的IP地址还需要知道邻居的L2 mac地址,为什么是邻居的L2地址而不是目的地的L2地址呢,这是因为目的地网络可 ...

  5. tcp/ip 协议栈Linux内核源码分析11 邻居子系统分析二 arp协议的实现处理

    内核版本:3.4.39 内核邻居子系统定义了一个基本的框架,使得不同的邻居协议可以共用一套代码.比起其它的内核模块,邻居子系统框架代码还是比较简单易懂的.邻居子系统位于网络层和流量控制子系统中间,它提 ...

  6. Linux 内核开发 - NetFilter

    入门 编写一个简单的内核模块 #include<linux/module.h> #include<linux/kernel.h> #include<linux/init. ...

  7. linux内核启动分析 三,Linux内核分析 实验三:跟踪分析Linux内核的启动过程

    贺邦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一. 实验过程 ...

  8. Linux内核--基于Netfilter的内核级包过滤防火墙实现

    知识基础:本防火墙的开发基于对Linux内核网络栈有个良好的概念,本人对网络栈的分析是基于早期版本(Linux 1.2.13),在明确了网络栈架构的前提下,上升一步分析高级版本内核中的Netfilte ...

  9. 介绍Calico eBPF数据平面:Linux内核网络、安全性和跟踪(Kubernetes、kube-proxy)

    目录 性能测试环境 Pod到Pod的吞吐量和CPU 改善kube-proxy 第一包延迟 保留外部源IP和直接服务器返回 高效的数据平面更新 "技术预览"是什么意思? 结论 推荐阅 ...

最新文章

  1. 牛客练习赛61 C 四个选项(并查集、DP、排列组合)难度⭐⭐⭐
  2. java process started_Java HistoricProcessInstanceQuery.startedBy方法代碼示例
  3. hibernate对象关系实现(二)一对一
  4. GIVE_A_TRY.exe 逆向(NCK逆向初级第9,10,11课作业)
  5. React 之 高阶组件的理解
  6. Java:数值-字符串转换(String转Double)
  7. 理解TCP为什么需要进行三次握手(白话)(转载)
  8. bzoj 2178 圆的面积并 —— 辛普森积分
  9. 多租户saas 架构_[译/注] Force.com 多租户互联网应用开发平台的设计
  10. 徽柏工业机器人_新松机器人股票(中国机器人公司排名是怎样的?)
  11. php生成excel范例,支持任意行列
  12. Spring Boot快速搭建Web开发框架(Hibernate+Thymeleaf)
  13. 开发微信网页及调试方法
  14. tp3.2中前台模板中日期时间的转换
  15. Java 解析pdf文档内容实战案例
  16. linux学习杂碎:权限
  17. 基于百度短语音API的语音识别实现
  18. C语言系列(5) --- C语言文件的操作
  19. 史蒂夫·乔布斯传记_Chapter 4: Atari and India
  20. js 购物车数量增减,总价格联动变化

热门文章

  1. 仿SlidingMenu自定义QQ侧滑菜单
  2. 1003 1、2、3、4、5...鸡兔同笼问题
  3. mat---Memory Monitor检测内存泄露
  4. 《Using Cardio-Respiratory Signals to Recognize Emotions Elicited by Watching Music Video Clips》部分意译
  5. 如何修改源码(以corda为例)
  6. evo轨迹曲线设置指令evo_config的使用
  7. [机缘参悟-93]:时间、空间、多维度、动态、全局、系统思考模型汇总
  8. 外星人 AW3423DW 评测
  9. Windows取证——基本网络命令
  10. 烽火HG680KA-免拆机-强刷固件(可救砖)