= !MapGuide RFC 118 - Support IPv6 = This page contains a change request (RFC) for the !MapGuide Open Source project. More !MapGuide RFCs can be found on the [wiki:MapGuideRfcs RFCs] page. == Status == ||RFC Template Version||(1.0)|| ||Submission Date||04 July 2011|| ||Last Modified||04 July 2011|| ||Author||Mars Wu|| ||RFC Status||adopted|| ||Implementation Status||pending|| ||Proposed Milestone||2.4|| ||Assigned PSC guide(s)||Bruce Dechant|| ||'''Voting History'''||24 July 2011|| ||+1: Bruce, Zac, Trevor, Tom, Paul, Jackie, Haris || ||+0|||| ||-0|||| ||-1|||| ||no vote|| || == Overview == This RFC proposes a solution to make MGOS work with both IPv4 and IPv6. == Motivation == IPv6 is the trend of network. Supporting IPv6 will be a must-have for MGOS in the future. == Proposed Solution == To make MGOS support IPv6, couple things need be done: 1. '''Enable IPv6 for ACE'''. ACE supports both IPv4 and IPv6. But by default, ACE is compiled without IPv6 support. To change this, one compiler predriective should be defined: {{{ #define ACE_HAS_IPV6 }}} It should be defined in '''{{{config.h}}}''' so that it could be effective for both Windows and Linux 2. '''Port the IP address validation logic'''. There is existing logic to validate if the IP address specified in the configuration file is valid. But the logic always assumes the IP address is in v4 format. We need port the logic to consider both v4 format and v6 format. One example is like below in in '''{{{\Common\MapGuideCommon\Util\IpUtil.cpp}}}'''. {{{ void MgIpUtil::ValidateAddress(CREFSTRING address, bool strict) { ... if (STRING::npos != address.rfind(L':')) { MgStringCollection arguments; arguments.Add(L"1"); arguments.Add(address); MgStringCollection whyArguments; whyArguments.Add(L":"); throw new MgInvalidArgumentException(L"MgIpUtil.ValidateAddress", __LINE__, __WFILE__, &arguments, L"MgStringContainsReservedCharacters", &whyArguments); } ... } }}} It treats the character '''{{{":"}}}''' as invalid. But for IPv6, '''{{{":"}}}''' is used as a splitter for the address segments. 3. '''Make MapGuide Server listen to IPv6 address''' When the service of MapGuide Server starts, it uses following code to listen to the client, site and admin ports: '''{{{Server\src\Core\Server.cpp}}}''' {{{ ACE_INET_Addr clientAddr((u_short)pServerManager->GetClientPort)); ACE_INET_Addr adminAddr((u_short)pServerManager->GetAdminPort()); ACE_INET_Addr siteAddr((u_short)pServerManager->GetSitePort()); }}} But this way it always listens to IPv4 address no matter if IPv6 is available. Then the 3 '''{{{ACE_INET_Addr}}}''' instances need be created by another constructor: {{{ ACE_INET_Addr (u_short port_number, const wchar_t host_name[], int address_family = AF_UNSPEC); }}} by passing in an IPv6 address as '''{{{host_name}}}''', MapGuide Server can listen to the IPv6 address instead of IPv4 address. But if the given '''{{{host_name}}}''' is a specific IP Address, the Client (Web Extension, Support Server, Admin etc.) has to connect to the Server through the given IP Address. E.g. if there is a computer whose IP Addresses are: {{{ IPv4 127.0.0.1 192.168.0.10 IPv6 ::1 fe80::9c4b:2cb5:5cda:5c3f%11 }}} MapGuide Server should be able to be connected with either '''{{{127.0.0.1}}}''' or '''{{{192.168.0.10}}}''' for IPv4 and either '''{{{::1}}}''' or '''{{{fe80::9c4b:2cb5:5cda:5c3f%11}}}''' for IPv6. But if we pass '''{{{127.0.0.1}}}''' as the '''{{{host_name}}}''', MapGuide Server will NOT listen to '''{{{192.168.0.10}}}'''. The same for IPv6. The solution is: we don’t pass in a specific IP Address, instead: * For IPv4, we pass in '''{{{0.0.0.0}}}'''. Then it will listen to all IPv4 addresses. * For IPv6, we pass in '''{{{::}}}'''. Then it will listen to all IPv6 address. 4. '''3.3.1.4 Detect IPv6 / v4 by !MachineIp parameter''' We need a way to determine if we should pass in '''{{{0.0.0.0}}}''' or '''{{{::}}}''' as the '''{{{host_name}}}''' parameter when instantiate the '''{{{ACE_INET_Addr}}}''' objects. Checking the machine IP address is a right way to determine the protocol version at runtime. The machine IP is specified by the '''{{{MachineIp}}}''' parameter in '''{{{serverconfig.ini}}}'''. Now the '''{{{MachineIp}}}''' parameter is optional. And if it’s not specified, '''{{{127.0.0.1}}}''' will be used as the default value. This behavior doesn’t have to be changed. A summary is: ||'''Value'''||'''Behavior'''||'''Listen to''' ||Empty||127.0.0.1 will be used as the default value. ||0.0.0.0 ||In IPv4 format||Entered value will be sued as machine IP ||0.0.0.0 ||In non-IPv4 format||Entered value will be sued as machine IP||:: 5. '''Change the way encoding Site !IpAddress into !SessionId''' MapGuide Server encodes the IP address of site server into the Session Id so that it could pick the right site server from the list by comparing the IP address. The encoding logic currently is: '''{{{(INTERNAL_API)}}}''' {{{ STRING MgSiteInfo::ToHexString() { STRING hexString; UINT32 n1, n2, n3, n4; wchar_t buffer[30]; if (4 == ::swscanf(m_target.c_str(), L"%u.%u.%u.%u", &n1, &n2, &n3, &n4)) { swprintf(buffer, 30, L"%.2X%.2X%.2X%.2X%.4X%.4X%.4X", n1, n2, n3, n4, m_sitePort, m_clientPort, m_adminPort); hexString = buffer; } return hexString; } }}} Then it works for only IPv4. And for IPv6, because the valid format is really complicated, then converting it to a hex string is difficult. The solution is: encode the IP as a Base64 string. The port numbers will still be converted to hex string so that they could be easily decoded. Then the new method will be like: {{{ STRING MgSiteInfo::ToHexString() { STRING hexString; char buf[100] = {0}; char* target = ACE_Wide_To_Ascii(m_target.c_str()).char_rep(); Base64::Encode(buf, (unsigned char*)target, (unsigned long)strlen(target)); wchar_t buffer[100] = {0}; swprintf(buffer, L"%s%.4X%.4X%.4X", ACE_Ascii_To_Wide(buf).wchar_rep(), m_sitePort, m_clientPort, m_adminPort); hexString = buffer; return hexString; } }}} One thing that needs attention is: A Base64 string might have “=” appended at the end for alignment. But “=” is treated as a reserved character. The solutions is: we remove all appending “=” when doing encoding, and then append them back when doing decoding 6. '''Make Quick Plot Support IPv6''' Quick Plot calls Http API to generate the map picture on Web Server. The code is like below (in PHP): {{{ // Get the correct port number // Just use the 127.0.0.1 specificly to point to localhost. Because the WebExtension will // be always on the same server with map agent. $mapAgent .= "://127.0.0.1:" . $_SERVER["SERVER_PORT"]; // Get the correct virtual directory $mapAgent .= substr($_SERVER["REQUEST_URI"], 0, strpos($_SERVER["REQUEST_URI"], "/", 1)); $mapAgent .="/mapagent/mapagent.fcgi?VERSION=1.0.0&OPERATION=GETMAPIMAGE" . "&SESSION=$sessionID" . "&MAPNAME=" . rawurlencode($mapName) . "&FORMAT=PNG" . "&SETVIEWCENTERX=" . $center->GetX() . "&SETVIEWCENTERY=" . $center->GetY() . "&SETVIEWSCALE=$scaleDenominator" . "&SETDISPLAYDPI=$printDpi" . "&SETDISPLAYWIDTH=$toSize->width" . "&SETDISPLAYHEIGHT=$toSize->height" . "&CLIP=0"; $image = imagecreatefrompng($mapAgent); }}} It uses '''{{{127.0.0.1}}}''' explicitly to connect to the Web Extension. There reason why it was implemented this way is like below: [[BR]] The url is actually a self-referencing URL (the http request is sent and responded by a same host). So the first idea would be just use '''{{{LOCALHOST}}}''' to connect to the Web Extension. But the problem is the php function '''{{{imagecreatefrompng(url)}}}''' will run into error if the the url contains '''{{{LOCALHOST}}}'''. So this solution doesn’t work. [[BR]] The second idea would be use '''{{{$_SERVER[“SERVER_NAME”]}}}''' to get the host name at runtime. But it doesn’t work if the Web Extension Server is behind a proxy Http server. In this case '''{{{$_SERVER[“SERVER_NAME”]}}}''' will be the name of proxy server instead of web extension server. And it’s possible that web extension server cannot connect to the proxy server. Then the picture cannot be generated by that url. So this solution also doesn’t work. [[BR]] '''{{{127.0.0.1}}}''' is the right solution considering above cases. [[BR]] But after IPv6 has been introduced, the solution of '''{{{127.0.0.1}}}''' will not work with IPv6. [[BR]] The solution is: don’t use the Http API to generate the map image. Use Web Tier API instead. The API should be one of the {{{ MgRenderingService::RenderMap(...) }}} The problem for the '''{{{MgRenderingService::RenderMap}}}''' API is: there is no way to set the DPI (In Http API, the DPI could be set by the '''{{{SETDISPLAYDPI}}}''' url parameter). So there is one new Web Tier API should be exposed: {{{ void MgMapBase::SetDisplayDpi(INT32 dpi) }}} == Implications == Since it just makes MGOS work with IPv6, it doesn't change any existing functions. == Test Plan == Unit test needs be created for the new WebTier API: '''{{{void MgMapBase::SetDisplayDpi(INT32 dpi)}}}''' == Funding / Resources == Autodesk supplied. == Addendum == 1. And to make the generated map image have the correct background color, another MgMapBase method needs be exposed as Web API to get the map's background color: {{{ STRING MgMapBase::GetBackgroundColor() }}} 2. If the '''{{{ACE_HAS_IPV6}}}''' is defined in '''config.h''', then it's not effective on Linux. The definition should be added to the begining of '''config-win32.h''' and '''config-linux.h'''