4

At first, I thought it's easy to find the regex which only matches public IPv4 address on the Internet. However, after tons of googling,I got nothing,so I try to write the regex,as follows(Perl flavor),

[0-9]\.(?:[0-9]{1,3}\.){2}[0-9]{1,3}|
1[1-9]\.(?:[0-9]{1,3}\.){2}[0-9]{1,3}|
[2-9][0-9]\.(?:[0-9]{1,3}\.){2}[0-9]{1,3}|
1[0-6][0-9]\.(?:[0-9]{1,3}\.){2}[0-9]{1,3}|
17[0-1]\.(?:[0-9]{1,3}\.){2}[0-9]{1,3}|
172\.0\.(?:[0-9]{1,3}\.)[0-9]{1,3}|
172\.1[0-5]\.(?:[0-9]{1,3}\.)[0-9]{1,3}|
(172\.3[2-9]\.)(?:[0-9]{1,3}\.)[0-9]{1,3}|
(172\.[4-9][0-9]\.)(?:[0-9]{1,3}\.)[0-9]{1,3}|
...

The regex seems inaccurate and inefficient , does someone have a better way to write the regex?

Matt Elson
  • 4,177
  • 8
  • 31
  • 46
  • https://metacpan.org/pod/Regexp::Common::net#RE-net-IPv4 would match an IPv4 address. I wouldn't put the "public" check into the regex. – melpomene Oct 31 '15 at 15:12
  • what are you trying to do ? `127\.\d{1,3}\.\d{1,3}\.\d{1,3}` isnt public !! – Abr001am Oct 31 '15 at 16:01
  • I've broken the string by `.`s then checked each value in the past `if (($sections[0] == 192 && $sections[1] == 168) || ($sections[0] == 172 && ($sections[1] >= 16 && $sections[1] <= 32)) || ($sections[0] == 10)) {`. What language are you running this in? (also that conditional is checking the inverse of your request; if that matches it is private). – chris85 Oct 31 '15 at 16:09
  • @chris85 You just check private IP address , not public IP address. – Matt Elson Oct 31 '15 at 16:13
  • Yea, it's the inverse, the language you are working with doesn't support `else`? – chris85 Oct 31 '15 at 16:15
  • @Agawa001 172 is NOT 127. :) – Matt Elson Oct 31 '15 at 16:15
  • @chris85 I am using an editor to search, not a language. – Matt Elson Oct 31 '15 at 16:19
  • There is a similar question here http://stackoverflow.com/questions/2284750/regex-for-ipv4-routable-address-only?rq=1 – ramana_k Oct 31 '15 at 16:52
  • @Ramana the link doesn't solve my question. – Matt Elson Oct 31 '15 at 16:56
  • Possible duplicate of [Validating IPv4 addresses with regexp](https://stackoverflow.com/q/5284147/608639) – jww Nov 12 '18 at 09:18

5 Answers5

12

Try this one:

^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(?<!172\.(16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31))(?<!127)(?<!^10)(?<!^0)\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(?<!192\.168)(?<!172\.(16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31))\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$

It correctly doesn't match this invalid ips:

999.999.999.999.
108.0.0.01
0.1.2.3
00.0000.00.00
192.168.l.1
912.456.123.123
.3.3.3.0
192.168.o.0

It doesn't match local IPs:

172.16.0.9
172.16.4.1
172.17.1.1
127.0.0.2
10.0.1.5
10.0.0.1
10.155.155.155
10.255.255.254
172.16.0.4
172.16.0.1
172.17.1.1
172.31.254.254
192.168.1.2
192.168.254.0

And matches pretty much every IP i've tested. Can't say I really done a lot of testing, so i'm welcome for suggestions

Anubioz
  • 435
  • 7
  • 13
  • What about private link-local addresses `169.254.0.0 to 169.254.255.255 = 169.254.0.0/16` since 2005? https://regex101.com/r/GWcMlF/1 – qräbnö Apr 08 '21 at 16:04
  • Actually they are 169.254.1.1 to 169.254.254.255, which brings unneeded complexity to this quick & dirty ipv4 address validation. An obvious way to do it without excluding first & last 256 addresses would be putting (?<!169\.254) right after (?<!192\.168) block, as mentioned in your regex101 link. – Anubioz Apr 08 '21 at 20:16
  • The first useful is 169.254.0.1. The last useful is 169.254.255.254 - IIRC. Apart from that, I agree. BTW: Is it really necessary to have that twice: `(?<!172\.(?:16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31))`? – qräbnö Apr 08 '21 at 21:06
  • this is wrong.... 60.123.247.255 is a VALID ip address, not a broadcast. So is 60.123.247.0. – Livio Zanol Puppim Jul 10 '21 at 12:28
  • @LivioZanolPuppim you're entirely correct. – Anubioz Jul 10 '21 at 16:11
7

I would much rather capture each octet and check if the subnet is private in code rather than with regex. However, I'm intrigued by your question.

According to Wikipedia, there are 3 ranges of private IP address.

10.0.0.0    - 10.255.255.255
172.16.0.0  - 172.31.255.255
192.168.0.0 - 192.168.255.255

Now assuming that you don't have crazy IP-like strings, like 55.300.666.1, you can use negative lookbehind to do what you want:

(\d+)(?<!10)\.(\d+)(?<!192\.168)(?<!172\.(1[6-9]|2\d|3[0-1]))\.(\d+)\.(\d+)

Let's see that again, with some line breaks added for clarity:

(\d+)(?<!10)
\.(\d+)(?<!192\.168)(?<!172\.(1[6-9]|2\d|3[0-1]))
\.(\d+)\.(\d+)

The first line checks that the first octet is not 10. The second line checks that the first 2 octerts are not 192.168 or between 172.16 and 172.31. The third line has nothing special. Regex101

PS: I do know that 127.0.0.1 is localhost but I have no idea if it's private (I'm not a network engineer). You may have to improvise as needed.

