分类目录归档:网络

临时端口号(ephemeral port)的动态分配

网络端口号是如何分配的?除了给常用服务保留的Well-known Port numbers之外,给客户端的端口号通常是动态分配的,称为ephemeral port(临时端口),在Linux系统上临时端口号的取值范围是通过这个内核参数定义的:net.ipv4.ip_local_port_range (/proc/sys/net/ipv4/ip_local_port_range),端口号动态分配时并不是从小到大依次选取的,而是按照特定的算法随机分配的。

临时端口号的分配发生在以下两处:
– bind();
– connect()。

bind()通过inet_csk_get_port()获取端口号,利用了net_random()产生的随机数 :

connect()通过inet_hash_connect()分配端口号。核心的代码是:
port = low + (i + offset) % remaining;
其中 offset 是随机数。

为以上代码生成随机数port_offset的函数是:

 

综上,临时端口号是这样产生的:

生成一个随机数,利用随机数在ip_local_port_range范围内取值,如果取到的值在ip_local_reserved_ports范围内 ,那就再依次取下一个值,直到不在ip_local_reserved_ports范围内为止。

注:
/proc/sys/net/ipv4/ip_local_port_range (net.ipv4.ip_local_reserved_ports) 是应用程序保留的端口号,不会参与内核动态分配。有些软件比如SAP通常会保留大量的端口号,如果导致剩下的临时端口数量太少的话,动态分配的随机算法往往会产生重复的端口号,造成新分配的端口号总是相同的现象。

ssh端口转发

ssh是个多用途的工具,不仅可以远程登录,还可以搭建socks代理、进行内网穿透,这是利用它的端口转发功能来实现的。

所谓ssh端口转发,就是在ssh连接的基础上,指定 ssh client 或 ssh server 的某个端口作为源地址,所有发至该端口的数据包都会透过ssh连接被转发出去;至于转发的目标地址,既可以指定,也可以不指定,如果指定了目标地址,称为定向转发,如果不指定目标地址则称为动态转发:

  • 定向转发
    定向转发把数据包转发到指定的目标地址。目标地址不限定是ssh client 或 ssh server,既可以是二者之一,也可以是二者以外的其他机器
  • 动态转发
    动态转发不指定目标地址,数据包转发的目的地是动态决定的

因为ssh端口转发是基于ssh连接的,所以如果ssh连接断开,那么设置好的端口转发也会随之停止。

在设置端口转发之前,必须确认ssh的端口转发功能是打开的。

怎样打开ssh的端口转发功能?

ssh端口转发功能默认是打开的。控制它的开关叫做 AllowTcpForwarding,位于ssh server的配置文件 /etc/ssh/sshd_config 里:
    AllowTcpForwarding yes
如果修改的话需要重启sshd服务才会生效。

怎样设置端口转发?

设置端口转发之前要注意 iptables 设置,确保相应的端口未被屏蔽,如果嫌麻烦的话也可以临时禁用 iptables:
# service iptables stop

定向转发和动态转发的设置方法是不一样的,以下分别介绍。

设置定向转发

定向转发可以把一个 IP:Port 定向映射到另一个 IP:Port,源和目的都必须指定。源地址既可以是 ssh client 的某个端口,也可以是 ssh server 的某个端口:

  • 如果源地址是 ssh client 的某个端口,称为本地转发(Local Port Forwarding),发往 ssh client 指定端口的数据包会经过 ssh server 进行转发;
  • 如果源地址是 ssh server 的某个端口,则称为远程转发(Remote Port Forwarding),发往 ssh server 指定端口的数据包会经过 ssh client 进行转发.

ssh-port-forwarding

设置本地转发:

先看一下基本命令:

在ssh client上执行:
{ssh client}# ssh -g -N -f -o ServerAliveInterval=60 \
-L <local port>:<remote host>:<remote port> username@<ssh server>
参数的含义在后面有解释。

