热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux4.13/4.14内核中带来的ULP(UpperLayerProtocol)

序过了一个很爽的国庆假期,跟小小的小男朋友家长一起回其老家尝到了潮汕美食,南澳岛捕鱼捕虾,海鲜撑到爆,回到深圳次日小小另一个小朋友家长又带我们到东莞长安尝到了正宗的恩施土家菜,几天下来喝了几

了一个很爽的国庆假期,跟小小的小男朋友家长一起回其老家尝到了潮汕美食,南澳岛捕鱼捕虾,海鲜撑到爆,回到深圳次日小小另一个小朋友家长又带我们到东莞长安尝到了正宗的恩施土家菜,几天下来喝了几顿爽酒,吃喝玩乐再没有比这更惬意的了,这多亏了小小拓展了我们的社交圈子…

  我想起了14年在上海跟邻居朋友一起从嘉定自驾到苏州,而后在一家金鸡湖边上的日料店作到杯盘狼藉而不思归,玩味了彻底淋漓江南烟雨中的感觉,想到了几年前的那些许一醉方休后的枝枝蔓蔓,加之假期后期看到摇滚君公众号关于唐朝乐队丁武的帖子,更加是热血沸腾,然后温州皮鞋厂老板又去了川西青藏高原旅游让我又忆起八个月前那难忘的高原之旅(只不过温州老板走的是入滇的线路),窦唯大仙人又出了新歌(为手游《重返魔域》而创作的主题曲)并有所争议…九味杂陈不一而足,我只能默默地看和想,这不,沉淀下来的是关于Linux 4.13/4.14内核ULP的一些内容,特意总结了一些乱语,以示自己活着的现实。


yet another KTLS

Linux 4.13/4.14内核带来了一个新玩意儿,它叫ULP(Upper Layer Protocol),这是一个框架,引入的初衷旨在支持KTLS(Kernel TLS Support),这就跟为了支持TCP BBR而对整个TCP实现进行重构一样。ULP完全就是为KTLS量身定制的,只是额外的,它恰到好处的可以做一些其它方面很漂亮的事情。本文后面将给出一个Dmeo。

  什么?KTLS?这难道不是早就有了么?

  是的,在Linux内核中支持TLS这个思路早就被Facebook提出来了,我之前也写过这方面的一篇文章:
《来自Facebook的KTLS(Kernel SSL/TLS)原理和实例》:http://blog.csdn.net/dog250/article/details/53868519
那么,Linux 4.14内核中的KTLS和Dave Watson的原始的KTLS实现又有何不同呢?答案是,4.14的KTLS利用ULP作为其底层支撑,妙处在于,ULP不但可以支撑KTLS,而且可以作为一个通用的框架来支撑其它的功能。

  而对于原始的KTLS,它基于一种新型的套接字类型来支持TLS记录协议,这种实现并不优雅,如果你要实现另外一个协议,就需要再来一个套接字类型,最终会形成像UNIX sockopt那样的噩梦。ULP不一般,它简直就是一个平铺在“传输层”之上的“任意上层”,从而完美映射了OSI模型的7层模型!


Linux协议栈

协议分层模型上来讲,Linux仅仅实现了TCP/IP模型,因此在Linux内核协议栈里是看不到传输层以上的各层的,如果你想实现一个表示层或者会话层,你就必须使用某种用户态的库,比如作为会话层协议的SSL/TLS握手协议,以及作为表示层协议的SSL/TLS记录协议,你可以使用OpenSSL来实现(libssl,libcrypto),但是在内核协议栈中并不支持它们。

  另一方面,从BSD socket接口上来说,socket仅仅提供一个接口规范,并没有规定接口如何实现。我们知道,在Windows系统中,socket程序必须初始化SPI,即必须以WSAStartup作为起始,安装了特定供应商的socket实现的WinSock,socket的行为便可以和特定的WinSock服务供应商的实现绑定,我在很早以前写过这么一个Demo:
《Windows上的OpenVPN如何封装真实IP作为源地址》:http://blog.csdn.net/dog250/article/details/7988939
很早前玩TAP网卡时写的一篇,现在Linux也可以实现类似的功能了。


动机

