Module: Mnet

Defined in:
lib/mnet.rb,
lib/mnet/version.rb

Defined Under Namespace

Modules: SessionIO Classes: Endpoint, Frame, KcpSession, Packet, Server, Session

Constant Summary collapse

MAGIC =
"\x12\xE9\x97".b
MAGIC_LEN =
3
SESSION_ID_LEN =
16
IP_MTU =

Conservative MTU for mobile networks / VPN tunnels (mosh uses 1280).

1280
DEFAULT_MSS =

IP(20) + UDP(8) + our header + GCM tag(16) leaves ~1190 for a segment.

1200
GCM_OVERHEAD =

AES-256-GCM 每包的额外开销:12 字节 nonce + 16 字节认证标签。

28
MIN_RTO =

RTO bounds (mosh: MIN_RTO = 50ms, MAX_RTO = 1000ms).

0.05
MAX_RTO =
1.0
MAX_OLD_SOCKET_AGE =

How long to keep a superseded (hopped-away-from) socket alive to catch delayed packets (mosh: MAX_OLD_SOCKET_AGE = 60000ms).

60.0
MSG_DONTWAIT =

非阻塞发送标志:POSIX 有 MSG_DONTWAIT,Windows 没有(用 nonblock 模式兜底)。

Socket.const_defined?(:MSG_DONTWAIT) ? Socket::MSG_DONTWAIT : 0
TYPE_SYN =

Message types (mirrors the enum-message-types pattern from tmux-protocol.h).

0x01
TYPE_SYNACK =
0x02
TYPE_DATA =
0x03
TYPE_ACK =
0x04
TYPE_FIN =
0x05
TYPE_PING =
0x06
TYPE_PONG =
0x07
FLAG_NONE =
0x00
FLAG_MIGRATE =
0x01
FLAG_KCP =

session uses the KCP (C) engine instead of the Ruby ARQ

0x02
KCP_WND_RCV =

KCP 接收窗口(ikcp.c 内 IKCP_WND_RCV,固定 128)。单次 ikcp_send 超过 窗口大小的分段数会返回 -2 丢弃,故 KcpSession#write 按此切块。

128
HEADER_LEN =
MAGIC_LEN + SESSION_ID_LEN + 8 + 8 + 1 + 1 + 4
MAX_RETRIES =
10
TYPES =
{
  TYPE_SYN => "SYN", TYPE_SYNACK => "SYNACK", TYPE_DATA => "DATA",
  TYPE_ACK => "ACK", TYPE_FIN => "FIN", TYPE_PING => "PING",
  TYPE_PONG => "PONG"
}.freeze
VERSION =
'0.1.6'

Class Method Summary collapse

Class Method Details

.addr_pair(addr) ⇒ Object



100
101
102
# File 'lib/mnet.rb', line 100

def addr_pair(addr)
  [addr[3], addr[1]]
end

.fmt(addr) ⇒ Object



104
105
106
# File 'lib/mnet.rb', line 104

def fmt(addr)
  "#{addr[0]}:#{addr[1]}"
end

.kcp_mtu(key = nil) ⇒ Object

KCP 引擎的 MTU。每个 KCP 包会被整体包进 Mnet 头(HEADER_LEN)再作为单个 UDP 数据报发送,加密时还要再叠加 GCM 开销,因此 KCP 自身的 MTU 必须为这些 开销留出空间,保证整包 <= IP_MTU,避免 IP 分片(移动网络/VPN 下分片极易丢)。



121
122
123
# File 'lib/mnet.rb', line 121

def kcp_mtu(key = nil)
  IP_MTU - HEADER_LEN - (key ? GCM_OVERHEAD : 0)
end

.nowObject



108
109
110
# File 'lib/mnet.rb', line 108

def now
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
end

.pack(session_id, seq, ack, type, flags, window, payload = "".b) ⇒ Object



83
84
85
86
# File 'lib/mnet.rb', line 83

def pack(session_id, seq, ack, type, flags, window, payload = "".b)
  [MAGIC, session_id, seq, ack, type, flags, window, payload]
    .pack("a3a16Q>Q>CCNa*")
end

.session_id_from_key(key) ⇒ Object

Session id is derived from the shared session key (mosh-style), so the id identifies the key and vice versa; without a key it is just random.



114
115
116
# File 'lib/mnet.rb', line 114

def session_id_from_key(key)
  OpenSSL::Digest::SHA256.digest(key)[0, SESSION_ID_LEN]
end

.socket_pairObject

A connected stream socket pair, portable across Windows/Linux/macOS. Prefers a UNIX socketpair; falls back to a loopback TCP pair where AF_UNIX socketpair is unavailable (Windows).



128
129
130
131
132
133
134
135
136
137
# File 'lib/mnet.rb', line 128

def socket_pair
  Socket.pair(:UNIX, :STREAM, 0)
rescue SystemCallError, NotImplementedError
  server = TCPServer.new("127.0.0.1", 0)
  port   = server.addr[1]
  client = TCPSocket.new("127.0.0.1", port)
  accepted = server.accept
  server.close
  [accepted, client]
end

.type_name(type) ⇒ Object



96
97
98
# File 'lib/mnet.rb', line 96

def type_name(type)
  TYPES.fetch(type, "UNKNOWN(#{type})")
end

.unpack(data) ⇒ Object



88
89
90
91
92
93
94
# File 'lib/mnet.rb', line 88

def unpack(data)
  return nil if data.bytesize < HEADER_LEN
  magic, sid, seq, ack, type, flags, window = data.unpack("a3a16Q>Q>CCN")
  return nil unless magic == MAGIC
  Packet.new(sid, seq, ack, type, flags, window,
             data.byteslice(HEADER_LEN, data.bytesize - HEADER_LEN) || "".b)
end