MediaWiki REL1_30
IP.php
Go to the documentation of this file.
1<?php
24use IPSet\IPSet;
25
26// Some regex definition to "play" with IP address and IP address ranges
27
28// An IPv4 address is made of 4 bytes from x00 to xFF which is d0 to d255
29define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])' );
30define( 'RE_IP_ADD', RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE );
31// An IPv4 range is an IP address and a prefix (d1 to d32)
32define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)' );
33define( 'RE_IP_RANGE', RE_IP_ADD . '\/' . RE_IP_PREFIX );
34
35// An IPv6 address is made up of 8 words (each x0000 to xFFFF).
36// However, the "::" abbreviation can be used on consecutive x0000 words.
37define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' );
38define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)' );
39define( 'RE_IPV6_ADD',
40 '(?:' . // starts with "::" (including "::")
41 ':(?::|(?::' . RE_IPV6_WORD . '){1,7})' .
42 '|' . // ends with "::" (except "::")
43 RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,6}::' .
44 '|' . // contains one "::" in the middle (the ^ makes the test fail if none found)
45 RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)' .
46 '|' . // contains no "::"
47 RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' .
48 ')'
49);
50// An IPv6 range is an IP address and a prefix (d1 to d128)
51define( 'RE_IPV6_RANGE', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
52// For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
53define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
54define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
55
56// This might be useful for regexps used elsewhere, matches any IPv4 or IPv6 address or network
57define( 'IP_ADDRESS_STRING',
58 '(?:' .
59 RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?' . // IPv4
60 '|' .
61 RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?' . // IPv6
62 ')'
63);
64
69class IP {
70
79 public static function isIPAddress( $ip ) {
80 return (bool)preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip );
81 }
82
90 public static function isIPv6( $ip ) {
91 return (bool)preg_match( '/^' . RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?$/', $ip );
92 }
93
101 public static function isIPv4( $ip ) {
102 return (bool)preg_match( '/^' . RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?$/', $ip );
103 }
104
113 public static function isValid( $ip ) {
114 return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip )
115 || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) );
116 }
117
127 public static function isValidBlock( $ipRange ) {
128 return self::isValidRange( $ipRange );
129 }
130
140 public static function isValidRange( $ipRange ) {
141 return ( preg_match( '/^' . RE_IPV6_RANGE . '$/', $ipRange )
142 || preg_match( '/^' . RE_IP_RANGE . '$/', $ipRange ) );
143 }
144
154 public static function sanitizeIP( $ip ) {
155 $ip = trim( $ip );
156 if ( $ip === '' ) {
157 return null;
158 }
159 /* If not an IP, just return trimmed value, since sanitizeIP() is called
160 * in a number of contexts where usernames are supplied as input.
161 */
162 if ( !self::isIPAddress( $ip ) ) {
163 return $ip;
164 }
165 if ( self::isIPv4( $ip ) ) {
166 // Remove leading 0's from octet representation of IPv4 address
167 $ip = preg_replace( '/(?:^|(?<=\.))0+(?=[1-9]|0\.|0$)/', '', $ip );
168 return $ip;
169 }
170 // Remove any whitespaces, convert to upper case
171 $ip = strtoupper( $ip );
172 // Expand zero abbreviations
173 $abbrevPos = strpos( $ip, '::' );
174 if ( $abbrevPos !== false ) {
175 // We know this is valid IPv6. Find the last index of the
176 // address before any CIDR number (e.g. "a:b:c::/24").
177 $CIDRStart = strpos( $ip, "/" );
178 $addressEnd = ( $CIDRStart !== false )
179 ? $CIDRStart - 1
180 : strlen( $ip ) - 1;
181 // If the '::' is at the beginning...
182 if ( $abbrevPos == 0 ) {
183 $repeat = '0:';
184 $extra = ( $ip == '::' ) ? '0' : ''; // for the address '::'
185 $pad = 9; // 7+2 (due to '::')
186 // If the '::' is at the end...
187 } elseif ( $abbrevPos == ( $addressEnd - 1 ) ) {
188 $repeat = ':0';
189 $extra = '';
190 $pad = 9; // 7+2 (due to '::')
191 // If the '::' is in the middle...
192 } else {
193 $repeat = ':0';
194 $extra = ':';
195 $pad = 8; // 6+2 (due to '::')
196 }
197 $ip = str_replace( '::',
198 str_repeat( $repeat, $pad - substr_count( $ip, ':' ) ) . $extra,
199 $ip
200 );
201 }
202 // Remove leading zeros from each bloc as needed
203 $ip = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip );
204
205 return $ip;
206 }
207
215 public static function prettifyIP( $ip ) {
216 $ip = self::sanitizeIP( $ip ); // normalize (removes '::')
217 if ( self::isIPv6( $ip ) ) {
218 // Split IP into an address and a CIDR
219 if ( strpos( $ip, '/' ) !== false ) {
220 list( $ip, $cidr ) = explode( '/', $ip, 2 );
221 } else {
222 list( $ip, $cidr ) = [ $ip, '' ];
223 }
224 // Get the largest slice of words with multiple zeros
225 $offset = 0;
226 $longest = $longestPos = false;
227 while ( preg_match(
228 '!(?:^|:)0(?::0)+(?:$|:)!', $ip, $m, PREG_OFFSET_CAPTURE, $offset
229 ) ) {
230 list( $match, $pos ) = $m[0]; // full match
231 if ( strlen( $match ) > strlen( $longest ) ) {
232 $longest = $match;
233 $longestPos = $pos;
234 }
235 $offset = ( $pos + strlen( $match ) ); // advance
236 }
237 if ( $longest !== false ) {
238 // Replace this portion of the string with the '::' abbreviation
239 $ip = substr_replace( $ip, '::', $longestPos, strlen( $longest ) );
240 }
241 // Add any CIDR back on
242 if ( $cidr !== '' ) {
243 $ip = "{$ip}/{$cidr}";
244 }
245 // Convert to lower case to make it more readable
246 $ip = strtolower( $ip );
247 }
248
249 return $ip;
250 }
251
268 public static function splitHostAndPort( $both ) {
269 if ( substr( $both, 0, 1 ) === '[' ) {
270 if ( preg_match( '/^\[(' . RE_IPV6_ADD . ')\](?::(?P<port>\d+))?$/', $both, $m ) ) {
271 if ( isset( $m['port'] ) ) {
272 return [ $m[1], intval( $m['port'] ) ];
273 } else {
274 return [ $m[1], false ];
275 }
276 } else {
277 // Square bracket found but no IPv6
278 return false;
279 }
280 }
281 $numColons = substr_count( $both, ':' );
282 if ( $numColons >= 2 ) {
283 // Is it a bare IPv6 address?
284 if ( preg_match( '/^' . RE_IPV6_ADD . '$/', $both ) ) {
285 return [ $both, false ];
286 } else {
287 // Not valid IPv6, but too many colons for anything else
288 return false;
289 }
290 }
291 if ( $numColons >= 1 ) {
292 // Host:port?
293 $bits = explode( ':', $both );
294 if ( preg_match( '/^\d+/', $bits[1] ) ) {
295 return [ $bits[0], intval( $bits[1] ) ];
296 } else {
297 // Not a valid port
298 return false;
299 }
300 }
301
302 // Plain hostname
303 return [ $both, false ];
304 }
305
317 public static function combineHostAndPort( $host, $port, $defaultPort = false ) {
318 if ( strpos( $host, ':' ) !== false ) {
319 $host = "[$host]";
320 }
321 if ( $defaultPort !== false && $port == $defaultPort ) {
322 return $host;
323 } else {
324 return "$host:$port";
325 }
326 }
327
334 public static function formatHex( $hex ) {
335 if ( substr( $hex, 0, 3 ) == 'v6-' ) { // IPv6
336 return self::hexToOctet( substr( $hex, 3 ) );
337 } else { // IPv4
338 return self::hexToQuad( $hex );
339 }
340 }
341
348 public static function hexToOctet( $ip_hex ) {
349 // Pad hex to 32 chars (128 bits)
350 $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0', STR_PAD_LEFT );
351 // Separate into 8 words
352 $ip_oct = substr( $ip_hex, 0, 4 );
353 for ( $n = 1; $n < 8; $n++ ) {
354 $ip_oct .= ':' . substr( $ip_hex, 4 * $n, 4 );
355 }
356 // NO leading zeroes
357 $ip_oct = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip_oct );
358
359 return $ip_oct;
360 }
361
368 public static function hexToQuad( $ip_hex ) {
369 // Pad hex to 8 chars (32 bits)
370 $ip_hex = str_pad( strtoupper( $ip_hex ), 8, '0', STR_PAD_LEFT );
371 // Separate into four quads
372 $s = '';
373 for ( $i = 0; $i < 4; $i++ ) {
374 if ( $s !== '' ) {
375 $s .= '.';
376 }
377 $s .= base_convert( substr( $ip_hex, $i * 2, 2 ), 16, 10 );
378 }
379
380 return $s;
381 }
382
390 public static function isPublic( $ip ) {
391 static $privateSet = null;
392 if ( !$privateSet ) {
393 $privateSet = new IPSet( [
394 '10.0.0.0/8', # RFC 1918 (private)
395 '172.16.0.0/12', # RFC 1918 (private)
396 '192.168.0.0/16', # RFC 1918 (private)
397 '0.0.0.0/8', # this network
398 '127.0.0.0/8', # loopback
399 'fc00::/7', # RFC 4193 (local)
400 '0:0:0:0:0:0:0:1', # loopback
401 '169.254.0.0/16', # link-local
402 'fe80::/10', # link-local
403 ] );
404 }
405 return !$privateSet->match( $ip );
406 }
407
419 public static function toHex( $ip ) {
420 if ( self::isIPv6( $ip ) ) {
421 $n = 'v6-' . self::IPv6ToRawHex( $ip );
422 } elseif ( self::isIPv4( $ip ) ) {
423 // T62035/T97897: An IP with leading 0's fails in ip2long sometimes (e.g. *.08),
424 // also double/triple 0 needs to be changed to just a single 0 for ip2long.
425 $ip = self::sanitizeIP( $ip );
426 $n = ip2long( $ip );
427 if ( $n < 0 ) {
428 $n += pow( 2, 32 );
429 # On 32-bit platforms (and on Windows), 2^32 does not fit into an int,
430 # so $n becomes a float. We convert it to string instead.
431 if ( is_float( $n ) ) {
432 $n = (string)$n;
433 }
434 }
435 if ( $n !== false ) {
436 # Floating points can handle the conversion; faster than Wikimedia\base_convert()
437 $n = strtoupper( str_pad( base_convert( $n, 10, 16 ), 8, '0', STR_PAD_LEFT ) );
438 }
439 } else {
440 $n = false;
441 }
442
443 return $n;
444 }
445
452 private static function IPv6ToRawHex( $ip ) {
453 $ip = self::sanitizeIP( $ip );
454 if ( !$ip ) {
455 return false;
456 }
457 $r_ip = '';
458 foreach ( explode( ':', $ip ) as $v ) {
459 $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
460 }
461
462 return $r_ip;
463 }
464
472 public static function parseCIDR( $range ) {
473 if ( self::isIPv6( $range ) ) {
474 return self::parseCIDR6( $range );
475 }
476 $parts = explode( '/', $range, 2 );
477 if ( count( $parts ) != 2 ) {
478 return [ false, false ];
479 }
480 list( $network, $bits ) = $parts;
481 $network = ip2long( $network );
482 if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 32 ) {
483 if ( $bits == 0 ) {
484 $network = 0;
485 } else {
486 $network &= ~( ( 1 << ( 32 - $bits ) ) - 1 );
487 }
488 # Convert to unsigned
489 if ( $network < 0 ) {
490 $network += pow( 2, 32 );
491 }
492 } else {
493 $network = false;
494 $bits = false;
495 }
496
497 return [ $network, $bits ];
498 }
499
515 public static function parseRange( $range ) {
516 // CIDR notation
517 if ( strpos( $range, '/' ) !== false ) {
518 if ( self::isIPv6( $range ) ) {
519 return self::parseRange6( $range );
520 }
521 list( $network, $bits ) = self::parseCIDR( $range );
522 if ( $network === false ) {
523 $start = $end = false;
524 } else {
525 $start = sprintf( '%08X', $network );
526 $end = sprintf( '%08X', $network + pow( 2, ( 32 - $bits ) ) - 1 );
527 }
528 // Explicit range
529 } elseif ( strpos( $range, '-' ) !== false ) {
530 list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
531 if ( self::isIPv6( $start ) && self::isIPv6( $end ) ) {
532 return self::parseRange6( $range );
533 }
534 if ( self::isIPv4( $start ) && self::isIPv4( $end ) ) {
535 $start = self::toHex( $start );
536 $end = self::toHex( $end );
537 if ( $start > $end ) {
538 $start = $end = false;
539 }
540 } else {
541 $start = $end = false;
542 }
543 } else {
544 # Single IP
545 $start = $end = self::toHex( $range );
546 }
547 if ( $start === false || $end === false ) {
548 return [ false, false ];
549 } else {
550 return [ $start, $end ];
551 }
552 }
553
562 private static function parseCIDR6( $range ) {
563 # Explode into <expanded IP,range>
564 $parts = explode( '/', self::sanitizeIP( $range ), 2 );
565 if ( count( $parts ) != 2 ) {
566 return [ false, false ];
567 }
568 list( $network, $bits ) = $parts;
569 $network = self::IPv6ToRawHex( $network );
570 if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) {
571 if ( $bits == 0 ) {
572 $network = "0";
573 } else {
574 # Native 32 bit functions WONT work here!!!
575 # Convert to a padded binary number
576 $network = Wikimedia\base_convert( $network, 16, 2, 128 );
577 # Truncate the last (128-$bits) bits and replace them with zeros
578 $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
579 # Convert back to an integer
580 $network = Wikimedia\base_convert( $network, 2, 10 );
581 }
582 } else {
583 $network = false;
584 $bits = false;
585 }
586
587 return [ $network, (int)$bits ];
588 }
589
603 private static function parseRange6( $range ) {
604 # Expand any IPv6 IP
605 $range = self::sanitizeIP( $range );
606 // CIDR notation...
607 if ( strpos( $range, '/' ) !== false ) {
608 list( $network, $bits ) = self::parseCIDR6( $range );
609 if ( $network === false ) {
610 $start = $end = false;
611 } else {
612 $start = Wikimedia\base_convert( $network, 10, 16, 32, false );
613 # Turn network to binary (again)
614 $end = Wikimedia\base_convert( $network, 10, 2, 128 );
615 # Truncate the last (128-$bits) bits and replace them with ones
616 $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
617 # Convert to hex
618 $end = Wikimedia\base_convert( $end, 2, 16, 32, false );
619 # see toHex() comment
620 $start = "v6-$start";
621 $end = "v6-$end";
622 }
623 // Explicit range notation...
624 } elseif ( strpos( $range, '-' ) !== false ) {
625 list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
626 $start = self::toHex( $start );
627 $end = self::toHex( $end );
628 if ( $start > $end ) {
629 $start = $end = false;
630 }
631 } else {
632 # Single IP
633 $start = $end = self::toHex( $range );
634 }
635 if ( $start === false || $end === false ) {
636 return [ false, false ];
637 } else {
638 return [ $start, $end ];
639 }
640 }
641
652 public static function isInRange( $addr, $range ) {
653 $hexIP = self::toHex( $addr );
654 list( $start, $end ) = self::parseRange( $range );
655
656 return ( strcmp( $hexIP, $start ) >= 0 &&
657 strcmp( $hexIP, $end ) <= 0 );
658 }
659
670 public static function isInRanges( $ip, $ranges ) {
671 foreach ( $ranges as $range ) {
672 if ( self::isInRange( $ip, $range ) ) {
673 return true;
674 }
675 }
676 return false;
677 }
678
689 public static function canonicalize( $addr ) {
690 // remove zone info (T37738)
691 $addr = preg_replace( '/\%.*/', '', $addr );
692
693 if ( self::isValid( $addr ) ) {
694 return $addr;
695 }
696 // Turn mapped addresses from ::ce:ffff:1.2.3.4 to 1.2.3.4
697 if ( strpos( $addr, ':' ) !== false && strpos( $addr, '.' ) !== false ) {
698 $addr = substr( $addr, strrpos( $addr, ':' ) + 1 );
699 if ( self::isIPv4( $addr ) ) {
700 return $addr;
701 }
702 }
703 // IPv6 loopback address
704 $m = [];
705 if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) ) {
706 return '127.0.0.1';
707 }
708 // IPv4-mapped and IPv4-compatible IPv6 addresses
709 if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . '(' . RE_IP_ADD . ')$/i', $addr, $m ) ) {
710 return $m[1];
711 }
712 if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD .
713 ':' . RE_IPV6_WORD . '$/i', $addr, $m )
714 ) {
715 return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) );
716 }
717
718 return null; // give up
719 }
720
727 public static function sanitizeRange( $range ) {
728 list( /*...*/, $bits ) = self::parseCIDR( $range );
729 list( $start, /*...*/ ) = self::parseRange( $range );
730 $start = self::formatHex( $start );
731 if ( $bits === false ) {
732 return $start; // wasn't actually a range
733 }
734
735 return "$start/$bits";
736 }
737
744 public static function getSubnet( $ip ) {
745 $matches = [];
746 $subnet = false;
747 if ( self::isIPv6( $ip ) ) {
748 $parts = self::parseRange( "$ip/64" );
749 $subnet = $parts[0];
750 } elseif ( preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
751 // IPv4
752 $subnet = $matches[1];
753 }
754 return $subnet;
755 }
756}
const RE_IPV6_WORD
Definition IP.php:37
const RE_IP_BYTE
Definition IP.php:29
const RE_IPV6_GAP
Definition IP.php:52
const RE_IPV6_RANGE
Definition IP.php:50
const RE_IPV6_V4_PREFIX
Definition IP.php:53
const RE_IP_RANGE
Definition IP.php:33
const RE_IP_ADD
Definition IP.php:30
const RE_IP_PREFIX
Definition IP.php:32
const RE_IPV6_PREFIX
Definition IP.php:38
const RE_IPV6_ADD
Definition IP.php:39
const IP_ADDRESS_STRING
Definition IP.php:56
A collection of public static functions to play with IP address and IP ranges.
Definition IP.php:67
static combineHostAndPort( $host, $port, $defaultPort=false)
Given a host name and a port, combine them into host/port string like you might find in a URL.
Definition IP.php:315
static isValid( $ip)
Validate an IP address.
Definition IP.php:111
static prettifyIP( $ip)
Prettify an IP for display to end users.
Definition IP.php:213
static isIPv4( $ip)
Given a string, determine if it as valid IP in IPv4 only.
Definition IP.php:99
static parseRange( $range)
Given a string range in a number of formats, return the start and end of the range in hexadecimal.
Definition IP.php:513
static sanitizeIP( $ip)
Convert an IP into a verbose, uppercase, normalized form.
Definition IP.php:152
static formatHex( $hex)
Convert an IPv4 or IPv6 hexadecimal representation back to readable format.
Definition IP.php:332
static parseCIDR6( $range)
Convert a network specification in IPv6 CIDR notation to an integer network and a number of bits.
Definition IP.php:560
static IPv6ToRawHex( $ip)
Given an IPv6 address in octet notation, returns a pure hex string.
Definition IP.php:450
static canonicalize( $addr)
Convert some unusual representations of IPv4 addresses to their canonical dotted quad representation.
Definition IP.php:687
static getSubnet( $ip)
Returns the subnet of a given IP.
Definition IP.php:742
static isValidBlock( $ipRange)
Validate an IP range (valid address with a valid CIDR prefix).
Definition IP.php:125
static parseRange6( $range)
Given a string range in a number of formats, return the start and end of the range in hexadecimal.
Definition IP.php:601
static sanitizeRange( $range)
Gets rid of unneeded numbers in quad-dotted/octet IP strings For example, 127.111....
Definition IP.php:725
static hexToQuad( $ip_hex)
Converts a hexadecimal number to an IPv4 address in quad-dotted notation.
Definition IP.php:366
static toHex( $ip)
Return a zero-padded upper case hexadecimal representation of an IP address.
Definition IP.php:417
static isPublic( $ip)
Determine if an IP address really is an IP address, and if it is public, i.e.
Definition IP.php:388
static hexToOctet( $ip_hex)
Converts a hexadecimal number to an IPv6 address in octet notation.
Definition IP.php:346
static isInRange( $addr, $range)
Determine if a given IPv4/IPv6 address is in a given CIDR network.
Definition IP.php:650
static isValidRange( $ipRange)
Validate an IP range (valid address with a valid CIDR prefix).
Definition IP.php:138
static isIPv6( $ip)
Given a string, determine if it as valid IP in IPv6 only.
Definition IP.php:88
static isInRanges( $ip, $ranges)
Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
Definition IP.php:668
static parseCIDR( $range)
Convert a network specification in CIDR notation to an integer network and a number of bits.
Definition IP.php:470
static splitHostAndPort( $both)
Given a host/port string, like one might find in the host part of a URL per RFC 2732,...
Definition IP.php:266
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition hooks.txt:181
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
MediaWiki has optional support for a high distributed memory object caching system For general information on but for a larger site with heavy like it should help lighten the load on the database servers by caching data and objects in Ubuntu and probably other Linux distributions If there s no package available for your you can compile it from epoll rt patch for Linux is current Memcached and libevent are under BSD style licenses The server should run on Linux and other Unix like systems you can run multiple servers on one machine or on multiple machines on a network
Definition memcached.txt:33