Code Different
  • 90,614
  • 16
  • 144
  • 163
5

If you are looking to validate purely public IPv4 addresses, we can eliminate all of the Reserved IPv4 addresses as follows:

  • 0.0.0.0/8: Current network
  • 10.0.0.0/8: Private network
  • 100.64.0.0/10: Shared Address Space
  • 127.0.0.0/8: Loopback
  • 169.254.0.0/16: Link-local
  • 172.16.0.0/12: Private network
  • 192.0.0.0/24: IETF Protocol Assignments
  • 192.0.2.0/24: TEST-NET-1, documentation and examples
  • 192.88.99.0/24: IPv6 to IPv4 relay (includes 2002::/16)
  • 192.168.0.0/16: Private network
  • 198.18.0.0/15: Network benchmark tests
  • 198.51.100.0/24: TEST-NET-2, documentation and examples
  • 203.0.113.0/24: TEST-NET-3, documentation and examples
  • 224.0.0.0/4: IP multicast (former Class D network)
  • 240.0.0.0/4: Reserved (former Class E network)
  • 255.255.255.255: Broadcast

(list taken from Wikipedia)


This can be put into a straightforward regex that doesn't use lookbehinds (and hence, the regex can be used in JavaScript):

(^0\.)|(^10\.)|(^100\.6[4-9]\.)|(^100\.[7-9]\d\.)|(^100\.1[0-1]\d\.)|(^100\.12[0-7]\.)|(^127\.)|(^169\.254\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.0\.0\.)|(^192\.0\.2\.)|(^192\.88\.99\.)|(^192\.168\.)|(^198\.1[8-9]\.)|(^198\.51\.100\.)|(^203.0\.113\.)|(^22[4-9]\.)|(^23[0-9]\.)|(^24[0-9]\.)|(^25[0-5]\.)

Likewise, this assumes that you have already validated beforehand that it actually is a valid IPv4 address.

Irvin Lim
  • 2,393
  • 17
  • 20
2
 ^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(?<!172\.(16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31))(?<!127)(?<!^10)(?<!^0)\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(?<!192\.168)(?<!172\.(16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31))\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(?<!\.255$)(?<!\b255.255.255.0\b)(?<!\b255.255.255.242\b)$

A slightly different solution that removes false positive addresses if you grep through firewall configs. removes common masks 255.255.255.0 and 255.255.255.242. add and remove as needed.

1

Anubioz's + Irvin Lim=

^(?!^0\.)(?!^10\.)(?!^100\.6[4-9]\.)(?!^100\.[7-9]\d\.)(?!^100\.1[0-1]\d\.)(?!^100\.12[0-7]\.)(?!^127\.)(?!^169\.254\.)(?!^172\.1[6-9]\.)(?!^172\.2[0-9]\.)(?!^172\.3[0-1]\.)(?!^192\.0\.0\.)(?!^192\.0\.2\.)(?!^192\.88\.99\.)(?!^192\.168\.)(?!^198\.1[8-9]\.)(?!^198\.51\.100\.)(?!^203.0\.113\.)(?!^22[4-9]\.)(?!^23[0-9]\.)(?!^24[0-9]\.)(?!^25[0-5]\.)(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))$

https://i.stack.imgur.com/vWtlj.png

(without broadcast)