我们以下面的示意图为例:你想telnet连接{remote host},但是无法直达,你只能直接连接ssh client,于是试图通过{ssh client}到{ssh server}这条通道中转:

{you} — {ssh client} — {ssh server} — {remote host}

我们要做的是在{ssh client}上执行以下命令:

{ssh client} # ssh -g -L 2323:<remote-host>:23 username@<ssh-server>

输入口令之后,就跟普通的ssh登录一样,我们进入了shell,在shell中可以正常操作,不同之处是,它同时还把 {ssh client} 的2323端口映射到了{remote host} 的23端口——亦即telnet端口,此后执行”telnet <ssh client> 2323″就相当于”telnet <remote-host>”,只要shell不退出,这个定向转发就一直有效。

  • 注1:如果以上命令不加”-g”选项,那么SSH Client上的监听端口2323会绑定在127.0.0.1上,意味着只有SSH Client自己才能连上。加上”-g”选项之后,SSH Client才允许网络上其他机器连接2323端口。
  • 注2:以上命令会生成一个shell,有时候并不符合我们的需要,因为多数时候我们只想要一个端口转发功能,挂一个shell是个累赘,而且shell一退出,端口转发也停了。这就是为什么我们需要”-N -f”选项的原因:
    -N 告诉ssh client,这个连接不需要执行任何命令,仅做端口转发
    -f 告诉ssh client在后台运行
  • 注3:为了避免长时间空闲导致ssh连接被断开,我们可以加上”-o ServerAliveInterval=60″选项,每60秒向ssh server发送心跳信号。还有一个TCPKeepAlive选项的作用是类似的,但是不如ServerAliveInterval 好,因为TCPKeepAlive在TCP层工作,发送空的TCP ACK packet,有可能会被防火墙丢弃;而ServerAliveInterval 在SSH层工作,发送真正的数据包,更可靠些。
  • 如果不是以root身份设置端口转发的话,转发端口只能使用大于1024的端口号。
设置远程转发:

先看一下基本命令,分为两部分:

在ssh server上:
编辑 /etc/ssh/sshd_config,设置以下内容然后重启sshd服务
    GatewayPorts yes
在ssh client上执行:
{ssh client}# ssh -f -N -o ServerAliveInterval=60 \
-R <ssh server port>:<remote host>:<remote port> username@<ssh server>

这次的实例如下所示,你想用telnet连接{remote host},但是无法直达,于是试图通过{ssh server}到{ssh client}这条通道中转,注意与前面介绍的本地转发的不同之处是,本地转发的案例中你只能直接连接到 ssh client,而这里你只能直接连到 ssh server:

{you} — {ssh server} — {ssh client} — {remote host}

我们要做的是在{ssh client}上执行以下命令:

{ssh client} # ssh -f -N -R 2323:<remote-host>:23 username@<ssh-server>

输入口令之后,{ssh server}的2323端口映射到了{remote host}的23端口——亦即telnet端口,此后执行”telnet <ssh server> 2323″就相当于”telnet <remote-host>”。

本地转发与远程转发的区别与适用场景

定向转发(包括本地转发和远程转发)通常用于内网穿透,本地转发和远程转发的区别就在于监听端口是开在ssh client上还是ssh server上。常见的使用场景是:

  • 如果ssh client在内网里面,ssh server在Internet上,你想让Internet上的机器穿进内网之中,那就使用远程转发;
  • 如果ssh server在内网里面,ssh client在外面,你想穿进内网就应该使用本地转发。
设置动态转发

定向转发(包括本地转发和远程转发)的局限性是必须指定某个目标地址,如果我们需要借助一台中间服务器访问很多目标地址,一个一个地定向转发显然不是好办法,这时我们要用的是ssh动态端口转发,它相当于建立一个SOCKS服务器。

先看一下基本命令:

在ssh client上执行:
{ssh client}# ssh -f -N -o ServerAliveInterval=60 \
-D <ssh client port> username@<ssh server>