果你要问把一些编码封装之类的功能放到内核里实现到底有什么好处,答案可不仅仅是这可以提高性能这么简单,很多人对待用户态实现和内核态实现之间的比较时,总喜欢从性能入手,这其实远不正确。内核支持比库支持更底层,可以让你远离库依赖导致的兼容性,并且可以让你做到不改一行用户态代码就可以用新的协议封装数据
诱惑够大了吧!事实上,很多时候性能并非关注点,用户态的库实现性能如今都堪比甚至超越内核实现了,至于说用户态和内核态之间的数据拷贝开销,也有很多不同的解决之道。


Linux 4.14的KTLS

Linux 4.14内核内置了KTLS的支持,然而如果你使用它的话,请务必注意,目前的KTLS并不是一个完整的TLS实现,它仅仅实现记录协议的一部分(即对称加密,目前依然没有实现对称解密操作)并完全没有实现握手协议,相关的资料链接列如下:
KTLS支撑-ULP
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=734942cc4ea6478eed125af258da1bdbb4afe578
ULP说明
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=99c195fb4eea405160ade58f74f62aed19b1822c
KTLS实现
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3c4d7559159bfe1e3b94df3a657b2cda3a34e218
仔细看下这个实现,就会发现,它虽然没有完整实现整个TLS,但是搭建了一个好用的架子,要比Dave Watson的原始版本规整很多,在ULP基础上,所有的功能都可以很容易实现,让实现者更多的关注于业务逻辑而不是平台相关的细节,这是多么的OO啊!


UTP Demo-Caesar cipher

了,以下才是本文我想着重表达的东西,即正文。

  本文中,我想给出一个Demo,实现一个无缝的凯撒加密传输,所谓的无缝,就是说交互双方并不了解自己的数据被凯撒加密算法加密了,应用程序依然使用send/recv这类socket接口,然而仅仅通过一个显式的setsockopt或者一个被劫持的setsockopt调用,数据就被加密了,待数据到达接收端的时候,它被自然解密而还原。秘密何在,我来揭开!

  首先看一下传输双方的代码,我以一个典型的基于TCP协议的C/S程序为例,首先我给出C和S的代码:

服务端代码(编译成server)

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

int port = 1234;
// 两边的key必须一致
int key = 12354;

#define TCP_ULP 31
#define TCP_NONE 100

int main()
{
int sockfd;
int ret;
char buf[256];
struct sockaddr_in serveraddr;
struct sockaddr_in clientaddr;
int addr_len = sizeof(clientaddr);
int clientfd;

bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);

sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1){
perror("socket error_1");
return 1;
}

ret = bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if(ret == -1){
perror("bind error_1");
close(sockfd);
return 1;
}

ret = listen(sockfd, 5);
if (ret <0) {
perror("listen");
close(sockfd);
return 1;
}

while(1){
clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, (socklen_t*)&addr_len);
if(clientfd <0) {
perror("accept");
continue;
}
// 设置使用支持凯撒加密的ULP
setsockopt(clientfd, SOL_TCP, TCP_ULP, "Caesar cipher", sizeof("Caesar cipher"));
perror("ulp init");
// 设置加密算法的key
setsockopt(clientfd, SOL_TCP, TCP_NONE, &key, sizeof(int));
perror("cipher init");

ret = recv(clientfd, buf, sizeof(buf), 0);
if (ret <0) {
perror("recv error");
close(clientfd);
close(sockfd);
return 1;
}
buf[ret] = 0;
printf("%s\n", buf);
close(clientfd);
}
close(sockfd);
return 0;
}

客户端代码(编译成client)

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

int port = 1234;
// 两边的key必须一致
int key = 12354;

#define TCP_ULP 31
#define TCP_NONE 100

int main()
{
int sockfd;
int i = 0;
int ret;
struct sockaddr_in serveraddr;
char *buf = "abcdefg";

bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");

sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket error!");
return 1;
}
// 设置使用凯撒加密的ULP
setsockopt(sockfd, SOL_TCP, TCP_ULP, "Caesar cipher", sizeof("Caesar cipher"));
perror("ulp init");
// 设置加密算法的key
setsockopt(sockfd, SOL_TCP, TCP_NONE, &key, sizeof(int));
perror("cipher init");

ret = connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if (ret <0) {
perror("connect");
close(sockfd);
return 1;
}

