概念
比如docker等容器在不同的机器之间无缝迁移(可能由于调度,维护,交割等原因),是常见的需求场景
但是又希望不能中断服务,因此各种虚拟机和容器的热迁移就得到很多关注。
linux也在3.5版本中引入TCP_REPAIR socket选项来支持热迁移
获取状态及还原
当需要迁移的时候,为迁移的socket进入repair模式
setsockopt设置TCP_PREPAIR选项
进入repair模式的要求:
- 需要CAP_NET_ADMIN, 用户命名空间需要有网络管理能力
- socket处于CLOSE状态或ESTABLISHED状态
从内核读取缓存数据
内核缓存区中未发送或未被确认的数据,或者未被应用程序读取的数据
setsockopt设置TCP_REPAIR_QUEUE选项的值分别为TCP_SEND_QUEUE和TCP_RECV_QUEUE, 从两个缓存中读取数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock,
int flags,
int *addr_len)
{
...
if (unlikely(tp->repair)) {
err = -EPERM;
if (!(flags & MSG_PEEK))
goto
out;
if (tp->repair_queue == TCP_SEND_QUEUE)
err = tcp_peek_sndq(sk, msg, len);
goto
out;
err = -EINVAL;
if (tp->repair_queue == TCP_NO_QUEUE)
goto
out;
}
...
}
|
迁移完,把这些数据还原到对应的socket缓存中,通过send()接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
int tcp_sendmsg(
struct sock *sk,
struct msghdr *msg, size_t size)
{
...
if (unlikely(tp->repair)) {
if (tp->repair_queue == TCP_RECV_QUEUE) {
copied = tcp_send_rcvq(sk, msg, size);
goto out_nopush;
}
err = -EINVAL;
if (tp->repair_queue == TCP_NO_QUEUE)
goto out_err;
}
...
...
}
|
读取和还原tcp协商信息
当握手的时候,会用tcp扩展选项来协商支持的情况,比如sack,timestamp,wscale等
getsockopt TCP_REPAIR_OPTIONS选项来获取这些值, 迁移之后setsockopt还原这些值
读取和还原序号
1
2
3
4
5
6
7
8
|
case TCP_QUEUE_SEQ:
if (tp->repair_queue == TCP_SEND_QUEUE)
val = tp->write_seq;
else
if (tp->repair_queue == TCP_RECV_QUEUE)
val = tp->rcv_nxt;
else
return -EINVAL;
break;
|
读取和还原MSS
1
2
3
4
5
6
7
|
case
TCP_MAXSEG:
val = tp->mss_cache;
if (!
val && ((
1 << sk->sk_state) & (
TCPF_CLOSE |
TCPF_LISTEN)))
val = tp->rx_opt.user_mss;
if (tp->repair)
val = tp->rx_opt.mss_clamp;
break;
|
读取和还原滑动窗口信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
case TCP_REPAIR_WINDOW: {
struct tcp_repair_window opt;
if (get_user(len, optlen))
return -EFAULT;
if (len !=
sizeof(opt))
return -EINVAL;
if (!tp->repair)
return -EPERM;
opt.snd_wl1 = tp->snd_wl1;
opt.snd_wnd = tp->snd_wnd;
opt.max_window = tp->max_window;
opt.rcv_wnd = tp->rcv_wnd;
opt.rcv_wup = tp->rcv_wup;
if (copy_to_user(optval, &opt, len))
return -EFAULT;
return
0;
}
|
静默关闭连接
close()将静默关闭,不会发送FIN
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
|
void tcp_close(
struct sock *sk, long timeout)
{
if (unlikely(tcp_sk(sk)->repair)) {
sk->sk_prot->disconnect(sk,
0);
}
else
if (data_was_unread) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);
tcp_set_state(sk, TCP_CLOSE);
tcp_send_active_reset(sk, sk->sk_allocation);
}
else
if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) {
sk->sk_prot->disconnect(sk,
0);
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
}
else
if (tcp_close_state(sk)) {
tcp_send_fin(sk);
}
...
}
int tcp_disconnect(
struct sock *sk,
int flags)
{
struct inet_sock *inet = inet_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
int err =
0;
int old_state = sk->sk_state;
if (old_state != TCP_CLOSE)
tcp_set_state(sk, TCP_CLOSE);
if (old_state == TCP_LISTEN) {
inet_csk_listen_stop(sk);
}
else
if (unlikely(tp->repair)) {
sk->sk_err = ECONNABORTED;
}
...
}
|
还原连接
connect()直接进入ESTABLISH状态, 然后setsockopt各个状态选项,再用send()两个缓存到对应内核队列中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
int tcp_connect(
struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *buff;
int err;
tcp_connect_init(sk);
if (unlikely(tp->repair)) {
tcp_finish_connect(sk, NULL);
return
0;
}
...
}
|
原文来自:http://www.cnhalo.net/2016/09/13/linux-tcp-repair/
资料
TCP connection repair
CRIU