You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Node.js net.connect()的TCP数据包设置标记以实现IP规则路由

在Node.js的net.connect()中为TCP连接设置SO_MARK标记

要解决Web代理流量源IP单一导致路由规则失效的问题,核心是利用Linux内核的SO_MARK套接字选项为不同目标的TCP连接打上标记,让VPN服务器的路由规则可以基于标记区分流量。Node.js的net模块本身没有直接暴露该API,但可以通过底层句柄结合系统调用实现。

前提条件

  • 你的VPN服务器运行在Linux系统上(SO_MARK是Linux特有的特性)
  • Node.js进程拥有足够权限:要么以root用户运行,要么给Node.js添加CAP_NET_ADMIN能力(执行setcap cap_net_admin+ep /path/to/node)

方案一:使用专用Socket库(推荐)

使用@socketsupply/socket库,它封装了底层socket系统调用,简化标记设置流程:

  1. 安装依赖
npm install @socketsupply/socket
  1. 实现带标记的连接函数
const net = require('net');
const { Socket: SupplySocket, SOL_SOCKET, SO_MARK } = require('@socketsupply/socket');

// 创建带SO_MARK标记的TCP连接
function connectWithMark(host, port, mark) {
  return new Promise((resolve, reject) => {
    const socket = net.connect(port, host, () => {
      // 获取底层socket的文件描述符
      const fd = socket._handle.fd;
      const supplySocket = new SupplySocket(fd);
      
      // 设置SO_MARK标记
      supplySocket.setOption(SOL_SOCKET, SO_MARK, mark);
      resolve(socket);
    });

    socket.on('error', reject);
  });
}

// 连接extern/target1,标记为1
connectWithMark('extern/target1', 80, 1)
  .then(socket => {
    socket.write('GET / HTTP/1.1\r\nHost: extern/target1\r\n\r\n');
    socket.on('data', data => console.log(data.toString()));
  })
  .catch(err => console.error('连接失败:', err));

// 连接extern/target2,标记为2
connectWithMark('extern/target2', 80, 2)
  .then(socket => {
    socket.write('GET / HTTP/1.1\r\nHost: extern/target2\r\n\r\n');
    socket.on('data', data => console.log(data.toString()));
  })
  .catch(err => console.error('连接失败:', err));

方案二:直接调用系统调用(底层实现)

如果不想引入第三方库,可以用ffi-napi直接调用Linux的setsockopt系统函数:

  1. 安装依赖
npm install ffi-napi ref-napi
  1. 实现带标记的连接函数
const net = require('net');
const ffi = require('ffi-napi');
const ref = require('ref-napi');

// Linux套接字常量
const SOL_SOCKET = 1;
const SO_MARK = 36; // 多数Linux内核版本中该值为36,可通过`grep SO_MARK /usr/include/linux/socket.h`确认

// 封装setsockopt系统调用
const libc = ffi.Library(null, {
  'setsockopt': ['int', ['int', 'int', 'int', ref.refType('int'), 'int']]
});

// 给指定fd的socket设置SO_MARK标记
function setSocketMark(fd, mark) {
  const markBuf = ref.alloc('int', mark);
  return libc.setsockopt(fd, SOL_SOCKET, SO_MARK, markBuf, ref.sizeof.int);
}

// 创建带SO_MARK标记的TCP连接
function connectWithMark(host, port, mark) {
  return new Promise((resolve, reject) => {
    const socket = net.connect(port, host, () => {
      const fd = socket._handle.fd;
      const result = setSocketMark(fd, mark);
      
      if (result !== 0) {
        reject(new Error('设置SO_MARK标记失败:权限不足或系统不支持'));
      } else {
        resolve(socket);
      }
    });

    socket.on('error', reject);
  });
}

// 使用示例同方案一
connectWithMark('extern/target1', 80, 1)
  .then(socket => { /* 处理连接 */ })
  .catch(err => console.error(err));

路由规则配置(VPN服务器端)

设置好标记后,需要在VPN服务器上配置基于标记的路由规则,示例:

# 为标记1的流量创建路由表100,并指向对应边缘路由器
ip rule add fwmark 1 table 100
ip route add default via <边缘路由器1的IP> dev <VPN接口> table 100

# 为标记2的流量创建路由表200,并指向对应边缘路由器
ip rule add fwmark 2 table 200
ip route add default via <边缘路由器2的IP> dev <VPN接口> table 200

注意事项

  • socket._handle是Node.js内部属性,不同版本可能存在兼容性差异,生产环境需测试目标Node.js版本。
  • 非Linux系统(如Windows、BSD)不支持SO_MARK,该方案仅适用于Linux环境。

内容的提问来源于stack exchange,提问作者Tom Freudenberg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 10:23:28