当前位置: 首页 > news >正文

nginx stream proxy 模块的ssl连接源码分析

目录

  • 1. 源起
  • 2. 分析验证环境的配置
  • 3. 源码分析
    • 3.1 代理模块的请求入口点分析
    • 3.2 发起与上游服务器的连接
    • 3.3 连接回调
    • 3.4 TCP连接建立成功后为上下游数据透传做准备
    • 3.5 TCP连接的ssl上下文初始化
    • 3.6 ssl握手成功后的处理
    • 3.7 连接数据的收与发

1. 源起

  我一直来对ssl建立连接的过程一知半解,以前分析nginx代码的时候一旦碰到ssl连接部分的代码都是直接跳过,前面在分析ngx_http_upstream_dynamic_module的时候正好想到了是不是可以给它添加一个能够支持https健康检查的功能,所以今天决定沉下心来仔细分析一下nginx本身的与上游服务器建立连接的实现逻辑。

  为了简单起见,我决定选用ngx_stream_proxy_module模块作为分析学习的目标,因为相对于ngx_http_stream_proxy_module来说,前者逻辑上更加纯粹,少了七层的业务逻辑,让我能够更加专注地去分析关于ssl连接处理逻辑部分。

  希望这次通过分析,能够对ssl连接的建立以及其后续的读写交互的实现逻辑有个整体的把握,在此基础上,将来为ngx_http_upstream_dynamic_module实现一个能够支持https主动健康检测的功能。

  这次,我准备采用官方原生的最新稳定版nginx 1.24.0作为分析对象。为什么不用tengine呢?因为现在的tengine还夹杂了国密openssl的支持逻辑,采用官方原生版本更加纯粹,让分析的目标更加聚焦。

2. 分析验证环境的配置

  配置一个分析验证环境来进行测试分析还是非常简单的,过程如下:

  1. 从官网下载好nginx 1.24.0版本后进行解压,然后用以下命令进行配置:

	./configure --prefix=`pwd` --with-stream --with-stream_ssl_module

  这样nginx从源码层面就开启了支持ssl的TCP代理能力。

  2. 对nginx.conf文件进行配置,配置内容如下:

#user  nobody;
worker_processes  1;#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;#pid        logs/nginx.pid;events {worker_connections  1024;
}stream {server {listen 9000;proxy_ssl on;               /* 连接上游服务器采用ssl协议 */proxy_pass 104.193.88.77:443;}
}

  3. 启动nginx,进行curl测试:

 curl "http://127.0.0.1:9000/test/" -v*   Trying 127.0.0.1:9000...
