顯示IP歸屬地原理&Java實現IP歸屬地顯示功能

上個星期開始,微博、抖音、公衆號等多個平臺紛紛上線了 IP 歸屬地功能。

我想很多小夥伴會好奇互聯網平臺商們是怎麼通過 IP 定位到我們所屬地區的?這背後的原理是什麼?IP 歸屬地背後又有哪些實際的應用?

所以,今天我們就來聊聊 IP 歸屬地背後的技術原理

如何通過 IP 找到地址?

在我們印象中,我們都知道可以通過 IP 地址找到某個人。但當我們細想一下,我們會發現其實 IP 地址與地理位置並不是直接相關的。那我們到底是如何通過 IP 地址找到地址的呢?

答案是:通過 自治系統(Autonomous System)

互聯網是由不同網絡組成的網絡,自治系統是組成 Internet 的大型網絡,連接到 Internet 的每臺計算機或設備都連接到一個 AS。而每一個自治系統都會有一個編碼,我們稱之爲 ASN。


可以認爲 AS 類似於一個城鎮的郵局。

郵件從一個郵局到另一個郵局,直到到達正確的城鎮爲止,然後該城鎮的郵局將在該城鎮內傳遞郵件。每個 AS 都控制一組特定的 IP 地址,就像每個鎮的郵局負責將郵件傳遞到該鎮內的所有地址一樣。

通常,每個 AS 由單個大型組織(例如 Internet 服務提供商(ISP)、大型企業技術公司、大學或政府機構)運營。

到這裏,我們可以捋清楚這樣一個邏輯關係:IP地址 -> 地址塊 -> 自治網絡編碼(ASN) -> 組織 -> 國家

通過 IP 地址,我們就可以定位到一個大致的地理位置,例如:北京朝陽區、深圳南山區等。例如我現在的 IP 地址就歸屬於編碼爲 AS4xxx 這個自治網絡,通過這個 ASN 可以知道位置在中國深圳,這個 ASN 編碼所屬的組織爲 中國電信

但是通過 ASN 也只能是找到縣級或者區級的地理位置,再細的位置就找不到了。

但怎麼有些時候同學說:他被查水錶了,直接定位到某個單元某一戶呢?其實原理也很簡單!上面我們說到可以根據 IP 地址定位到 ASN 所屬組織,而 ASN 所屬組織在進行 IP 地址分配的時候,都是會進行 IP 地址分配記錄的。

某個 IP 地址分配給了誰,都記錄得一清二楚。因此警察叔叔想找你喝茶,那還不是一抓一個準。

但要提示一下的是,並不是誰都有那個權限去運營商查詢這些數據。所以那些說可以爬着網線找到你的人,基本上可以忽略,都是在嚇唬你。只有警察叔叔立案,並且出示相關手續之後纔可以進行數據查詢。

IP 地址的隱私問題

那是不是隻有運營商才能查到某個人的住址信息呢?

在大數據時代的今天,各種互聯網應用搜集了大量的數據信息,它們其實也可以根據這些信息,推斷出某個人的大致地址位置。

例如百度地圖會一直用 App SDK 以及網頁的方式記錄 IP 和地址位置的關聯,並允許反向查詢,也就是可以根據 IP 地址反向查詢到某個位置,這個數據精度可能精確到幾百米。

其實不止國內的公司會這麼做,其實國外的公司同樣也會這麼做,就比如 Google 也做了。

只是國外對這方面控制得非常嚴格,因此它們會比較明確地披露所使用的用戶隱私數據,並且還提供了對應功能可以讓用戶關閉。

有朋友說了,那我可以改變 IP,那是不是某些 App 就不知道我的精確位置了呀?其實並不是的,因爲你的鄰居可以出賣了你

某些 App 發現,鄰居周圍的 WIFI、藍牙等和你的非常像。而且當某個 WiFi 信號消失時,鄰居那邊的也同步消失了。那麼他們就可以猜測,你隱藏了自己的真實 IP,你的地理位置和鄰居的非常近。這就是大數據時代背景下的應用。

因此,當某些設備彈出提示「是否允許掃描本地設備」時,你就要謹慎選擇了

如果不是內網 NAS 或者投屏類的,基本上沒有必要允許這個操作,這個操作都是在盜取你的個人隱私信息。如果你允許了這個操作,那他就會開始掃描整個局域網的設備信息,然後記錄下來。最終,其會將你的 IP 地址、手機 IMEI、WiFi 等信息彙總起來,從而做一些商業化的信息。例如 ,你在電腦上搜了下房子這個關鍵詞,等會你刷手機抖音就會給你推送房地產廣告。

說白了,「查找並連接到本地網絡上的設備」的使用爲跨平臺廣告提供了方便,而從個人信息保護和隱私保護體驗的角度來講,這種對設備的監控、跟蹤可能會給用戶帶來擔憂。

接下來使用Java實現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地址轉換成字節
     *
     * @param text IPv4地址
     * @return byte 字節
     */
    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;
        // 內網不查詢
        if (IpUtils.internalIp(ip)) {
            return "內網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;
    }
}

.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章