这个月接到一个功能添加需求,需要为一个代理程序支持ipv6的功能。但是在支持ipv6的同时还不能影响ipv4的功能。于是做了如下调研。
1.在linux中,尽管用AF_INET6协议族写的socket是兼容AF_INET连接的,但是这种只是针对服务,但是如果想拓展为代理程序,可能就比较局限,所以要检索本机的ipv4地址和ipv6的地址,分别建立socket.
2.多个socket可以同时监听同一个端口。例如我们常用的ssh远程工具
3.当同时处理ipv6和ipv4连接时,需要将accept修改为非阻塞模式
解决以上三个问题,基本就可以实现此服务。
现在来讲下具体实现以及实现过程中遇到的坑;
1.建立支持AF_INET和AF_INET6的socket,并且监听同一个端口
struct addrinfo hints, *ai, *aitop;
char strport1[32];
int gaierr;
char *listen_addrs;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
//得到本机的ipv4和ipv6的地址信息
if ((gaierr = getaddrinfo(NULL, strport1, &hints, &aitop)) != 0)
print2log("bad addr or host");
char strport[32];
int ret, listen_sock, on = 1;
char ntop[1024];
for (ai = listen_addrs; ai; ai = ai->ai_next) {
if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
continue;
if (num_listen_socks >= 16)
print2log("Too many listen sockets. "
"Enlarge MAX_LISTEN_SOCKS");
if ((ret = getnameinfo(ai->ai_addr, ai->ai_addrlen,
ntop, sizeof(ntop), strport, sizeof(strport),
NI_NUMERICHOST|NI_NUMERICSERV)) != 0) {
continue;
}
/* 分别监听ipv4和ipv6的socket */
listen_sock = socket(ai->ai_family, ai->ai_socktype,
ai->ai_protocol);
if (listen_sock < 0) {
/* kernel may not support ipv6 */
print2log("socket: %.100s", strerror(errno));
continue;
}
if (set_nonblock(listen_sock) == -1) {
close(listen_sock);
continue;
}
/*
* Set socket options.
* Allow local port reuse in TIME_WAIT.
*/
if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR,
&on, sizeof(on)) == -1)
print2log("setsockopt SO_REUSEADDR: %s", strerror(errno));
if (ai->ai_family == AF_INET6)
//sock_set_v6only(listen_sock);
if (setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1)
error("setsockopt IPV6_V6ONLY: %s", strerror(errno));
/* Bind the socket to the desired port. */
if (bind(listen_sock, ai->ai_addr, ai->ai_addrlen) < 0) {
print2log("Bind to port %s on %s failed: %.200s. ai_addrlen = %d\n",
strport, ntop, strerror(errno), ai->ai_addrlen);
close(listen_sock);
continue;
}
listen_socks[num_listen_socks] = listen_sock;
num_listen_socks++;
/* Start listening on the port. */
if (listen(listen_sock, 128) < 0)
print2log("listen on [%s]:%s: %.100s",
ntop, strport, strerror(errno));
error("Server listening on %s port %s.\n", ntop, strport);
//return listen_sock;
}
freeaddrinfo(listen_addrs);
if (!num_listen_socks)
print2log("Cannot bind any address.");
return 0;
2.建立非阻塞accept,我用select实现,当然还有效率更高的epoll
for (i = 0; i < num_listen_socks; i++)
if (listen_socks[i] > maxfd)
maxfd = listen_socks[i];
while (1) {
/*
* hier kommt ein accept an
*/
fd_set accept_fds;
FD_ZERO (&accept_fds);
for (i = 0; i < num_listen_socks; i++)
FD_SET(listen_socks[i], &accept_fds);
// Wait in select until there is a connection.
ret = select(maxfd+1, &accept_fds, NULL, NULL, NULL);
if (ret < 0) {
continue;
}
for (i = 0; i < num_listen_socks; i++) {
if (!FD_ISSET(listen_socks[i], &accept_fds))
continue;
len = sizeof(from);
if ((connect = accept(listen_socks[i], (struct sockaddr *) &from, &len)) < 0) {
if (errno == EINTR || errno == ECONNABORTED)
continue;
fprintf (stderr, "%04X: accept error: %s\n", getpid(), strerror(errno));
continue;
}
if ((pid = fork()) < 0) {
fprintf (stderr, "%04X: can't fork process: %s\n", getpid(), strerror(errno));
exit (1);
}else if (pid == 0) {
int optlen;
struct linger linger;
linger.l_onoff = 1;
linger.l_linger = 2;
optlen = sizeof(linger);
if (setsockopt(connect, SOL_SOCKET, SO_LINGER, &linger, optlen) != 0)
fprintf (stderr, "%04X: can't set linger\n", getpid());
dup2(connect, 0);
dup2(connect, 1);
close (connect);
close (listen_socks[i]);
//break;
return (0);
}
实现的效果如下:
1.监听同一个端口
2.同时接受ipv4和ipv6请求