本文主要是介绍内核实现PPTP数据通道,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
开源的PPTP程序pptp-1.4.0未使用内核的新版PPTP驱动程序,如文章所述。结果是,所有的GRE报文都要上送到应用层pptpd处理,处理完GRE报头之后,还需要通过ppp的tty驱动送入内核的PPP系统处理内层的ppp数据部分。内核新增加的PPTP模块把GRE的处理放到了内核驱动中,不再上送PPTPD应用。并且,内核PPTP驱动提供了一个新的协议族套接口AF_PPPOX,可以为上层应用使用提供接收GRE报文,另外也可以对接内核PPP系统,作为内核PPP的通道使用。
PPTP-GRE套接口的建立
根据PPTP协议在PPTP的协商完成之后,开始交互PPTP-GRE的报文。所以在此之前,pptpd服务端程序需要创建一个PPPOX的套接口,以便接收PPTP-GRE报文,同时需要绑定本地的call id和地址,用于区分不同的pptp连接。pptp_bind函数处理套接口绑定请求,套接口地址需要指定为sockaddr_pppox类型,每个call id对应一个内核中的通道channel,故绑定函数主要是添加一个channel,如果用户未指定call id,内核将自动选择一个未使用的id,最大支持65535个id值。参见函数add_chan,内核将call id与套接口的对应关系保存在全局变量callid_sock中。之后将此套接口的状态更改为已绑定PPPOX_BOUND。
static int add_chan(struct pppox_sock *sock, struct pptp_addr *sa)
{if (!sa->call_id) {call_id = find_next_zero_bit(callid_bitmap, MAX_CALLID, call_id + 1);if (call_id == MAX_CALLID) {call_id = find_next_zero_bit(callid_bitmap, MAX_CALLID, 1);}sa->call_id = call_id;} else if (test_bit(sa->call_id, callid_bitmap)) {goto out_err;}sock->proto.pptp.src_addr = *sa;set_bit(sa->call_id, callid_bitmap);rcu_assign_pointer(callid_sock[sa->call_id], sock);
}
另外此套接口还需要绑定一个远端通信的PPPOX套接口地址,以及作为一个通道channel注册自身到内核的PPP系统中。具体由pptp_connect函数实现。用户层需要在connect函数中提供远端的地址(sockaddr_pppox),并且初始化通道参数,比如操作集ops、头部长度hdrlen等,调用ppp_register_channel函数进行注册。
static int pptp_connect(struct socket *sock, struct sockaddr *uservaddr, int sockaddr_len, int flags)
{po->chan.private = sk;po->chan.ops = &pptp_chan_ops;po->chan.hdrlen = 2 + sizeof(struct pptp_gre_header);error = ppp_register_channel(&po->chan);opt->dst_addr = sp->sa_addr.pptp;sk->sk_state |= PPPOX_CONNECTED;
}
PPTP-GRE通道提供给内核PPP系统发送和ioctl控制的函数接口,注册完成之后ppp报文即可由此通道发送。至此,PPTP-GRE套接口的初始化工作完成。
static const struct ppp_channel_ops pptp_chan_ops = {.start_xmit = pptp_xmit,.ioctl = pptp_ppp_ioctl,
};
GRE-PPP报文接收
内核使用gre_add_protocol注册了PPTP GRE的接收函数pptp_rcv,注意第二个参数GREPROTO_PPTP,其值为1,即为GRE协议的版本1。版本0为通用的CISCO定义的GRE标准。这样在数据包由IP层向四层协议提交时,根据协议号47(GRE)和版本1,将会进入到pptp_rcv函数处理。
static const struct gre_protocol gre_pptp_protocol = {.handler = pptp_rcv,
};
static int __init pptp_init_module(void)
{err = gre_add_protocol(&gre_pptp_protocol, GREPROTO_PPTP);
}
pptp_rcv函数查找是否有AF_PPPOX套接口在监听此数据包中指定的call_id,判断数据包的源地址是否与套接口中指定目的地址相同,此两个条件已经在套接口初始化中分别使用bind和connect系统调用完成,找到之后,将数据包送往此套接口,通过sk_receive_skb调用接收。条件判断在函数lookup_chan中实现。
static struct pppox_sock *lookup_chan(u16 call_id, __be32 s_addr)
{struct pppox_sock *sock;struct pptp_opt *opt;sock = rcu_dereference(callid_sock[call_id]);if (sock) {opt = &sock->proto.pptp;if (opt->dst_addr.sin_addr.s_addr != s_addr)sock = NULL;elsesock_hold(sk_pppox(sock));}return sock;
}
在套接口创建(pptp_create)时,内核注册了套接口接收函数(pptp_rcv_core),此函数负责处理GRE头部信息字段,之后将数据包送往内核ppp系统进行处理,即ppp_input函数。
static int pptp_rcv_core(struct sock *sk, struct sk_buff *skb)
{ppp_input(&po->chan, skb);return NET_RX_SUCCESS;
}
对于PPP协商报文,由内核PPP系统与用户层PPPD交互处理,对于PPP数据可直接在内核的PPP系统中处理完成,最终通过PPTP通道注册的start_xmit回调函数(pptp_xmit)发出,ppto_xmit对数据包封装GRE头部信息和IP头部信息,调用ip_local_out发往对端。
以上可见,PPTP-GRE数据包的处理不再需要到用户层pptpd进行处理,通过PPTP套接口驱动在内核中就可处理,以及与内核PPP系统进行直接的交互。PPTP-GRE数据流程如下:
+--------------+| || USER PPPD || |+--------------+| || |+----------------+ +--------------+IN ---->| |----->| || KERNEL PPTP | | KERNEL PPP |OUT <----| |<-----| |+----------------+ +--------------+
内核版本
Linux-4.15
这篇关于内核实现PPTP数据通道的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!