WebRTC如何获取用户真实IP

没太深入细节,仅仅当作科普看看了。

原理简述

WebRTC参考:给好奇者的WebRTC,这个文档看前三节就可以理解为什么可以用WebRTC获取真实IP了。

WebRTC要实现的是P2P的传输,如果通信的两端在一个网络还好说,但我们目的肯定是要让世界上任意两个终端通信。

要让世界上不同内网的内网相互通信,用到的方法就是NAT。假如我要和远在伦敦的J先生通信的话,我就要拿到NAT为我这次通信提供的映射地址,这个映射地址是公网上的。然后在于J先生建立通信的时候,我把这个映射地址给他作为我的目标地址。

STUN协议就是配合NAT来实现这个过程的,它可以让用户拿到映射地址。这个过程如下:

  • 用户向STUN服务器发送一个 STUN Binding Request
  • STUN服务器回应一个 STUN Binding Response
  • STUN Binding Response将包含映射地址

为什么说这个IP会比较“真实”,是因为STUN协议用的是UDP,而大部分代理工具只代理了TCP,这就导致了真实IP的泄露。作为一个安全狗,拿到这个IP地址之后WebRTC对我就可以说是结束了,至于什么ICE、TURN、信令了解下就可以。

代码实现

方法实现参考:WebRTC疑似可以获取你的真实IP地址

获取IP的核心代码其实就下面一块:

// 定义STUN服务器的配置,这里使用了Google的公共STUN服务器
const iceServers = [
    { urls: 'stun:stun.l.google.com:19302' }
];

// 函数用于获取用户的公网IP地址
function getUserIPs(callback) {
    // 创建一个新的RTCPeerConnection实例,传入STUN服务器配置
    const myPeerConnection = new RTCPeerConnection({ iceServers });

    // 创建一个数据通道,这在此例中主要是为了触发ICE过程
    myPeerConnection.createDataChannel("");

    // 创建一个SDP offer,启动WebRTC协商过程
    myPeerConnection.createOffer().then(offer => 
        // 将生成的offer设置为本地描述
        myPeerConnection.setLocalDescription(offer)
    );

    // 当发现新的ICE候选时触发此事件处理函数
    myPeerConnection.onicecandidate = function(event) {
        // 检查是否存在候选信息
        if (event.candidate) {
            // 从ICE候选信息中提取IP地址
            // ICE候选信息格式通常是 "candidate:<id> <generation> <transport> <priority> <ip> <port> typ <type> ..."
            const parts = event.candidate.candidate.split(' ');
            const ip = parts[4]; // IP地址通常位于分割后的第五个元素

            // 使用回调函数返回找到的IP地址
            callback(ip);
        }
    };
}

完整代码
index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Get IP Addresses</title>
    <link rel="stylesheet" href="https://unpkg.com/element-ui@2.15.6/lib/theme-chalk/index.css">
    <script src="https://unpkg.com/vue@2.6.14"></script>
    <script src="https://unpkg.com/element-ui@2.15.6/lib/index.js"></script>
</head>
<body>
    <div id="app">
        <el-container>
            <el-header>
                <h1>Your IP Addresses:</h1>
            </el-header>
            <el-main>
                <el-table :data="ipAddresses" style="width: 100%" border>
                    <el-table-column prop="type" label="Type" width="180"></el-table-column>
                    <el-table-column prop="address" label="IP Address"></el-table-column>
                </el-table>
            </el-main>
        </el-container>
    </div>

    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    ipAddresses: [
                        { type: 'XFF or CDN IP Address', address: 'Loading...' },
                        { type: 'WebRTC Local IP Address', address: 'Loading...' },
                        { type: 'WebRTC IPv4 Address', address: 'Loading...' },
                        { type: 'WebRTC IPv6 Address', address: 'Loading...' }
                    ]
                };
            },
            mounted() {
                // Fetch XFF or CDN IP address
                const xhr = new XMLHttpRequest();
                xhr.open('GET', './get_real_ip.php', true);
                xhr.onreadystatechange = () => {
                    if (xhr.readyState === 4 && xhr.status === 200) {
                        const jsonResponse = JSON.parse(xhr.responseText);
                        this.ipAddresses[0].address = jsonResponse.ip;
                    }
                };
                xhr.send();

                // WebRTC IPs
                const iceServers = [
                    { urls: 'stun:stun.l.google.com:19302' },
                    { urls: 'stun:stun1.l.google.com:19302' },
                    { urls: 'stun:stun2.l.google.com:19302' },
                    { urls: 'stun:stun3.l.google.com:19302' },
                    { urls: 'stun:stun4.l.google.com:19302' },
                ];
                // getUserIPs function
                function getUserIPs(callback) {
                    const myPeerConnection = new RTCPeerConnection({ iceServers });
                    myPeerConnection.createDataChannel("");
                    myPeerConnection.createOffer().then(offer => myPeerConnection.setLocalDescription(offer));
        
                    myPeerConnection.onicecandidate = function(event) {
                        if (event.candidate) {
                            const parts = event.candidate.candidate.split(' ');
                            const ip = parts[4];
                            callback(ip);
                        }
                    };
                }

                getUserIPs((ip) => {
                    const ipv4Regex = /^(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]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
                    const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}(([0-9a-fA-F]{1,4}:){1,4}|((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
                    if (ipv4Regex.test(ip)) {
                        this.ipAddresses[2].address = ip;
                    } else if (ipv6Regex.test(ip)) {
                        this.ipAddresses[3].address = ip;
                    } else {
                        this.ipAddresses[1].address = ip;
                    }
                });
            }
        });
    </script>
</body>
</html>

get_real_ip.php

<?php
function getUserIP() {
    $ip = '';
    if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    } else {
        $ip = $_SERVER['REMOTE_ADDR'];
    }
    return $ip;
}

header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');

$ip = getUserIP();
echo json_encode(['ip' => $ip]);
?>

效果

参考链接

https://zhuanlan.zhihu.com/p/623495107?utm_id=0
https://webrtcforthecurious.com/zh/
https://scz.617.cn/web/202304131224.txt
https://michaelyou.github.io/2018/08/01/%E7%9C%9F%E5%AE%9E%E4%B8%96%E7%95%8C%E4%B8%AD%E7%9A%84WebRTC/