6ff8d018525b73c8836fa687bf16f45ccc1a11f1
galt
  Wed Jun 21 23:33:34 2023 -0700
Adding a utility for using our existing geoIp infrastructure to lookup a 2-letter country code from an IP address (IPv4 or IPv6). Gives an error if the IP address is malformed, gives an error if hgcentral.geoIpCountry6 table does not exist, then uses it to lookup the country. If it is not found in our Maxmind table, then it returns "not found". Hopefully Hiram and others can use this.

diff --git src/hg/lib/geoMirror.c src/hg/lib/geoMirror.c
index 9b5495e..3a90532 100644
--- src/hg/lib/geoMirror.c
+++ src/hg/lib/geoMirror.c
@@ -1,214 +1,280 @@
 /* geoMirror - support geographic based mirroring (e.g. euro and asia nodes) */
 
 /* Copyright (C) 2014 The Regents of the University of California 
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 
 #include "common.h"
 #include "geoMirror.h"
 #include "hgConfig.h"
 #include "internet.h"
 
 /* geographic server (mirror) support
 
    Customize 'Mirrors' drop-down menu based on current server.  If the server
    is a UCSC-sponsored site (USA/Ca or European), show this in drop-down menu
    with a checkmark, and allow user to change server.  Based on design notes here:
         http://genomewiki.ucsc.edu/genecats/index.php/Euronode
 
    NOTE: Uses hgcentral.gbNode table
    to populate menu items and locate redirects.  This implementation uses the
    spec from above wiki page:
         browser.node=1 -> US server (genome.ucsc.edu)
         browser.node=2 -> European server (genome-euro.ucsc.edu)
         browser.node=3 -> Asian server (genome-asia.ucsc.edu)
 
 
    For Testing, use browser.geoSuffix: e.g.
         browser.geoSuffix=Test
    will cause it to use in hgcentral the tables  gbNodeTest and geoIpNodeTest instead.
    See the genomewiki link above for more documentation on testing.
 
 */
 
 
 boolean geoMirrorEnabled()
 {
 // return TRUE if this site has geographic mirroring turned on
 return geoMirrorNode() != NULL;
 }
 
 char *geoMirrorNode()
 {
 // return which geo mirror node this is (or NULL if geo mirroring is turned off)
 return cfgOption("browser.node");
 }
 
 
 int geoMirrorDefaultNode4(struct sqlConnection *centralConn, char *ipStr)
 // return default node for given IP
 {
 char query[1024];
 bits32 ip = 0;
 int defaultNode = 1;
 
 if (isIpv6Address(ipStr))  // ipv6 which we do not handle yet. TODO
     return defaultNode;  // at least tolerate IPV6 remote addr
 
 internetDottedQuadToIp(ipStr, &ip);
 
 char *geoSuffix = cfgOptionDefault("browser.geoSuffix","");
 
 // We (sort-of) assume no overlaps in geoIpNode table, so we can use limit 1 to make query very efficient;
 // we do accomodate a range that is completely contained in another (to accomodate the hgroaming entry for testing);
 // this is accomplished by "<= ipEnd" in the sql query. 
 // TODO The hgroaming thing is probably obsolete and testing is done with browser.geoSuffix= instead.
 // If so, we may wish to remove the loop below since that was added by Larry and reformulate
 // it as it was originally done by Galt.  However it does not seem to affect performance so we can leave it for now.
 
 sqlSafef(query, sizeof query, 
     "select ipStart, ipEnd, node from geoIpNode%s where %u >= ipStart and %u <= ipEnd order by ipStart desc limit 1"
     , geoSuffix, ip, ip);
 char **row;
 struct sqlResult *sr = sqlGetResult(centralConn, query);
 
 if ((row = sqlNextRow(sr)) != NULL)
     {
     uint ipStart = sqlUnsigned(row[0]);
     uint ipEnd = sqlUnsigned(row[1]);
     if (ipStart <= ip && ipEnd >= ip)
         {
         defaultNode = sqlSigned(row[2]);
         }
     }
 sqlFreeResult(&sr);
 
 return defaultNode;
 }
  
 int geoMirrorDefaultNode6(struct sqlConnection *centralConn, char *ipStr)
 // return default node for given IP
 {
 char query[1024];
 char newIpStr[NI_MAXHOST];
 struct in6_addr ip;
 ZeroVar(&ip);
 char ipHex[33];
 
 int defaultNode = 1;
 
 if (isIpv6Address(ipStr))
     safef(newIpStr, sizeof newIpStr, "%s", ipStr);
 else if (isIpv4Address(ipStr))
     safef(newIpStr, sizeof newIpStr, "%s%s", IPV4MAPPED_PREFIX, ipStr); // "::ffff:"
 else 
     {
     warn("Unexpected strange ip address string: %s", ipStr);
     return defaultNode;
     }
 
 if (!internetIpStringToIp6(newIpStr, &ip))
     errAbort("internetIpStringToIp6 failed for %s", ipStr);
 
 ip6AddrToHexStr(&ip, ipHex, sizeof ipHex);
 
 char *geoSuffix = cfgOptionDefault("browser.geoSuffix","");
 
 // We (sort-of) assume no overlaps in geoIpNode table, so we can use limit 1 to make query very efficient;
 // we do accomodate a range that is completely contained in another (to accomodate the hgroaming entry for testing);
 // this is accomplished by "<= ipEnd" in the sql query. 
 // TODO The hgroaming thing is probably obsolete and testing is done with browser.geoSuffix= instead.
 // If so, we may wish to remove the loop below since that was added by Larry and reformulate
 // it as it was originally done by Galt.  However it does not seem to affect performance so we can leave it for now.
 
 sqlSafef(query, sizeof query, 
     "select ipStart, ipEnd, node from geoIpNode6%s where unhex('%s') >= ipStart and unhex('%s') <= ipEnd order by ipStart desc limit 1"
     , geoSuffix, ipHex, ipHex);
 char **row;
 struct sqlResult *sr = sqlGetResult(centralConn, query);
 
 if ((row = sqlNextRow(sr)) != NULL)
     {
     struct in6_addr ipStart;
     ip6AddrCopy((struct in6_addr *)row[0], &ipStart);
 
     struct in6_addr ipEnd;
     ip6AddrCopy((struct in6_addr *)row[1], &ipEnd  );
 
     if (
 	(ip6AddrCmpBits(&ipStart, &ip) <= 0)
 	&&
 	(ip6AddrCmpBits(&ipEnd  , &ip) >= 0)
        )
         {
         defaultNode = sqlSigned(row[2]);
         }
     }
 sqlFreeResult(&sr);
 
 return defaultNode;
 }
 
 int geoMirrorDefaultNode(struct sqlConnection *centralConn, char *ipStr)
 // return default node for given IP
 {
 char *geoSuffix = cfgOptionDefault("browser.geoSuffix","");
 char fullTableName[1024];
 safef(fullTableName, sizeof fullTableName, "%s%s", "geoIpNode6", geoSuffix);
 int defaultNode = 1;
 if (sqlTableExists(centralConn, fullTableName))
     {
     defaultNode = geoMirrorDefaultNode6(centralConn, ipStr);
     }
 else
     {
     defaultNode = geoMirrorDefaultNode4(centralConn, ipStr);
     }
 return defaultNode;
 }
 