ret = send(sockfd, buf, sizeof(buf), 0);
if (ret <0) {
perror("recvfrom error");
perror("connect");
return 1;
}

close(sockfd);
return 0;
}

先不要看代码里的那些setsockopt,只管看send/recv这些,可以看到和往常并没有什么不一样,客户端发送给服务端一串字符串“abcdefg”,服务端如实打印出来的就是“abcdefg”,毫不失真,然而,如果你抓包,就会看到下面的包:

这里写图片描述

显然,数据被加密了!

  没有隧道,没有任何额外的外部配置,仅仅一个setsockopt就能让数据被加密?匪夷所思吧…这就是ULP实现的表示层,我们马上就来看看这是怎么实现的。


非常简单,内核中的ULP框架实际上做了两件事:

  1. ULP支持框架做的事
    替换setsockopt为自己的setsockopt回调,默认调用原始的setsockopt。

  2. 具体ULP实现做的事
    实现setsockopt回调,在setsockopt回调中替换proto结构体,择情况重新实现proto结构体中的sendmsg,sendpage,recvmsg等回调函数。

实现有点简陋,但先不评鉴,先实现了一个Demo再说,我接下来马上就给出实现,即凯撒加密的实现,代码如下(编译成Caesar_cipher.ko):

#include 
#include

static struct proto Caesar_cipher_base_prot;
static struct proto ulp_Caesar_cipher_prot;

#define TCP_NONE 100

struct Caesar_cipher_context {
// 简陋的密钥
int key;
// 保留的原始的proto结构的setsockopt函数
int (*setsockopt)(struct sock *sk, int level, int optname, char __user *optval, unsigned int optlen);
// 保留的原始的proto结构的getsockopt函数
int (*getsockopt)(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen);
// 保留的原始的proto结构的sendmsg函数
int (*sendmsg)(struct sock *sk, struct msghdr *msg, size_t len);
// 保留的原始的proto结构的recvmsg函数
int (*recvmsg)(struct sock *sk, struct msghdr *msg, size_t len, int noblock, int flags, int *addr_len);
// 保留的原始的proto结构的close函数
void (*close)(struct sock *sk, long timeout);
};

static inline struct Caesar_cipher_context *Caesar_cipher_get_ctx(const struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
return icsk->icsk_ulp_data;
}

// 感觉实现这个没有什么意义,暂不实现
static int Caesar_cipher_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen)
{
struct Caesar_cipher_context *ctx = Caesar_cipher_get_ctx(sk);
return ctx->getsockopt(sk, level, optname, optval, optlen);
}

static int Caesar_cipher_setsockopt(struct sock *sk, int level, int optname,
char __user *optval, unsigned int optlen)
{
struct Caesar_cipher_context *ctx = Caesar_cipher_get_ctx(sk);
struct proto *prot = NULL;
int val;

if (level != TCP_NONE) {
return ctx->setsockopt(sk, level, optname, optval, optlen);
}

if (get_user(val, (int __user *)optval)) {
ctx->key = 0;
} else {
ctx->key = val;
}

// 替换socket的proto并保留原始的close回调
prot = &ulp_Caesar_cipher_prot;
ctx->close = sk->sk_prot->close;
sk->sk_prot = prot;

return 0;
}

int Caesar_cipher_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
{
struct Caesar_cipher_context *ctx = Caesar_cipher_get_ctx(sk);
char *userbuf;
struct msghdr newmsg;
struct iovec iov[1];
int err;

mm_segment_t oldfs = get_fs();

// 分配一个新的buffer,这是为了用户空间和内核空间冲突
userbuf = vmalloc(size);
if (!userbuf) {
return -ENOMEM;
}

iov[0].iov_base = userbuf;
iov[0].iov_len = size;
err = memcpy_from_msg(userbuf, msg, size);
if (err) {
vfree(userbuf);
return -EINVAL;
}

// inline encrypt 凯撒加密操作
{
int i = 0;
for (i = 0; i userbuf[i] = userbuf[i] + ctx->key;
}
}

iov_iter_init(&newmsg.msg_iter, WRITE, iov, 1, size);
newmsg.msg_name = NULL;
newmsg.msg_namelen = 0;
newmsg.msg_cOntrol= NULL;
newmsg.msg_cOntrollen= 0;
newmsg.msg_flags = msg->msg_flags;

// 申明现在是在内核态发送数据
set_fs(KERNEL_DS);
// 调用原始的sendmsg发送新分配的buffer
err = ctx->sendmsg(sk, &newmsg, size);
set_fs(oldfs);

vfree(userbuf);

return err;
}

