JS/node获取本机ip的方法

13,743 阅读4分钟

公众号:暮北林
Q Q 群 : 一起学前端
Javascript/node如何获取本机IP

[TOC]

为什么要获取本机IP

  1. 以前做的一个Electron的客户端启动Server需要把本机IP显示出来
  2. 一个基于RK3399的硬件平台的APP进行串口通信另一方需要进行IP校验

如何获取

我们知道JS是客户端语言,单纯的JS是无法获取本机IP的,即使有navigator也只能获取网络的连接类型和监听网络的变化,而无法得知IP信息

window.navagator.connection;
{
  downlink: 2.95
  downlinkMax: Infinity
  effectiveType: "4g"
  onchange: null
  ontypechange: null
  rtt: 50
  saveData: false
  type: "wifi"
}

虽然BOM的API无法获取我们可以采取其他方式

JS获取本机IP

  1. 通过接口让Server端告知我们的client ip
  2. 通过WebRTC来获取本机IP

JS通过接口获取

我们可以发送一个Request,在Server端就可以得知我们的IP,连接外网的情况外网Server得知的就是外网IP,只有内网的情况下Server得知的就是我们的内网IP,总之一句话通过接口获取的IP都是相对IP

const express = require("express");
const app = express();

app.set('trust proxy', true);
app.get("/api/get_pubip", (req, res) => {
    const { ip } = req;
    res.send({code: 0, data: {ip}, msg: "success"});
})
app.listen(3002);
  • 经过测试在没有经过代理的情况下本机localhost访问返回数据为
// http://localhost:3002/api/get_pubip
{
  "code": 0,
  "data": {"ip": "::1"},
  "msg": "success"
}
  • 经过nginx代理本机访问的返回数据
// http://localhost:8080/api/get_pubip
{
  "code": 0,
  "data": {"ip": "::ffff:127.0.0.1"},
  "msg": "success"
}
  • 其他机器(192.168.0.110)访问返回数据
// http://l192.168.0.108/api/get_pubip
{
  "code": 0,
  "data": {"ip": "::ffff:192.168.0.110"},
  "msg": "success"
}

WebRTC获取本机IP

WebRTC进行音视频通话时是需要进行媒体协商和网络协商的,在网络协商的过程中就需要知道双方的内外网IP地址,网络协商是需要借助IceServer的帮助的,协商通过后就可以进行后续的UPD通信进行音视频数据的packet发送

function getIPs(callback){
    var ip_dups = {};
    var RTCPeerConnection = window.RTCPeerConnection
        || window.mozRTCPeerConnection
        || window.webkitRTCPeerConnection;
    var useWebKit = !!window.webkitRTCPeerConnection;
    var mediaConstraints = {
        optional: [{RtpDataChannels: true}]
    };
    // 这里就是需要的ICEServer了
    var servers = {
        iceServers: [
            {urls: "stun:stun.services.mozilla.com"}, 
            {urls: "stun:stun.l.google.com:19302"},
        ]
    };
    var pc = new RTCPeerConnection(servers, mediaConstraints);
    function handleCandidate(candidate){
        var ip_regex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/
        var hasIp = ip_regex.exec(candidate)
        if (hasIp) {
            var ip_addr = ip_regex.exec(candidate)[1];
            if(ip_dups[ip_addr] === undefined)
                callback(ip_addr);
            ip_dups[ip_addr] = true;
        }
    }
    // 网络协商的过程
    pc.onicecandidate = function(ice){
        if(ice.candidate) {
            handleCandidate(ice.candidate.candidate);
        }   
    };
    pc.createDataChannel("");
    //创建一个SDP(session description protocol)会话描述协议 是一个纯文本信息 包含了媒体和网络协商的信息
    pc.createOffer(function(result){
      pc.setLocalDescription(result, function(){}, function(){});
    }, function(){});
    setTimeout(function(){
        var lines = pc.localDescription.sdp.split('\n');
        lines.forEach(function(line){
            if(line.indexOf('a=candidate:') === 0)
                handleCandidate(line);
        });
    }, 1000);
}
getIPs(function(ip){console.log(ip);});

