Repository navigation
Read and choose the client IP as the IP Intelligence specification says, and round coordinates - #97
Merged
Merged
Conversation
ClientIpTests gains a case for each form the IP Intelligence specification's Reading the client IP value section accepts, for every value in its table of values that are not read, and for a refused value not hiding a later key. SwigDataTests gains a case that Latitude and Longitude numbers are given to 3 decimal places through every accessor, and one that other numbers keep every place.
A client IP value is read only when all of it is an IPv4 or IPv6 address, an IPv4 address with a port, or an IPv6 address in brackets with or without a port, as the IP Intelligence specification's Reading the client IP value section says. The port is dropped. The value is no longer trimmed, and a zone index is refused rather than read in part. Latitude and Longitude numbers are rounded to 3 decimal places, half away from zero, as the value types section of the Pipeline specification requires. The native code already writes their text and the points of Areas to 3 places.
ClientIpTests now gives the engine's keys, in the order getKeys() lists them, and has a case for each rule of the IP Intelligence specification's Choosing the IP address section: every query key before every server key, the keys under one prefix in the data file's order, the same key under both prefixes, the first value used whatever its family, and no Evidence value read other than the keys the engine accepts. The old case that kept the first address of each family is replaced, as only the chosen address is reported. IpIntelligenceOnPremiseTests checks the same order through the engine with a real data file, and that each case gets the answers of the chosen address given alone.
The on-premise engine now chooses one client IP value as the IP Intelligence specification's Choosing the IP address section says. It reads only the keys the engine accepts, every query key before every server key, each in the order of the engine's getKeys(), which lists the data file's client IP headers in priority order. The first value that is read is looked up whatever its family, and only its address is reported in Ip or IpV6. Before, query.client-ip and server.client-ip were tried before the other keys, every Evidence value was read, and the first IPv4 address was looked up ahead of any IPv6 address. Closes #99
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #99.
Problem
Three gaps in the on-premise IP Intelligence engine at
rc6d95eee, the first and third found in an audit and the second while making this draft.203.0.113.1:8080, or an IPv6 address in brackets, such as[2001:db8::1]:8080, was never read.clientIpsFromEvidencetrimmed each value and passed it tofilter_varwithFILTER_VALIDATE_IP, which takes a bare address only. The trim also meant that a value with a space, tab or line ending around the address was read, which the specification refuses.ClientIpTests.phphad no case for either.clientIpsFromEvidencetriedquery.client-ipandserver.client-ipbefore the other keys, read every Evidence value the Flow Data held (line 528) and not only the keys the engine accepts, and kept the first IPv4 and the first IPv6 address it found, of which line 258 looked up the IPv4 one whenever there was one.round(inSwigData.phpis for weightings.What the specifications say
The IP Intelligence on-premise page is quoted at specifications c8128ca4. Its client IP sections are the same on
mainat 6570727d, three lines earlier, as the page there differs only in its results cache note and its rule for reads by name.Choosing the IP address
Lines 150 to 166 (147 to 163 on
main):Reading the client IP value
Lines 168 to 267. Lines 175 to 183:
The grammar, lines 189 to 194:
Lines 212 to 220:
The table at lines 224 to 243 lists the values that are not read, and lines 249 to 252 say "The Engine MUST NOT read a value that holds a zone index, in any form, with or without brackets or a port.
fe80::1%eth0,fe80::1%1,[fe80::1%eth0]:80and the percent-encoded form[fe80::1%25eth0]are all refused whole."The cloud side needs nothing. The IP Intelligence Cloud page, lines 15 to 18, says "This element uses no Evidence, it works on Property values in Element Data in the Flow Data", and the Cloud Request Engine page has no rule for client IP values. PHP's
IpIntelligenceCloudand the Cloud Request Engine read no client IP, and send the Evidence to the cloud service as it was given.Latitude and Longitude
data-model.md lines 53 to 56:
value-types.md lines 97 to 112:
Both pages are the same on
main. The value types page names no rounding for a value exactly half way. The native code writes the text half away from zero (stringBuilder.c lines 164 to 166, at the cxx commit this repository pins), as Rust'sround_angledoes, and PHP'srounddoes the same. No value converted from a 16 bit Azimuth or Declination is exactly half way at the third place in any case, because 32767 is 7 times 31 times 151, which shares no factor with 180,000 or 360,000.What the native code does with several keys
All of this is at cxx 7992af70, the commit this repository pins.
IpiGetEvidenceKeys, ipi.c lines 2422 to 2455, which takes the data file's client IP headers in order and writes each one underquery.and thenserver.. With the free data file, every job of the two runs for Choose the client IP address in the order the IP Intelligence specification gives #99 and of run 43548725414 below printed them asquery.true-client-ip-51d,server.true-client-ip-51d,query.client-ip-51d,server.client-ip-51d,query.client-ip,server.client-ip.ResultsIpiFromEvidence, lines 2855 to 2893, tries those headers in the same order under thequeryprefix, and under theserverprefix only when nothing was read.setResultsFromEvidence, lines 2791 to 2829, looks up the first value that its parser reads and skips any other.IpAddressParsein ip.c lines 198 to 253, reads the same forms as this draft, with a port, with brackets, with no leading zero, and with nothing but hexadecimal digits, colons and dots in an IPv6 address, so a zone index is refused.So this draft reads the same keys, in the same order, with the same forms as the native code, and chooses the value the native code would choose if it were given the Evidence.
Choosing in PHP, and why
PHP chooses the value and passes its one address to the native
process(const char *ipAddress)(EngineIpi.cpp lines 323 to 337), as it did before, rather than handing the native code every accepted key.std::map(EvidenceBase.i line 28), and setting a key on it stops PHP. Run 43554906863 built the extension of this draft's head 6a93144 as CI does, from the wrapper that #88 generated with SWIG 4.5.1 and committed, built an engine from the free data file on PHP 8.3, and ran a script undergdb. Itsprocesswith the string116.154.188.222worked and gave UNICOM and CN. Its next step,set('query.client-ip', '116.154.188.222')on a newFiftyOneDegreesIpiEvidenceIpiSwig, ended in a segmentation fault insidestd::map's insert, reached fromzim_FiftyOneDegreesIpiMapStringStringSwig_setat line 2459 ofipi_wrap.cxx(job 132325448876). Run 43548725414 had stopped in the same place on the head before the merge, with the wrapper that the runner's SWIG 4.2.0 generated then (job 132305975198). That is the failure the comment inprocessInternaldescribes. So the native choice could not be run from PHP to compare with this draft's, and the agreement rests on the code in the previous section and on the tests.IpandIpV6Properties report the chosen address, so PHP needs to know which address was looked up. The results do havegetTargetIpAddress()(ResultsIpi.i line 105), but it gives the address itself only throughgetCopyOfIpAddress(unsigned char copy[], uint32_t size), and IpAddress.i lines 29 to 33 map that buffer for Java alone. So PHP would have to choose the address for these two Properties in any case.What changed and why
Choosing the client IP, in
ip-intelligence/src/IpIntelligenceOnPremise.phpgetClientIpsandclientIpsFromEvidencetake the engine's keys, whichinitEngineMetaDataalready read fromgetKeys(). Only those keys are read, everyquerykey before everyserverkey (CLIENT_IP_PREFIXES), each in the order of the engine's keys.Ipfor an IPv4 or IPv4-mapped address and inIpV6otherwise.Reading a client IP value, in the same file
addressInValuegives the address a value holds, or null when the whole value is not one of the four forms. A value that starts with[must close the bracket, hold an IPv6 address inside it, and have nothing or a port after it. Any other value must be an address, or an IPv4 address followed by a colon and a port. The port is dropped, soIpandIpV6report the address alone and the lookup uses it.isIpv4andisIpv6keepfilter_var, which already refuses a leading zero in an IPv4 part, a second::, more than eight groups and a group of five digits. Each adds a check of the characters, which refuses a zone index (it follows a%) and anything around the address whateverfilter_varmakes of it.isPorttakes one or more digits for a number from 0 to 65535, as the grammar'sportrule says.Latitude and Longitude, in
ip-intelligence/src/SwigData.phpround($value, 3), which gives the double nearest the rounded decimal, whether they are read throughgetValuesAsWeightedDoubleList(typesWeightedDoubleandWeightedSingle) orgetValueAsDouble(typesDoubleandSingle). SeenumberandisAngle.DefaultWktDecimalPlaces(constantsIpi.h lines 43 to 47) whenever it writes a value as text (ResultsIpi.cpp lines 355, 499 and 583). So no text changes.ResultsIpi::getPropertyValueTypeis private (ResultsIpi.hpp line 500) and not in the SWIG interface, and a property's metadata type never names Azimuth or Declination (propertyValueType.c lines 50 to 93). So the two properties are named, as the data model names them. They are the only number properties in .NET's IP Intelligence interface (IIpIntelligenceData.cs lines 211 and 230). Rust reads the stored type and rounds by it (double_text). .NET atmain8b3220b3 gives the number as the native code gives it (WeightedDoubleListSwigWrapper.cs line 51).Before you upgrade
header.x-forwarded-for, is no longer looked up. Give the address under one of the engine's keys, such asquery.client-iporserver.client-ip.querykey comes first and the data file's order decides between keys under one prefix, and the first value read is looked up whatever its family. So the address looked up, and with itIp,IpV6and every other Property value, can differ from before for such a request.IpandIpV6no longer both have a value, because only the chosen address is reported.Ip,IpV6and the other Property values can change for such a request.Tests
ClientIpTests.phpgivesclientIpsFromEvidencethe free data file's keys in theirgetKeys()order (lines 38 to 63). For #99, lines 80 to 197:testOnlyTheChosenAddressIsReportedreplaces the old case that kept the first address of each family, which was the rule Choose the client IP address in the order the IP Intelligence specification gives #99 removes.testQueryKeysComeBeforeServerKeyssets twoserverkeys, one of which the data file lists first, ahead of aquerykey in the Evidence.testKeysUnderOnePrefixFollowTheDataFileOrdertakes two and three keys under one prefix set in the opposite order, and keys from a data file that lists the headers the other way round.testTheSameKeyUnderBothPrefixestakes thequeryvalue, and theservervalue when thequeryvalue is not read.testTheFirstValueIsUsedWhateverItsFamilyputs an IPv6 address under a key ahead of an IPv4 address, under one prefix and across the two.testAValueTheEngineDoesNotAcceptIsNotReadtakesheader.x-forwarded-for,query.ipandcookie.client-ip, andquery.client-ipwith the keys of a data file that has noclient-ipheader.For reading a value, lines 205 to 312:
testEachAcceptedFormIsReadtakes each form at lines 177 to 183,0.0.0.0(line 201),2001:db8::1:80(line 214), ports 0 and 65535, and an IPv4-mapped address in brackets with a port.testEachRefusedValueIsNotReadtakes every row of the table, the space row as a leading or trailing space, tab, line feed, and carriage return with line feed, the four zone index forms of lines 250 and 251,1.02.3.4(line 199), and the bracket form of the colon, port and IPv4 rows.testARefusedValueDoesNotHideALaterKeytakes the rule at lines 218 to 220.IpIntelligenceOnPremiseTests::testTheClientIpIsChosenInTheEngineOrdergoes through the engine with a data file and the keys it gives, and checksIp,IpV6and that every Property value is the one the chosen address gets when it is given alone.SwigDataTests.php:testLatitudeAndLongitudeAreGivenToThreeDecimalPlacestakes the specification's example (a stored Declination of 6751 gives 18.543), Rust's (a stored Azimuth of 4274 gives 23.478), their negatives, and all four accessor types.testOtherNumbersAreNotRoundedis a guard and passes before and after the change.The red and green runs used temporary workflows on Ubuntu that build the IP Intelligence extension with
ci/build-project.ps1and runip-intelligence/ci/run-unit-tests.ps1, as CI does, with PHPUnit 9.6.34. The two runs for #99 also download the free data file withcxx/data/get-data-files.shand runIpIntelligenceOnPremiseTests.phpwith it. Each tmp branch held the commits named plus one commit adding the workflow.rc6d95eee with the tests of 0b0f033 and 0c5f947, where 0c5f947 is c584a02's tests on 0b0f033 (c584a02 itself sits on the first fix). The three test files are the same at 4af81a2, c584a02 and the headrcwas mergedrcwith 0b0f033The nine unit failures in the red run are the six new ClientIpTests cases for #99 less
testTheSameKeyUnderBothPrefixes, whichrcalready passes, plus the four cases for the first change. The engine failure istestTheClientIpIsChosenInTheEngineOrder. The two skipped engine tests need the engine that PHP builds when it starts, and these runs start PHP with no data file set (requireStartupEngine). Each job of the two runs for #99 also ran a first version of the comparison script after the tests, which could not fail the job. It stopped in the script's own chained call on a returned value that was not held, the failure #92 describes, before it set any Evidence, and run 43548725414 above is the corrected script. All the tmp branches are deleted.Full build
The head is 6a93144, which merges
rcat 70424ea into the branch with a merge commit, bringing in #88 and #89 with no conflict. In the files of this change, the merge brings only #88's new class names in the constructor ofIpIntelligenceOnPremise.php, which this change does not use. The red and green runs above tested the change before the merge.Run 43554873847 built the head 6a93144 with common-ci's full pull request build, with merging turned off, and finished green (the run's last update is 04:35:45 UTC on 14 November). All nine
Setting 'PullRequestSha'lines in its job logs read 6a93144, and Configure printed "Left out 5 macOS entries".On Ubuntu with PHP 8.0, 8.1, 8.2, 8.3, 8.4 and 8.5, the IP Intelligence unit suite gave OK (46 tests, 176 assertions) and its integration suite gave OK (70 tests), with 846 assertions on PHP 8.0 and 842 on the others. The PHP 7.4 job leaves IP Intelligence out because the package needs PHP 8.0 or later, and the Windows job leaves it out because no extension is built on Windows. The draft's own checks are green on the same head.
An earlier full build, run 43547186289, was green on 5ca958d, the head before the merge.
macOS was left out of this draft's full build under the 9 November decision. The rc head is built with macOS before it merges into main.
What remains
fiftyone.ipintelligencehas no mirror and is not on Packagist (ci/packages.ps1line 81).