电脑技术学习

SPARC/Solaris 8下快速终结TCP有限状态机的TIME_WAIT状态

dn001
我们这里讨论的是在程序不可控的情况下,比如别人的程序,又未使用SO_REUSEADDR选项,如何快 速终结TCP有限状态机的TIME_WAIT状态。

参看这个链接"closing half-open connections"

http://www-mice.cs.ucl.ac.uk/multimedia/misc/tcp_ip/8707.mm.www/0009.html

1987年10月1日(可真够早的)cdjohns@nswc-g.arpa提供了一个shell script,用于
SunOS 4.x系统下快速终结TCP有限状态机的TIME_WAIT状态。很奇怪,这个脚本并未
得到人们的足够重视并广泛流传,直到SunOS 4.x退出历史舞台,它也就销声匿迹了。
昨天被backend从故纸堆里翻了出来,我们就趁机移植到SPARC/Solaris 8下来。

参看UNP 图2.4了解TCP有限状态机的变迁过程。

/usr/include/netinet里的文件多是为用户空间编程准备的,/usr/include/inet里
的文件多是为内核空间编程准备的。下面内容取自SPARC/Solaris 8

--------------------------------------------------------------------------
/*
* /usr/include/inet/tcp.h
*/

/*
* TCP states
*/
#define TCPS_CLOSED -6
#define TCPS_IDLE -5 /* idle (opened, but not bound) */
#define TCPS_BOUND -4 /* bound, ready to connect or accept */
#define TCPS_LISTEN -3 /* listening for connection */
#define TCPS_SYN_SENT -2 /* active, have sent syn */
#define TCPS_SYN_RCVD -1 /* have received syn (and sent ours) */
/*
* states < TCPS_ESTABLISHED are those where connections not established
*/
#define TCPS_ESTABLISHED 0 /* established */
#define TCPS_CLOSE_WAIT 1 /* rcvd fin, waiting for close */
/*
* states > TCPS_CLOSE_WAIT are those where user has closed
*/
#define TCPS_FIN_WAIT_1 2 /* have closed and sent fin */
#define TCPS_CLOSING 3 /* closed, xchd FIN, await FIN ACK */
#define TCPS_LAST_ACK 4 /* had fin and close; await FIN ACK */
/*
* states > TCPS_CLOSE_WAIT && < TCPS_FIN_WAIT_2 await ACK of FIN
*/
#define TCPS_FIN_WAIT_2 5 /* have closed, fin is acked */
#define TCPS_TIME_WAIT 6 /* in 2*msl quIEt wait after close */

#if (defined(_KERNEL) || defined(_KMEMUSER))

