SNI是一个TLS的扩展字段,经常用于访问域名跳转到不同的后端地址。

配置方式如下:打开nginx.conf文件,以ttbb/nginx:nake镜像为例/usr/local/openresty/nginx/conf/nginx.conf

如下为默认的nginx.conf配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117

#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;
}


http {
include mime.types;
default_type application/octet-stream;

#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';

#access_log logs/access.log main;

sendfile on;
#tcp_nopush on;

#keepalive_timeout 0;
keepalive_timeout 65;

#gzip on;

server {
listen 80;
server_name localhost;

#charset koi8-r;

#access_log logs/host.access.log main;

location / {
root html;
index index.html index.htm;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}


# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;

# location / {
# root html;
# index index.html index.htm;
# }
#}


# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;

# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;

# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;

# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;

# location / {
# root html;
# index index.html index.htm;
# }
#}

}

在最后面添加上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
stream {

map $ssl_preread_server_name $name {
backend.example.com backend;
default backend2;
}

upstream backend {
server 192.168.0.3:12345;
server 192.168.0.4:12345;
}

upstream backend2 {
server 127.0.0.1:8071;
}

server {
listen 12346;
proxy_pass $name;
ssl_preread on;
}
}

这个时候,我们已经开启了SNI转发的功能,如果你使用backend.example.com的域名访问服务器,就会转发到backend,如果使用其他域名,就会转发到backend2

测试的时候,让我们在/etc/hosts里进行设置,添加

1
127.0.0.1 backend.example.com

然后进行请求

1
curl https://backend.example.com:12346

这里注意请求要使用https,http协议或者是tcp可没有SNI的说法

nginx-sni-backend

发现请求的确实是backend

然后测试请求127.0.0.1:12346

1
curl https://127.0.0.1:12346

nginx-sni-127

业务需求分析与解决方案#

在业务场景中,当需要利用ping命令对主机进行心跳探测时,直接在代码中fork进程执行ping命令虽然可行,但这种方法开销较大,并且处理流程易出错,与使用标准库相比缺乏优雅性。因此,本文探讨了使用Java的InetAddress类的isReachable方法作为替代方案。

根据资料指出,Java的InetAddress类在root用户权限下通过执行ping命令进行探测,在非root用户权限下则通过访问TCP端口7进行探测。为验证这一点,本文撰写了相应的demo代码并进行了测试(详见:GitHub - heart-beat)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import lombok.extern.slf4j.Slf4j;

import java.net.InetAddress;

@Slf4j
public class PingTestMain {

public static void main(String[] args) throws Exception {
String testIp = System.getProperty("TestIp");
InetAddress inetAddress = InetAddress.getByName(testIp);
boolean addressReachable = inetAddress.isReachable(500);
log.info("address is reachable is {}", addressReachable);
}

}

测试实验#

root用户下执行程序#

java-ping-root-success.png

java程序打印结果也是true

在普通用户权限下的测试#

java-ping-user-fail.png

此时可以看到,我们的客户端程序向目标tcp7端口发送了一个报文,虽然java程序打印结果为true,但是因为收到了RST包导致的.在当今的网络安全要求下,7端口往往不会开放

在目标网络屏蔽TCP端口7的情况下执行程序#

1
iptables -A INPUT -p tcp --dport 7 -j DROP

发送的报文没有收到RST包,此时java程序返回false.不是我们预期的结果

普通用户权限下携带特权的测试#

进一步的研究发现,Java程序发送ping命令需要创建raw socket,这要求程序具有root权限或cap_net_raw权限。赋予Java程序创建raw socket的权限后重新测试,发现程序能够正确发送ping命令,达到预期效果。

1
2
setcap cap_net_raw+ep /usr/java/jdk-13.0.1/bin/java

发现如下报错

1
java: error while loading shared libraries: libjli.so: cannot open shared object file: No such file or directory

使用https://askubuntu.com/questions/334365/how-to-add-a-directory-to-linker-command-line-in-linux规避添加so文件权限

随后抓包,发现还是发送了ping命令,达到了我们预期的效果

总结#

本文通过一系列测试得出结论,root用户权限下的Java程序会使用ping命令进行探测。若普通用户不具备相应权限,则会尝试探测TCP端口7,但在安全组未开启该端口的情况下会导致预期结果不一致。推荐赋予java程序特权,使得InetAddress类能够使用ping命令进行探测

如何使用显示过滤器#

wireshark-display-filter1
或者按住 CTRL + F,输入显示过滤器
wireshark-display-filter2

二层显示过滤器举例#

长度小于128字节的数据包#

frame.len<=128

排除ARP流量#

!arp

三层显示过滤器举例#

只显示192.168.0.1 IP相关数据包#

ip.addr==192.168.0.1

四层显示过滤器举例#

排除RDP流量#

!tcp.port==3389

具有SYN标志的TCP数据包#

tcp.flags.syn==1

具有RST标志的TCP数据包#

tcp.flags.rst==1

TCP确认时间较久#

tcp.analysis.ack_rtt > 0.2 and tcp.len == 0
###启用TCP Relative Sequence Number的情况
如何启用?
Edit -> Preferences -> Protocols -> TCP Relative Sequence Numbers

握手被对方拒绝的包#

tcp.flags.reset == 1 && tcp.seq == 1

客户端重传#

tcp.flags.syn == 1 && tcp.analysis.retransmission

Tcp包含#

tcp contains {str}

应用层显示过滤器举例#

所有http流量#

http

文本管理流量#

tcp.port == 23 || tcp.port == 21

文本email流量#

email || pop || imap

只显示访问某指定主机名的HTTP协议数据包#

http.host == <”hostname”>

只显示包含HTTP GET方法的HTTP协议数据包#

http.request.method == ‘GET’

只显示HTTP 客户端发起的包含指定URI请求的HTTP协议数据包#

http.request.uri == <”Full request URI”>

只显示包含ZIP文件的数据包#

http matches “.zip” && http.request.method == ‘GET’

如何使用捕获过滤器#

点击捕获,选项,然后在所选择的捕获过滤器上输入对应的捕获表达式

wireshark-capture-filter1

wireshark-capture-filter2

抓包过滤器#

  • type(类型) 限定符: 比如host,net,port限定符等
  • dir(方向) 限定符: src dst
  • Proto(协议类型)限定符: ether ip arp

二层过滤器举例#

1
2
3
4
5
6
7
8
tcp dst port 135 //tcp协议,目标端口为135的数据包
ether host <Ethernet host> //让wireshark只抓取这个地址相关的以太网帧
ether dst <Ethernet host>
ether src <Ethernet src>
ether broadcast //Wireshark只抓取所有以太网广播流量
ether multicast //只抓取多播流量
ether proto <protocol>
vlan <vlan_id>

三层过滤器举例#

1
2
3
4
5
6
7
8
ip #只抓取ipv4流量
ipv6
host 10.0.0.2
dest host <host>
src host <host>
broadcast #ip广播包
multicast #ip多播包
ip proto <protocol code> #ip数据包有多种类型,比如TCP(6), UDP(17) ICMP(1)

只抓取源于或者发往IPv6 2001::/16的数据包#

net 2001::/16

只抓取ICMP流量#

ip proto 1

只抓取ICMP echo request流量#

icmp[icmptype]==icmp-echo
icmp[icmptype]==8

只抓取特定长度的IP数据包#

ip[2:2] ==

只抓取具有特定TTL的IP数据包#

ip[8] ==

抓取数据包的源和目的IP地址相同#

ip[12:4] ==1 ip[16:4]

四层抓包过滤器举例#

1
2
3
4
port <port>
dst port <port>
src port <port>
tcp portrange <p1>-<p2>

只抓取TCP中SYN或者FIN的数据包#

tcp [tcpflags] & (tcp-syn | tcp-fin) != 0

只抓所有RST标记位置为1的TCP数据包#

tcp[tcpflags] & (tcp-rst) != 0

tcp头部的常用标记位#

  • SYN: 用来表示打开连接
  • FIN: 用来表示拆除连接
  • ACK: 用来确认收到的数据
  • RST: 用来表示立刻拆除连接
  • PSH: 用来表示应将数据提交给末端应用程序处理

抓取所有标记位都未置1的TCP流量#

该报文可能用于端口探测,即如果
tcp[13] & 0x00 = 0

设置了URG位的TCP数据包#

URG位,表示该数据包十分紧急,不进入缓冲区,直接送给进程
tcp[13] & 32 == 32

设置了ACK位的TCP数据包#

tcp[13] & 16 == 16

设置了PSH位的TCP数据包#

PSH代表这个消息要从缓冲区立刻发送给应用程序
tcp[13] & 8 == 8

设置了RST位的TCP数据包#

tcp[13] & 4 == 4

设置了SYN位的TCP数据包#

tcp[13] & 2 == 2

设置了FIN位的TCP数据包#

tcp[13] & 1 == 1

TCP SYN-ACK数据包#

tcp[13] == 18

抓取目的端口范围的数据包#

tcp portrange 2000-2500

###tcpdump捕获过滤器

常见命令介绍

1
tcpdump -w hzj.pcap -s0 -iany port 1028

上面的命令代表
-w hzj.pcap 存储在hzj.pcap这个文件中
-s 0 代表抓取字节数不限制,在大多数linux系统下,默认捕获每个帧的前96个字节

tcpdump捕获一定范围的端口(9200-9400)#

tcpdump portrange 9200-9400

tcpdump -r 可以阅读捕获的文件(建议拷贝到wireshark中分析)#

WireShark安装#

wireshark在windows和mac上的安装方式都比较简单,下面是Linux下的安装方式

1
2
3
4
5
sudo apt-add-repository ppa:wireshark-dev/stable
sudo apt-get update
sudo apt-get install wireshark
#以root权限启动
sudo wireshark

WireShark的名字解析#

wireshark-name-resolve

  • L2层的名字解析,对Mac地址进行解析,返回机器名
  • L3层 ip解析为域名
  • L4层 端口号解析为协议端口号

Wireshark抓到的包更改时间格式#

wireshark-time-format

查看EndPoint#

点击Statistics->EndPoints,可以查看每一个捕获文件里的每个端点

wireshark-endpoint

查看网络会话#

Statistics->Conversations. 查看地址A和地址B,以及每个设备发送或收到的数据包和字节数

wireshark-conversation

基于协议分层结构的统计数据#

Statistics->Protocol Hierarchy

wireshark-protocol-hierarchy

跟随流功能#

右键选中一个数据包,然后右键,follow。比如我在这里跟随一个tcp流

wireshark-tcp-stream

//这里也可以使用decode as解码功能,但是没有例子,暂不附图

查看IO图#

Statistics->IO Graphs

wireshark-io-graph

双向时间图#

Statistics->TCP Stream Graph -> Round Trip Time Graph
wireshark-rtt-graph

数据流图#

Statistics->Flow Graph
wireshark-flow-graph

专家信息#

Analyze->Expert Info Composite
wireshark-expert-info

触发的专家信息#

对话消息#

窗口更新 由接收者发送,用来通知发送者TCP接收窗口的大小已被改变#

注意消息#

TCP重传输 数据包丢失的结果,发生在收到重复的ACK,或者数据包的重传输计时器超时的时候#

重复ACK 当一台主机没有收到下一个期望序列的数据包时,它会生成最近收到一次数据的重复ACK#

零窗口探查ACK 用来响应零窗口探查数据包#

窗口已满 用来通知传输主机及其接收者的TCP接收窗口已满#

警告消息#

上一段丢失 指明数据包丢失,发生在当数据流中一个期望的序列号被跳过时。#

收到丢失数据包的ACK 发生在当一个数据包已经确认丢失但受到了其ACK数据包时#

保活 当一个连接的保活数据包出现时触发#

零窗口 当接收方已经达到TCP接收窗口大小时,发出一个零窗口通知,要求发送方停止传输数据#

乱序 当数据包被乱序接收时,会利用序列号进行检测#

快速重传输 一次重传会在收到一个重复ACK的20ms内进行#

WireShark性能#

Statistics -> Summary 查看平均速度#

Analyze -> Expert Infos#

Statistics -> TCP StreamGraph -> TCP Sequence Graph(Stenens)#

TCP Previous segment not captured#

在TCP传输过程中,同一台主机发出的数据段应该是连续的,即后一个包的Seq号等于前一个包的Seq + Len. 如果在网络包中没有找到,就会出现这个错误

TCP ACKed unseen segment#

Wireshark发现被Ack的那个包没被wireshark捕获

TCP Out-of-Order#

在TCP传输过程中,同一台主机发出的数据段应该是连续的,即后一个包的Seq号等于前一个包的Seq +
Len.当Wireshark发现后一个包的Seq号小于前一个包的Seq+Len 就乱序le

TCP Dup ACK#

当乱序或者丢包的时候,接收方会收到Seq号比期望值大的包,每收到一个这种包就会Ack一次期望的Seq值

TCP Fast Retransmission#

当发送方收到3个或以上TCP Dup ACK,就意识到之前发的包可能丢了,触发快速重传

TCP Retransmission#

没有触发tcp超时重传,超时重传

TCP zerowindow#

缓存区已满,不能再接收数据了

TCP window FUll#

Wireshark检测到,发送方发送的数据会把接收方的接收窗口耗尽

0%