c/c++语言实现wss客户端Nginx反向代理
需求:
最近公司让实现一个工具,通过这个工具可以与后台服务器进程建立连接并发送数据包。这个工具实际上相当于将游戏客户端的网络部分的功能剥离出来。利用这个工具可以达到的目的非常多,其中包括模拟发包探测后台进程是否正常运行,模拟发包检验后台代码鲁棒性以及对抄袭我们公司游戏的竞争对手发起DDos攻击(估计是最后一个原因领导才想要做的吧,给别人平淡的生活来点刺激)。
过程分析:
为了用c语言实现该工具,需要对当前项目的技术栈有个了解。
其中客户端和Nginx是通过TCP建立连接,然后在此基础上利用openSSL将连接升级为加密连接。最后在此连接上利用http协议握手将此连接升级为websocket协议。
Ngnix和后台服务器进程是通过websocket明文通信的。
过程实现:
通过上述的过程分析就能知道解决以下问题就能将需求完整实现:
1.配置Nginx反向代理http:
其中server块就是虚拟主机,在里面设置监听哪个端口。Location块是根据用户请求的URI来进行不同的定位,定位到不同的处理方式上,匹配成功即进行相关的操作。
server { #限制单IP连接数 limit_conn perip 100; #监听端口 listen 443 ssl; #listen 443; #配置域名为servername server_name xxxxxxx.net; #配置ssl,其中有加密算法和证书 ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_certificate /usr/local/ssl/Nginx/xxxxxx.pem; ssl_certificate_key /usr/local/ssl/Nginx/xxxxx.key; ssl_session_timeout 60m; ssl_prefer_server_ciphers on; location / { # 判断HTTP的Upgrade协议头,当为websocket时,转给ws,反之转给http if ( $http_upgrade ~* websocket$ ) { proxy_pass http://127.0.0.1:1001; } proxy_pass http://127.0.0.1:80; proxy_http_version 1.1; proxy_read_timeout 3600s; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'Upgrade'; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; } }
其中location的规则中匹配了两条规则,其中一条是如果$http_upgrade字段中包含websocket字符串,那么将请求转发到1001端口的进程中。在这里是对http升级到websockt协议时起的作用。当升级到websocket协议以后,后续的数据包依然会转发到1001端扣的进程中。即使数据包中已经没有包含websocket字符串了。
2.调用哪个openSSL接口实现加密。
依据Nginx配置里面配置的:
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
SSL_library_init(); SSLeay_add_ssl_algorithms(); SSL_load_error_strings(); const SSL_METHOD *meth = TLSv1_2_client_method(); SSL_CTX *ctx = SSL_CTX_new (meth); ssl = SSL_new (ctx); if (!ssl) { printf("Error creating SSL.\n"); log_ssl(); return -1; } sock = SSL_get_fd(ssl); SSL_set_fd(ssl, s); int err = SSL_connect(ssl); if (err <= 0) { printf("Error creating SSL connection. err=%x\n", err); log_ssl(); fflush(stdout); return -1; } printf ("SSL connection using %s\n", SSL_get_cipher (ssl));
3.如何利用http将协议升级为websocket协议
客户端拼装一个头发送给服务端,例如一个http request header:
GET / HTTP/1.1 Upgrade:websocket Connection: Upgrade Host: xxx.xxx.net Sec-WebSocket-Key: J2BJc+GQuSw34hi2TjyVpg== Sec-WebSocket-Version: 13
4.http的额外知识
1.HTTP是无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
2.客户端http请求的格式:
3.http请求方式:常见: GET, POST方法
4.http常见状态码:
404:资源不存在。
101:切换协议(Websockt切换成功以后会返回)
500:内部服务器错误。
200:请求成功
源码:
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <openssl/ssl.h> #include <openssl/err.h> #include <string.h> using namespace std; SSL *ssl; int sock; int RecvPacket() { int len=100; char buf[1000000]; do { len=SSL_read(ssl, buf, 100); buf[len]=0; printf("%s\n",buf); // fprintf(fp, "%s",buf); } while (len > 0); if (len < 0) { int err = SSL_get_error(ssl, len); if (err == SSL_ERROR_WANT_READ) return 0; if (err == SSL_ERROR_WANT_WRITE) return 0; if (err == SSL_ERROR_ZERO_RETURN || err == SSL_ERROR_SYSCALL || err == SSL_ERROR_SSL) return -1; } } int SendPacket(const char *buf) { int len = SSL_write(ssl, buf, strlen(buf)); if (len < 0) { int err = SSL_get_error(ssl, len); switch (err) { case SSL_ERROR_WANT_WRITE: return 0; case SSL_ERROR_WANT_READ: return 0; case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_SYSCALL: case SSL_ERROR_SSL: default: return -1; } } } void log_ssl() { int err; while (err = ERR_get_error()) { char *str = ERR_error_string(err, 0); if (!str) return; printf(str); printf("\n"); fflush(stdout); } } int main(int argc, char *argv[]) { int s; s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) { printf("Error creating socket.\n"); return -1; } struct sockaddr_in sa; memset (&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("xxx.xxx.xxx.xxx"); //nginx所在服务器的ip sa.sin_port = htons (443); socklen_t socklen = sizeof(sa); if (connect(s, (struct sockaddr *)&sa, socklen)) { printf("Error connecting to server.\n"); return -1; } SSL_library_init(); SSLeay_add_ssl_algorithms(); SSL_load_error_strings(); const SSL_METHOD *meth = TLSv1_2_client_method(); SSL_CTX *ctx = SSL_CTX_new (meth); ssl = SSL_new (ctx); if (!ssl) { printf("Error creating SSL.\n"); log_ssl(); return -1; } sock = SSL_get_fd(ssl); SSL_set_fd(ssl, s); int err = SSL_connect(ssl); if (err <= 0) { printf("Error creating SSL connection. err=%x\n", err); log_ssl(); fflush(stdout); return -1; } printf ("SSL connection using %s\n", SSL_get_cipher (ssl)); char *request = char *request = "GET / HTTP/1.1\r\nUpgrade:websocket\r\nConnection: Upgrade\r\nHost: xxxxxxx(服务器域名)\r\nSec-WebSocket-Key: J2BJc+GQuSw34hi2TjyVpg==\r\n\r\n"; SendPacket(request); RecvPacket(); return 0; }
编译链接的时候需要用到openssl库和加密算法库:
g++ main.c -lssl -lcrypto