/*
* If the information represented by the field is required even in the
* TIME_WAIT state, it must be part of tcpb_t. Otherwise it must be part
* of tcp_t. In other Words, the tcp_t captures the information that is
* not required, after a connection has entered the TIME_WAIT state.
*/
typedef struct tcp_base_s
{
struct tcp_base_s *tcpb_bind_hash; /* Bind hash chain */
struct tcp_base_s **tcpb_ptpbhn; /* Pointer to previous bind hash next. */
struct tcp_base_s *tcpb_conn_hash; /* Connect hash chain */
struct tcp_base_s **tcpb_ptpchn; /* Pointer to previous conn hash next. */
struct tcp_base_s *tcpb_time_wait_next; /* Pointer to next T/W block */
struct tcp_base_s *tcpb_time_wait_prev; /* Pointer to previous T/W next */
/*
* /usr/include/sys/types.h
* typedef long clock_t;
* offset: 8 * 6 -> 48 -> 0x30,clock_t占8个字节(64-bit kernel mode)
*/
clock_t tcpb_time_wait_expire; /* time in hz when t/w expires */
clock_t tcpb_last_rcv_lbolt; /* lbolt on last packet, used for PAWS */
/*
* offset: 0x40
*/
int32_t tcpb_state;
int32_t tcpb_rcv_ws; /* My window scale power */
int32_t tcpb_snd_ws; /* Sender's window scale power */
uint32_t tcpb_ts_recent; /* Timestamp of earliest unacked */
/*
* data segment
* offset: 0x50
*/
clock_t tcpb_rto; /* Round trip timeout */
uint32_t tcpb_snd_ts_ok : 1,
tcpb_snd_ws_ok : 1,
tcpb_is_secure : 1,
tcpb_reuseaddr : 1, /* SO_REUSEADDR "socket" option. */
tcpb_exclbind : 1, /* ``exclusive'' binding */
tcpb_junk_fill_thru_bit_31 : 27;
/*
* offset: 0x5c
*/
uint32_t tcpb_snxt; /* Senders next seq num */
uint32_t tcpb_swnd; /* Senders window (relative to suna) */
uint32_t tcpb_mss; /* Max segment size */
uint32_t tcpb_iss; /* Initial send seq num */
/*
* offset: 0x6c
*/
uint32_t tcpb_rnxt; /* Seq we expect to recv next */
uint32_t tcpb_rwnd; /* Current receive window */
/*
* /usr/include/sys/mutex.h
* 无论64-bit还是32-bit kernel mode,kmutex_t都占8字节空间,这里需要对
* 齐在数据类型自然连界上,0x6c + 8 -> 0x74不在kmutex_t自然边界上,继
* 续后移4个字节
*
* offset: 0x78
*/
kmutex_t tcpb_reflock; /* Protects tcp_refcnt */
/*
* offset: 0x80
*/
ushort_t tcpb_refcnt; /* Number of pending upstream msg */
union
{
struct
{
uchar_t v4_ttl; /* Dup of tcp_ipha.iph_type_of_service */
uchar_t v4_tos; /* Dup of tcp_ipha.iph_ttl */
} v4_hdr_info;
struct
{
/*
* 对齐在uint_t的自然边界上
* offset: 0x84
*/
uint_t v6_vcf; /* Dup of tcp_ip6h.ip6h_vcf */
/*
* offset: 0x88
*/
uchar_t v6_hops; /* Dup of tcp_ip6h.ip6h_hops */
} v6_hdr_info;
} tcpb_hdr_info;

#define tcpb_ttl tcpb_hdr_info.v4_hdr_info.v4_ttl
#define tcpb_tos tcpb_hdr_info.v4_hdr_info.v4_tos
#define tcpb_ip6_vcf tcpb_hdr_info.v6_hdr_info.v6_vcf
#define tcpb_ip6_hops tcpb_hdr_info.v6_hdr_info.v6_hops

/*
* offset: 0x8c,先是远端地址,后是本地地址
* /usr/include/netinet/in.h
* in6_addr_t长16字节,在内核空间里这个结构对齐在uint32_t的自然边界上
*/
in6_addr_t tcpb_remote_v6; /* true remote address - needed for */
/* source routing. */
in6_addr_t tcpb_bound_source_v6; /* IP address in bind_req */
/*
* offset: 0xac
*/
in6_addr_t tcpb_ip_src_v6; /* same as tcp_iph.iph_src. */

#ifdef _KERNEL
/*
* Note: V4_PART_OF_V6 is meant to be used only for _KERNEL defined stuff
*/
#define tcpb_remote V4_PART_OF_V6(tcpb_remote_v6)
#define tcpb_bound_source V4_PART_OF_V6(tcpb_bound_source_v6)
#define tcpb_ip_src V4_PART_OF_V6(tcpb_ip_src_v6)
#endif /* _KERNEL */

/*
* These fields contain the same information as tcp_tcph->th_*port.
* However, the lookup functions can not use the header fields
* since during IP option manipulation the tcp_tcph pointer
* changes.
*/
/*
* offset: 0xbc,先是远端端口,后是本地端口
*/
union
{
struct
{
in_port_t tCPU_fport; /* Remote port */
in_port_t tcpu_lport; /* Local port */
} tcpu_ports1;
uint32_t tcpu_ports2; /* Rem port, local port */
/*
* Used for TCP_MATCH performance
*/
} tcpb_tcpu;
#define tcpb_fport tcpb_tcpu.tcpu_ports1.tcpu_fport
#define tcpb_lport tcpb_tcpu.tcpu_ports1.tcpu_lport
#define tcpb_ports tcpb_tcpu.tcpu_ports2
/*
* IP sends back 2 mblks with the unbind ACK for handling
* IPSEC policy for detached connections. Following two fields
* are initialized then.
*/
mblk_t *tcpb_ipsec_out;
mblk_t *tcpb_ipsec_req_in;
/*
* offset: 0xd0
*/
tcp_t *tcpb_tcp;
/*
* offset: 0xd8
*/
/*
* IP format that packets transmitted from this struct should use.
* Value can be IPV4_VERSION or IPV6_VERSION. Determines whether
* IP+TCP header template above stores an IPv4 or IPv6 header.
*/
ushort_t tcpb_ipversion;
uint_t tcpb_bound_if; /* IPV6_BOUND_IF */
uid_t tcpb_ownerid; /* uid of process that did open */
#define tcp_bind_hash tcp_base->tcpb_bind_hash
#define tcp_ptpbhn tcp_base->tcpb_ptpbhn
#define tcp_conn_hash tcp_base->tcpb_conn_hash
#define tcp_ptpchn tcp_base->tcpb_ptpchn
#define tcp_time_wait_next tcp_base->tcpb_time_wait_next
#define tcp_time_wait_prev tcp_base->tcpb_time_wait_prev
#define tcp_time_wait_expire tcp_base->tcpb_time_wait_expire
#define tcp_last_rcv_lbolt tcp_base->tcpb_last_rcv_lbolt
#define tcp_state tcp_base->tcpb_state
#define tcp_rcv_ws tcp_base->tcpb_rcv_ws
#define tcp_snd_ws tcp_base->tcpb_snd_ws
#define tcp_ts_recent tcp_base->tcpb_ts_recent
#define tcp_rto tcp_base->tcpb_rto
#define tcp_snd_ts_ok tcp_base->tcpb_snd_ts_ok
#define tcp_snd_ws_ok tcp_base->tcpb_snd_ws_ok
#define tcp_is_secure tcp_base->tcpb_is_secure
#define tcp_snxt tcp_base->tcpb_snxt
#define tcp_swnd tcp_base->tcpb_swnd
#define tcp_mss tcp_base->tcpb_mss
#define tcp_iss tcp_base->tcpb_iss
#define tcp_rnxt tcp_base->tcpb_rnxt
#define tcp_rwnd tcp_base->tcpb_rwnd
#define tcp_reflock tcp_base->tcpb_reflock
#define tcp_refcnt tcp_base->tcpb_refcnt
#define tcp_remote_v6 tcp_base->tcpb_remote_v6
#define tcp_remote tcp_base->tcpb_remote
#define tcp_bound_source_v6 tcp_base->tcpb_bound_source_v6
#define tcp_bound_source tcp_base->tcpb_bound_source
#define tcp_lport tcp_base->tcpb_tcpu.tcpu_ports1.tcpu_lport
#define tcp_fport tcp_base->tcpb_tcpu.tcpu_ports1.tcpu_fport
#define tcp_ports tcp_base->tcpb_tcpu.tcpu_ports2
#define tcp_ipsec_out tcp_base->tcpb_ipsec_out
#define tcp_ipsec_req_in tcp_base->tcpb_ipsec_req_in
#define tcp_ipversion tcp_base->tcpb_ipversion
#define tcp_bound_if tcp_base->tcpb_bound_if
#define tcp_reuseaddr tcp_base->tcpb_reuseaddr
#define tcp_exclbind tcp_base->tcpb_exclbind
#define tcp_ownerid tcp_base->tcpb_ownerid
} tcpb_t;
#endif /* (defined(_KERNEL) || defined(_KMEMUSER)) */
--------------------------------------------------------------------------

