X-Recipient: archive-cygwin AT delorie DOT com X-SWARE-Spam-Status: No, hits=-3.2 required=5.0 tests=AWL,BAYES_00,RCVD_IN_DNSWL_LOW X-Spam-Check-By: sourceware.org Message-ID: <4B9EEF35.9000701@cwilson.fastmail.fm> Date: Mon, 15 Mar 2010 22:38:45 -0400 From: Charles Wilson User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.8.1.23) Gecko/20090812 Thunderbird/2.0.0.23 Mnenhy/0.7.6.666 MIME-Version: 1.0 To: cygwin AT cygwin DOT com Subject: IPv6 help (Re: inetutils, r* commands) Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit Mailing-List: contact cygwin-help AT cygwin DOT com; run by ezmlm List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: cygwin-owner AT cygwin DOT com Mail-Followup-To: cygwin AT cygwin DOT com Delivered-To: mailing list cygwin AT cygwin DOT com There are two questions, below...so skip to those if you don't need the background. I've been trying to deal with the various issues that have cropped up in inetutils since its last release, and since 1.7.1 was released. It seems that most of the issues are related to the IPv6 support in cygwin, giving results back to the daemons that they are not prepared for. Well. inetutils appears hopeless; I can't see where any work has gone in to supporting IPv6. So, I plan to *replace* inetutils with a number of smaller packages, derived from sources where such work HAS already occurred. I'm using this page as my starting point: http://www.deepspace6.net/docs/ipv6_status_page_apps.html Also, I plan to ITA xinetd, and drop inetutils' version of inetd with no direct replacement. Similarly, I plan to drop syslogd in favor of Corinna's syslog-ng. In these two cases, I'll repackage the existing binaries that have no direct replacement, so that people's installations won't just "break" all of the sudden. But these two apps will definitely be EOLed. So, first up: rsh/rsh-server, based on the fedora release(s) of the same name. The fedora rpm is based in turn on netkit-rsh, but heavily patched. However, the underlying codebase still shows its age: lots of BSD signal functions (sigblock, sigsetmask, etc) which we don't support. However, those fixes are relatively easy. The clients (rsh, rlogin, rcp, rexec) all seem to work when contacting a separate (linux) server (*). And the servers all seem to work when contacted by a separate (linux) client (**). But, connecting locally doesn't appear to work correctly. (*) It seems that you now need to have an identd server running on the *client* box, or r* authentication takes 30 seconds or so. We don't currrently have one of these ported; I'll try to do that at some point unless someone beats me to it. I've been using the following (closed source, free-as-in-beer) version that seems to be well-regarded: http://rndware.info/products/windows-ident-server.html (**) After I modified the function that compares the client's IP address extracted from the connection, with the IP address determined by reverse DNS lookup. At least on my Vista machine, ALL local connections come in as IPv6, as ::ffff:192.168.x.y. (or ::ffff:127.0.0.1) As the original "address checker" did this: memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; error = getaddrinfo(hname_buf, NULL, &hints, &res); res0 = res; while (res) { if (soaddr_eq_ip(fromp, res->ai_addr)) { hostok = 1; break; } res = res->ai_next; } freeaddrinfo(res0); where the comparison function soaddr_eq_ip did: static int soaddr_eq_ip(const struct sockaddr *s1, const struct sockaddr *s2) { if (s1->sa_family != s2->sa_family) return 0; /* no match */ ... Well, since ALL of the values returned by getaddrinfo were IPv4, all connections were rejected. So, I made soaddr_eq_ip somewhat smarter (pasted below), and now -- as I said above -- connections from other machines on my local net work. But...localhost (or using the localhost's actual name/IP) do not. Here's some debugging output from rlogind, when doing 'rlogin localhost' from the same machine: mymachine rlogind: PID 5960: warning: /etc/hosts.allow, line 11: host name/address mismatch: 127.0.0.1 != mymachine mymachine rlogind: PID 5960: connect from ::ffff:127.0.0.1 (::ffff:127.0.0.1) mymachine rlogind: PID 5960: doit: hostok=0 mymachine rlogind: PID 5960: soaddr_eq_ip: (::ffff:127.0.0.1,192.168.199.1) mymachine rlogind: PID 5960: soaddr_eq_ip: (::ffff:127.0.0.1,192.168.154.1) mymachine rlogind: PID 5960: soaddr_eq_ip: (::ffff:127.0.0.1,192.168.1.3) mymachine rlogind: PID 5960: doit: hostok=0 (hostok=0 means "no match/reject connection). Notice that getaddrinfo returns three different networks. Two of these are inactive (.199.1 and .154.1). 192.168.1.3 is mymachine's "real" IP addr. But 127.0.0.1 is /not/ included in the list...so it can't be matched. /etc/hosts has: 127.0.0.1 localhost ::1 localhost # Start of entries inserted by Spybot - Search & Destroy So.... ********************* QUESTION #1. Should cygwin's getaddrinfo return an entry for the loopback interface? ********************* Well, next I tried using 'rlogin mymachine'. On this, I think tcp_wrappers choked: rlogind: PID 5072: refused connect from mymachine (::ffff:192.168.199.1) Notice that only the "first" interface from the previous list -- assuming getaddrinfo returned its results in the same order as before -- is (in)validated. So, probably a bug -- or incompatibility of assumptions between tcp_wrappers and cygwin1.dll. I'll have to dig into that, later. FWIW, hosts.allow has: ALL : localhost 127.0.0.1/32 [::1]/128 : allow rlogind: 192.168.1.0/255.255.255.0 rshd: 192.168.1.0/255.255.255.0 rexecd: 192.168.1.0/255.255.255.0 Finally, I tried 'rlogin mymachine.mydomain'. This was even weirder. With my fix outlined above, it kinda works -- THAT part, anyway: rlogind: PID 7904: connect from mymachine.mydomain (::ffff:192.168.1.3) rlogind: PID 7904: doit: hostok=0 rlogind: PID 7904: soaddr_eq_ip: (::ffff:192.168.1.3,192.168.1.3) rlogind: PID 7904: doit: hostok=1 Yay! hostok=1! So, what's it look like over in the client window? $ rlogin mymachine.mydomain Last login: Mon Mar 15 22:34:40 from mymachine.mydomain ____________________, ______________________________________ .QQQQQQQQQQQQQQQQQQQQQQQQL_ | | .gQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ__ | | gQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ== | _.---.) | QQQQQQQQQrlogin: closed connection. Oops. This doesn't bode well -- it means that even if the answer to #1 is yes, all that will accomplish is moving the 'rlogin localhost' into the same boat with 'rlogin fqdn' -- where it looks like the forked rlogind process is crashing. (It doesn't do that when coming from a different box). Well, more debugging... So, it's getting there -- remember that all these difficulties are solely with regards to same-machine connections. The clients seem to work fine with actual outbound connections, and the servers work fine (now) with actual inbound ones. So, what's the second question? ********************* QUESTION #2. Is there a cleaner way to do the address matching than the version that I've modified below? I basically only changed the guts of soaddr_eq_ip(); the rest is factory equipment... ********************* (find_hostname is the actual entry point; it populates *portname as well as setting *hostok) /* * Check whether host h is in our local domain, * defined as sharing the last two components of the domain part, * or the entire domain part if the local domain has only one component. * If either name is unqualified (contains no '.'), * assume that the host is local, as it will be * interpreted as such. */ static int local_domain(const char *h) { char localhost[MAXHOSTNAMELEN]; const char *p1, *p2; localhost[0] = 0; (void) gethostname(localhost, sizeof(localhost)); p1 = topdomain(localhost); p2 = topdomain(h); if (p1 == NULL || p2 == NULL || !strcasecmp(p1, p2)) return(1); return(0); } static int soaddr_eq_ip(const struct sockaddr *s1, const struct sockaddr *s2) { const struct sockaddr_in6 * ipv6p = NULL; const struct sockaddr_in * ipv4p = NULL; if (s1->sa_family != s2->sa_family) { /* handle IPv4-in-IPv6 */ if (s1->sa_family == AF_INET6) { ipv6p = ((const struct sockaddr_in6 *)s1); } else if (s1->sa_family == AF_INET) { ipv4p = ((const struct sockaddr_in *)s1); } else { syslog(LOG_INFO, "soaddr_eq_ip: s1 is neither IPv4 nor IPv6"); return 0; } if (s2->sa_family == AF_INET6) { ipv6p = ((const struct sockaddr_in6 *)s2); } else if (s2->sa_family == AF_INET) { ipv4p = ((const struct sockaddr_in *)s2); } else { syslog(LOG_INFO, "soaddr_eq_ip: s2 is neither IPv4 nor IPv6"); return 0; } if (IN6_IS_ADDR_V4MAPPED(&(ipv6p->sin6_addr))) { char ipv6buf[INET6_ADDRSTRLEN + 1]; char ipv4buf[INET_ADDRSTRLEN + 1]; memset (ipv6buf, 0x00, INET6_ADDRSTRLEN + 1); memset (ipv4buf, 0x00, INET_ADDRSTRLEN + 1); if (inet_ntop(AF_INET6, (const void *)&(ipv6p->sin6_addr), ipv6buf, INET6_ADDRSTRLEN+1) != NULL && inet_ntop(AF_INET, (const void *)&(ipv4p->sin_addr), ipv4buf, INET_ADDRSTRLEN+1) != NULL&& ipv4p->sin_addr.s_addr != INADDR_ANY && strncmp (ipv6buf, "::ffff:", 7) == 0) { syslog(LOG_INFO, "soaddr_eq_ip: (%s,%s)",ipv6buf,ipv4buf); return (strncmp(ipv6buf+7, ipv4buf, sizeof(ipv4buf)) == 0); } syslog(LOG_INFO, "soaddr_eq_ip: failed to compare IPv4-mapped address"); return 0; } syslog(LOG_INFO, "soaddr_eq_ip: IPv6 value is not IPv4-mapped addr"); return 0; } if (s2->sa_family == AF_INET6) { syslog(LOG_INFO, "soaddr_eq_ip: comparing two AF_INET6 addrs"); return (memcmp( (const void*)( &((const struct sockaddr_in6 *)s1)->sin6_addr ), (const void*)( &((const struct sockaddr_in6 *)s2)->sin6_addr ), sizeof(struct in6_addr)) == 0); } else { syslog(LOG_INFO, "soaddr_eq_ip: comparing two AF_INET addrs"); return (memcmp( (const void*)( &((const struct sockaddr_in *)s1)->sin_addr ), (const void*)( &((const struct sockaddr_in *)s2)->sin_addr ), sizeof(struct in_addr)) == 0); } } static char * find_hostname(struct sockaddr *fromp, socklen_t fromlen, char *portname, int *hostokp) { int error; char *hname; char hname_buf[NI_MAXHOST]; int hostok = 0; error = getnameinfo(fromp, fromlen, hname_buf, sizeof(hname_buf), portname, NI_MAXSERV, NI_NUMERICSERV); if ((error == EAI_AGAIN) && !check_all) error = getnameinfo(fromp, fromlen, hname_buf, sizeof(hname_buf), portname, NI_MAXSERV, NI_NUMERICSERV|NI_NUMERICHOST); assert(error == 0); if (check_all || local_domain(hname_buf)) { /* * If name returned is in our domain, * attempt to verify that we haven't been fooled by someone * in a remote net; look up the name and check that this * address corresponds to the name. */ struct addrinfo hints; struct addrinfo *res0, *res; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; error = getaddrinfo(hname_buf, NULL, &hints, &res); assert(error == 0); res0 = res; while (res) { if (soaddr_eq_ip(fromp, res->ai_addr)) { hostok = 1; break; } res = res->ai_next; } freeaddrinfo(res0); } else { hostok = 1; } hname = strdup(hname_buf); /* * Actually it might be null if we're out of memory, but * where do we go then? We'd have to bail anyhow. */ assert(hname != NULL); *hostokp = hostok; return hname; } -- Chuck -- Problem reports: http://cygwin.com/problems.html FAQ: http://cygwin.com/faq/ Documentation: http://cygwin.com/docs.html Unsubscribe info: http://cygwin.com/ml/#unsubscribe-simple