模仿openssh工具,实现ipv4和ipv6兼容的服务(C实现)

这个月接到一个功能添加需求,需要为一个代理程序支持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请求

 

你可能感兴趣的:(c)