下面这两条命令其实是用分号连在一起执行的,否则前后数据不对应,我是telnet上
去执行命令。

# ndd /dev/tcp tcp_status | grep ESTABLISHED
TCPB dest snxt suna swnd rnxt rack rwnd rto mss w sw rw t recent [lport,fport] state
30000cc21d0 ::ffff:192.168.5.8 8e0e46dc 8e0e46da 0000064010 3d49e751 3d49e751 0000024820 00583 01460 0 00 00 0 00000000 [23, 4613] TCP_ESTABLISHED
# skd64 0x30000cc21d0 256
byteArray [ 256 bytes ] ---->
0000000000000000 00 00 03 00 00 47 C1 80-00 00 00 00 10 48 18 88 .....G羳.....H.?
0000000000000010 00 00 00 00 00 00 00 00-00 00 03 00 00 3A 60 80 .............:`�
0000000000000020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0000000000000030 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0000000000000040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0000000000000050 00 00 00 00 00 00 02 47-10 00 00 00 8E 0E 46 DC .......G....?F?
0000000000000060 00 00 FA 0A 00 00 05 B4-8E 0B 77 07 3D 49 E7 51 ..?...磶.w.=I鏠
0000000000000070 00 00 60 F4 00 00 00 00-00 00 00 00 00 00 00 00 ..`?...........
0000000000000080 00 01 00 00 3C 00 00 00-3C 00 00 00 00 00 00 00 ....<...<.......
0000000000000090 00 00 00 00 00 00 FF FF-C0 A8 05 08 00 00 00 00 ......��括......
00000000000000A0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000000000B0 00 00 00 00 00 00 FF FF-C0 A8 05 82 12 05 00 17 ......��括.?...
00000000000000C0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000000000D0 00 00 03 00 00 BB 6F D0-00 04 00 00 00 00 00 00 .....籵?.......
00000000000000E0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000000000F0 00 00 03 00 00 08 2B 68-00 00 00 00 00 00 00 00 ......+h........
#

