44

I'm wondering if this is the best way to match a string that starts with a private IP address (Perl-style Regex):

(^127\.0\.0\.1)|(^192\.168)|(^10\.)|(^172\.1[6-9])|(^172\.2[0-9])|(^172\.3[0-1])

Thanks much!

Liath
  • 9,913
  • 9
  • 51
  • 81
caseyamcl
  • 1,046
  • 1
  • 10
  • 18
  • 3
    First, you should review RFC1918 to get the proper list. Second, I suggest that a solution not involving regexps will be easier to maintain. Once you convert an IP address to numeric, it is fairly easy to match it against a list of private IP ranges. This will also let you easily use the publicly-available bogon lists, which contain much more than RFC1918. – derobert May 11 '10 at 20:25
  • 1
    @derobert true, but for uses such as a Tomcat Remote Address Filter you need a regular expression. – Raedwald Feb 25 '13 at 12:43
  • It's a common beginner error to think `^` means "not" in this context, so it bears pointing out: Each `^` in your expression simply anchors the match to the beginning of line. In traditional regex, there is no simple way to say "not this string" though Perl-compatible / PCRE expressions have negative lookaheads with `(?!...)` – tripleee Dec 14 '17 at 04:36

15 Answers15

72

I'm assuming you want to match these ranges:

127.  0.0.0 – 127.255.255.255     127.0.0.0 /8
 10.  0.0.0 –  10.255.255.255      10.0.0.0 /8
172. 16.0.0 – 172. 31.255.255    172.16.0.0 /12
192.168.0.0 – 192.168.255.255   192.168.0.0 /16

You are missing some dots that would cause it to accept for example 172.169.0.0 even though this should not be accepted. I've fixed it below. Remove the new lines, it's just for readability.