// 本回调函数的实现和sendmsg思路无异
int Caesar_cipher_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock,
int flags, int *addr_len)
{
struct Caesar_cipher_context *ctx = Caesar_cipher_get_ctx(sk);
int ret, err;

char *userbuf;
struct msghdr newmsg;
struct iovec iov[1];

mm_segment_t oldfs = get_fs();

userbuf = vmalloc(len);
if (!userbuf) {
return -ENOMEM;
}

iov[0].iov_base = userbuf;
iov[0].iov_len = len;

iov_iter_init(&newmsg.msg_iter, READ, iov, 1, len);
newmsg.msg_name = NULL;
newmsg.msg_namelen = 0;
newmsg.msg_cOntrol= NULL;
newmsg.msg_cOntrollen= 0;
newmsg.msg_flags = msg->msg_flags;

set_fs(KERNEL_DS);
ret = ctx->recvmsg(sk, &newmsg, len, nonblock, flags, addr_len);
set_fs(oldfs);

// inline decrypt 凯撒解密操作
{
int i = 0;
for (i = 0; i userbuf[i] = userbuf[i] - ctx->key;
}
}

err = memcpy_to_msg(msg, userbuf, ret);
if (err) {
ret = -EINVAL;
}

vfree(userbuf);
return ret;
}

static int Caesar_cipher_init(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct Caesar_cipher_context *ctx;
int rc = 0;

ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx) {
rc = -ENOMEM;
goto out;
}

icsk->icsk_ulp_data = ctx;

// 保留set/getsockopt的核心操作
ctx->setsockopt = sk->sk_prot->setsockopt;
ctx->getsockopt = sk->sk_prot->getsockopt;
// 保留send/recvmsg的核心操作
ctx->sendmsg = sk->sk_prot->sendmsg;
ctx->recvmsg = sk->sk_prot->recvmsg;

// 整体替换socket的proto操作回调,但事实上就是复制了原始的proto操作回调,真正替换操作在setsockopt中进行
sk->sk_prot = &Caesar_cipher_base_prot;
out:
return rc;
}

static void Caesar_cipher_close(struct sock *sk, long timeout)
{
struct Caesar_cipher_context *ctx = Caesar_cipher_get_ctx(sk);
kfree (ctx);
ctx->close(sk, timeout);
}

static struct tcp_ulp_ops tcp_Caesar_cipher_ulp_ops __read_mostly = {
.name = "Caesar cipher",
.owner = THIS_MODULE,
.init = Caesar_cipher_init,
};

static int __init Caesar_cipher_register(void)
{
Caesar_cipher_base_prot = tcp_prot;
Caesar_cipher_base_prot.setsockopt = Caesar_cipher_setsockopt;
Caesar_cipher_base_prot.getsockopt = Caesar_cipher_getsockopt;

// 替换个别的回调函数
ulp_Caesar_cipher_prot = Caesar_cipher_base_prot;
ulp_Caesar_cipher_prot.sendmsg = Caesar_cipher_sendmsg;
ulp_Caesar_cipher_prot.recvmsg = Caesar_cipher_recvmsg;
ulp_Caesar_cipher_prot.close = Caesar_cipher_close;

// 这个注册不再解释,无非就是把一个结构体链入一个链表,等到setsockopt执行ULP绑定时再查找而已
tcp_register_ulp(&tcp_Caesar_cipher_ulp_ops);

return 0;
}

static void __exit Caesar_cipher_unregister(void)
{
tcp_unregister_ulp(&tcp_Caesar_cipher_ulp_ops);
}

module_init(Caesar_cipher_register);
module_exit(Caesar_cipher_unregister);

MODULE_AUTHOR("Zhao Ya ");
MODULE_DESCRIPTION("Linux 4.14 ULP Demo");
MODULE_LICENSE("GPL");

