Copyright 2008 M. Blapp, ImproWare AG ------------------------------------- This patch works around an order problem with dns servers, especially bind 8. Newer bind implementations offer configuration settings for this issue: see rrset-order {order random;}; ----------------------------------------------------------- If you have more then one milter, it is easy to do load balancing by unsing dns-round robin with different IP adresses. Let's imagine we have three filter servers: 192.168.0.1, 192.168.0.2 and 192.168.0.3. All of them should get the same workload - in a perfect world of course. But unfortunately servers crash, or they are down for maintainance. And here the problems start: not all DNS implementations have well designed ip randomisation. In our example marks 'X' the IP which gets the job. Case 1: ------- nslookup filter Address: 192.168.0.1 X Address: 192.168.0.2 Address: 192.168.0.3 Case 2: ------- nslookup filter Address: 192.168.0.2 X Address: 192.168.0.3 Address: 192.168.0.1 Case 3: ------- nslookup filter Address: 192.168.0.3 X Address: 192.168.0.1 Address: 192.168.0.2 As you see, we have three milter servers defined for filter. As long as all three servers are working fine, there is no problem at all. All milters get 33,3% of the work jobs. But what happens if, for example, the server with the IP 192.168.0.2 has crashed, or is out of service ? Case 1: ------- nslookup filter Address: 192.168.0.1 X Address: 192.168.0.2 crashed Address: 192.168.0.3 Case 2: ------- nslookup filter Address: 192.168.0.2 crashed Address: 192.168.0.3 X Address: 192.168.0.1 Case 3: ------- nslookup filter Address: 192.168.0.3 X Address: 192.168.0.1 Address: 192.168.0.2 crashed Now we have 33% of the jobs served to 192.168.0.1, and 66% of the jobs are given to 192.168.0.3. This is bad. This patch randomizes the answer of the dns server, and doesn't keep the order of the answer if there is any. The perfect world is there - again :-) --- sendmail/milter.c.orig 2008-08-07 16:34:30.000000000 +0200 +++ sendmail/milter.c 2008-08-07 16:35:27.000000000 +0200 @@ -650,6 +650,10 @@ SOCKADDR_LEN_T addrlen = 0; int addrno = 0; int save_errno; + int tryaddr, countaddr; + int *hostlist; + int *tmphostlist; + char *tmpaddr; char *p; char *colon; char *at; @@ -973,25 +977,61 @@ return -1; } addr.sa.sa_family = hp->h_addrtype; + + /* Count available hosts */ + countaddr = 0; + while ((tmpaddr = hp->h_addr_list[countaddr]) != 0) + countaddr++; + + hostlist = (int *) xalloc((countaddr+1) * sizeof(int)); + tmphostlist = (int *) xalloc((countaddr+1) * sizeof(int)); + + if (countaddr < 2) { + /* Just use the first adress */ + addrno = 0; + } else { + /* If we have more than two hosts use a random int value to + * connect to one of those adresses. Some DNS are not able to + * provide a working round robin DNS, they always give the + * list in the same order. + */ + struct timeval t; + int i; + + /* Init hostlist, we may need it later */ + for (i=0; i < (countaddr+1); i++) { + if (i <= countaddr) { + hostlist[i] = i; + } else { + hostlist[i] = -1; + } + tmphostlist[i] = -1; + } + + /* Get a random value between zero and countaddr) */ + gettimeofday(&t, NULL); + srandom(t.tv_usec); + addrno = (random() % (countaddr)); + hostlist[addrno] = -1; + } + switch (hp->h_addrtype) { # if NETINET case AF_INET: memmove(&addr.sin.sin_addr, - hp->h_addr, INADDRSZ); + hp->h_addr_list[addrno], INADDRSZ); addr.sin.sin_port = port; addrlen = sizeof(struct sockaddr_in); - addrno = 1; break; # endif /* NETINET */ # if NETINET6 case AF_INET6: memmove(&addr.sin6.sin6_addr, - hp->h_addr, IN6ADDRSZ); + hp->h_addr_list[addrno], IN6ADDRSZ); addr.sin6.sin6_port = port; addrlen = sizeof(struct sockaddr_in6); - addrno = 1; break; # endif /* NETINET6 */ @@ -1011,6 +1051,8 @@ milter_error(m, e); # if NETINET6 freehostent(hp); + free(hostlist); + free(tmphostlist); # endif /* NETINET6 */ return -1; } @@ -1039,6 +1081,8 @@ # if NETINET6 if (hp != NULL) freehostent(hp); + free(hostlist); + free(tmphostlist); # endif /* NETINET6 */ return 0; } @@ -1055,6 +1099,8 @@ # if NETINET6 if (hp != NULL) freehostent(hp); + free(hostlist); + free(tmphostlist); # endif /* NETINET6 */ return -1; } @@ -1078,6 +1124,8 @@ # if NETINET6 if (hp != NULL) freehostent(hp); + free(hostlist); + free(tmphostlist); # endif /* NETINET6 */ return -1; } @@ -1115,14 +1163,47 @@ (void) close(sock); /* try next address */ - if (hp != NULL && hp->h_addr_list[addrno] != NULL) + + if (countaddr > 1) { + int i, j; + struct timeval t; + + /* + * Get a list of adresses we have not tried yet + * to connect to. + */ + j = 0; + countaddr--; + for (i=0; i <= countaddr ; i++) { + if (hostlist[i] != -1) { + tmphostlist[j] = hostlist[i]; + j++; + } + } + /* + * Get a random value between zero and countaddr + */ + gettimeofday(&t, NULL); + srandom(t.tv_usec); + tryaddr = (random() % (countaddr)); + addrno = tmphostlist[tryaddr]; + /* + * Now we can invalidate the entry and restore + * the hostlist. + */ + tmphostlist[tryaddr] = -1; + memcpy(hostlist,tmphostlist, sizeof hostlist); + } else + addrno++; + + if (countaddr > 0 && hp != NULL && hp->h_addr_list[addrno] != NULL) { switch (addr.sa.sa_family) { # if NETINET case AF_INET: memmove(&addr.sin.sin_addr, - hp->h_addr_list[addrno++], + hp->h_addr_list[addrno], INADDRSZ); break; # endif /* NETINET */ @@ -1130,7 +1211,7 @@ # if NETINET6 case AF_INET6: memmove(&addr.sin6.sin6_addr, - hp->h_addr_list[addrno++], + hp->h_addr_list[addrno], IN6ADDRSZ); break; # endif /* NETINET6 */ @@ -1148,6 +1229,8 @@ milter_error(m, e); # if NETINET6 freehostent(hp); + free(hostlist); + free(tmphostlist); # endif /* NETINET6 */ return -1; } @@ -1167,6 +1250,8 @@ # if NETINET6 if (hp != NULL) freehostent(hp); + free(hostlist); + free(tmphostlist); # endif /* NETINET6 */ return -1; } @@ -1177,6 +1262,10 @@ freehostent(hp); hp = NULL; } + if (hostlist) + free(hostlist); + if (tmphostlist) + free(tmphostlist); # endif /* NETINET6 */ # if MILTER_NO_NAGLE && !defined(TCP_CORK) {