背景
Connection Migration是QUIC
协议的特性之一。协议通信一般依赖网络标识比如(IP和port), 如果网络切换,通信需要重新开始,这对于频繁网络切换场景体验很差,比如无线网络使用APP时,网络切换可能需要重新登陆,终端用户可能莫名其妙。QUIC
协议设计的时候,使用Connection ID
标识对方,网络切换只要保证Connection ID
正确就可以。Connection Migration
就是描述这一过程交互规范。
细节
Connection Migration
需要在Handshake
完成后才能执行。发送方可能发送PATH_CHALLANGE
帧给对方,对方可能发送PATH_RESPONSE
帧回应。发送方也可以不发送probe frame, 直接切换继续通信。
实现
本地实验最好的方式就是变更port,比如从9000
变为9001
。下面使用ngtcp2库实现Connection Migration
的代码片段。
- 生成新的
UDP socket
struct sockaddr_in source;
source.sin_addr.s_addr = htonl(INADDR_ANY);
source.sin_family = AF_INET;
source.sin_port = htons(9001);
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
bind(fd, (struct sockaddr *)&source, sizeof(source));
connect(fd, (struct sockaddr *)(&remote), remote_len);
getsockname(fd, (struct sockaddr *)&local, &local_len)
- 使用
ngtcp2
提供的Connection Migration
接口。
int ngtcp2_conn_initiate_immediate_migration(ngtcp2_conn *conn, const ngtcp2_path *path, ngtcp2_tstamp ts)
int ngtcp2_conn_initiate_migration(ngtcp2_conn *conn, const ngtcp2_path *path, ngtcp2_tstamp ts)
ngtcp2
提供了两种接口,都会发送PATH_CHALLANGE
, 但是前者不会等待对方的PATH_RESPONSE
就迁移。后者会等待对端的PATH_RESPONSE
帧才迁移本地的网络路径。
ngtcp2_addr addr;
ngtcp2_addr_init(&addr, (struct sockaddr *)&local, local_len);
if (0) // nat rebinding
{
ngtcp2_conn_set_local_addr(conn, &addr);
ngtcp2_conn_set_path_user_data(conn, client);
}
else
{
ngtcp2_path path = {
addr,
{
(struct sockaddr *)&remote,
remote_len,
},
client,
};
if ((res = ngtcp2_conn_initiate_immediate_migration(conn, &path, timestamp())) != 0)
// if ((res = ngtcp2_conn_initiate_migration(conn, &path, timestamp())) != 0)
{
fprintf(stderr, "ngtcp2_conn_initiate_immediate_migration: %s\n", ngtcp2_strerror(res));
return -1;
}
}
- 处理
path validation callback
,比如对端回复了PATH_RESPONSE
帧
int path_validation(ngtcp2_conn *conn, uint32_t flags, const ngtcp2_path *path,
const ngtcp2_path *old_path,
ngtcp2_path_validation_result res, void *user_data)
{
(void)conn;
if (old_path)
{
get_ip_port((struct sockaddr_storage *)(old_path->local.addr), ip, &port);
fprintf(stdout, ", old local: %s:%d", ip, port);
}
if (flags & NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR)
{
struct client *c = (struct client *)(user_data);
memcpy(&c->remote_addr, path->remote.addr, path->remote.addrlen);
c->remote_addrlen = path->remote.addrlen;
}
return 0;
}
最后
可以参考ngtcp2
的example(https://github.com/ngtcp2/ngtcp2/tree/main/examples), 如果想看简单的,可以参考我的http3 client
Top comments (0)