根据以上的描述和源码,这就很容易理解为什么需要两个setsockopt来完成注册和绑定了。第一个setsockopt只是替换了setsockopt函数,第二个setsockopt替换了部分的proto操作回调并且设置了加密密钥,然后新的proto操作就可以自由挥洒了,仅此而已。

  如果你仔细看KTLS的实现,它也仅此而已。妙处就在ULP而别无他。然而,然而我不是说可以无缝进行应用程序的数据加密吗?如果应用程序不能改,也就说说不能分别加入那两个setsockopt,那可怎么办?简单!用Linux的LD_PRELOAD来劫持系统调用即可,仅以服务端为例。请把上述的服务端的C代码中的setsockopt分别注释掉,然后编译以下的代码:

#define _GNU_SOURCE
#include
#include
#include
#include

#define TCP_ULP 31
#define TCP_NONE 100

int key = 12354;

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
int clisd;
typeof(accept) *_accept;

_accept = dlsym(RTLD_NEXT, "accept");
clisd = (*_accept)(sockfd, addr, addrlen);
setsockopt(clisd, SOL_TCP, TCP_ULP, "Caesar cipher", sizeof("Caesar cipher"));
perror("client ulp init");
setsockopt(clisd, SOL_TCP, TCP_NONE, &key, sizeof(int));
perror("client cipher init");

return clisd;
}

编译方法如下:

gcc -shared -fPIC preload_ulp.c -o preload_ulp.so

然后再运行服务端前引用一个环境变量:

LD_PRELOAD=./preload_ulp.so ./server

即可!

  好啦,现在可以统一运行它们了。首先加载ULP内核模块

insmod ./Caesar_cipher.ko

然后执行server

LD_PRELOAD=./preload_ulp.so ./server

最后执行client

./client

OK,服务端完美打印出了“abcdefg”。结束了吗?结束了!


现在要评鉴它了,ULP确实有点Low,基于它的Low,我也只能实现一个凯撒加密,更高大上的实现我止步于那些复杂的数据结构解析,当然这可能是因为我术业不精所致,但这难道不也印证了ULP的接口不友好吗?一个友好的接口可以让人们编程像是烧锅炉一样,就像吃饭一样,当一个接口调用像是做引体向上一样的时候,那就悲哀了!

  我的感触是,并且我的愿望是,实现一个iptables的target,可以为任意一个既有的连接在端到端实现无缝的凯撒加密,比如这样:

iptables -A INPUT -s 1.1.1.1 -p tcp --sport 123 -d 2.2.2.2 --dport 234 -j GaiusDecrypt --key 12354 
iptables -A OUTPUT -s 1.1.1.1 -p tcp --sport 123 -d 2.2.2.2 --dport 234 -j GaiusDecrypt --key 12354

然后对应五元组TCP-1.1.1.1:123/2.2.2.2:234流量就被密钥为12354给加密了,这比较帅了,所需要做的只是在target处理函数中查找一下socket然后调用一下setsockopt而已,或者更加简单的,找到conntrack,然后将key设置到conntrack表项。然而这只是一个非常简单的思路,更加复杂的玩法尽在想象中,完全不受限制。

过程

写这个Caesar cipher内核模块是艰辛的,这倒不是说ULP原理上有多难,代码多么难写,事实上代码是很简单的,问题出在环境的搭建上。

  我的手头有Debian 9的开发机,它内置了4.9的内核,已经够新的了吧,但是很抱歉,ULP以及KTLS只在4.13+上被支持,这个从下面的链接可以看到:
Linux 4.13#Networking:https://kernelnewbies.org/Linux_4.13
所以我选择了4.14版本进行Demo的开发。我为源码编译分配了4G的磁盘空间,使用Debian 9自带的config文件进行编译,然而没过多久就提示空间不足,我只能全部重来,这个时候我把空间加大了一倍,到达8G的样子,于是我就睡觉了,次日醒来,几个提示空间不足的大字扎瞎了我的眼睛…由于我看到已经到最后一步了,所以我把空间加大到了9G,开启脚本后就去上班去了,晚上下班到家,终于搞定了….这才开始写这个Demo,空间不足问题足足耗了我一天一夜的时间!

  我在想什么时候编译一个内核都需要这么大的磁盘空间了,难道诸发行版真的是越来越臃肿了吗?这还怎么玩啊!都胖成这样了,为何还不减肥…


