reSIProcate
SIP stack, Dialog Usage Manager and related libraries
Loading...
Searching...
No Matches
GeoProximityTargetSorter.cxx
Go to the documentation of this file.
1#ifdef HAVE_CONFIG_H
2#include "config.h"
3#endif
4
5#ifndef RESIP_FIXED_POINT
6#include <algorithm>
7#include <random>
8
9#ifdef USE_MAXMIND_GEOIP
10// MaxMind GeoIP header
11#include <GeoIP.h>
12#include <GeoIPCity.h>
13#endif
14
16
19#include "repro/Proxy.hxx"
20#include "repro/Target.hxx"
21
24#include "rutil/Data.hxx"
25#include "rutil/Random.hxx"
26#include "rutil/Logger.hxx"
27#include "repro/ProxyConfig.hxx"
29
30
31#define RESIPROCATE_SUBSYSTEM resip::Subsystem::REPRO
32
33using namespace resip;
34using namespace repro;
35using namespace std;
36
38
39// Custom NameAddr parameter that Regitering party or request sender can use on their Contact
40// header to pass their geo location information to repro, so that a lookup in the MaxMind
41// Geo IP tables is not required
42static ExtensionParameter p_geolocation("x-repro-geolocation");
43
46
48{
49public:
50 GeoProximityTargetContainer(double distance, Target* target) : mDistance(distance), mTarget(target) {}
52
53 // Sort targets by distance from client
56 {
57 return lhs.mDistance < rhs.mDistance;
58 }
59
60 Target* getTarget() { return mTarget; }
61 double getDistance() { return mDistance; }
62
63private:
64 double mDistance;
65 Target* mTarget;
66};
67
69 Processor("GeoProximityTargetHandler"),
70 mRUriRegularExpressionData(config.getConfigData("GeoProximityRequestUriFilter", "")),
71 mRUriRegularExpression(0),
72 mDefaultDistance(config.getConfigUnsignedLong("GeoProximityDefaultDistance", 0)),
73 mLoadBalanceEqualDistantTargets(config.getConfigBool("LoadBalanceEqualDistantTargets", true))
74{
75 // Note: this code originally used the PCRE (Perl Compatible) regular expression library. The ECMAScript standard is a subset
76 // of the Perl regular expression syntax. Posix regular expression syntax is quite a bit different (ie: std::regex_contacts::basic or
77 // std::regex_contacts::extended). To be as backwards compatible with existing regular expressions as possible, we want to
78 // use the EMCAScript syntax.
79 std::regex_constants::syntax_option_type flags = std::regex_constants::ECMAScript | std::regex_constants::nosubs;
80
82 {
83 try
84 {
86 }
87 catch(std::regex_error&)
88 {
90 ErrLog( << "GeoProximityRequestUriFilter rule has invalid match expression: "
93 }
94 }
95 else
96 {
98 }
99
100#ifdef USE_MAXMIND_GEOIP
101 // Initialize GeoIP library - load data
102 Data geoIPv4Database = config.getConfigData("GeoProximityIPv4CityDatabaseFile", "GeoLiteCity.dat", false);
103 if(!geoIPv4Database.empty())
104 {
105 mGeoIPv4 = (GeoIP*)GeoIP_open(geoIPv4Database.c_str(), GEOIP_MEMORY_CACHE); // Cache entire DB in memory - could make this configurable
106 if(mGeoIPv4 != 0)
107 {
108 char* dbInfo = GeoIP_database_info((GeoIP*)mGeoIPv4);
109 InfoLog(<< "GeoProximityTargetSorter: IPv4 db info: " << dbInfo);
110 free(dbInfo);
111
112 int i = GeoIP_database_edition((GeoIP*)mGeoIPv4);
113
114 if(i != GEOIP_CITY_EDITION_REV0 /* 6 */ &&
115 i != GEOIP_CITY_EDITION_REV1 /* 2 */ &&
116 i != GEOIP_CITYCONFIDENCE_EDITION /* 15 */ &&
117 i != GEOIP_CITYCONFIDENCEDIST_EDITION /* 16 */)
118 {
119 // Wrong DB Type
120 ErrLog(<< "GeoProximityTargetSorter: IPv4 GeoIP database is not of the correct type, IPv4 geo lookups will not take place.");
121 GeoIP_delete((GeoIP*)mGeoIPv4);
122 mGeoIPv4 = 0;
123 }
124 }
125 else
126 {
127 ErrLog(<< "GeoProximityTargetSorter: Failed to open IPv4 GeoIP database, IPv4 geo lookups will not take place: " << geoIPv4Database);
128 }
129 }
130 else
131 {
132 InfoLog(<< "GeoProximityTargetSorter: No IPv4 GeoIP database specified, IPv4 geo lookups will not take place.");
133 }
134
135#ifdef USE_IPV6
136 Data geoIPv6Database = config.getConfigData("GeoProximityIPv6CityDatabaseFile", "GeoLiteCityv6.dat", false);
137 if(!geoIPv6Database.empty())
138 {
139 mGeoIPv6 = (GeoIP*)GeoIP_open(geoIPv6Database.c_str(), GEOIP_MEMORY_CACHE); // Cache entire DB in memory - could make this configurable
140 if(mGeoIPv6 != 0)
141 {
142 char* dbInfo = GeoIP_database_info((GeoIP*)mGeoIPv6);
143 InfoLog(<< "GeoProximityTargetSorter: IPv6 db info: " << dbInfo);
144 free(dbInfo);
145
146 int i = GeoIP_database_edition((GeoIP*)mGeoIPv6);
147
148 if(i != GEOIP_CITY_EDITION_REV1_V6 /* 30 */ &&
149 i != GEOIP_CITY_EDITION_REV0_V6 /* 31 */)
150 {
151 // Wrong DB Type
152 ErrLog(<< "GeoProximityTargetSorter: IPv6 GeoIP database is not of the correct type, IPv6 geo lookups will not take place.");
153 GeoIP_delete((GeoIP*)mGeoIPv6);
154 mGeoIPv6 = 0;
155 }
156 }
157 else
158 {
159 ErrLog(<< "GeoProximityTargetSorter: Failed to open IPv6 GeoIP database, IPv6 geo lookups will not take place: " << geoIPv6Database);
160 }
161 }
162 else
163 {
164 InfoLog(<< "GeoProximityTargetSorter: No IPv6 GeoIP database specified, IPv6 geo lookups will not take place.");
165 }
166#else
167 mGeoIPv6 = 0;
168#endif
169
170#endif
171}
172
174{
176 {
179 }
180#ifdef USE_MAXMIND_GEOIP
181 if(mGeoIPv4)
182 {
183 GeoIP_delete((GeoIP*)mGeoIPv4);
184 mGeoIPv4 = 0;
185 }
186 if(mGeoIPv6)
187 {
188 GeoIP_delete((GeoIP*)mGeoIPv6);
189 mGeoIPv6 = 0;
190 }
191#endif
192}
193
196{
197 // Check if we've already run this Baboon or not - if not, then run it. Baboons
198 // are dispatched multiple times, once when the Monkeys are done, and once for each
199 // response received (after the Lemurs have run).
200 //
201 // Note: This check causes us to only sort the Targets once (ie. the first time
202 // the Monkeys are complete). In the future we may want to allow GeoProximity
203 // sorting of multiple contacts returned in a 3xx response (when
204 // RecursiveRedirect is enabled).
206 {
208
210
211 // If there is not 2 or more candidate targets then there is nothing to sort
212 if(rsp.getCandidateTransactionMap().size() >= 2)
213 {
214 // This is the first run, no Targets should be active yet
216 !rsp.hasActiveTransactions() &&
218
219 // Check if request meets filter criteria
220 if(!mRUriRegularExpressionData.empty()) // If empty we consider all URI's matched
221 {
223 {
224 // Note: Using regex_search instead of regex_match, so that we don't need to fully match
225 // the string, this is backwards compatible with the previous regexec PCRE implementation
227 {
228 // did not match
229 DebugLog( << "GeoProximityTargetSorter: Skipped - request URI "<< rc.getOriginalRequest().header(h_RequestLine).uri() << " did not match.");
230 return Processor::Continue;
231 }
232 }
233 else
234 {
235 // Regular expression provided is bad - error was logged at startup time
236 return Processor::Continue;
237 }
238 }
239
240 SipMessage& request = rc.getOriginalRequest();
241 double clientLatitude;
242 double clientLongitude;
243 getClientGeoLocation(request, clientLatitude, clientLongitude);
244
245 DebugLog(<< "GeoProximityTargetSorter: numCandidates=" << rsp.getCandidateTransactionMap().size()
246 << ", clientLatitude=" << clientLatitude << ", clientLongitude=" << clientLongitude);
247
248 std::vector<GeoProximityTargetContainer> flatTargetList;
249
250 std::list<std::list<resip::Data> >& targetCollection = rsp.mTransactionQueueCollection;
251 std::list<std::list<resip::Data> >::iterator outer = targetCollection.begin();
252 std::list<resip::Data>::iterator inner;
253 int outerCounter=1;
254 for(; outer!=targetCollection.end(); outer++, outerCounter++)
255 {
256 inner=outer->begin();
257 for(; inner != outer->end(); inner++)
258 {
259 resip_assert(rsp.isCandidate(*inner));
260 Target* target = rsp.getTarget(*inner);
261 if(target)
262 {
263 resip::ContactInstanceRecord& rec = target->rec();
264 double distance = getTargetDistance(*target, clientLatitude, clientLongitude);
265
267 {
268 DebugLog(<< "GeoProximityTargetSorter: TransactionQueueCollection[" << outerCounter << "]: Target=" << rec.mContact
269 << ", PublicAddress=" << rec.mPublicAddress
270 << ", DistanceFromClient=" << distance);
271 }
272 else
273 {
274 DebugLog(<< "GeoProximityTargetSorter: TransactionQueueCollection[" << outerCounter << "]: Target=" << rec.mContact
275 << ", DistanceFromClient=" << distance);
276 }
277
278 // Flatten batches - since we will be serial routing
279 flatTargetList.push_back(GeoProximityTargetContainer(distance, target));
280 }
281 else
282 {
283 WarningLog(<< "GeoProximityTargetSorter: TransactionQueueCollection[" << outerCounter << "]: TID=" << *inner << " - Couldn't find target");
284 }
285 }
286 }
287
289 {
290 random_device rd;
291 mt19937 randomgen(rd());
292 // Shuffle Targets - so that targets of equal distance will be in random order
293 shuffle(flatTargetList.begin(), flatTargetList.end(), randomgen);
294 }
295
296 // Sort List by distance - note: if we don't know the client location, there is no point in sorting
297 // just use the newly flattened / shuffled target list in the existing order
298 if(clientLatitude != 0 && clientLongitude != 0)
299 {
300 sort(flatTargetList.begin(), flatTargetList.end(), GeoProximityTargetContainer::instanceCompare);
301 }
302
303 // Rebuild targetCollection - Note: All batches are split up to individual / serial targets
304 targetCollection.clear();
305 std::vector<GeoProximityTargetContainer>::iterator it = flatTargetList.begin();
306 outerCounter=1;
307 for(; it != flatTargetList.end(); it++, outerCounter++)
308 {
309 std::list<resip::Data> queue;
310 queue.push_back(it->getTarget()->tid());
311 targetCollection.push_back(queue);
312
313 DebugLog(<< "GeoProximityTargetSorter: Sorted TransactionQueueCollection[" << outerCounter << "]: Target=" << it->getTarget()->rec().mContact
314 << ", DistanceFromClient=" << it->getDistance());
315 }
316 }
317 else
318 {
319 DebugLog( << "GeoProximityTargetSorter: Skipped since only one target for request URI "<< rc.getOriginalRequest().header(h_RequestLine).uri());
320 }
321 }
322
323 return Processor::Continue;
324}
325
326void
327GeoProximityTargetSorter::getClientGeoLocation(const SipMessage& request, double& latitude, double& longitude)
328{
329 resip_assert(request.isRequest());
330
331 // First check to see if x-repro-geolocation parameter is on Contact header
332 if(request.exists(h_Contacts) && request.header(h_Contacts).size() >= 1)
333 {
334 // Only need to look at first contact - this won't be called for REGISTER messages or 3xx responses
335 if(request.header(h_Contacts).front().exists(p_geolocation))
336 {
337 parseGeoLocationParameter(request.header(h_Contacts).front().param(p_geolocation), latitude, longitude);
338 return;
339 }
340 }
341
342 // If we cannot determine geo location of target - then return 0,0
343 latitude = 0;
344 longitude = 0;
345
346 // Next - try and find the public IP of the client that sent the request
347 Tuple publicAddress = Helper::getClientPublicAddress(request);
348 if(publicAddress.getType() != UNKNOWN_TRANSPORT)
349 {
350 // Do a MaxMind GeoIP lookup to determine latitude and longitude
351 geoIPLookup(publicAddress, &latitude, &longitude);
352 }
353}
354
355void
356GeoProximityTargetSorter::getTargetGeoLocation(const Target& target, double& latitude, double& longitude)
357{
358 // First check to see if x-repro-geolocation parameter is on Contact header
359 if(target.rec().mContact.exists(p_geolocation))
360 {
361 parseGeoLocationParameter(target.rec().mContact.param(p_geolocation), latitude, longitude);
362 return;
363 }
364
365 // If we cannot determine geo location of client - then return 0,0
366 latitude = 0;
367 longitude = 0;
368
369 // Next - see if we stored a public IP of the client at registration time
371 {
372 // Do a MaxMind GeoIP lookup to determine latitude and longitude
373 geoIPLookup(target.rec().mPublicAddress, &latitude, &longitude);
374 }
375 else
376 {
377 // Next - see if contact address is public or not
378 Tuple contactAddress(target.rec().mContact.uri().host(), 0, UNKNOWN_TRANSPORT);
379 if(!contactAddress.isPrivateAddress())
380 {
381 // Do a MaxMind GeoIP lookup to determine latitude and longitude
382 geoIPLookup(contactAddress, &latitude, &longitude);
383 }
384 }
385}
386
387double
388GeoProximityTargetSorter::getTargetDistance(const Target& target, double clientLatitude, double clientLongitude)
389{
390 if(clientLatitude == 0 && clientLongitude == 0)
391 {
392 return (double)mDefaultDistance;
393 }
394 double targetLatitude;
395 double targetLongitude;
396 getTargetGeoLocation(target, targetLatitude, targetLongitude);
397 if(targetLatitude == 0 && targetLongitude == 0)
398 {
399 return (double)mDefaultDistance;
400 }
401
402 return calculateDistance(clientLatitude, clientLongitude, targetLatitude, targetLongitude);
403}
404
405void
406GeoProximityTargetSorter::parseGeoLocationParameter(const Data& parameter, double& latitude, double& longitude)
407{
408 ParseBuffer pb(parameter);
409
410 latitude = 0.0;
411 longitude = 0.0;
412 Data token;
413 const char* anchor = pb.position();
415 pb.data(token, anchor);
416 latitude = token.convertDouble();
417 if(!pb.eof())
418 {
419 pb.skipChar();
420 if(!pb.eof())
421 {
422 anchor = pb.position();
423 pb.skipToOneOf(",\""); // Skip to comma or end double quote - be tolerant of altitude being specified
424 pb.data(token, anchor);
425 longitude = token.convertDouble();
426 return;
427 }
428 }
429 DebugLog(<< "GeoProximityTargetSorter: parseGeoLocationParameter - invalid parameter format: " << parameter);
430}
431
432static const double DEG_TO_RAD = 0.017453292519943295769236907684886;
433static const double EARTH_RADIUS = 6372.797560856; // km
434//static const double EARTH_RADIUS = 3963.0; // miles
435double
436GeoProximityTargetSorter::calculateDistance(double lat1, double long1, double lat2, double long2)
437{
438 double dLat = (lat1 - lat2) * DEG_TO_RAD;
439 double dLong = (long1 - long2) * DEG_TO_RAD;
440 double hLat = sin(dLat * 0.5);
441 double hLong = sin(dLong * 0.5);
442 double distance;
443
444 hLat *= hLat;
445 hLong *= hLong;
446
447 distance = 2.0 * asin(sqrt(hLat + (cos(lat1*DEG_TO_RAD) * cos(lat2*DEG_TO_RAD)) *hLong)) * EARTH_RADIUS;
448 return distance;
449}
450
451bool
452GeoProximityTargetSorter::geoIPLookup(const Tuple& address, double* latitude, double* longitude, Data* country, Data* region, Data* city)
453{
454#ifdef USE_MAXMIND_GEOIP
455 GeoIPRecord *gir = 0;
456 // ?slg? - should we introduce a local cache? or is GeoIP library performance good enough?
457 if(address.ipVersion() == V6)
458 {
459 if(mGeoIPv6)
460 {
461 gir = GeoIP_record_by_ipnum_v6((GeoIP*)mGeoIPv6, reinterpret_cast<const sockaddr_in6&>(address.getSockaddr()).sin6_addr);
462 }
463 }
464 else
465 {
466 if(mGeoIPv4)
467 {
468 gir = GeoIP_record_by_ipnum((GeoIP*)mGeoIPv4, ntohl(reinterpret_cast<const sockaddr_in&>(address.getSockaddr()).sin_addr.s_addr));
469 }
470 }
471
472 if(gir != 0)
473 {
474 Data countryData(Data::Share, gir->country_code ? gir->country_code : "");
475 Data regionData(Data::Share, gir->region ? gir->region : "");
476 Data cityData(Data::Share, gir->city ? gir->city : "");
477 if(latitude) *latitude = gir->latitude;
478 if(longitude) *longitude = gir->longitude;
479 if(country) *country = countryData;
480 if(region) *region = regionData;
481 if(city) *city = cityData;
482
483 DebugLog(<< "GeoProximityTargetSorter::geoIPLookup: Tuple=" << address
484 << ", Country=" << countryData
485 << ", Region=" << regionData
486 << ", City=" << cityData
487 << ", Lat/Long=" << gir->latitude << "/" << gir->longitude);
488
489 GeoIPRecord_delete(gir);
490 return true;
491 }
492 else
493 {
494 DebugLog(<< "GeoProximityTargetSorter::geoIPLookup: no geo location information found for Tuple=" << address);
495 }
496#endif
497
498 return false;
499}
500
501
502#endif // ndef RESIP_FIXED_POINT
503
504/* ====================================================================
505 * The Vovida Software License, Version 1.0
506 *
507 * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved.
508 *
509 * Redistribution and use in source and binary forms, with or without
510 * modification, are permitted provided that the following conditions
511 * are met:
512 *
513 * 1. Redistributions of source code must retain the above copyright
514 * notice, this list of conditions and the following disclaimer.
515 *
516 * 2. Redistributions in binary form must reproduce the above copyright
517 * notice, this list of conditions and the following disclaimer in
518 * the documentation and/or other materials provided with the
519 * distribution.
520 *
521 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
522 * and "Vovida Open Communication Application Library (VOCAL)" must
523 * not be used to endorse or promote products derived from this
524 * software without prior written permission. For written
525 * permission, please contact vocal@vovida.org.
526 *
527 * 4. Products derived from this software may not be called "VOCAL", nor
528 * may "VOCAL" appear in their name, without prior written
529 * permission of Vovida Networks, Inc.
530 *
531 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
532 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
533 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
534 * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
535 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
536 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
537 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
538 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
539 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
540 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
541 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
542 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
543 * DAMAGE.
544 *
545 * ====================================================================
546 */
static const double EARTH_RADIUS
static ExtensionParameter p_geolocation("x-repro-geolocation")
static const double DEG_TO_RAD
#define DebugLog(args_)
Definition Logger.hxx:128
#define InfoLog(args_)
Definition Logger.hxx:131
#define WarningLog(args_)
Definition Logger.hxx:134
#define ErrLog(args_)
Definition Logger.hxx:137
#define resip_assert(x)
This file needs to work for both C and C++ code.
static const struct nv flags[]
Definition adig.c:62
GeoProximityTargetContainer(double distance, Target *target)
static bool instanceCompare(const GeoProximityTargetContainer &lhs, const GeoProximityTargetContainer &rhs)
static bool geoIPLookup(const resip::Tuple &address, double *latitude, double *longitude, resip::Data *country=0, resip::Data *region=0, resip::Data *city=0)
virtual processor_action_t process(RequestContext &)
void parseGeoLocationParameter(const resip::Data &parameter, double &latitude, double &longitude)
void getClientGeoLocation(const resip::SipMessage &request, double &latitude, double &longitude)
double getTargetDistance(const Target &target, double clientLatitude, double clientLongitude)
double calculateDistance(double latitude1, double longitude1, double latitude2, double longitude2)
void getTargetGeoLocation(const Target &target, double &latitude, double &longitude)
static resip::KeyValueStore::Key mGeoTargetSortingDoneKey
static resip::KeyValueStore::Key allocateRequestKeyValueStoreKey()
Definition Proxy.cxx:58
resip::SipMessage & getOriginalRequest()
Returns the SipMessage associated with the server transaction.
ResponseContext & getResponseContext()
resip::KeyValueStore & getKeyValueStore()
const TransactionMap & getCandidateTransactionMap() const
Self-explanatory.
bool hasTerminatedTransactions() const
bool hasCandidateTransactions() const
std::list< std::list< resip::Data > > mTransactionQueueCollection
Target * getTarget(const resip::Data &serial) const
Self-explanatory.
bool isCandidate(const resip::Data &tid) const
virtual const resip::ContactInstanceRecord & rec() const
Definition Target.cxx:81
resip::Data getConfigData(const resip::Data &name, const resip::Data &defaultValue, bool useDefaultIfEmpty=false) const
A single contact record, bound to an Aor during registration.
An alternative to std::string, encapsulates an arbitrary buffer of bytes.
Definition Data.hxx:90
const char * c_str() const
Returns a null-terminated string representing.
Definition Data.cxx:1097
@ Share
The Data instance will use the buffer in a read-only mode.
Definition Data.hxx:266
bool empty() const noexcept
Checks whether the Data is empty.
Definition Data.hxx:591
static Data from(const T &x)
Converts from arbitrary other type to Data.
Definition Data.hxx:398
double convertDouble() const
Takes the contents of the Data and converts them to a double precision floating point value.
Definition Data.cxx:1827
static Tuple getClientPublicAddress(const SipMessage &request)
Look at Via headers, and find the first public IP address closest to the sending client.
Definition Helper.cxx:2400
void setBoolValue(Key key, bool value)
const bool & getBoolValue(Key key) const
bool exists(const Param< NameAddr > &paramType) const
Definition NameAddr.cxx:477
const Data & param(const ExtensionParameter &param) const
Const accessor for non-natively-supported parameter types.
Provides string-parsing functionality with protection from buffer overflow.
CurrentPosition skipToChar(char c)
CurrentPosition position() const
CurrentPosition skipToOneOf(const char *cs)
CurrentPosition skipChar()
void data(Data &data, const char *start) const
make the passed in data share memory with the buffer (uses Data::Share)
const Uri & uri() const
Represents a SIP message.
bool isRequest() const
Returns true if message is a request, false otherwise.
const RequestLine & header(const RequestLineType &l) const
Returns the RequestLine. This is only valid for request messages.
bool exists(const HeaderBase &headerType) const
Returns true if the given header field is present, false otherwise.
static const char * COMMA
Definition Symbols.hxx:35
Represents a network address.
Definition Tuple.hxx:59
IpVersion ipVersion() const
Returns V4 or V6 as appropriate.
Definition Tuple.cxx:537
bool isPrivateAddress() const
Definition Tuple.cxx:552
TransportType getType() const
Definition Tuple.hxx:122
const sockaddr & getSockaddr() const
Retrieve a const binary representation of the socket address for this tuple.
Definition Tuple.hxx:109
Data & host()
Definition Uri.hxx:40
This Class is used to implement a primitive form of RPC using loose XML formatting.
dcm! – add UnusedChecking(_enum) below;
@ UNKNOWN_TRANSPORT
RequestLineType h_RequestLine
Definition Headers.cxx:347