* Connected to 127.0.0.1 (127.0.0.1) port 9000 (#0)
> GET /test/ HTTP/1.1
> Host: 127.0.0.1:9000
> User-Agent: curl/7.81.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 500 Internal Server Error
< Content-Length: 0
< Content-Type: text/plain; charset=utf-8
< Date: Wed, 07 Feb 2024 02:57:01 GMT
< Server: bfe
< 
* Connection #0 to host 127.0.0.1 left intact

  从上面的信息中已经可以看到上游服务器已经响应了,只不过它响应是500错误,这个无所谓,至少表明https透明代理的功能已经正常工作了。

3. 源码分析

  本文主要聚焦在ssl连接逻辑的分析,所以中间会跳过和ssl逻辑不太相关的代码,虽然可能这部分对nginx本身的功能逻辑非常重要。另外,本文也假设只是TCP代理,不对UDP代理进行分析。

  下面直接进入主题,从代理模块的请求入口点开始分析。

3.1 代理模块的请求入口点分析

  代理模块的请求入口点是ngx_stream_proxy_handler函数,一旦客户端和nginx建立了TCP连接后,nginx就会调用代理模块的这个函数,开始与上游服务器建立连接。

  该函数源码主要就是以下列出的三个步骤:

static void
ngx_stream_proxy_handler(ngx_stream_session_t *s)
{/* 创建ngx_stream_upstream_t上下文,对它进行必要的初始化,并关联到ngx_stream_session_t中 *//* 如果上游服务器的地址已经解析好就调用ngx_stream_proxy_connect开始连接上游服务器 *//* 如果上游服务器的地址需要域名解析则开启异步解析流程 */   
}

  因此,我们需要重点关注的是ngx_stream_proxy_connect,它负责与上游服务器建立TCP连接。

3.2 发起与上游服务器的连接

static void
ngx_stream_proxy_connect(ngx_stream_session_t *s)
{ngx_int_t                     rc;ngx_connection_t             *c, *pc;ngx_stream_upstream_t        *u;ngx_stream_proxy_srv_conf_t  *pscf;c = s->connection;c->log->action = "connecting to upstream";pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);u = s->upstream;u->connected = 0;u->proxy_protocol = pscf->proxy_protocol;if (u->state) {u->state->response_time = ngx_current_msec - u->start_time;}u->state = ngx_array_push(s->upstream_states);if (u->state == NULL) {ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);return;}ngx_memzero(u->state, sizeof(ngx_stream_upstream_state_t));u->start_time = ngx_current_msec;u->state->connect_time = (ngx_msec_t) -1;u->state->first_byte_time = (ngx_msec_t) -1;u->state->response_time = (ngx_msec_t) -1;/* 创建SOCKET,并发起异步连接请求*/rc = ngx_event_connect_peer(&u->peer);ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "proxy connect: %i", rc);if (rc == NGX_ERROR) {ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);return;}u->state->peer = u->peer.name;if (rc == NGX_BUSY) {ngx_log_error(NGX_LOG_ERR, c->log, 0, "no live upstreams")

相关文章:

  • VLAN间通信
  • Redis篇之缓存雪崩
  • OpenAI研究揭示:ChatGPT对生物武器制造影响有限
  • PKI - 04 证书授权颁发机构(CA) 数字证书
  • Leetcode 322 零钱兑换
  • Hadoop搭建(完全分布式)
  • TI的电量计驱动在卸载时导致Linux卡死
  • C++ dfs搜索枚举(四十九)【第九篇】
  • Spark安装(Yarn模式)
  • WebAssembly002 FFmpegWasmLocalServer项目
  • 单选全选功能实现
  • k8s弃用docker后使用ctr导入镜像
  • 代码随想录算法训练营29期|day43 任务以及具体任务
  • leetcode-hot100树的专题
  • 验证码倒计时:用户界面的小细节,大智慧
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • Apache的80端口被占用以及访问时报错403
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • codis proxy处理流程
  • GDB 调试 Mysql 实战(三)优先队列排序算法中的行记录长度统计是怎么来的(上)...
  • Hibernate最全面试题
  • iOS筛选菜单、分段选择器、导航栏、悬浮窗、转场动画、启动视频等源码
  • Java Agent 学习笔记
  • JS创建对象模式及其对象原型链探究(一):Object模式
  • Linux Process Manage
  • Quartz初级教程
  • Redis 中的布隆过滤器
  • 动态魔术使用DBMS_SQL
  • 浅谈web中前端模板引擎的使用
  • 实现菜单下拉伸展折叠效果demo
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 学习笔记:对象,原型和继承(1)
  • 译自由幺半群
  • 自制字幕遮挡器
  • ​Base64转换成图片,android studio build乱码,找不到okio.ByteString接腾讯人脸识别
  • ​sqlite3 --- SQLite 数据库 DB-API 2.0 接口模块​
  • #微信小程序(布局、渲染层基础知识)
  • (7)STL算法之交换赋值
  • (待修改)PyG安装步骤
  • (附源码)springboot宠物医疗服务网站 毕业设计688413
  • (附源码)ssm考试题库管理系统 毕业设计 069043
  • (附源码)计算机毕业设计SSM智慧停车系统
  • (力扣记录)235. 二叉搜索树的最近公共祖先
  • (四) Graphivz 颜色选择
  • (五)关系数据库标准语言SQL
  • (一)VirtualBox安装增强功能
  • (已解决)vue+element-ui实现个人中心,仿照原神
  • (原創) 是否该学PetShop将Model和BLL分开? (.NET) (N-Tier) (PetShop) (OO)
  • (轉貼) UML中文FAQ (OO) (UML)
  • .chm格式文件如何阅读
  • .java 指数平滑_转载:二次指数平滑法求预测值的Java代码
  • .NET Entity FrameWork 总结 ,在项目中用处个人感觉不大。适合初级用用,不涉及到与数据库通信。
  • .net 按比例显示图片的缩略图
  • .NET 发展历程
  • .NET/ASP.NETMVC 大型站点架构设计—迁移Model元数据设置项(自定义元数据提供程序)...