推荐阅读
  • 基于iSCSI的SQL Server 2012群集测试(一)SQL群集安装
    一、测试需求介绍与准备公司计划服务器迁移过程计划同时上线SQLServer2012,引入SQLServer2012群集提高高可用性,需要对SQLServ ... [详细]
  • 用阿里云的免费 SSL 证书让网站从 HTTP 换成 HTTPS
    HTTP协议是不加密传输数据的,也就是用户跟你的网站之间传递数据有可能在途中被截获,破解传递的真实内容,所以使用不加密的HTTP的网站是不 ... [详细]
  • 本文详细介绍了 InfluxDB、collectd 和 Grafana 的安装与配置流程。首先,按照启动顺序依次安装并配置 InfluxDB、collectd 和 Grafana。InfluxDB 作为时序数据库,用于存储时间序列数据;collectd 负责数据的采集与传输;Grafana 则用于数据的可视化展示。文中提供了 collectd 的官方文档链接,便于用户参考和进一步了解其配置选项。通过本指南,读者可以轻松搭建一个高效的数据监控系统。 ... [详细]
  • 本文详细介绍了在Linux系统上编译安装MySQL 5.5源码的步骤。首先,通过Yum安装必要的依赖软件包,如GCC、GCC-C++等,确保编译环境的完备。接着,下载并解压MySQL 5.5的源码包,配置编译选项,进行编译和安装。最后,完成安装后,进行基本的配置和启动测试,确保MySQL服务正常运行。 ... [详细]
  • 本文介绍了 Go 语言中的高性能、可扩展、轻量级 Web 框架 Echo。Echo 框架简单易用,仅需几行代码即可启动一个高性能 HTTP 服务。 ... [详细]
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • 为什么多数程序员难以成为架构师?
    探讨80%的程序员为何难以晋升为架构师,涉及技术深度、经验积累和综合能力等方面。本文将详细解析Tomcat的配置和服务组件,帮助读者理解其内部机制。 ... [详细]
  • 在 CentOS 6.4 上安装 QT5 并启动 Qt Creator 时,可能会遇到缺少 GLIBCXX_3.4.15 的问题。这是由于系统中的 libstdc++.so.6 版本过低。本文将详细介绍如何通过更新 GCC 版本来解决这一问题。 ... [详细]
  • Linux CentOS 7 安装PostgreSQL 9.5.17 (源码编译)
    近日需要将PostgreSQL数据库从Windows中迁移到Linux中,LinuxCentOS7安装PostgreSQL9.5.17安装过程特此记录。安装环境&#x ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • 在配置Nginx的SSL证书后,虽然HTTPS访问能够正常工作,但HTTP请求却会遇到400错误。本文详细解析了这一问题,并提供了Nginx配置的具体示例。此外,还深入探讨了DNS服务器证书、SSL证书的申请与安装流程,以及域名注册、查询方法和CDN加速技术的应用,帮助读者全面了解相关技术细节。 ... [详细]
  • Keepalived 提供了多种强大且灵活的后端健康检查机制,包括 HTTP_GET、SSL_GET、TCP_CHECK、SMTP_CHECK 和 MISC_CHECK 等多种检测方法。这些健康检查功能确保了高可用性环境中的服务稳定性和可靠性。通过合理配置这些检查方式,可以有效监测后端服务器的状态,及时发现并处理故障,从而提高系统的整体性能和可用性。 ... [详细]
  • 深入探索HTTP协议的学习与实践
    在初次访问某个网站时,由于本地没有缓存,服务器会返回一个200状态码的响应,并在响应头中设置Etag和Last-Modified等缓存控制字段。这些字段用于后续请求时验证资源是否已更新,从而提高页面加载速度和减少带宽消耗。本文将深入探讨HTTP缓存机制及其在实际应用中的优化策略,帮助读者更好地理解和运用HTTP协议。 ... [详细]
  • 线程能否先以安全方式获取对象,再进行非安全发布? ... [详细]
  • 在PHP中实现腾讯云接口签名,以完成人脸核身功能的对接与签名配置时,需要注意将文档中的POST请求改为GET请求。具体步骤包括:使用你的`secretKey`生成签名字符串`$srcStr`,格式为`GET faceid.tencentcloudapi.com?`,确保参数正确拼接,避免因请求方法错误导致的签名问题。此外,还需关注API的其他参数要求,确保请求的完整性和安全性。 ... [详细]
author-avatar
mobiledu2502876847
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有