]> git.ozlabs.org Git - ccan/blob - ccan/net/net.c
net: new module to help IPv4/IPv6 transition.
[ccan] / ccan / net / net.c
1 #include <ccan/net/net.h>
2 #include <ccan/noerr/noerr.h>
3 #include <sys/types.h>
4 #include <sys/socket.h>
5 #include <poll.h>
6 #include <netdb.h>
7 #include <string.h>
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <fcntl.h>
11 #include <errno.h>
12 #include <stdbool.h>
13
14 struct addrinfo *net_client_lookup(const char *hostname,
15                                    const char *service,
16                                    int family,
17                                    int socktype)
18 {
19         struct addrinfo hints;
20         struct addrinfo *res;
21
22         memset(&hints, 0, sizeof(hints));
23         hints.ai_family = family;
24         hints.ai_socktype = socktype;
25         hints.ai_flags = 0;
26         hints.ai_protocol = 0;
27
28         if (getaddrinfo(hostname, service, &hints, &res) != 0)
29                 return NULL;
30
31         return res;
32 }
33
34 static bool set_nonblock(int fd, bool nonblock)
35 {
36         long flags;
37
38         flags = fcntl(fd, F_GETFL);
39         if (flags == -1)
40                 return false;
41
42         if (nonblock)
43                 flags |= O_NONBLOCK;
44         else
45                 flags &= ~(long)O_NONBLOCK;
46
47         return (fcntl(fd, F_SETFL, flags) == 0);
48 }
49
50 /* We only handle IPv4 and IPv6 */
51 #define MAX_PROTOS 2
52
53 static void remove_fd(struct pollfd pfd[],
54                       const struct addrinfo *addr[],
55                       socklen_t slen[],
56                       unsigned int *num,
57                       unsigned int i)
58 {
59         memmove(pfd + i, pfd + i + 1, (*num - i - 1) * sizeof(pfd[0]));
60         memmove(addr + i, addr + i + 1, (*num - i - 1) * sizeof(addr[0]));
61         memmove(slen + i, slen + i + 1, (*num - i - 1) * sizeof(slen[0]));
62         (*num)--;
63 }
64
65 int net_connect(const struct addrinfo *addrinfo)
66 {
67         int sockfd = -1;
68         unsigned int i, num;
69         const struct addrinfo *ipv4 = NULL, *ipv6 = NULL;
70         const struct addrinfo *addr[MAX_PROTOS];
71         socklen_t slen[MAX_PROTOS];
72         struct pollfd pfd[MAX_PROTOS];
73
74         for (; addrinfo; addrinfo = addrinfo->ai_next) {
75                 switch (addrinfo->ai_family) {
76                 case AF_INET:
77                         if (!ipv4)
78                                 ipv4 = addrinfo;
79                         break;
80                 case AF_INET6:
81                         if (!ipv6)
82                                 ipv6 = addrinfo;
83                         break;
84                 }
85         }
86
87         num = 0;
88         /* We give IPv6 a slight edge by connecting it first. */
89         if (ipv6) {
90                 addr[num] = ipv6;
91                 slen[num] = sizeof(struct sockaddr_in6);
92                 pfd[num].fd = socket(AF_INET6, ipv6->ai_socktype,
93                                      ipv6->ai_protocol);
94                 if (pfd[num].fd != -1)
95                         num++;
96         }
97         if (ipv4) {
98                 addr[num] = ipv4;
99                 slen[num] = sizeof(struct sockaddr_in);
100                 pfd[num].fd = socket(AF_INET, ipv4->ai_socktype,
101                                      ipv4->ai_protocol);
102                 if (pfd[num].fd != -1)
103                         num++;
104         }
105
106         for (i = 0; i < num; i++) {
107                 if (!set_nonblock(pfd[i].fd, true)) {
108                         remove_fd(pfd, addr, slen, &num, i--);
109                         continue;
110                 }
111                 /* Connect *can* be instant. */
112                 if (connect(pfd[i].fd, addr[i]->ai_addr, slen[i]) == 0)
113                         goto got_one;
114                 if (errno != EINPROGRESS) {
115                         /* Remove dead one. */
116                         remove_fd(pfd, addr, slen, &num, i--);
117                 }
118                 pfd[i].events = POLLOUT;
119         }
120
121         while (num && poll(pfd, num, -1) != -1) {
122                 for (i = 0; i < num; i++) {
123                         int err;
124                         socklen_t errlen = sizeof(err);
125                         if (!pfd[i].revents)
126                                 continue;
127                         if (getsockopt(pfd[i].fd, SOL_SOCKET, SO_ERROR, &err,
128                                        &errlen) != 0)
129                                 goto out;
130                         if (err == 0)
131                                 goto got_one;
132
133                         /* Remove dead one. */
134                         errno = err;
135                         remove_fd(pfd, addr, slen, &num, i--);
136                 }
137         }
138
139 got_one:
140         /* We don't want to hand them a non-blocking socket! */
141         if (set_nonblock(pfd[i].fd, false))
142                 sockfd = pfd[i].fd;
143
144 out:
145         for (i = 0; i < num; i++)
146                 if (pfd[i].fd != sockfd)
147                         close_noerr(pfd[i].fd);
148         return sockfd;
149 }