云服务器价格_云数据库_云主机【优惠】最新活动-搜集站云资讯

企业邮箱_查看数据库所有表_0元

小七 141 0

了解您的SCM权限

随着TLS1.3在今年早些时候被批准,我还记得我们是如何在Cloudflare开始使用它的。两年多前,我们决定成为TLS1.3的早期采用者。这是一个非常重要的决定,我们非常认真地对待它。Cloudflare使用nginx处理用户流量已经不是什么秘密。一个鲜为人知的事实是,我们有几个nginx实例在运行。我不会详细介绍,但是有一个实例的任务是接受端口443上的连接,并将它们代理到nginx的另一个实例,该实例实际处理请求。否则它的功能相当有限。我们亲切地称之为nginxsl。当时我们在nginx中使用OpenSSL来实现TLS和Crypto,但是OpenSSL(和BoringSSL)还没有宣布tls1.3支持的时间表,因此我们必须实现自己的tls1.3堆栈。显然,我们需要一个不会影响任何不启用TLS1.3的客户机的实现。我们还需要一些可以快速迭代的东西,因为那时的规范非常灵活,而且我们可以经常发布,而不必担心Cloudflare堆栈的其他部分。在它上面实现ssl是显而易见的解决方案。我们使用的OpenSSL版本是1.0.2,但我们不仅期待着用1.1.0版或BoringSSL(我们最终做到了),而且它在我们的堆栈中根深蒂固,如此脆弱,以至于我们无法实现我们既定的目标,而不会冒出严重的错误。相反,Filippo Valsorda和Brendan McMillion建议更简单的方法是在Go TLS库之上实现tls1.3,并制作nginx ssl(Go ssl)的Go副本。围棋很容易迭代和原型化,有一个强大的标准库,而且我们有很多围棋天才可以使用,所以它很有意义。因此,tls崔斯诞生了。问题仍然存在,我们如何在让nginx处理所有以前版本的TLS的同时只处理tls1.3?问题就在这里。TLS 1.3和旧版本的TLS都在端口443上通信,众所周知,只有一个应用程序可以侦听给定的TCP端口,并且该应用程序是nginx,它仍然可以处理大部分TLS通信量。在Go中,我们可以将所有的TCP数据传输到另一个连接中,从而有效地创建一个额外的代理层,但是这其中的乐趣在哪里呢?而且它似乎有点低效。满足SCM权限那么如何使两个不同的进程,用两种不同的编程语言编写,共享同一个TCP套接字呢?幸运的是,Linux(或者更确切地说是UNIX)为我们提供了所需的工具。您可以使用UNIX域套接字在应用程序之间传递文件描述符,就像UNIX连接中的所有其他内容一样,文件也是文件。查看man 7 unix,我们可以看到以下内容:辅助信息使用sendmsg(2)和recvmsg(2)发送和接收辅助数据。由于历史原因,下面列出的辅助消息类型如下使用SOL帴u套接字类型指定,即使它们是AF帴UNIX特有的。要发送它们,请将结构cmsghdr的cmsg_level字段设置为SOL_套接字和cmsg_type字段。了解更多信息见cmsg(3)。SCM_权限从另一个发送或接收一组打开的文件描述符过程。数据部分包含文件的整数数组描述符。传递的文件描述符的行为就像已使用dup(2)创建。从技术上讲,你不会发送"文件描述符"。您在代码中处理的"文件描述符"只是指向进程的本地文件描述符表的索引,而本地文件描述符表又指向操作系统的open file表,最后指向代表文件的vnode。因此,另一个进程观察到的"文件描述符"很可能具有不同的数值,尽管它指向同一个文件。我们也可以按照建议查看man 3 cmsg,以找到一个关于如何使用SCM权限的简便示例:结构msghdr msg={0};结构cmsghdr*cmsg;int myfds[NUM_FD];/*包含要传递的文件描述符*/内*fdptr;union{/*辅助数据缓冲区,包装在union中以确保其适当对齐*/char buf[CMSG_SPACE(sizeof(myfds))];结构cmsghdr align;}u;msg.msg_控件=u.buf;msg.msg\u控制器=sizeof(单位:buf);cmsg=cmsg_FIRSTHDR(&msg);cmsg->cmsg_level=SOL_插座;cmsg->cmsg_type=SCM_权限;cmsg->cmsg_len=cmsg_len(sizeof(int)*NUM_FD);fdptr=(int*)CMSG_DATA(CMSG);/*初始化有效载荷*/memcpy(fdptr,myfds,NUM_FD*sizeof(int));这就是我们决定使用的。我们让OpenSSL从已建立的TCP连接中读取"clienthello"消息。如果"Client Hello"指示TLS版本1.3,我们将使用SCM_权限将其发送到Go进程。Go进程将依次尝试解析"Client Hello"的其余部分,如果成功,它将继续进行tls1.3连接,一旦失败,它将把文件描述符返回给OpenSSL,以便定期处理。那么你到底是如何实现这样的功能的呢?因为在我们的例子中,我们确定C进程将监听TCP连接,所以我们的另一个进程必须监听UNIX套接字,因为C将要转发的连接。例如在Go中:类型scmListener struct{*net.UnixListener}类型scmConn struct{*net.UnixConn}var path="/tmp/scm"_示例.sock"func listenSCM()(*scmListener,错误){系统调用。取消链接(路径)地址,错误:=net.ResolveUnixAddr("unix",路径)如果出错!等于零{返回nil,err}ul,错误:=net.ListenUnix网站("unix",地址)如果出错!等于零{返回nil,err}错误=操作系统.Chmod(路径0777)如果出错!等于零{返回nil,err}return和scmListener{ul},无}func(l*scmListener)Accept()(*scmConn,错误){uc,错误:=l.AcceptUnix()如果出错!等于零{返回nil,err}return&scmConn{uc},无}然后在C过程中,对于要传递的每个连接,我们将首先连接到该套接字:int connect\u unix(){结构sockaddr_un addr={.sun_family=AF_UNIX,.sun_path="/tmp/scm"_示例.sock"};int unix_sock=套接字(AF_unix,sock_STREAM,0);if(unix_sock==-1)返回-1;if(connect(unix_sock,(struct sockaddr*)&addr,sizeof(addr))==-1){关闭(unix_sock);返回-1;}返回unix_sock;}为了实际传递文件描述符,我们使用man 3 cmsg中的示例:int send_fd(int unix_sock,int fd){struct iovec iov={.iov_base=":)",//必须至少发送一个字节.iov_len=2};工会{char buf[CMSG_SPACE(sizeof(fd))];结构cmsghdr align;}u;struct msghdr msg={.msg\u iov=&iov,.msg_iovlen=1,.msg_control=u.buf,.msg_controllen=sizeof(u.buf)};结构cmsghdr*cmsg=cmsg_FIRSTHDR(&msg);*cmsg=(struct cmsghdr){.cmsg_level=SOL_套接字,.cmsg_type=SCM_权限,.cmsg_len=cmsg_len(sizeof(fd))};memcpy(CMSG_数据(CMSG),&fd,sizeof(fd));return sendmsg(unix_sock,&msg,0);}然后在Go中接收文件描述符:函数(c*scmConn)ReadFD()(*操作系统文件,错误){msg,oob:=make([]字节,2),make([]字节,128)_,oobn,,u,错误:=c.ReadMsgUnix(msg,oob)如果出错!等于零{返回nil,err}cmsgs,错误:=syscall.ParseSocketControlMessage(oob[0:oobn])如果出错!等于零{返回nil,err}否则如果len(cmsgs)!=1{返回零,错误。新的("接收到的CMSG数量无效")}fds,错误:=syscall.parseUnix权限(&cmsgs[0])如果出错!等于零{返回nil,err}否则,如果伦(fds)!=1{返回零,错误。新的("接收的FD数量无效")}fd:=os.NewFile文件(uintptr(fds[0]),"")如果fd==nil{返回零,错误。新的("无法打开fd")}返回fd,无}铁锈我们也可以在Rust中这样做,尽管Rust中的标准库还不支持UNIX套接字,但是它允许您通过libc板条箱来寻址C库。警告,前方有不安全代码!首先,我们要在Rust中实现一些UNIX套接字功能:使用libc::*;使用std::io::prelude::*;使用std::net::TcpStream;使用std::os::unix::io::FromRawFd;使用std::os::unix::io::RawFd;fn errno\u str()->字符串{让strerr=unsafe{strerror(*\uu error())};设c_str=unsafe{std::ffi::CStr::from_ptr(strerr)};c_字符串有损()。归自己所有()}pub结构UNIXSocket{fd:RawFd,}发布结构UNIXConn{fd:RawFd,}UNIXSocket的impl-Drop{fn-drop(&mut self){不安全{关闭(自我.fd) };}}UNIXConn的impl-Drop{fn-drop(&mut self){不安全{关闭(自我.fd) };}}执行UNIXSocket{pub fn new()->结果{匹配不安全{socket(AF\-UNIX,SOCK_-STREAM,0)}{-1=>错误(errno_str()),fd@@=>确定(UNIXSocket{fd}),}}pub fn bind(self,address:&str)->结果{断言!(地址.len()错误(errno_str()),_=>正常(自身),}}pub fn listen(self)->结果{匹配不安全{听(自我.fd,50)}{-1=>错误(errno_str()),_=>正常(自身),}}pub fn accept(&self)->结果{匹配不安全{接受(自我.fd,std::ptr::null_mut(),std::ptr::null_mut())}{-1=>错误(errno\u str())