上個星期開始伞鲫,微博戏售、抖音、公眾號等多個平臺紛紛上線了 IP 歸屬地功能圣絮。
我想很多小伙伴會好奇互聯(lián)網(wǎng)平臺商們是怎么通過 IP 定位到我們所屬地區(qū)的祈惶?這背后的原理是什么?IP 歸屬地背后又有哪些實際的應(yīng)用扮匠?
所以捧请,今天我們就來聊聊 IP 歸屬地背后的技術(shù)原理。
如何通過 IP 找到地址棒搜?
在我們印象中疹蛉,我們都知道可以通過 IP 地址找到某個人。但當(dāng)我們細(xì)想一下力麸,我們會發(fā)現(xiàn)其實 IP 地址與地理位置并不是直接相關(guān)的可款。那我們到底是如何通過 IP 地址找到地址的呢?
答案是:通過 自治系統(tǒng)(Autonomous System)克蚂。
互聯(lián)網(wǎng)是由不同網(wǎng)絡(luò)組成的網(wǎng)絡(luò)闺鲸,自治系統(tǒng)是組成 Internet 的大型網(wǎng)絡(luò),連接到 Internet 的每臺計算機(jī)或設(shè)備都連接到一個 AS陨舱。而每一個自治系統(tǒng)都會有一個編碼翠拣,我們稱之為 ASN版仔。
可以認(rèn)為 AS 類似于一個城鎮(zhèn)的郵局游盲。
郵件從一個郵局到另一個郵局,直到到達(dá)正確的城鎮(zhèn)為止蛮粮,然后該城鎮(zhèn)的郵局將在該城鎮(zhèn)內(nèi)傳遞郵件益缎。每個 AS 都控制一組特定的 IP 地址,就像每個鎮(zhèn)的郵局負(fù)責(zé)將郵件傳遞到該鎮(zhèn)內(nèi)的所有地址一樣然想。
通常莺奔,每個 AS 由單個大型組織(例如 Internet 服務(wù)提供商(ISP)、大型企業(yè)技術(shù)公司变泄、大學(xué)或政府機(jī)構(gòu))運營令哟。
到這里恼琼,我們可以捋清楚這樣一個邏輯關(guān)系:IP地址 -> 地址塊 -> 自治網(wǎng)絡(luò)編碼(ASN) -> 組織 -> 國家。
通過 IP 地址屏富,我們就可以定位到一個大致的地理位置晴竞,例如:北京朝陽區(qū)、深圳南山區(qū)等狠半。例如我現(xiàn)在的 IP 地址就歸屬于編碼為 AS4xxx 這個自治網(wǎng)絡(luò)噩死,通過這個 ASN 可以知道位置在中國深圳,這個 ASN 編碼所屬的組織為 中國電信神年。
但是通過 ASN 也只能是找到縣級或者區(qū)級的地理位置已维,再細(xì)的位置就找不到了。
但怎么有些時候同學(xué)說:他被查水表了已日,直接定位到某個單元某一戶呢垛耳?其實原理也很簡單!上面我們說到可以根據(jù) IP 地址定位到 ASN 所屬組織飘千,而 ASN 所屬組織在進(jìn)行 IP 地址分配的時候艾扮,都是會進(jìn)行 IP 地址分配記錄的。
某個 IP 地址分配給了誰占婉,都記錄得一清二楚泡嘴。因此警察叔叔想找你喝茶,那還不是一抓一個準(zhǔn)逆济。
但要提示一下的是酌予,并不是誰都有那個權(quán)限去運營商查詢這些數(shù)據(jù)。所以那些說可以爬著網(wǎng)線找到你的人奖慌,基本上可以忽略抛虫,都是在嚇唬你。只有警察叔叔立案简僧,并且出示相關(guān)手續(xù)之后才可以進(jìn)行數(shù)據(jù)查詢建椰。
IP 地址的隱私問題
那是不是只有運營商才能查到某個人的住址信息呢?
在大數(shù)據(jù)時代的今天岛马,各種互聯(lián)網(wǎng)應(yīng)用搜集了大量的數(shù)據(jù)信息棉姐,它們其實也可以根據(jù)這些信息,推斷出某個人的大致地址位置啦逆。
例如百度地圖會一直用 App SDK 以及網(wǎng)頁的方式記錄 IP 和地址位置的關(guān)聯(lián)伞矩,并允許反向查詢,也就是可以根據(jù) IP 地址反向查詢到某個位置夏志,這個數(shù)據(jù)精度可能精確到幾百米乃坤。
其實不止國內(nèi)的公司會這么做,其實國外的公司同樣也會這么做,就比如 Google 也做了湿诊。
只是國外對這方面控制得非常嚴(yán)格狱杰,因此它們會比較明確地披露所使用的用戶隱私數(shù)據(jù),并且還提供了對應(yīng)功能可以讓用戶關(guān)閉厅须。
有朋友說了浦旱,那我可以改變 IP,那是不是某些 App 就不知道我的精確位置了呀九杂?其實并不是的颁湖,因為你的鄰居可以出賣了你。
某些 App 發(fā)現(xiàn)例隆,鄰居周圍的 WIFI甥捺、藍(lán)牙等和你的非常像。而且當(dāng)某個 WiFi 信號消失時镀层,鄰居那邊的也同步消失了镰禾。那么他們就可以猜測,你隱藏了自己的真實 IP唱逢,你的地理位置和鄰居的非常近吴侦。這就是大數(shù)據(jù)時代背景下的應(yīng)用。
因此坞古,當(dāng)某些設(shè)備彈出提示「是否允許掃描本地設(shè)備」時备韧,你就要謹(jǐn)慎選擇了。
如果不是內(nèi)網(wǎng) NAS 或者投屏類的痪枫,基本上沒有必要允許這個操作织堂,這個操作都是在盜取你的個人隱私信息。如果你允許了這個操作奶陈,那他就會開始掃描整個局域網(wǎng)的設(shè)備信息易阳,然后記錄下來。最終吃粒,其會將你的 IP 地址潦俺、手機(jī) IMEI、WiFi 等信息匯總起來徐勃,從而做一些商業(yè)化的信息事示。例如 ,你在電腦上搜了下房子這個關(guān)鍵詞疏旨,等會你刷手機(jī)抖音就會給你推送房地產(chǎn)廣告很魂。
說白了扎酷,「查找并連接到本地網(wǎng)絡(luò)上的設(shè)備」的使用為跨平臺廣告提供了方便檐涝,而從個人信息保護(hù)和隱私保護(hù)體驗的角度來講,這種對設(shè)備的監(jiān)控、跟蹤可能會給用戶帶來擔(dān)憂谁榜。
接下來使用Java實現(xiàn)IP歸屬地顯示功能
工具類
package com.p.h.utils;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* 獲取IP方法
*/
public class IpUtils {
public static String getIpAddr(HttpServletRequest request) {
if (request == null) {
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}
public static boolean internalIp(String ip) {
byte[] addr = textToNumericFormatV4(ip);
return internalIp(addr) || "127.0.0.1".equals(ip);
}
private static boolean internalIp(byte[] addr) {
if (StringUtils.isNull(addr) || addr.length < 2) {
return true;
}
final byte b0 = addr[0];
final byte b1 = addr[1];
// 10.x.x.x/8
final byte SECTION_1 = 0x0A;
// 172.16.x.x/12
final byte SECTION_2 = (byte) 0xAC;
final byte SECTION_3 = (byte) 0x10;
final byte SECTION_4 = (byte) 0x1F;
// 192.168.x.x/16
final byte SECTION_5 = (byte) 0xC0;
final byte SECTION_6 = (byte) 0xA8;
switch (b0) {
case SECTION_1:
return true;
case SECTION_2:
if (b1 >= SECTION_3 && b1 <= SECTION_4) {
return true;
}
case SECTION_5:
switch (b1) {
case SECTION_6:
return true;
}
default:
return false;
}
}
/**
* 將IPv4地址轉(zhuǎn)換成字節(jié)
*
* @param text IPv4地址
* @return byte 字節(jié)
*/
public static byte[] textToNumericFormatV4(String text) {
if (text.length() == 0) {
return null;
}
byte[] bytes = new byte[4];
String[] elements = text.split("\\.", -1);
try {
long l;
int i;
switch (elements.length) {
case 1:
l = Long.parseLong(elements[0]);
if ((l < 0L) || (l > 4294967295L)) {
return null;
}
bytes[0] = (byte) (int) (l >> 24 & 0xFF);
bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 2:
l = Integer.parseInt(elements[0]);
if ((l < 0L) || (l > 255L)) {
return null;
}
bytes[0] = (byte) (int) (l & 0xFF);
l = Integer.parseInt(elements[1]);
if ((l < 0L) || (l > 16777215L)) {
return null;
}
bytes[1] = (byte) (int) (l >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 3:
for (i = 0; i < 2; ++i) {
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L)) {
return null;
}
bytes[i] = (byte) (int) (l & 0xFF);
}
l = Integer.parseInt(elements[2]);
if ((l < 0L) || (l > 65535L)) {
return null;
}
bytes[2] = (byte) (int) (l >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 4:
for (i = 0; i < 4; ++i) {
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L)) {
return null;
}
bytes[i] = (byte) (int) (l & 0xFF);
}
break;
default:
return null;
}
} catch (NumberFormatException e) {
return null;
}
return bytes;
}
public static String getHostIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
}
return "127.0.0.1";
}
public static String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
}
return "未知";
}
}
獲取地址類
package com.p.h.utils;
import com.alibaba.fastjson.JSONObject;
import com.fish.common.constant.Constants;
import com.fish.common.utils.http.HttpUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 獲取地址類
*/
public class AddressUtils {
private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);
// IP地址查詢
public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";
// 未知地址
public static final String UNKNOWN = "XX XX";
public static String getRealAddressByIP(String ip) {
String address = UNKNOWN;
// 內(nèi)網(wǎng)不查詢
if (IpUtils.internalIp(ip)) {
return "內(nèi)網(wǎng)IP";
}
try {
String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK);
if (StringUtils.isEmpty(rspStr)) {
log.error("獲取地理位置異常 {}", ip);
return UNKNOWN;
}
JSONObject obj = JSONObject.parseObject(rspStr);
String region = obj.getString("pro");
String city = obj.getString("city");
return String.format("%s %s", region, city);
} catch (Exception e) {
log.error("獲取地理位置異常 {}", e);
}
return address;
}
}