导读:本期聚焦于巫师创作的《如何用Ruby实现简单的GRE隧道并完成封装解封装与路由表配置?》,敬请观看详情。GRE隧道常用来把不同网络的报文包装进另一个IP包里传输。直接用Ruby做一套轻量隧道,得先弄清封装时怎么加GRE头、怎么算校验和,解封装又如何从接收缓冲区剥离外层头并还原内包。路由表配置决定哪些流量进隧道,在Linux下可用ioctl或调用ip命令完成。本文给出一个可运行示例,比较纯Ruby socket实现与系统工具配合的差异,说明MTU设置和递归路由的坑,帮你在没有内核模块权限时快速搭建实验性点对点通道。

GRE(Generic Routing Encapsulation)是一种通用的报文隧道协议,可以把任意网络层协议的数据包封装进另一个IP包中传输。使用Ruby语言实现一套简单的GRE隧道,重点在于理解封装与解封装的报文结构,以及怎样通过路由表把特定流量引入隧道接口。Ruby虽然不像C那样能直接操作内核网络栈,但借助原始套接字(raw socket)和系统命令,依然可以搭建出可用的实验性隧道。

如何用Ruby实现简单的GRE隧道并完成封装解封装与路由表配置?

GRE报文封装的原理与Ruby实现

GRE头最少占用4个字节,结构包括标志位、协议类型等字段。最基础的GRE头格式为:前16位中的高3位为标志(C、K、S),其余为版本号(通常为0),接着的16位是协议类型,例如0x0800代表IPv4。在Ruby中我们可以用Array#pack把头部字段打包成二进制串,再把内层IP包接在后面,最后交给外层IP头封装发送。

下面的代码展示了如何用Ruby构造一个不带校验和的GRE头,并将内层的原始IP报文封装起来。这里使用Socket::IPPROTO_RAW创建原始套接字,由我们自己构造完整的IP头,因此外层源和目的地址都需要明确写出。

require 'socket'

def build_gre_header(protocol = 0x0800)
  # 标志位为0,版本0,协议类型为IPv4
  [0, protocol].pack('nn')
end

def build_ip_header(src, dst, payload)
  # 简化版IPv4头,20字节
  version_ihl = 0x45
  total_len = 20 + payload.bytesize
  [version_ihl, 0, total_len, 0, 0, 64, Socket::IPPROTO_RAW, 0,
   src.split('.').map(&:to_i), dst.split('.').map(&:to_i)].pack('CCnnnCCnCCCCCCCC')
end

src = '192.168.1.1'
dst = '192.168.2.1'
inner_packet = "x45x00x00x1cx00x01x00x00x40x01x00x00x0ax00x00x01x0ax00x00x02"
gre = build_gre_header
ip_outer = build_ip_header(src, dst, gre + inner_packet)

sock = Socket.open(Socket::AF_INET, Socket::SOCK_RAW, Socket::IPPROTO_RAW)
sock.send(ip_outer, 0, Socket.pack_sockaddr_in(0, dst))
sock.close

上面这段程序在真实环境里还需要处理校验和以及IP头中部分字段的字节序问题。Ruby的pack模板使用网络字节序(大端)比较方便,但如果运行在小型机上就要注意转换。此外,自己构造IP头时要把IPPROTO_RAW的发送权限考虑进去,普通用户通常需要root才能创建这类套接字。

从工程角度看,纯Ruby封装适合做原型验证,它的性能远低于内核态的GRE模块。每封装一个包都要经过Ruby解释器,吞吐量很难超过每秒几万包。如果你的场景只是打通两个测试网段,这种方式足够;若是生产环境,还是应当交给系统自带的GRE接口。

接收端与解封装处理逻辑

解封装是封装的逆过程:从原始套接字读取外层IP包,跳过外层IP头(通常20字节,若有选项则更多),再跳过GRE头(至少4字节),剩下的就是内层报文。Ruby里可以用String#unpack或者直接用偏移量切片拿到内部数据,然后决定是交给本地协议栈还是从另一块虚拟网卡转发出去。

下面示例展示了一个简单的接收循环,它监听IPPROTO_GRE类型的原始套接字,把收到的包去掉外层头后打印内层协议类型。注意不同系统对IPPROTO_GRE的定义可能不同,Linux下通常是47。

require 'socket'

begin
  sock = Socket.open(Socket::AF_INET, Socket::SOCK_RAW, 47)
rescue Errno::EPERM
  abort '需要root权限才能打开GRE原始套接字'
end

loop do
  pkt = sock.recv(65535)
  # 跳过外层IPv4头(假设无选项)
  outer_ihl = (pkt[0].ord & 0x0f) * 4
  gre_start = outer_ihl
  flags_ver, proto = pkt[gre_start, 4].unpack('nn')
  inner = pkt[gre_start + 4..-1]
  puts "收到GRE包,内层协议: 0x#{proto.to_s(16)},长度 #{inner.bytesize}"
end

解封装时最易踩的坑是分片。外层IP包如果在传输中被分片,接收端必须等所有分片到齐才能正确解析GRE头。Ruby的raw socket默认不会自动重组分片,你要么依靠内核的IP重组(某些系统会在递交给raw socket前重组),要么自己写分片缓存。实验环境下建议直接依靠内核重组,避免复杂逻辑。

另一个关键是内层包出来后怎么处理。如果是IPv4,你可以再用一个TUN设备写入,让系统路由去决策;也可以直接在Ruby里做应用层代理。前者更通用,但需要配合系统中的虚拟网卡;后者更轻量,但只适合特定协议。

路由表配置与隧道联通

隧道建起来后,必须让系统知道哪些流量该走隧道。在Linux上,最简单的方法是用ip route命令添加一条静态路由,把目标网段指向隧道接口。Ruby可以通过systemIO.popen调用这些命令,也可以解析/proc/net/route来读取现有表项。

以下代码演示了如何用Ruby调用ip命令添加路由,并设置一个较小的MTU以避免GRE开销导致分片。这里把到192.168.100.0/24的流量指向名为gre0的接口,MTU设为1400以留出外层头空间。

# 假设系统已创建gre0接口并配置本地/远端
system('ip link set gre0 up')
system('ip addr add 10.0.0.1/24 dev gre0')
system('ip route add 192.168.100.0/24 dev gre0')
system('ip link set gre0 mtu 1400')

# 读取当前路由表
routes = File.readlines('/proc/net/route')
routes.each do |line|
  next if line.start_with?('Iface')
  iface, dest = line.split[0, 2]
  puts "接口 #{iface} 目标 #{dest}"
end

在配置路由时要小心递归路由:如果隧道的远端地址本身也匹配了你添加的那条路由,包就会在隧道里套隧道,最终TTL耗尽。解决方法是为隧道外层通信单独写一条主机路由,明确走真实物理网卡。Ruby脚本里可以先查默认网关,再据此排除外层地址。

此外,Ruby实现的用户态隧道往往没有内核模块那样自动处理ARP、ICMP重定向等机制。你可能会发现某些应用层程序因为PMTU发现问题而连接缓慢。此时除了调小MTU,还可以在路由上加上advmss参数限制TCP最大报文段,减少中途分片概率。这样整套Ruby GRE隧道加路由方案就能在实验网络中稳定工作。

RubyGRE_tunnelroute_table修改时间:2026-08-16 19:30:19

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。