Webrtc得到的IP结果和开源API获取的结果对比

// WebRTC获取的结果 122.4.121.156
// 开源API获取到的IP地址
{
  "ip": "122.4.121.156",
  "city": 1569,
  "region": "中国|0|山东省|青岛市|电信"
}

其实Webrtc在媒体协商时获取的IP不止一个,总共有四种类型host srflx prflx relay

candidate四种类型说明

候选类型code来源传输用途
主机候选项host网卡信令服务从网卡中获取本地传输地址
服务器反射候选项srflxSTUN信令服务从发给stun服务的stun检查中获取到的传输地址
对象反射候选项prflxICE代理STUN Binding从对方ice代理发送stun连接检查中获取的传输地址
中继候选者relayTURN信令服务器媒体中继服务器的传输地址

你会发现Webrtc是无法获取到本机IP

host获取的一直是xxxxx.local的东东为什么呢? 请参考文末的mdns文档

接下来看看用node如何获取本机ip

    1. 通过os模块来获取
    1. 通过广播来获取

OS模块获取本机IP

let netDict = os.networkInterfaces();
for (const devName in netDict) {
    let netList = netDict[devName];
    for (var i = 0; i < netList.length; i++) {
        let { address, family, internal,mac } = netList[i],
        let isvm = isVmNetwork(mac);
        if (family === 'IPv4' && address !== '127.0.0.1' && !internal && !isvm) {
            return address;
        }
    }
}

以上方法确实获取本机IP 但是假如你本机装了一个虚拟机获取的ip可能就是你虚拟机的IP了 或者你通过VPN访问网络获取到的有可能是你的VPN分配的IP,所以我们需要根据MAC地址来判断是否是虚拟机/VPN

// 增加一个判断VM虚拟机的方法  
// 在上面方法的if中加上这个方法的返回判断就行了
function isVmNetwork (mac) {
    // 常见的虚拟网卡MAC地址和厂商
    let vmNetwork = [
        "00:05:69", //vmware1
        "00:0C:29", //vmware2
        "00:50:56", //vmware3
        "00:1C:42", //parallels1
        "00:03:FF", //microsoft virtual pc
        "00:0F:4B", //virtual iron 4
        "00:16:3E", //red hat xen , oracle vm , xen source, novell xen
        "08:00:27", //virtualbox
        "00:00:00", // VPN
    ]
    for (let i = 0; i < vmNetwork.length; i++) {
        let mac_per = vmNetwork[i];
        if (mac.startsWith(mac_per)) {
            return true
        }
    }
    return false;
}

广播获取本机IP

开启一个广播服务端监听message并发送广播,就可以接受到本机发送的广播,从而获取到发送端(本机)的address信息

const dgram = require("dgram");
const socket = dgram.createSocket("udp4");

const getLocalIp = () => {
    return new Promise((resolve, reject) => {
        socket.on("error", err => {
            reject(err);
            socket.close();
        })
        
        socket.on("message", (msg, rinfo) => {
            let { address, port } = rinfo;
            resolve(address);
        })
        
        socket.bind(19319, _=> {
            socket.setBroadcast(true);
        });
        var message = new Buffer.from("hello");
        socket.send(message, 0, message.length,  19319, "255.255.255.255", (err, bytes)=> {
            if (err) {
                reject(err);
                return
            }
        })
    })
}
getLocalIp().then(res => {
    console.log(res);
    // 192.168.0.108  这里打印的结果和ifconf得到的结果一致
})

你还知道哪些获取IP的方法欢迎留言或进群讨论

参考文档

CSDN
WebRTC cancidate文档
关于虚拟机MAC
mdns
mdns