(^127\.)|
(^10\.)|
(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|
(^192\.168\.)

Also note that this assumes that the IP addresses have already been validated - it accepts things like 10.foobar.

bramp
  • 9,581
  • 5
  • 40
  • 46
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
  • If you want to exclude private ip addresses from google analytics tracking, use this: https://gist.github.com/1000402 – Aron Woost May 31 '11 at 12:12
  • 2
    The first line should be (^127\.)| – Mark Rose Jun 07 '12 at 19:18
  • It does not validate against our networks internal IPs: For instance: 10.131.43.84 Your 10 block is broken – mfriis Jul 26 '13 at 07:43
  • 2
    Java version: `'(^127\\.0\\.0\\.1)|(^10\\.)|(^172\\.1[6-9]\\.)|(^172\\.2[0-9]\\.)|(^172\\.3[0-1]\\.)|(^192\\.168\\.)'` – Phani May 22 '15 at 23:56
  • 1
    more complex but more accurate: `(^127\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$)|(^10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$)|(^172\.1[6-9]{1}[0-9]{0,1}\.[0-9]{1,3}\.[0-9]{1,3}$)|(^172\.2[0-9]{1}[0-9]{0,1}\.[0-9]{1,3}\.[0-9]{1,3}$)|(^172\.3[0-1]{1}[0-9]{0,1}\.[0-9]{1,3}\.[0-9]{1,3}$)|(^192\.168\.[0-9]{1,3}\.[0-9]{1,3}$)` – dr.dimitru Jan 15 '16 at 05:36
  • 4
    The auto link-local private addresses 169.254.*.* should also be in this set. Written compactly: `^(10|127|169\.254|172\.1[6-9]|172\.2[0-9]|172\.3[0-1]|192\.168)\.` – karmakaze Mar 28 '16 at 18:26
  • @karmakaze This can still be simplified/shortened as `^(10|127|169\.254|172\.(1[6-9]|2[0-9]|3[0-1])|192\.168)\.` (or even `^1(0|27|69\.254|72\.(1[6-9]|2[0-9]|3[0-1])|92\.168)\.` but this could be one step too far). – Nicolas Melay Oct 12 '20 at 14:35
21

This is the same as the correct answer by Mark, but now including IPv6 private addresses.

/(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1$)|(^[fF][cCdD])/
blueyed
  • 27,102
  • 4
  • 75
  • 71
Edward
  • 766
  • 2
  • 8
  • 18
  • 5
    Not good - According to Wikipedia, (http://en.wikipedia.org/wiki/Unique_local_address), the block (fc00::/7) is reserved for Unique Local Addresses. This Regex only adds the localhost address (::1) to the accepted solution. – dana Mar 13 '13 at 21:32
  • 5
    According to https://github.com/rails/rails/pull/12651/files the regexp for this would be `^[fF][cCdD]` - I am editing the answer accordingly. – blueyed Mar 15 '15 at 07:59
  • The `/` character at the beginning and at the end of the script suggest me that you are using the expression in some language like Javascript, but that isn't valid in other languages. Despite that, a shorter version (and without the slashes) is `^(127\.)|(192\.168\.)|(10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(::1$)|([fF][cCdD])` – Mariano Ruiz Jun 28 '17 at 15:50
  • @MarianoRuiz I believe you need a capture group around the whole thing if you have the caret at the beginning, else you end up matching 127. at the beginning, and the rest of the groups anywhere in the string. – wmassingham Aug 19 '19 at 19:35
21

I have generated this

REGEXP FOR CLASS A NETWORKS :

(10)(\.([2]([0-5][0-5]|[01234][6-9])|[1][0-9][0-9]|[1-9][0-9]|[0-9])){3}

REGEXP FOR CLASS B NETWORKS :

(172)\.(1[6-9]|2[0-9]|3[0-1])(\.(2[0-4][0-9]|25[0-5]|[1][0-9][0-9]|[1-9][0-9]|[0-9])){2}

REGEXP FOR CLASS C NETWORKS :

(192)\.(168)(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){2}

Let me know if you encounter any error

If you are sure of your output (say for example netstat) and you have no need to check about IP address validity because it is already done, then you can catch private ip addresses with this formula

grep -P "(10.|192.168|172.1[6-9].|172.2[0-9].|172.3[01].).* "

Justin
  • 115
  • 4
Giamma Theo
  • 319
  • 2
  • 6
  • 4
    By just using `[2][0-5][0-5]` for the 200 to 255 range, numbers like 206 to 209 and 216 to 219, etc. will not match. You'll have to replace this with `([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])`. – kielabokkie Mar 15 '14 at 10:02
  • What language is this used for? I cant get it to work, https://regex101.com/r/jdVmny/1 – Justin Jun 07 '17 at 20:44
6

here is what I use in python:

rfc1918 = re.compile('^(10(\.(25[0-5]|2[0-4][0-9]|1[0-9]{1,2}|[0-9]{1,2})){3}|((172\.(1[6-9]|2[0-9]|3[01]))|192\.168)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{1,2}|[0-9]{1,2})){2})$')

You can remove the ^ and/or $ anchors if you wish.

I prefer the above regex because it weeds out invalid octets (anything above 255).

example usage:

if rfc1918.match(ip):
    print "ip is private"
vich
  • 11,836
  • 13
  • 49
  • 66
Josh Worley
  • 179
  • 2
  • 7
3

Looks right. Personally, I'd change the first one to:

^127\.0 

With this: (^127\.0\.0\.1) you looking for anything that starts with 127.0.0.1 and will miss out on 127.0.0.2*, 127.0.2.*, 127.0.* etc.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
KM.
  • 1,382
  • 1
  • 19
  • 34
  • 6
    I know this is old, but this question was first or second on my google search. I believe the whole 127.0.0.0/8 block is reserved for loopback, so that 0 shouldn't even be there. – Leagsaidh Gordon Jan 17 '14 at 19:24
3

This is in case you decide to go with my comment, suggesting you don't use regexps. Untested (but probably works, or at least close), in Perl:

@private = (
    {network => inet_aton('127.0.0.0'),   mask => inet_aton('255.0.0.0')   },
    {network => inet_aton('192.168.0.0'), mask => inet_aton('255.255.0.0') },
    # ...
);

$ip = inet_aton($ip_text);
if (grep $ip & $_->{mask} == $_->{network}, @private) {
    # ip address is private
} else {
    # ip address is not private
}

Note now how @private is just data, which you can easily change. Or download on the fly from the Cymru Bogon Reference.

edit: It occurs to me that asking for a Perl regexp doesn't mean you know Perl, so the key line is there is the 'grep', which just loops over each private address range. You take your IP, bitwise and it with the netmask, and compare to the network address. If equal, its part of that private network.

derobert
  • 49,731
  • 15
  • 94
  • 124
2

10 years late. Credits to Mark Byers, bramp, Edward, blueyed, user3177026, Justin, karmakaze, Ron Maupin.

Answer TLDR

Beginning only

Please remove the line breaks - this is just to make it easier to read:

^(?:
127\.|
0?10\.|
172\.0?1[6-9]\.|
172\.0?2[0-9]\.|
172\.0?3[01]\.|
192\.168\.|
169\.254\.|
::1|
[fF][cCdD][0-9a-fA-F]{2}:|
[fF][eE][89aAbB][0-9a-fA-F]:
)

The ?: at the beginning of the parenthesis means that this parenthesis is not captured and may make it a bit faster.

Please be careful with the backslash. In Postgres e.g. you have to use an E string and have to escape the backslash with a backslash - or just use [.] instead of E'\\.'!

The whole IP (decimal, leading zeros optional, max. three digits)

Please remove the line breaks - this is just to make it easier to read:

\b(
127\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|
0?10\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|
172\.0?1[6-9]\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|
172\.0?2[0-9]\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|
172\.0?3[01]\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|
192\.168\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|
169\.254\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|
::1|
[fF][cCdD][0-9a-fA-F]{2}(?:[:][0-9a-fA-F]{0,4}){0,7}|
[fF][eE][89aAbB][0-9a-fA-F](?:[:][0-9a-fA-F]{0,4}){0,7}
)
(?:\/([789]|1?[0-9]{2}))?
\b

https://regex101.com/r/JCLOZL/14 (Backup: https://etherpad.wikimedia.org/p/JCLOZL)

Details

IPv4

  127.0.0.0 to 127.255.255.255 is   127.0.0.0/8   # localhost, loopback etc.
   10.0.0.0 to  10.255.255.255 is    10.0.0.0/8   # approximately/formerly class A
 172.16.0.0 to  172.31.255.255 is  172.16.0.0/12  # approximately/formerly class B
192.168.0.0 to 192.168.255.255 is 192.168.0.0/16  # approximately/formerly class C
169.254.0.0 to 169.254.255.255 is 169.254.0.0/16  # link-local addresses since 2005

Example:

172.17.50.33 or more explicit:
172.17.50.33/32

I'm not sure if the part behind the slash can have leading zeros (unlikely), but the IP can have leading zeros.

IPv4 with leading zeros (decimal)

127.000.000.000 to 127.255.255.255 is 127.000.000.000/8   # localhost, loopback etc.
010.000.000.000 to 010.255.255.255 is 010.000.000.000/8   # approx/formerly class A
172.016.000.000 to 172.031.255.255 is 172.016.000.000/12  # approx/formerly class B
192.168.000.000 to 192.168.255.255 is 192.168.000.000/16  # approx/formerly class C
169.254.000.000 to 169.254.255.255 is 169.254.000.000/16  # link-local addresses

Example:

172.017.050.033 or more explicit:
172.017.050.033/32

IPv4 with leading zeros (octal)

Not supported in my regex. To make your program perfect, check and warn the user about octal IP addresses and/or more than three digits!

0177.0000.0000.0000 to 0177.0377.0377.0377 is 0177.0000.0000.0000/8   # loopback
0012.0000.0000.0000 to 0012.0377.0377.0377 is 0012.0000.0000.0000/8   # A
0254.0020.0000.0000 to 0254.0037.0377.0377 is 0254.0020.0000.0000/12  # B
0300.0250.0000.0000 to 0300.0250.0377.0377 is 0300.0250.0000.0000/16  # C
0251.0376.0000.0000 to 0251.0376.0377.0377 is 0251.0376.0000.0000/16  # link-local

Example:

0254.0021.0062.0041 or more explicit:
0254.0021.0062.0041/32

Yes, 172.017.050.033 is the same as 0254.0021.0062.0041 on different tools. Tested with ping on macOS.

Of course, you can also mix decimal (no leading zero) with octal (at least one leading zero) in some tools. :S

IPv6

fc00:0000:… to fdff:ffff:… is fc00::/7
fe80:0000:… to febf:ffff:… is fe80::/10  # link-local addresses

This might not exactly what you want because:

Private IPv4 addresses are defined by RFC 1918, Address Allocation for Private Internets, and the addresses are used in multiple networks all over. IPv6 ULA addresses are meant to be unique (the "U" in ULA), not to be reused in multiple places, which is the reason they are required to have 40 random bits in the Global ID, giving a high expectation of uniqueness.

All the addresses from the public Internet will be in the range from 2000:: to 3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff (2000::/3). You need to properly mask an address to see if it is in the public range, then do the same thing for the non-public ranges in the public range. If it passes the public range test, it must fail the non-public ranges within the public range. If it passes all that, it is a public address. IOT devices also get Global (public) IPv6 addresses. The point of IPv6 is that there are enough addresses that every interface on every device gets a Global IPv6 address, restoring the way IP was originally designed (no more NAT kludge to work around).

Some addresses internal to your webserver will be Link-Local addresses (fe80::/10), and it is possible to assign ULA addresses (fc00::/7 of which you can assign from fd00::/8 with certain limitations) for traffic that will never be allowed on the public Internet.

Example:

fdff:1234:abcd:5678:effe:9098:dcba:7654 or more explicit:
fdff:1234:abcd:5678:effe:9098:dcba:7654/128

Incomplete link list

When I came here, I did not know about:

qräbnö
  • 2,722
  • 27
  • 40
1

If you're looking for system.net defaultProxy and proxy bypasslist config that uses a proxy for external but uses direct connections for internal hosts (could do with some ipv6 support)...

<system.net>
  <defaultProxy enabled="true">
    <proxy proxyaddress="http://proxycluster.privatedomain.net:8080" bypassonlocal="True"  />
    <bypasslist>
      <!-- exclude local host -->
      <add address="^(http|https)://localhost$" />
      <!-- excludes *.privatedomain.net -->
      <add address="^(http|https)://.*\.privatedomain\.net$" />
      <!-- excludes simple host names -->
      <add address="^(http|https)://[a-z][a-z0-9\-_]*$" />
      <!-- exclude private network addresses 192.168, 172.16..31 through 31, 127.* etc. -->
      <add address="^(http|https)://((((127)|(10))\.[0-9]+\.[0-9]+\.[0-9]+)|(((172\.(1[6-9]|2[0-9]|3[0-1]))|(192\.168))\.[0-9]+\.[0-9]+))$"/>
    </bypasslist>
  </defaultProxy>
  <connectionManagement>
    <add address="*" maxconnection="10" />
  </connectionManagement>
</system.net>
Mhano
  • 11
  • 2
1

This is the compact form of Mark Byers solution:

^(172\.(1[6-9]\.|2[0-9]\.|3[0-1]\.)|192\.168\.|10\.|127\.)
aldemarcalazans
  • 1,309
  • 13
  • 16
0
     //RegEx to check for the following ranges. IPv4 only
         //172.16-31.xxx.xxx
         //10.xxx.xxx.xxx
         //169.254.xxx.xxx
         //192.168.xxx.xxx

     var regex = /(^127\.)|(^(0)?10\.)|(^172\.(0)?1[6-9]\.)|(^172\.(0)?2[0-9]\.)|(^172\.(0)?3[0-1]\.)|(^169\.254\.)|(^192\.168\.)/;
HellKnight Hicks
  • 125
  • 2
  • 11
0

You need an end separator in order to get the whole 4-th octet in case it is more than one digit.

^(10.([0-9][0-9]?|[0-1][0-9]?[0-9]?|2[0-4]?[0-9]?|25[0-5])|172.(1[6-9]|2[0-9]|3[0-1])|192.168).([0-9][0-9]?|[0-1][0-9]?[0-9]?|2[0-4]?[0-9]?|25[0-5]).([0-9][0-9]?|[0-1][0-9]?[0-9]?|2[0-4]?[0-9]?|25[0-5])$

Vesselin Yanev
  • 165
  • 2
  • 9
0

As per https://en.wikipedia.org/wiki/Reserved_IP_addresses#IPv4, the best way to check is comparing it with IP ranges one by one. For example, JavaScript code:

/**
 * Check if IPv4 is reserved
 *
 * Reserved IPv4
 * ```
 * 0.0.0.0/8         : 0.0.0.0      - 0.255.255.255
 * 10.0.0.0/8        : 10.0.0.0     - 10.255.255.255
 * 100.64.0.0/10     : 100.64.0.0   - 100.127.255.255
 * 127.0.0.0/8       : 127.0.0.0    - 127.255.255.255
 * 169.254.0.0/16    : 169.254.0.0  - 169.254.255.255
 * 172.16.0.0/12     : 172.16.0.0   - 172.31.255.255
 * 192.0.0.0/24      : 192.0.0.0    - 192.0.0.255
 * 192.0.2.0/24      : 192.0.2.0    - 192.0.2.255
 * 192.88.99.0/24    : 192.88.99.0  - 192.88.99.255
 * 192.168.0.0/16    : 192.168.0.0  - 192.168.255.255
 * 198.18.0.0/15     : 198.18.0.0   - 198.19.255.255
 * 198.51.100.0/24   : 198.51.100.0 - 198.51.100.255
 * 203.0.113.0/24    : 203.0.113.0  - 203.0.113.255
 * 224.0.0.0/4       : 224.0.0.0    - 239.255.255.255
 * 233.252.0.0/24    : 233.252.0.0  - 233.252.0.255
 * 240.0.0.0/4       : 240.0.0.0    - 255.255.255.254
 * 255.255.255.255/32: 255.255.255.255
 * ```
 *
 * @param {string} ip
 */
function reservedIPv4(ip) {
  const [a, b, c, d] = ip.split('.').map(n => +n);

  if (a === 0 || a === 10 || a === 127 ) return true;
  if (a === 100 && b >= 64 && b <= 127) return true;
  if (a === 169 && b === 254) return true;
  if (a === 172 && b >= 16 && b <= 31) return true;
  if (a === 192) {
    if (b === 0 && (c === 0 || c === 2)) return true;
    if (b === 88 && c === 99) return true;
    if (b === 168) return true;
  }
  if (a === 198) {
    if (b >= 18 && b <= 19) return true;
    if (b === 51 && c === 100) return true;
  }
  if (a === 203 && b === 0 && c === 113) return true;
  if (a >= 224 && a <= 239) return true;
  if (a === 233 && b === 252 && c === 0) return true;
  if (a >= 240 && a <= 255 && d != 255) return true;
  if (a === 255 &&  b === 255 && c === 255 && d === 255) return true;

  return false;
}
xuxu
  • 6,374
  • 1
  • 17
  • 11
0

The best way to match a string that starts with private IP address (ipv6 included)

   private_ip_identifier("127.0.0.1");
   private_ip_identifier("47.29.161.118");
    
    function private_ip_identifier(ip) {
      const regex_exp = /(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1$)|(^[fF][cCdD])/;
          const test_result =  regex_exp.test(ip);
          console.log(test_result);
          // true for 127.0.0.1 and false for 47.29.161.118
    }
0

Try this, This code corrects some errors in the range of private IPs, in some examples the last three digits the range could exceed 255.

(^10\.(\b(?:(?:25[0-5]|2?[0-4][0-9]|[01]?[0-9][0-9]?)\.){2})\b(\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b))|(^172\.1[6-9]\.(\b(?:(?:25[0-5]|2?[0-4][0-9]|[01]?[0-9][0-9]?)\.)\b)(\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b))|(^172\.2[0-9]\.(\b(?:(?:25[0-5]|2?[0-4][0-9]|[01]?[0-9][0-9]?)\.)\b)(\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b))|(^172\.3[0-1]\.(\b(?:(?:25[0-5]|2?[0-4][0-9]|[01]?[0-9][0-9]?)\.)\b)(\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b))|(^192\.168\.(\b(?:(?:25[0-5]|2?[0-4][0-9]|[01]?[0-9][0-9]?)\.)\b)(\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b))

  • 1
    While this code may solve the question, [including an explanation](//meta.stackexchange.com/q/114762) of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please [edit] your answer to add explanations and give an indication of what limitations and assumptions apply. – Yunnosch Feb 07 '23 at 19:36
-1

FWIW this pattern was over 10% faster using pattern.matcher:

^1((0)|(92\\.168)|(72\\.((1[6-9])|(2[0-9])|(3[0-1])))|(27))\\.
Floern
  • 33,559
  • 24
  • 104
  • 119
scott
  • 1