实际使用时有两种常见场景:

  • 你把自己的机器(127.0.0.1)当作 sock5 代理服务器:
    {you / 
    ssh client} — {ssh server} — {other hosts}

命令如下:

{ssh client} # ssh -f -N -D 1080 username@<ssh-server>

这种情况下,我们得到的socks5代理服务器是:127.0.0.1:1080,仅供ssh client自己使用。
然后你就可以在浏览器中或其他支持socks5代理的软件中进行设置。

  • ssh client 和 ssh server 是同一台机器,并充当socks5代理:
    {you}
     — {ssh client / ssh server} — {other hosts}

命令如下:

{ssh client} # ssh -f -N -g -D 1080 username@127.0.0.1

这种情况下,我们得到的socks5代理服务器是:
{ssh client IP}:1080,可供网络上其他机器使用,只要能连接ssh client即可。

通过SSH建立的SOCKS服务器使用的是SOCKS5协议,在为应用程序设置SOCKS代理的时候要注意。

观察网络性能时如何选择工具

Linux系统上的网络工具甚多,如何根据实际需要选择称手的工具呢?在此作一个简单介绍:

观察网络流量:

  • sar -n DEV 1 5” 可以统计每个网卡上的网络流速:

  • iptraf 是观察网络流速的强力工具,它可以让你的观察逐步深入,从硬件层(网卡),到网络层(IPv4,IPv6),到传输层(TCP,UDP etc.),一直到每一对socket pair。

iptraf-d

观察网络连接的状态:

  • netstat -a
    这是传统的工具,但是它无力处理海量的网络连接。所以在大规模网络连接的主机上,建议使用ss

  • ss -a” 列出所有的网络连接。ss特别适合海量连接的主机。
    如果加上”-p”选项,还可以显示对应的进程号。

观察静态统计值:

  • netstat -i

  • ifconfig 可以看到网卡层面的少量统计值,packet数量,collision,errors等:

  • ip -s link” 看到的信息基本类似:

  • netstat -s” 提供了各个协议下的统计信息,有些统计值比如retransmit是很有用的,只有 “netstat -s” 能看到:

 

观察网络流量的工具:iptraf

想知道你的Linux系统上网络流量有多大吗?想知道是哪一块网卡承载着网络流量吗?想知道哪一个进程产生了网络流量吗?iptraf可以帮你做到。在最新的Linux release上,比如CentOS 7.0,采用了衍生版本iptraf-ng

通常我们会先看看总体状况,“iptraf -g” 显示每一个网卡上的流量:

iptraf -g

找到感兴趣的网卡之后,再看看那个网卡的总体状况,“iptraf -d eth0” 显示指定网卡上的流量统计,总体流量、流入量、流出量、以及按协议分类的流量统计:

iptraf-d

以上我们看到大部分流量来自TCP协议,需要进一步找出这些流量通过哪一个TCP port,“iptraf -s eth0” 统计各port的流量::

iptraf-s

很明显是TCP port 22,即SSH端口。如果我们还想进一步看看是哪些远程主机在跟我们的SSH端口通信,“iptraf -i eth0” 可以帮忙:

iptraf_i

“iptraf -i eth0” 的输出分为两个窗口,上面是TCP socket pairs,下面是UDP。这里我们看到,连接我们SSH端口的远程IP是16.29.48.9。如果你愿意,根据这个socket pair的信息,还可以利用 lsof 工具找出进程号。

顺便介绍一个类似的工具:iftop ,它与 “iptraf -i” 有点像,显示每一对主机之间的动态流速,如果加上 “-P” 选项,就与 “iptraf -i” 一样可以显示每一对socket pair的动态流速,而且它还统计UDP端口,不像iptraf那样把UDP流量放在单独的窗口中显示。

用ping测网络延迟要注意的几个因素

ping常被用来测试网络延迟,但是有时ping的延迟并不是网络引起的,所以为了正确理解ping的结果,有必要了解影响ping延迟的几个因素。

