4

I'm an iphone developer - new to web dev, so please be patient!
I'm currently using MAMP for local testing.

I have a highly secure section on my site. Along with requiring a user/pass - it also checks the user's IP. If the account hasn't been used from that IP before, it will add that IP, along with a unique ID, to a holding table, and fire the user an email asking to confirm access them to their account from that location.

If the user logs in, and their IP doesn't match any IPs associated with their user ID in my 'allowed' table, it performs the above task, and they receive an email.

The code I use to generate the url of the link they click on looks a bit like this:

if (preg_match('/^127./',$ip)) {
    // accessed from this machine
    $val_url = "http://localhost:8888/mywebsite/admin/aproove_ip.php?email=$admin_email&val=$hash";
}
else if (preg_match('/^192\.168./',$ip)) {
    // accessed form a local networked computer
    $val_url = "http://super.local:8888/mywebsite/admin/aproove_ip.php?email=$admin_email&val=$hash";
    // note super.local is my machine's address, 8888 is MAMP port
}
else {
    // accessed from the WWW
    $val_url = "http://www.mywebsite.com/admin/aproove_ip.php?email=$admin_email&val=$hash";
}

Now, this has worked perfectly when testing on my computer.

However, I decided (don't ask why) to test from my iPod Touch and in the email it sent me (to validate the IP), it gave me the full online address as if it had been accessed from the WWW (i.e. neither of the regexs were satisfied). I looked in the holding table which contains the requests, and the requested IP was: fe80::da30:62ff:fe18:6681.

I'm guessing that's ipv6? - What I need to know is the following:

  • Should I expect ipv6 addressess to hit my site when it goes live?
  • How can I tell if it was a local request (like my regex's for 192.168... and 127...)

I would very much appreciate any advice on this as I find it really confusing

Alex Coplan
  • 13,211
  • 19
  • 77
  • 138
  • 1
    +1 for wanting to make your code IPv6 compliant! Too many programs written today still do not support IPv6 even though IPv4 addresses have run out already. – snap Sep 03 '11 at 11:31

2 Answers2

8

If you want to ensure that no IPv6 connections happen can add Listen 0.0.0.0:80 to your apache configuration. But most web hosts don't support IPv6 yet anyway (ya I know, we're in 2011), so it's very unlikely that people can even connect to you via IPv6. The only reason your seeing this is because bonjour (what makes .local address work) runs over IPv6.

If you want to get your code IPv6 ready, almost no changes need to be made as most IP PHP function work on both IPv4 and IPv6. The only change I remember making was increasing the varchar datatype in the MySQL table to the max length of a IPv6 address (39).

I'm not sure that IPv6 plays by the same rules of subnet that IPv4 does, but I expect it would be quite a bit harder to validate a IPv6 address is local.

EDIT:

fe80::/10 appears to be local link addresses, it might be as simple as checking the first 4 digits.

Kendall Hopkins
  • 43,213
  • 17
  • 66
  • 89
  • thanks! - already made my varchar 39 chars long - I guess I shouldn't really worry about bonjour then, it's only for testing, and in the end when it's online it will do what I want it to do, IPv6 or not! – Alex Coplan Sep 03 '11 at 01:08
  • 3
    You shouldn't be using varchar to store IP addresses – whether v4 or v6. It prevents you from efficiently querying them with a bitmask, and wastes much storage. Please use INET_ATON() and INET_NTOA() for storing and retrieving (respectively) IP addresses in MySQL. – Jeremy Visser Sep 03 '11 at 01:18
  • 2
    For most uses of storing IPs in general applications is just to trace a user, and since MySQL doesn't prove a native way of storing IP in a readable way I normally just use varchar for convenience (esp when it could be IPv6 or IPv4). – Kendall Hopkins Sep 03 '11 at 02:21
  • just to clarify is that `fe80::` OR `fe10::` means local?? – Alex Coplan Sep 03 '11 at 09:18
  • Some additional info for storing and using IPv6 in MySQL: [here](http://stackoverflow.com/questions/6964143/storing-ipv6-addresses-in-mysql) and [here](http://stackoverflow.com/questions/2049681/store-ipv6-in-database). – snap Sep 03 '11 at 11:37
  • @Alex: fe80::/10 means link-local. That is the range fe80:0000:0000:0000:0000:0000:0000:0000 - febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff – Sander Steffann Sep 05 '11 at 08:31
  • so a regex like this might do it... `/^fe[1-8][0-9]./` – Alex Coplan Sep 05 '11 at 10:00
  • 1
    AFAICT private network addresses are specified as fc00::/7, not fe... see http://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses – poop-deck Sep 30 '11 at 10:10
2

To see if an IPv6 address is local you must know which addresses are used locally. IPv6 doesn't do NAT (usually). Some networks use ULA (unique local addresses) internally, but many just use the addresses they get from the ISP internally.

One thing you have to take into account is that some (most? These days) use privacy extensions. This means that their IPv6 addresses will change over time. This will cause your table to grow a lot, and it will make user re-authenticate over and over again. I think your best option is to store only the subnet (the first 64 bits) and match on that.

Just in case you don't know the IPv6 address syntax: addresses use hexadecimal digits and have the form ssss:ssss:ssss:ssss:nnnn:nnnn:nnnn:nnnn where s is the subnet and n is the node/host. Leading zeroes in each block of 4 digits are omitted, and multiple blocks of 0 are replaced with :: once. So fe80::da30:62ff:fe18:6681 is actually fe80:0000:0000:0000:da30:62ff:fe18:6681. My own webserver has address 2001:4038:0:16::16, which is short for 2001:4038:0000:0016:0000:0000:0000:0016, and the subnet is 2001:4038:0:16::/64.

Example code to get the subnet from the address:

<?php

# Get the original IP address, for example from the command line
$original_ip_str = $argv[1];

# Converto to binary form (suppress errors, we handle them)
$original_ip_bin = @inet_pton($original_ip_str);
if ($original_ip_bin === FALSE) {
  $subnet_str = FALSE;
  $subnet_bin = FALSE;
} else {
  if (strlen($original_ip_bin) == 16) {
    # IPv6: Replace the last 64 bits with zeroes
    $subnet_bin = substr_replace($original_ip_bin, str_repeat("\000", 8), -8);
  }

  # Convert the result back to readable form (optional)
  $subnet_str = inet_ntop($subnet_bin);
}

# Show the result
echo "IPv6 address: $original_ip_str\n";
echo "IPv6 subnet: $subnet_str\n";
Sander Steffann
  • 9,509
  • 35
  • 40