显然0x30000cc21d0对应着一个tcpb_t结构。

# adb -k /dev/ksyms /dev/mem
physmem 3b68
0x30000cc21d0$<tcpb

30000cc21d0: tcp_bind_hash tcp_ptpbhn tcp_conn_hash
3000047c180 10481888 0
30000cc21e8: tcp_ptpchn time_wait_next time_wait_prev
300003a6080 0 0
30000cc2200: time_wait_expir last_rcv_lbolt tcp_state
0 0 0
30000cc2214: tcp_rcv_ws tcp_snd_ws tcp_ts_recent
0 0 0
30000cc2220: tcp_rto
24c
snd_ts_ok 0
snd_ws_ok 0
is_secure 0
reuseaddr 1
exclbind 0

30000cc222c: tcp_snxt tcp_swnd tcp_mss
8e176ce4 f8dc 5b4
30000cc2238: tcp_iss tcp_rnxt tcp_rwnd
8e0b7707 3d49ecf0 60f4
30000cc2248: tcpb_reflock
30000cc2248: owner/waiters
0


30000cc2250: tcp_refcnt tcpb_vcf tcpb_hops
1 3c000000 3c
30000cc225c: tcpb_remote_v6

30000cc225c: 0 0 ffff c0a80508

30000cc226c: tcpb_bound_source_v6

30000cc226c: 0 0 0 0

30000cc227c: tcpb_ip_src_v6

30000cc227c: 0 0 ffff c0a80582

30000cc228c: tcpu_fport tcpu_lport ipsec_out
1205 17 0
30000cc2298: ipsec_req_in tcpb_tcp tcpb_ipversion
0 30000bb6fd0 4
30000cc22ac: bound_if ownerid
0 0

$q
#

于是我们可以写这样一个脚本kill_timewait.sh

--------------------------------------------------------------------------
#! /sbin/sh
#