ping的原理是通过发送ICMP echo request包,在收到ICMP echo reply包之后,计算发送时间与接收时间之间的差值,得出延迟的时间。ping的输出举例如下:

ping缺省每秒发一个echo request,发包的时候不会输出任何信息,直至收到echo reply的时候才输出一条信息,格式如上,最后一列是延迟时间,ms表示毫秒。

影响ping延迟的因素主要有:

ping延迟包含了进程调度的延迟

由于ping本身是用户态的程序,它首先会受到进程调度的影响,比方说高优先级的进程与ping争抢CPU的话,ping的执行就会遭到拖延,这个调度延迟如果是发生在发包之后、收包之前,就会被计入ping的延迟之中。

多个同时运行的ping进程之间会互相干扰,导致延迟

ping通过raw socket发送和接收ICMP包,而raw socket不仅会收到给自己的包,也会收到给别人的包,假如有多个ping进程同时在运行,你的ping就有可能会收到别人的ping的echo reply,当然,ping程序可以从中挑出给自己的包,因为包里嵌入了对应的ping进程号,但是每个包都打开看看、并判断是不是给自己的——这都要消耗时间的,所以说,多个ping进程之间会互相干扰,导致延迟加大。不同的UNIX版本由于实现方式的差异,受这个因素的影响程度也不一样,比如HP-UX受影响较大,而Linux受影响相对较小,因为Linux采用了一种过滤机制:Linux Socket Filtering,亦即Berkeley Packet Filter (BPF),ping程序利用BPF给raw socket加上一个过滤器,这样内核会只把对应的echo reply传递给ping程序,给其他ping进程的echo reply不会再传给这个ping,避免了CPU和buffer资源的浪费,也减少了ping延迟。

其他类型的ICMP包也会对ping造成干扰

ICMP包有好几种类型,ping希望收到的是ICMP_ECHOREPLY,但是其他类型的包也都会传递给ping,我们上面说过,这是因为ping使用raw socket的缘故,raw socket会看到所有的ICMP包。ping需要消耗额外的时间和资源去查看这些本来不相干的包,故而有可能会产生延迟。以下列出各种ICMP包的类型供参考:

ICMP_ECHO
ICMP_ECHOREPLY
ICMP_SOURCE_QUENCH
ICMP_REDIRECT
ICMP_DEST_UNREACH
ICMP_TIME_EXCEEDED
ICMP_PARAMETERPROB

 注1:在Linux上,虽然ping采用了BPF过滤机制,但是只过滤掉了发给其他ping进程的ICMP_ECHOREPLY包,其他类型的包是不过滤的,所以仍然会受到影响。
注2:在HP-UX上ICMP_SOURCE_QUENCH是最常见的影响ping延迟的因素。Source Quench是一种简陋的流控机制,当接收端有缓冲区满的时候,通过向发送端返回Source Quench,告知发送端降低发送速度,而满溢的缓冲区不一定与ICMP有关,更常见的事UDP的缓冲区。由于这种机制存在种种问题,有许多反对使用Source Quench的声音,比如:RFC6633Linux从2.2起就不再支持ICMP Source Quench了

还有些其它因素,比如网卡驱动,防火墙软件什么的,但比较少见,就不深入探讨了。

怎样判断ping延迟是网络延迟还是其它因素导致的呢?

如果有1秒以上的延迟的话,观察一下icmp_seq,它表示包的顺序,在下例中,第3个包的延迟是2.068秒,而第4个包的延迟只有0.183秒,如果发包的频率严格保持每秒一次的话,第4个包应该比第3个包先收到才对,而下例中,包的顺序并未颠倒,这说明第4个包的发送也被延迟了。发送延迟,那就不是网络延迟了,肯定有其它原因。

还有一个简单的方法可以一试,ping一下127.0.0.1或者ping本机的IP地址,它们不用通过网络,甚至不用进入网卡驱动程序,所以延迟应该非常小,可以作为一个基准值,如果它们的延迟比较大,那最大的可能是存在调度延迟或者ICMP包干扰之类的问题。