`
Michaelmatrix
  • 浏览: 206906 次
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Linux内核中netlink协议族的实现(下)

 
阅读更多
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严
禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn
5.3 连接

连接通常是针对客户端连接服务器
static int netlink_connect(struct socket *sock, struct sockaddr *addr,
int alen, int flags)
{
int err = 0;
struct sock *sk = sock->sk;
struct netlink_sock *nlk = nlk_sk(sk);
struct sockaddr_nl *nladdr=(struct sockaddr_nl*)addr;
if (addr->sa_family == AF_UNSPEC) {
// 目的地址协议族为AF_UNSPEC(未指定), 简单返回成功
sk->sk_state= NETLINK_UNCONNECTED;
nlk->dst_pid= 0;
nlk->dst_group = 0;
return 0;
}
// 限制目的地址协议族类型为AF_NETLINK
if (addr->sa_family != AF_NETLINK)
return -EINVAL;
/* Only superuser is allowed to send multicasts */
// 只有ROOT权限才能多播
if (nladdr->nl_groups && !netlink_capable(sock, NL_NONROOT_SEND))
return -EPERM;
// 没指定pid的话自动绑定一个pid
if (!nlk->pid)
err = netlink_autobind(sock);
if (err == 0) {
// 已经指定了pid或者自动绑定成功时设置sock的对方参数, 状态为连接成功
sk->sk_state= NETLINK_CONNECTED;
nlk->dst_pid = nladdr->nl_pid;
nlk->dst_group = ffs(nladdr->nl_groups);
}
return err;
}
5.4 获取sock名称

// 填充sockaddr_nl结构中的数据
static int netlink_getname(struct socket *sock, struct sockaddr *addr, int *addr_len, int
peer)
{
struct sock *sk = sock->sk;
struct netlink_sock *nlk = nlk_sk(sk);
struct sockaddr_nl *nladdr=(struct sockaddr_nl *)addr;
// 协议族
nladdr->nl_family = AF_NETLINK;
nladdr->nl_pad = 0;
*addr_len = sizeof(*nladdr);
if (peer) {
// 对方sock的pid和groups
nladdr->nl_pid = nlk->dst_pid;
nladdr->nl_groups = netlink_group_mask(nlk->dst_group);
} else {
// 自己sock的pid和groups
nladdr->nl_pid = nlk->pid;
nladdr->nl_groups = nlk->groups ? nlk->groups[0] : 0;
}
return 0;
}

5.5 poll

poll是用poll(2)或select(2)系统调用选择套接口数据是否准备好时的处理函数,netlink用的是通用
的数据报的poll处理函数dategram_poll(), 说明略。
5.6 setsockopt

设置netlink sock的各种控制参数:
static int netlink_setsockopt(struct socket *sock, int level, int optname,
char __user *optval, int optlen)
{
struct sock *sk = sock->sk;
struct netlink_sock *nlk = nlk_sk(sk);
int val = 0, err;
// sock层次要为SOL_NETLINK
if (level != SOL_NETLINK)
return -ENOPROTOOPT;
// 读取用户空间的设置信息
if (optlen >= sizeof(int) &&
get_user(val, (int __user *)optval))
return -EFAULT;
switch (optname) {
case NETLINK_PKTINFO:
// 处理NETLINK_RECV_PKTINFO标志, 非0设置, 0为清除
if (val)
nlk->flags |= NETLINK_RECV_PKTINFO;
else
nlk->flags &= ~NETLINK_RECV_PKTINFO;
err = 0;
break;
case NETLINK_ADD_MEMBERSHIP:
case NETLINK_DROP_MEMBERSHIP: {
// 加入或退出多播组
unsigned int subscriptions;
int old, new = optname == NETLINK_ADD_MEMBERSHIP ? 1 : 0;
// 检查权限
if (!netlink_capable(sock, NL_NONROOT_RECV))
return -EPERM;
// 如果当前sock的多播组为空是分配空间
if (nlk->groups == NULL) {
err = netlink_alloc_groups(sk);
if (err)
return err;
}
// 检查数据范围
if (!val || val - 1 >= nlk->ngroups)
return -EINVAL;
netlink_table_grab();
// 原来的状态标志
old = test_bit(val - 1, nlk->groups);
// 如果old=1, new=0, subscriptions-1
// 如果old=0, new=1, subscriptions+1
subscriptions = nlk->subscriptions - old + new;
// 设置或清除相应状态标志
if (new)
__set_bit(val - 1, nlk->groups);
else
__clear_bit(val - 1, nlk->groups);
// 更新sock参数
netlink_update_subscriptions(sk, subscriptions);
netlink_update_listeners(sk);
netlink_table_ungrab();
err = 0;
break;
}
default:
err = -ENOPROTOOPT;
}
return err;
}

// 分配netlink sock的多播组空间
static int netlink_alloc_groups(struct sock *sk)
{
struct netlink_sock *nlk = nlk_sk(sk);
unsigned int groups;
int err = 0;
netlink_lock_table();
// 组的数量是内核初始化时固定的, 最小值32, 尽量是8的倍数
groups = nl_table[sk->sk_protocol].groups;
if (!nl_table[sk->sk_protocol].registered)
err = -ENOENT;
netlink_unlock_table();
if (err)
return err;
// NLGRPSZ(groups)进行8字节对齐
nlk->groups = kzalloc(NLGRPSZ(groups), GFP_KERNEL);
if (nlk->groups == NULL)
return -ENOMEM;
nlk->ngroups = groups;
return 0;
}

5.7 getsockopt

获取netlink sock的各种控制参数:

static int netlink_getsockopt(struct socket *sock, int level, int optname,
char __user *optval, int __user *optlen)
{
struct sock *sk = sock->sk;
struct netlink_sock *nlk = nlk_sk(sk);
int len, val, err;
// sock层次要为SOL_NETLINK
if (level != SOL_NETLINK)
return -ENOPROTOOPT;
// 读取用户空间的查询信息
if (get_user(len, optlen))
return -EFAULT;
if (len < 0)
return -EINVAL;
switch (optname) {
case NETLINK_PKTINFO:
// 只提供一种选项信息PKTINFO
if (len < sizeof(int))
return -EINVAL;
len = sizeof(int);
// 看sock标志是否有NETLINK_RECV_PKTINFO返回1或0
val = nlk->flags & NETLINK_RECV_PKTINFO ? 1 : 0;
if (put_user(len, optlen) ||
put_user(val, optval))
return -EFAULT;
err = 0;
break;
default:
err = -ENOPROTOOPT;
}
return err;
}

5.8 发送消息

从用户层发送数据到内核, 内核的sock是接收方
static int netlink_sendmsg(struct kiocb *kiocb, struct socket *sock,
struct msghdr *msg, size_t len)
{
// sock的IO控制块
struct sock_iocb *siocb = kiocb_to_siocb(kiocb);
// socket -> sock
struct sock *sk = sock->sk;
// sock -> netlink sock
struct netlink_sock *nlk = nlk_sk(sk);
struct sockaddr_nl *addr=msg->msg_name;
u32 dst_pid;
u32 dst_group;
struct sk_buff *skb;
int err;
// scm: Socket level control messages processing
struct scm_cookie scm;
// 设置了OOB(out of band)标志, 在TCP中支持,netlink不支持
if (msg->msg_flags&MSG_OOB)
return -EOPNOTSUPP;
if (NULL == siocb->scm)
siocb->scm = &scm;
// scm这些处理是干什么的以后再看
err = scm_send(sock, msg, siocb->scm);
if (err < 0)
return err;
// 确定目的pid和组
if (msg->msg_namelen) {
if (addr->nl_family != AF_NETLINK)
return -EINVAL;
dst_pid = addr->nl_pid;
dst_group = ffs(addr->nl_groups);
if (dst_group && !netlink_capable(sock, NL_NONROOT_SEND))
return -EPERM;
} else {
dst_pid = nlk->dst_pid;
dst_group = nlk->dst_group;
}
// 如果sock的pid为0, 自动绑定一个pid
if (!nlk->pid) {
err = netlink_autobind(sock);
if (err)
goto out;
}
err = -EMSGSIZE;
// 消息长度太大
if (len > sk->sk_sndbuf - 32)
goto out;
err = -ENOBUFS;
// 新生成一个skb数据包
skb = nlmsg_new(len, GFP_KERNEL);
if (skb==NULL)
goto out;
// 设置该skb的netlink控制块参数
NETLINK_CB(skb).pid= nlk->pid;
NETLINK_CB(skb).dst_pid = dst_pid;
NETLINK_CB(skb).dst_group = dst_group;
NETLINK_CB(skb).loginuid = audit_get_loginuid(current->audit_context);
selinux_get_task_sid(current, &(NETLINK_CB(skb).sid));
memcpy(NETLINK_CREDS(skb), &siocb->scm->creds, sizeof(struct ucred));
/* What can I do? Netlink is asynchronous, so that
we will have to save current capabilities to
check them, when this message will be delivered
to corresponding kernel module. --ANK (980802)
*/
err = -EFAULT;
// 将发送的信息拷贝到skb的存储区
if (memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len)) {
kfree_skb(skb);
goto out;
}
/* @netlink_send:
*Save security information for a netlink message so that permission
*checking can be performed when the message is processed. The security
*information can be saved using the eff_cap field of the
* netlink_skb_parms structure. Also may be used to provide fine
*grained control over message transmission.
*@sk associated sock of task sending the message.,
*@skb contains the sk_buff structure for the netlink message.
*Return 0 if the information was successfully saved and message
*is allowed to be transmitted.
*/
err = security_netlink_send(sk, skb);
if (err) {
kfree_skb(skb);
goto out;
}
// 如果是多播的,先进行广播发送
if (dst_group) {
// 增加使用者计数, 使skb不会真正释放
atomic_inc(&skb->users);
netlink_broadcast(sk, skb, dst_pid, dst_group, GFP_KERNEL);
}
// 单播发送
err = netlink_unicast(sk, skb, dst_pid, msg->msg_flags&MSG_DONTWAIT);
out:
return err;
}

// netlink广播, 发送到组内的全部sock
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid,
u32 group, gfp_t allocation)
{
// netlink广播数据结构信息
struct netlink_broadcast_data info;
struct hlist_node *node;
struct sock *sk;
// 调整skb空间
skb = netlink_trim(skb, allocation);
// 填充info结构基本参数
info.exclude_sk = ssk;
info.pid = pid;
info.group = group;
info.failure = 0;
info.congested = 0;
info.delivered = 0;
info.allocation = allocation;
info.skb = skb;
info.skb2 = NULL;
/* While we sleep in clone, do not allow to change socket list */
netlink_lock_table();
// 遍历多播链表, 分别对每个sock进行单播
sk_for_each_bound(sk, node, &nl_table[ssk->sk_protocol].mc_list)
do_one_broadcast(sk, &info);
// 释放skb, 其实没有立即释放, 要先减少使用者数
kfree_skb(skb);
netlink_unlock_table();
// 如果分配了skb2,释放之
if (info.skb2)
kfree_skb(info.skb2);
if (info.delivered) {
if (info.congested && (allocation & __GFP_WAIT))
yield();
return 0;
}
if (info.failure)
return -ENOBUFS;
return -ESRCH;
}
// 单一广播
static inline int do_one_broadcast(struct sock *sk,
struct netlink_broadcast_data *p)
{
struct netlink_sock *nlk = nlk_sk(sk);
int val;
if (p->exclude_sk == sk)
goto out;
// 检查pid和组是否合法
if (nlk->pid == p->pid || p->group - 1 >= nlk->ngroups ||
!test_bit(p->group - 1, nlk->groups))
goto out;
if (p->failure) {
netlink_overrun(sk);
goto out;
}
sock_hold(sk);
if (p->skb2 == NULL) {
if (skb_shared(p->skb)) {
// 克隆skb
p->skb2 = skb_clone(p->skb, p->allocation);
} else {
// 此时skb2不会为NULL的
p->skb2 = skb_get(p->skb);
/*
* skb ownership may have been set when
* delivered to a previous socket.
*/
skb_orphan(p->skb2);
}
}
if (p->skb2 == NULL) {
// 如果还是为NULL必然是克隆失败
netlink_overrun(sk);
/* Clone failed. Notify ALL listeners. */
p->failure = 1;
// 否则发送skb2
} else if ((val = netlink_broadcast_deliver(sk, p->skb2)) < 0) {
netlink_overrun(sk);
} else {
// 数据正常发送
p->congested |= val;
p->delivered = 1;
p->skb2 = NULL;
}
sock_put(sk);
out:
return 0;
}

static __inline__ int netlink_broadcast_deliver(struct sock *sk, struct sk_buff *skb)
{
struct netlink_sock *nlk = nlk_sk(sk);
// 发送缓冲中要有足够空间
if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf &&
!test_bit(0, &nlk->state)) {
skb_set_owner_r(skb, sk);
// 添加到接收队列尾, 由于是本机内部通信, 可以自己找到要发送的目的方,
// 所以直接将数据扔给目的方, 所以是接收队列
skb_queue_tail(&sk->sk_receive_queue, skb);
// 调用netlink sock的sk_data_ready函数处理, 由此进入内核中netlink各协议
// 的回调处理
sk->sk_data_ready(sk, skb->len);
return atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf;
}
return -1;
}

// netlink单播
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock)
{
struct sock *sk;
int err;
long timeo;
// 调整skb大小
skb = netlink_trim(skb, gfp_any());
// 获取超时时间
timeo = sock_sndtimeo(ssk, nonblock);
retry:
// ssk是服务器端的sock, 然后根据pid找到客户端的sock
sk = netlink_getsockbypid(ssk, pid);
if (IS_ERR(sk)) {
kfree_skb(skb);
return PTR_ERR(sk);
}
// 将数据包附着在客户端sock上
err = netlink_attachskb(sk, skb, nonblock, timeo, ssk);
if (err == 1)
goto retry;
if (err)
return err;
// 发送netlink数据包
return netlink_sendskb(sk, skb, ssk->sk_protocol);
}
/*
* Attach a skb to a netlink socket.
* The caller must hold a reference to the destination socket. On error, the
* reference is dropped. The skb is not send to the destination, just all
* all error checks are performed and memory in the queue is reserved.
* Return values:
* < 0: error. skb freed, reference to sock dropped.
* 0: continue
* 1: repeat lookup - reference dropped while waiting for socket memory.
*/
// 注意这个是内核全局函数, 非static
int netlink_attachskb(struct sock *sk, struct sk_buff *skb, int nonblock,
long timeo, struct sock *ssk)
{
struct netlink_sock *nlk;
nlk = nlk_sk(sk);
// 检查接收缓存大小是否足够, 不够的话阻塞等待直到出错或条件满足
if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
test_bit(0, &nlk->state)) {
// 声明当前进程的等待队列
DECLARE_WAITQUEUE(wait, current);
if (!timeo) {
if (!ssk || nlk_sk(ssk)->pid == 0)
netlink_overrun(sk);
sock_put(sk);
kfree_skb(skb);
return -EAGAIN;
}
// 设置当前进程状态为可中断的
__set_current_state(TASK_INTERRUPTIBLE);
// 将sock挂接到等待队列
add_wait_queue(&nlk->wait, &wait);
// 空间不够的话阻塞, timeo为阻塞超时
if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
test_bit(0, &nlk->state)) &&
!sock_flag(sk, SOCK_DEAD))
timeo = schedule_timeout(timeo);
// 进程状态运行
__set_current_state(TASK_RUNNING);
// 删除等待队列
remove_wait_queue(&nlk->wait, &wait);
sock_put(sk);
if (signal_pending(current)) {
// 阻塞是通过超时解开的,而不是空间条件符合解开, 属于错误状态
kfree_skb(skb);
return sock_intr_errno(timeo);
}
// 返回1, 重新选sock
return 1;
}
// 条件满足, 直接将skb的所有者设为该netlink sock
skb_set_owner_r(skb, sk);
return 0;
}
// 注意这个是内核全局函数, 非static
int netlink_sendskb(struct sock *sk, struct sk_buff *skb, int protocol)
{
int len = skb->len;
// 将skb添加到接收队列末尾
skb_queue_tail(&sk->sk_receive_queue, skb);
// 调用netlink sock的sk_data_ready函数处理
sk->sk_data_ready(sk, len);
sock_put(sk);
return len;
}
5.9 接收消息

数据是内核传向用户空间的
static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock,
struct msghdr *msg, size_t len,
int flags)
{
// sock的IO控制块
struct sock_iocb *siocb = kiocb_to_siocb(kiocb);
// scm
struct scm_cookie scm;
// socket -> sock
struct sock *sk = sock->sk;
// sock -> netlink sock
struct netlink_sock *nlk = nlk_sk(sk);
// 是否是非阻塞的
int noblock = flags&MSG_DONTWAIT;
size_t copied;
struct sk_buff *skb;
int err;
// 不能带OOB标志
if (flags&MSG_OOB)
return -EOPNOTSUPP;
copied = 0;
// 接收一个数据包
skb = skb_recv_datagram(sk,flags,noblock,&err);
if (skb==NULL)
goto out;
msg->msg_namelen = 0;
// 收到的实际数据长度
copied = skb->len;
// 接收缓冲小于数据长度, 设置数据裁剪标志
if (len < copied) {
msg->msg_flags |= MSG_TRUNC;
copied = len;
}
skb->h.raw = skb->data;
// 将skb的数据拷贝到接收缓冲区
err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
if (msg->msg_name) {
// sock有效, 填写nl sock的数据
struct sockaddr_nl *addr = (struct sockaddr_nl*)msg->msg_name;
addr->nl_family = AF_NETLINK;
addr->nl_pad = 0;
addr->nl_pid= NETLINK_CB(skb).pid;
addr->nl_groups= netlink_group_mask(NETLINK_CB(skb).dst_group);
msg->msg_namelen = sizeof(*addr);
}
// 接收数据包信息标志, 将消息头拷贝到用户空间
if (nlk->flags & NETLINK_RECV_PKTINFO)
netlink_cmsg_recv_pktinfo(msg, skb);
if (NULL == siocb->scm) {
memset(&scm, 0, sizeof(scm));
siocb->scm = &scm;
}
siocb->scm->creds = *NETLINK_CREDS(skb);
skb_free_datagram(sk, skb);
if (nlk->cb && atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2)
netlink_dump(sk);
scm_recv(sock, msg, siocb->scm, flags);
out:
// 接收唤醒
netlink_rcv_wake(sk);
return err ? : copied;
}
6. 结论

netlink处理代码不是很好懂, 毕竟和其他协议不同之处是内核中同时存在服务器和客户端的sock, 因
此接收发送数据要注意数据的流向。不过在实际使用中感觉不是很稳定, 流量大时会发生各种奇异的死机现象。
分享到:
评论

相关推荐

    Linux内核中netlink协议族的实现

    介绍linux内核中netlink协议族的实现,主要是用户态与内核的通信。

    linux netlink 示例代码: 通过net link 进行 内核和用户空间的数据通讯

    用户程序源码 eknetlink.c -内核程序源码 netlink提供了一种很好很强大的的用户与内核之间的通讯机制,本文通过静态的新增一个netlink协议类型,并使用这个新的netlink类型实现用户态和内核态的双向通讯,对linux的...

    linux netlink 示例代码

    netlink提供了一种很好很强大的的用户与内核之间的通讯机制,本文通过静态的新增一个netlink协议类型,并使用这个新的netlink类型实现用户态和内核态的双向通讯,对linux的netlink通讯方式有了一个初步的认识。...

    libnl库实质是对基于netlink协议的linux内核api进行封装,从而间接来操作内核api进行工作

    libnl库实质是对基于netlink协议的linux内核api进行封装,从而间接来操作内核api进行工作;netlink底层原理则是套接字(socket),其主要作用是进行用户态和内核态的通信。

    w1:Linux内核W1 Netlink客户端

    w1 Linux内核W1 Netlink客户端Linux内核w1 netlink文档: //www.kernel.org/doc/Documentation/w1/w1.netlink通过netlink的w1可用于与用户空间中的w1硬件(主设备和从设备)一起使用,延迟显着低于通过sysfs文件。...

    netlink.pdf

    netlink socket编程why & how netlink socekt是一种用于在内核态和用户态进程之间进行数据传输的特殊的IPC。它通过为内核模块提 ...socket在内核头文件include/linux/netlink.h中定义自己的协议类型。

    audit-go, 在golang和Lua中,使用netlink协议编写的heka审计插件.zip

    audit-go, 在golang和Lua中,使用netlink协议编写的heka审计插件 #Linux 审计Heka插件( GO ):项目现在被分成两部分,用于处理linux内核审计框架的API现在由 libaudit提供,它提供了一个示例,并提供了一个例子来利用...

    Go-Linux网络块设备协议的Go实现

    NBD网络协议(包括客户端和服务器端),用于NBD的Netlink API来配置内核中的NBD客户端。一个使用这些实现的基本CLI工具。

    Linux-Kernal-Exploits-m-:Linux内核漏洞

    = 2.26) [Sudo] (须藤1.8.6p7-1.8.20) [由于UFO到非UFO路径切换而导致的内存损坏] [由BPF验证程序引起的内存损坏]( 之前Linux内核) [Netlink套接字子系统中的UAF – XFRM](4.13.11之前Linux内核) [Samba远程...

    Tenus:Go中Linux网络

    它通过与Linux内核通信,以促进在Linux主机上创建和配置网络设备。 该软件包还允许使用Linux容器(包括进行更高级的网络设置。 tenus使用的netlink协议实现。 该软件包仅与较新Linux Kernels(3.10+)配合使用,...

    puppy-hids:Linux主机入侵检测系统演示,采用netlink_audit协议,支持主机与容器进程,网络,文件监控

    响应代理定时获取在线服务器列表服务器:提供代理rpc服务器调用,从mongodb中获取代理监控配置,保存服务器在线信息到mongodb,发送日志到进行可视化代理:netlink家族的NETLINK_AUDIT协议监听SYSCALL时间,规则可以...

    linux-kernel-exploits:linux-kernel-exploits Linux平台提权伸缩集合

    = 2.26) [Sudo](Sudo 1.8.6p7-1.8.20) [由于UFO到非UFO路径切换而导致的内存损坏] [由BPF验证程序导致的内存损坏]( 之前Linux内核) [Netlink套接字子系统中的UAF – XFRM](4.13.11之前Linux内核) [Samba远程...

    RED HAT LINUX 6大全

    本书全面系统地介绍了Red Hat Linux 6。全书共分为五个部分,包括35章和四个附录。第一部分为Red Hat Linux的介绍和安装;第二部分为服务配置;第三部分为系统管理;第四部分为Linux编程;第五部分为附录。本书内容...

    分布式高性能路由器邻居发现协议实现研究*) (2006年)

    基于Linux操作系统,本文提出了适合于具有分布式结构的T比特级高性能路由器的一种ND协议实现方案,该方案利用Linux内核提供的netlink机制,分别给出了T比特路由器中先应式地址解析以及主机路由的实现方法,测试结果...

    understanding linux network internals

    Kernel Infrastructure for Component Initialization 组件初始化的底层内核(实现) Section 7.1. Boot-Time Kernel Options 内核启动选项 Section 7.2. Module Initialization Code 模块初始化 Section 7.3. ...

    linux网路编程 中文 23M 版

    1.5.1 Linux内核的主要模块............................................ 7 1.5.2 Linux的文件结构................................................ 9 1.6 G N U 通用公共许可证...................................

    harmonyos底层是linux-Doc:文件

    harmonyos底层是linux Doc 用于记录一些优秀的文档 epoll详细讲解: 多路复用: EPOLL 事件之 EPOLLRDHUP: 陈硕blog: Go内存分析: 蓝牙和wifi管理: NetworkManager proxy: DHCP doc: XScreensaver: NetworkManager...

    入门学习Linux常用必会60个命令实例详解doc/txt

    虚拟控制台的切换可以通过按下Alt键和一个功能键来实现,通常使用F1-F6 。 例如,用户登录后,按一下“Alt+ F2”键,用户就可以看到上面出现的“login:”提示符,说明用户看到了第二个虚拟控制台。然后只需按“Alt+...

    nf-hipac-0.9.2

    nf-HiPAC能十分高效的在linux 2.4的netfilter构架下执行包过滤。它一个用户空间工具,称作“nf-HiPAC”,它被设计为完美兼容'iptables -t filter'。 'nf- hipac'和使用相同的钩子钩在linux 2.4核心的网络栈中,'...

    gear-lib:Gear-Lib,用于物联网嵌入式多媒体和网络的C库

    librbtree:来自linux内核rbtree。 libsort: libvector: libdarray:动态数组 网络 librtsp:适用于ipcamera或NVR的实时流协议服务器 librtmpc:用于实时显示的实时消息协议客户端 libsock:易于使用的套接字...

Global site tag (gtag.js) - Google Analytics