Date: Thu, 27 Oct 2005 23:04:50 -0500 From: Craig Boston <craig@tobuj.gank.org> To: freebsd-hackers@freebsd.org Cc: Pawel Jakub Dawidek <pjd@FreeBSD.org> Subject: [PATCH] IPv6 support for ggate Message-ID: <20051028040450.GB50419@nowhere>
next in thread | raw e-mail | index | archive | help
--17pEHd4RhPHOinZp Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Hi hackers: Today I had a need to run ggate over an IPv6-only network. I was a little surprised that it didn't seem to like that, but not discouraged. So here's a patch that adds IPv6 support for ggated(8) and ggatec(8) ;) Overview: * Standardizes on sockaddr_storage for both sockets and addresses * A few semantic differences for certain functions due to the above change (ip2str, g_gate_str2ip) * ggated by default listens on both v4 and v6 sockets (uses select loop instead of accept loop). You can listen on v4 only with "-a 0.0.0.0" or v6 only with "-a ::", or of course bind to a specific address * Use getaddrinfo in place of gethostbyname, and parse addresses with inet_pton * All address are stored internally in network byte order now. Mask comparisons should be independent of byte order since it's purely a "vertical" comparison. Things I'm not entirely happy with: * More AF_INET/AF_INET6 conditional code than I'd have liked to add. Unfortunately there doesn't seem to be an easy way to be protocol neutral with a lot of the socket functions. * The v6 part of countmask is really ugly and complicated. I need to check the ifconfig source and see if there's any standard way for converting a /nnn mask to a binary mask. * When resolving DNS names, it will only use the first entry if there are multiple. It technically always had this problem, but it's more noticeable now. Networks are more likely to have names with both a v4 and v6 record than to have multiple v4 addresses. Fixing this the 'right way' would require significantly more invasive changes. Other than that, I was running with this patch for most of the afternoon with great success. Now I just need to write a kernel mode ggatec so I can mount root over ggate :P -- Craig --17pEHd4RhPHOinZp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="ggate.ipv6.diff" diff -ruN ggate.orig/ggatec/ggatec.c ggate/ggatec/ggatec.c --- ggate.orig/ggatec/ggatec.c Thu Oct 27 22:12:19 2005 +++ ggate/ggatec/ggatec.c Thu Oct 27 22:28:22 2005 @@ -244,21 +244,27 @@ struct g_gate_version ver; struct g_gate_cinit cinit; struct g_gate_sinit sinit; - struct sockaddr_in serv; + struct sockaddr_storage serv; int sfd; /* * Do the network stuff. */ bzero(&serv, sizeof(serv)); - serv.sin_family = AF_INET; - serv.sin_addr.s_addr = g_gate_str2ip(host); - if (serv.sin_addr.s_addr == INADDR_NONE) { - g_gate_log(LOG_DEBUG, "Invalid IP/host name: %s.", host); - return (-1); + serv = g_gate_str2ip(host); + switch(serv.ss_family) { + case AF_INET: + ((struct sockaddr_in*)&serv)->sin_port = htons(port); + break; + case AF_INET6: + ((struct sockaddr_in6*)&serv)->sin6_port = htons(port); + break; + default: + g_gate_log(LOG_DEBUG, + "Invalid IP/host name: %s.", host); + return (-1); } - serv.sin_port = htons(port); - sfd = socket(AF_INET, SOCK_STREAM, 0); + sfd = socket(serv.ss_family, SOCK_STREAM, 0); if (sfd == -1) { g_gate_log(LOG_DEBUG, "Cannot open socket: %s.", strerror(errno)); @@ -267,7 +273,7 @@ g_gate_socket_settings(sfd); - if (connect(sfd, (struct sockaddr *)&serv, sizeof(serv)) == -1) { + if (connect(sfd, (struct sockaddr *)&serv, serv.ss_len) == -1) { g_gate_log(LOG_DEBUG, "Cannot connect to server: %s.", strerror(errno)); close(sfd); diff -ruN ggate.orig/ggated/ggated.c ggate/ggated/ggated.c --- ggate.orig/ggated/ggated.c Thu Oct 27 22:12:19 2005 +++ ggate/ggated/ggated.c Thu Oct 27 22:37:55 2005 @@ -68,7 +68,7 @@ time_t c_birthtime; char *c_path; uint64_t c_token; - in_addr_t c_srcip; + struct sockaddr_storage c_srcaddr; LIST_ENTRY(ggd_connection) c_next; }; @@ -84,15 +84,16 @@ struct ggd_export { char *e_path; /* path to device/file */ - in_addr_t e_ip; /* remote IP address */ - in_addr_t e_mask; /* IP mask */ + struct sockaddr_storage e_addr; /* remote IP address */ + struct sockaddr_storage e_mask; /* IP mask */ + unsigned e_prefixlen; unsigned e_flags; /* flags (RO/RW) */ SLIST_ENTRY(ggd_export) e_next; }; static const char *exports_file = GGATED_EXPORT_FILE; static int got_sighup = 0; -in_addr_t bindaddr; +struct sockaddr_storage bindaddr; static TAILQ_HEAD(, ggd_request) inqueue = TAILQ_HEAD_INITIALIZER(inqueue); static TAILQ_HEAD(, ggd_request) outqueue = TAILQ_HEAD_INITIALIZER(outqueue); @@ -116,31 +117,71 @@ } static char * -ip2str(in_addr_t ip) +ip2str(struct sockaddr *addr) { - static char sip[16]; + static char sip[40]; + void *data; - snprintf(sip, sizeof(sip), "%u.%u.%u.%u", - ((ip >> 24) & 0xff), - ((ip >> 16) & 0xff), - ((ip >> 8) & 0xff), - (ip & 0xff)); + switch(addr->sa_family) { + case AF_INET: + data = &((struct sockaddr_in*)addr)->sin_addr; + break; + case AF_INET6: + data = &((struct sockaddr_in6*)addr)->sin6_addr; + break; + default: + strcpy(sip, "Unknown"); + return sip; + } + + if (!inet_ntop(addr->sa_family, data, sip, sizeof(sip))) + strcpy(sip, "Unknown"); return (sip); } -static in_addr_t -countmask(unsigned m) +static struct sockaddr_storage +countmask(unsigned m, sa_family_t f) { - in_addr_t mask; + struct sockaddr_storage ss; - if (m == 0) { - mask = 0x0; - } else { - mask = 1 << (32 - m); - mask--; - mask = ~mask; + bzero(&ss, sizeof(ss)); + ss.ss_family = f; + + if (f == AF_INET) { + in_addr_t mask4; + if (m == 0) { + mask4 = 0x0; + } else { + mask4 = 1 << (32 - m); + mask4--; + mask4 = ~mask4; + } + + ((struct sockaddr_in*)&ss)->sin_addr.s_addr = htonl(mask4); + } else if (f == AF_INET6) { + uint32_t mask6; + int i; + unsigned cm; + + for (i = 3; i >= 0; i--) { + if (m <= 32*(unsigned)i) { + mask6 = 0; + } else { + cm = m - 32*i; + if (cm >= 32) { + mask6 = 0xffffffff; + } else { + mask6 = 1 << (32 - cm); + mask6--; + mask6 = ~mask6; + } + } + + ((struct sockaddr_in6*)&ss)->sin6_addr.__u6_addr.__u6_addr32[i] = htonl(mask6); + } } - return (mask); + + return (ss); } static void @@ -149,9 +190,11 @@ struct ggd_export *ex; char *word, *path, *sflags; unsigned flags, i, vmask; - in_addr_t ip, mask; + struct sockaddr_storage ip, mask; - ip = mask = flags = vmask = 0; + flags = vmask = 0; + bzero(&ip, sizeof(ip)); + bzero(&mask, sizeof(mask)); path = NULL; sflags = NULL; @@ -160,13 +203,14 @@ switch (i) { case 0: /* IP address or host name */ ip = g_gate_str2ip(strsep(&word, "/")); - if (ip == INADDR_NONE) { + if (ip.ss_family == AF_UNSPEC) { g_gate_xlog("Invalid IP/host name at line %u.", lineno); } - ip = ntohl(ip); - if (word == NULL) + if (word == NULL && ip.ss_family == AF_INET) vmask = 32; + else if (word == NULL && ip.ss_family == AF_INET6) + vmask = 128; else { errno = 0; vmask = strtoul(word, NULL, 10); @@ -174,12 +218,15 @@ g_gate_xlog("Invalid IP mask value at " "line %u.", lineno); } - if ((unsigned)vmask > 32) { - g_gate_xlog("Invalid IP mask value at line %u.", - lineno); + if (((unsigned)vmask > 32 && + ip.ss_family == AF_INET) || + ((unsigned)vmask > 128 && + ip.ss_family == AF_INET6)) { + g_gate_xlog("Invalid IP mask value at " + "line %u.", lineno); } } - mask = countmask(vmask); + mask = countmask(vmask, ip.ss_family); break; case 1: /* flags */ if (strcasecmp("rd", word) == 0 || @@ -217,14 +264,15 @@ g_gate_xlog("No enough memory."); /* Made 'and' here. */ - ex->e_ip = (ip & mask); + ex->e_addr = ip; ex->e_mask = mask; + ex->e_prefixlen = vmask; ex->e_flags = flags; SLIST_INSERT_HEAD(&exports, ex, e_next); g_gate_log(LOG_DEBUG, "Added %s/%u %s %s to exports list.", - ip2str(ex->e_ip), vmask, path, sflags); + ip2str((struct sockaddr*)&ex->e_addr), vmask, path, sflags); } static void @@ -302,12 +350,11 @@ exports_check(struct ggd_export *ex, struct g_gate_cinit *cinit, struct ggd_connection *conn) { - char ipmask[32]; /* 32 == strlen("xxx.xxx.xxx.xxx/xxx.xxx.xxx.xxx")+1 */ + char ipmask[44]; /* 44 == strlen("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/xxx")+1 */ int error = 0, flags; - strlcpy(ipmask, ip2str(ex->e_ip), sizeof(ipmask)); - strlcat(ipmask, "/", sizeof(ipmask)); - strlcat(ipmask, ip2str(ex->e_mask), sizeof(ipmask)); + snprintf(ipmask, sizeof(ipmask), "%s/%u", + ip2str((struct sockaddr*)&ex->e_addr), ex->e_prefixlen); if ((cinit->gc_flags & GGATE_FLAG_RDONLY) != 0) { if (ex->e_flags == O_WRONLY) { g_gate_log(LOG_WARNING, "Read-only access requested, " @@ -355,17 +402,56 @@ return (0); } +static int +mask_compare(struct sockaddr *a, struct sockaddr *b, struct sockaddr *m) { + + if (a->sa_family != m->sa_family) { + g_gate_log(LOG_DEBUG, + "mask_check: Address/mask family mismatch."); + return 0; + } + + if (m->sa_family == AF_INET) { + struct sockaddr_in *a4 = (struct sockaddr_in*)a; + struct sockaddr_in *b4 = (struct sockaddr_in*)b; + struct sockaddr_in *m4 = (struct sockaddr_in*)m; + + if ((a4->sin_addr.s_addr & m4->sin_addr.s_addr) == + (b4->sin_addr.s_addr & m4->sin_addr.s_addr)) { + return 1; + } + } else if (m->sa_family == AF_INET6) { + struct sockaddr_in6 *a6 = (struct sockaddr_in6*)a; + struct sockaddr_in6 *b6 = (struct sockaddr_in6*)b; + struct sockaddr_in6 *m6 = (struct sockaddr_in6*)m; + uint32_t *aaddr = a6->sin6_addr.__u6_addr.__u6_addr32; + uint32_t *baddr = b6->sin6_addr.__u6_addr.__u6_addr32; + uint32_t *maddr = m6->sin6_addr.__u6_addr.__u6_addr32; + int i; + + for (i = 0; i < 4; i++) { + if ((aaddr[i] & maddr[i]) != + (baddr[i] & maddr[i])) { + return 0; + } + } + + return 1; + } + + return 0; +} + static struct ggd_export * exports_find(struct sockaddr *s, struct g_gate_cinit *cinit, struct ggd_connection *conn) { struct ggd_export *ex; - in_addr_t ip; int error; - ip = htonl(((struct sockaddr_in *)(void *)s)->sin_addr.s_addr); SLIST_FOREACH(ex, &exports, e_next) { - if ((ip & ex->e_mask) != ex->e_ip) { + if (!mask_compare(s, (struct sockaddr*)&ex->e_addr, + (struct sockaddr*)&ex->e_mask)) { g_gate_log(LOG_DEBUG, "exports[%s]: IP mismatch.", ex->e_path); continue; @@ -384,7 +470,7 @@ } } g_gate_log(LOG_WARNING, "Unauthorized connection from: %s.", - ip2str(ip)); + ip2str(s)); errno = EPERM; return (NULL); } @@ -404,7 +490,8 @@ LIST_REMOVE(conn, c_next); g_gate_log(LOG_NOTICE, "Connection from %s [%s] removed.", - ip2str(conn->c_srcip), conn->c_path); + ip2str((struct sockaddr*)&conn->c_srcaddr), + conn->c_path); close(conn->c_diskfd); close(conn->c_sendfd); close(conn->c_recvfd); @@ -430,7 +517,6 @@ connection_new(struct g_gate_cinit *cinit, struct sockaddr *s, int sfd) { struct ggd_connection *conn; - in_addr_t ip; /* * First, look for old connections. @@ -449,8 +535,7 @@ return (NULL); } conn->c_token = cinit->gc_token; - ip = htonl(((struct sockaddr_in *)(void *)s)->sin_addr.s_addr); - conn->c_srcip = ip; + memcpy(&conn->c_srcaddr, s, s->sa_len); conn->c_sendfd = conn->c_recvfd = -1; if ((cinit->gc_flags & GGATE_FLAG_SEND) != 0) conn->c_sendfd = sfd; @@ -461,7 +546,7 @@ time(&conn->c_birthtime); conn->c_flags = cinit->gc_flags; LIST_INSERT_HEAD(&connections, conn, c_next); - g_gate_log(LOG_DEBUG, "Connection created [%s, %s].", ip2str(ip), + g_gate_log(LOG_DEBUG, "Connection created [%s, %s].", ip2str(s), conn->c_path); return (conn); } @@ -470,13 +555,11 @@ connection_add(struct ggd_connection *conn, struct g_gate_cinit *cinit, struct sockaddr *s, int sfd) { - in_addr_t ip; - ip = htonl(((struct sockaddr_in *)(void *)s)->sin_addr.s_addr); if ((cinit->gc_flags & GGATE_FLAG_SEND) != 0) { if (conn->c_sendfd != -1) { g_gate_log(LOG_WARNING, - "Send socket already exists [%s, %s].", ip2str(ip), + "Send socket already exists [%s, %s].", ip2str(s), conn->c_path); return (EEXIST); } @@ -485,12 +568,12 @@ if (conn->c_recvfd != -1) { g_gate_log(LOG_WARNING, "Receive socket already exists [%s, %s].", - ip2str(ip), conn->c_path); + ip2str(s), conn->c_path); return (EEXIST); } conn->c_recvfd = sfd; } - g_gate_log(LOG_DEBUG, "Connection added [%s, %s].", ip2str(ip), + g_gate_log(LOG_DEBUG, "Connection added [%s, %s].", ip2str(s), conn->c_path); return (0); } @@ -505,7 +588,7 @@ LIST_REMOVE(conn, c_next); g_gate_log(LOG_DEBUG, "Connection removed [%s %s].", - ip2str(conn->c_srcip), conn->c_path); + ip2str((struct sockaddr*)&conn->c_srcaddr), conn->c_path); if (conn->c_sendfd != -1) close(conn->c_sendfd); if (conn->c_recvfd != -1) @@ -815,10 +898,8 @@ static void log_connection(struct sockaddr *from) { - in_addr_t ip; - ip = htonl(((struct sockaddr_in *)(void *)from)->sin_addr.s_addr); - g_gate_log(LOG_INFO, "Connection from: %s.", ip2str(ip)); + g_gate_log(LOG_INFO, "Connection from: %s.", ip2str(from)); } static int @@ -940,14 +1021,18 @@ int main(int argc, char *argv[]) { - struct sockaddr_in serv; - struct sockaddr from; + fd_set listenfds; + struct sockaddr_in serv4; + struct sockaddr_in6 serv6; + struct sockaddr_storage from; socklen_t fromlen; - int sfd, tmpsfd; + int sfd4, sfd6, tmpsfd, maxfd; unsigned port; - bindaddr = htonl(INADDR_ANY); + bindaddr.ss_family = AF_UNSPEC; port = G_GATE_PORT; + sfd4 = sfd6 = -1; + maxfd = 0; for (;;) { int ch; @@ -957,7 +1042,7 @@ switch (ch) { case 'a': bindaddr = g_gate_str2ip(optarg); - if (bindaddr == INADDR_NONE) { + if (bindaddr.ss_family == AF_UNSPEC) { errx(EXIT_FAILURE, "Invalid IP/host name to bind to."); } @@ -1006,28 +1091,84 @@ signal(SIGCHLD, SIG_IGN); - sfd = socket(AF_INET, SOCK_STREAM, 0); - if (sfd == -1) - g_gate_xlog("Cannot open stream socket: %s.", strerror(errno)); - bzero(&serv, sizeof(serv)); - serv.sin_family = AF_INET; - serv.sin_addr.s_addr = bindaddr; - serv.sin_port = htons(port); - - g_gate_socket_settings(sfd); - - if (bind(sfd, (struct sockaddr *)&serv, sizeof(serv)) == -1) - g_gate_xlog("bind(): %s.", strerror(errno)); - if (listen(sfd, 5) == -1) - g_gate_xlog("listen(): %s.", strerror(errno)); + if (bindaddr.ss_family != AF_INET6) { + struct sockaddr_in *bindaddr4 = (struct sockaddr_in*)&bindaddr; + + sfd4 = socket(AF_INET, SOCK_STREAM, 0); + if (sfd4 == -1) + g_gate_xlog("Cannot open stream socket: %s.", + strerror(errno)); + + bzero(&serv4, sizeof(serv4)); + serv4.sin_family = AF_INET; + if (bindaddr.ss_family == AF_INET) + serv4.sin_addr.s_addr = bindaddr4->sin_addr.s_addr; + else + serv4.sin_addr.s_addr = INADDR_ANY; + serv4.sin_port = htons(port); + + g_gate_socket_settings(sfd4); + + if (bind(sfd4, (struct sockaddr *)&serv4, sizeof(serv4)) == -1) + g_gate_xlog("bind(): %s.", strerror(errno)); + if (listen(sfd4, 5) == -1) + g_gate_xlog("listen(): %s.", strerror(errno)); + + g_gate_log(LOG_INFO, "Listen on IPv4 port: %d.", port); + + if (maxfd <= sfd4) + maxfd = sfd4 + 1; + } + if (bindaddr.ss_family != AF_INET) { + struct sockaddr_in6 *bindaddr6 = (struct sockaddr_in6*)&bindaddr; + + sfd6 = socket(AF_INET6, SOCK_STREAM, 0); + if (sfd6 == -1) + g_gate_xlog("Cannot open stream socket: %s.", + strerror(errno)); + bzero(&serv6, sizeof(serv6)); + serv6.sin6_family = AF_INET6; + if (bindaddr.ss_family == AF_INET6) { + serv6.sin6_addr = bindaddr6->sin6_addr; + } else { + struct in6_addr in6any = IN6ADDR_ANY_INIT; + serv6.sin6_addr = in6any; + } + serv6.sin6_port = htons(port); + + g_gate_socket_settings(sfd6); - g_gate_log(LOG_INFO, "Listen on port: %d.", port); + if (bind(sfd6, (struct sockaddr *)&serv6, sizeof(serv6)) == -1) + g_gate_xlog("bind(): %s.", strerror(errno)); + if (listen(sfd6, 5) == -1) + g_gate_xlog("listen(): %s.", strerror(errno)); + + g_gate_log(LOG_INFO, "Listen on IPv6 port: %d.", port); + + if (maxfd <= sfd6) + maxfd = sfd6 + 1; + } signal(SIGHUP, huphandler); for (;;) { + FD_ZERO(&listenfds); + if (sfd4 > 0) + FD_SET(sfd4, &listenfds); + if (sfd6 > 0) + FD_SET(sfd6, &listenfds); + + select(maxfd, &listenfds, NULL, NULL, NULL); + fromlen = sizeof(from); - tmpsfd = accept(sfd, &from, &fromlen); + if (sfd4 > 0 && FD_ISSET(sfd4, &listenfds)) { + tmpsfd = accept(sfd4, (struct sockaddr*)&from, + &fromlen); + } else if (sfd6 > 0 && FD_ISSET(sfd6, &listenfds)) { + tmpsfd = accept(sfd6, (struct sockaddr*)&from, + &fromlen); + } else continue; + if (tmpsfd == -1) g_gate_xlog("accept(): %s.", strerror(errno)); @@ -1036,9 +1177,12 @@ exports_get(); } - if (!handshake(&from, tmpsfd)) + if (!handshake((struct sockaddr*)&from, tmpsfd)) close(tmpsfd); } - close(sfd); + if (sfd4 > 0) + close(sfd4); + if (sfd6 > 0) + close(sfd6); exit(EXIT_SUCCESS); } diff -ruN ggate.orig/shared/ggate.c ggate/shared/ggate.c --- ggate.orig/shared/ggate.c Thu Oct 27 22:12:19 2005 +++ ggate/shared/ggate.c Thu Oct 27 22:39:22 2005 @@ -376,20 +376,47 @@ } #endif /* LIBGEOM */ -in_addr_t +struct sockaddr_storage g_gate_str2ip(const char *str) { - struct hostent *hp; - in_addr_t ip; + struct sockaddr_storage ss; + struct addrinfo *addr; + struct sockaddr_in *ip = (struct sockaddr_in*)&ss; + struct sockaddr_in6 *ip6 = (struct sockaddr_in6*)&ss; - ip = inet_addr(str); - if (ip != INADDR_NONE) { - /* It is a valid IP address. */ - return (ip); + bzero(&ss, sizeof(ss)); + + if (inet_pton(AF_INET, str, &ip->sin_addr.s_addr)) { + /* It is a valid IPv4 address. */ + ss.ss_len = sizeof(struct sockaddr_in); + ss.ss_family = AF_INET; + return ss; + } else if (inet_pton(AF_INET6, str, &ip6->sin6_addr)) { + /* It is a valid IPv6 address. */ + ss.ss_len = sizeof(struct sockaddr_in6); + ss.ss_family = AF_INET6; + return ss; } + /* Check if it is a valid host name. */ - hp = gethostbyname(str); - if (hp == NULL) - return (INADDR_NONE); - return (((struct in_addr *)(void *)hp->h_addr)->s_addr); + if (!getaddrinfo(str, NULL, NULL, &addr)) { + struct sockaddr* sa = (struct sockaddr*)&ss; + + sa->sa_family = addr->ai_family; + switch(sa->sa_family) { + case AF_INET: + sa->sa_len = sizeof(struct sockaddr_in); + break; + case AF_INET6: + sa->sa_len = sizeof(struct sockaddr_in6); + break; + } + + memcpy(&sa->sa_data, addr->ai_addr, addr->ai_addrlen); + freeaddrinfo(addr); + return ss; + } + + ss.ss_family = AF_UNSPEC; + return ss; } diff -ruN ggate.orig/shared/ggate.h ggate/shared/ggate.h --- ggate.orig/shared/ggate.h Thu Oct 27 22:12:19 2005 +++ ggate/shared/ggate.h Thu Oct 27 11:14:30 2005 @@ -110,7 +110,7 @@ #ifdef LIBGEOM void g_gate_list(int unit, int verbose); #endif -in_addr_t g_gate_str2ip(const char *str); +struct sockaddr_storage g_gate_str2ip(const char *str); /* * g_gate_swap2h_* - functions swap bytes to host byte order (from big endian). --17pEHd4RhPHOinZp--
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20051028040450.GB50419>