+char *geoMirrorCountry6(struct sqlConnection *centralConn, char *ipStr)
+/* Return 2 letter country code for given IP. user has already checked table geoIpCountry6 exists.
+ * Return error string otherwise. Free the response string. */
+{
+char query[1024];
+char newIpStr[NI_MAXHOST];
+struct in6_addr ip;
+ZeroVar(&ip);
+char ipHex[33];
+
+char response[256];
+safef(response, sizeof response, "not found");
+
+if (isIpv6Address(ipStr))
+    safef(newIpStr, sizeof newIpStr, "%s", ipStr);
+else if (isIpv4Address(ipStr))
+    safef(newIpStr, sizeof newIpStr, "%s%s", IPV4MAPPED_PREFIX, ipStr); // "::ffff:"
+else 
+    {
+    warn("Unexpected strange ip address string: %s", ipStr);
+    safef(response, sizeof response, "Unexpected strange ip address string: %s", ipStr);
+    return cloneString(response);
+    }
+
+if (!internetIpStringToIp6(newIpStr, &ip))
+    errAbort("internetIpStringToIp6 failed for %s", ipStr);
+
+ip6AddrToHexStr(&ip, ipHex, sizeof ipHex);
+
+char *geoSuffix = cfgOptionDefault("browser.geoSuffix","");
+
+// We (sort-of) assume no overlaps in geoIpCountry6 table, so we can use limit 1 to make query very efficient;
+// we do accomodate a range that is completely contained in another (to accomodate the hgroaming entry for testing);
+// this is accomplished by "<= ipEnd" in the sql query. 
+// TODO The hgroaming thing is probably obsolete and testing is done with browser.geoSuffix= instead.
+// If so, we may wish to remove the loop below since that was added by Larry and reformulate
+// it as it was originally done by Galt.  However it does not seem to affect performance so we can leave it for now.
+
+sqlSafef(query, sizeof query, 
+    "select ipStart, ipEnd, countryId from geoIpCountry6%s where unhex('%s') >= ipStart and unhex('%s') <= ipEnd order by ipStart desc limit 1"
+    , geoSuffix, ipHex, ipHex);
+char **row;
+struct sqlResult *sr = sqlGetResult(centralConn, query);
+
+if ((row = sqlNextRow(sr)) != NULL)
+    {
+    struct in6_addr ipStart;
+    ip6AddrCopy((struct in6_addr *)row[0], &ipStart);
+
+    struct in6_addr ipEnd;
+    ip6AddrCopy((struct in6_addr *)row[1], &ipEnd  );
+
+    if (
+	(ip6AddrCmpBits(&ipStart, &ip) <= 0)
+	&&
+	(ip6AddrCmpBits(&ipEnd  , &ip) >= 0)
+       )
+        {
+        safef(response, sizeof response, "%s", row[2]);
+        }
+    }
+sqlFreeResult(&sr);
+
+return cloneString(response);
+}
+
 char *geoMirrorMenu()
 /* Create customized geoMirror menu string for substitution of  into 
  * <!-- OPTIONAL_MIRROR_MENU --> in htdocs/inc/globalNavBar.inc 
  * Reads hgcentral geo tables and hg.conf settings. 
  * Free the returned string when done. */
 {
 struct dyString *dy = dyStringNew(0);  // by default replacment is just an empty string.
 if (geoMirrorEnabled())
     {
     dyStringAppend(dy, "<li id=\"geoMirrorMenu\" class=\"noHighlight\"><hr></li>\n");
     // Geo mirror functionality (e.g. in nav bar)
     char *myNode = geoMirrorNode();
     /* hgcentral.gbNode table so UI can share w/ browser GEO mirror redirect code */
     char **row = NULL;
     struct sqlConnection *conn = hConnectCentral();  // after hClade since it access hgcentral too
     // get the gbNode table
     char *geoSuffix = cfgOptionDefault("browser.geoSuffix","");
     char query[256];
     sqlSafef(query, sizeof query, "SELECT node, domain, shortLabel from gbNode%s order by node", geoSuffix);
     struct sqlResult *sr = sqlGetResult(conn, query);
     while ((row = sqlNextRow(sr)) != NULL)
 	{
 	char *node = row[0];
 	char *domain = row[1];
 	char *shortLabel = row[2];
         dyStringPrintf(dy, "<li id=\"server%s\"", node);
         if (sameString(node, myNode))
             dyStringAppend(dy, " class=\"noHighlight\"");
         dyStringAppend(dy, ">\n");
         dyStringAppend(dy, "<img alt=\"X\" width=\"16\" height=\"16\" style=\"float:left;");
         if (!sameString(node, myNode))
             dyStringAppend(dy, "visibility:hidden;");
         dyStringAppend(dy, "\" src=\"../images/greenChecksmCtr.png\">\n");
         if (!sameString(node, myNode))
             dyStringPrintf(dy, "<a href=\"http://%s/cgi-bin/hgGateway?redirect=manual\">", domain);
         dyStringPrintf(dy, "%s", shortLabel);
         if (!sameString(node, myNode))
             dyStringAppend(dy, "</a>");
         dyStringAppend(dy, "</li>\n");
 
 	}
     sqlFreeResult(&sr);
     hDisconnectCentral(&conn);
     }
 return dyStringCannibalize(&dy);
 }