#
# @(#)kill_timewait.sh 2002-07-07 NSFocus Copyleft 2002-2012
#
# Notice here is copyleft but not copyright, enjoy it by yourself.
#
# ------------------------------------------------------------------------
# File : kill_timewait.sh
# Platform : SPARC/Solaris 8 64-bit kernel mode
# Author : NSFocus Security Team <security@nsfocus.com>
# : http://www.nsfocus.com
# Date : 2002-07-07 12:27
# Modify :
# Thanks : cdjohns@nswc-g.arpa for SunOS 4.x implementation
#

netstat -na -P tcp -f inet | grep TIME_WAIT

echo
echo 'TCPB dest [lport,fport] state'
echo
ndd /dev/tcp tcp_status | nawk '{print $1 " " $2 " " $16 $17 " " $18}' | egrep 'TIME_WAIT'

echo
/usr/bin/echo 'TCPB address to terminate: c'
read tcpb_addr
echo

adb -k /dev/ksyms /dev/mem << NSFOCUS_EOF
$tcpb_addr$</usr/lib/adb/sparcv9/tcpb
$q
NSFOCUS_EOF

#
# Check to see if this was the correct address and TCPB. state should be 6
#
echo
echo 'tcp_state = 6 = TCPS_TIME_WAIT'
/usr/bin/echo 'Is this the correct TCPB (y/n)? c'
read answer
echo
case $answer in
[Yy]*)

*)
echo 'No Changes.'
exit

esac

#
# Kernel Hacking, please. These value are expressed in hexadecimal.
#
TIME_WAIT_EXPIRE_OFFSET=0x30
STATE_OFFSET=0x40

#
# This value is expressed in decimal and must be greater than zero.
#
TIME_WAIT_EXPIRE=0t06

#
# Use adb on kernel to set the tcpb_time_wait_expire=6 and
# tcpb_state=TCPS_CLOSED (-6)
#
adb -kw /dev/ksyms /dev/mem << NSFOCUS_EOF
$tcpb_addr+$TIME_WAIT_EXPIRE_OFFSET/Z $TIME_WAIT_EXPIRE
$tcpb_addr+$STATE_OFFSET/W -6
$q
NSFOCUS_EOF

echo
echo "TIME_WAIT state will disappear."
echo

netstat -na -P tcp -f inet | grep TIME_WAIT
--------------------------------------------------------------------------

不要设置tcpb_time_wait_expire成零,只要是一个很小的值就可以了。这里必须同
时设置tcpb_time_wait_expire和tcpb_state,只设置其中一个达不到效果。

利用adb从TCPS_ESTABLISHED变为TCPS_CLOSE_WAIT,可以使一条TCP连接不再工作,
但这条连接并未销毁,tcpb_t结构也未删除。

利用adb从TCPS_ESTABLISHED变为TCPS_CLOSED,会导致整个操作系统崩溃。可能是下
层tcpb_t结构被删除,而上层socket并不了解,出现非法指针。

简化一下kill_timewait.sh

--------------------------------------------------------------------------
#! /sbin/sh

ndd /dev/tcp tcp_status | nawk '{print $1 " " $2 " " $16 $17 " " $18}' | egrep 'TIME_WAIT'

echo
/usr/bin/echo 'TCPB address to terminate: c'
read tcpb_addr
echo

adb -kw /dev/ksyms /dev/mem << NSFOCUS_EOF
$tcpb_addr+0x30/Z 0t6
$tcpb_addr+0x40/W -6
$q
NSFOCUS_EOF
--------------------------------------------------------------------------

还可以写一个脚本自动清除所有TIME_WAIT状态TCP连接

--------------------------------------------------------------------------
#! /sbin/sh

ndd /dev/tcp tcp_status | nawk '{print $1 " " $2 " " $16 $17 " " $18}' |
egrep 'TIME_WAIT' | cut -d' ' -f1 | while read tcpb_addr
do
adb -kw /dev/ksyms /dev/mem << NSFOCUS_EOF
$tcpb_addr+0x30/Z 0t6
$tcpb_addr+0x40/W -6
$q
NSFOCUS_EOF
done
----------------------------------------------------------------------

标签: