MDD Sim Gateway v1.4.0
新增
可以拨运营商服务码
*21*<号码>#(设置转移)、*#21#(查询转移)、#225#(查余额)这类代码现在能正常拨出。此前有三层各自独立地把它们拦掉:浏览器把输入判定为「短号码或 E.164 号码」二选一、JsSIP 拒绝构造含 # 的 SIP URI、拨号方案的号码模式只认 + 或数字开头。三层现在都原样放行,传输时转义 #、送出前还原,到达网络的就是你拨的。
*#06# 这类手机本机应答的代码,直接由本线路的配置作答,不再发往网络。服务码在蜂窝模块通道上会被拒绝而不是当语音呼叫拨出——那条路要走 AT+CUSD,本网关没有实现。
结果也不再套用通话的词汇。以前显示的是「已拒接」「无人接听」、还跑着通话时长,没有一个能描述「一秒内应答并挂断」的请求,反而掩盖了唯一重要的信息:这家运营商到底支不支持这个码。现在按 Q.850 原因值区分:未知代码(404)、格式错误(484)、未实现(501/488)读作「不支持」;403/603 读作「被拒绝」——那是另一个问题、另一种解法,请求到达了运营商,是账户策略拒的。没有任何响应就如实报「无响应」,不替它下结论。
服务码会显示运营商的真实回复
#225# 的答复不是语音,而是放在已建立对话内的 SIP 请求体里(T-Mobile US 放在 BYE 上)——这正是这种「通话」无声且不到一秒的原因。以前这个 body 被丢弃,所以只能确认运营商响应了,不知道它说了什么。
现在引擎把 application/vnd.3gpp.ussd+xml 载荷取出,控制面按 3GPP TS 24.390 解析,余额或转移状态就显示在你拨号的地方。两端都有边界:引擎在拷贝到栈上之前拒绝大于 4KB 的 body,解析器对存储文本长度封顶;不可解码的内容记录后忽略而非入库。
运营商的机器短信不再以乱码混进会话
不是每条短信都是给人看的:运营商和服务方也发机器载荷——SIM 卡数据下载、应用静默推送——内容是任意字节而非字符。PDU 头里写明了这一点,但 Asterisk 把 8 位数据按每字节一个字符展开成字符串,于是它们混进消息列表,看着像编码坏掉的短信,每条还弹一次通知。
现在引擎上报 TP-PID、TP-DCS、用户数据头和原始 PDU,控制面把 8 位编码、发往 SIM(class 2)、或标记为 SIM 数据下载的内容归档到独立存储。已在数据库里的载荷会在启动时迁移过去,没有丢弃任何东西,字节原样保留——识别一个加密载荷只能靠它到达时的 PDU,而不是对它的解码。
短信页新增可折叠的「非文本载荷」区,列出归档内容、归档原因和原始字节。这一步是必要的:判定读的是 PDU 头,而运营商可能给一条真实短信标错编码方案,那样它会被永久隐藏且无从察觉。
修复
-
线路可能对着一条已死的出口连接无限重传。 出口被归咎但无法更换时(strikes 不够、节点池耗尽、或节点被固定),保持不动对节点选择是对的,却留下一个本可恢复的故障没人管。sing-box 按 5 元组标识 UDP 会话、用空闲计时器回收,而重建隧道的线路每次 IKE 重传都会刷新这个计时器——出站已死的会话,恰恰被本该恢复它的重传保持着。重建容器也没用,它产生相同的 5 元组、落到同一条死会话上。现在控制面在归咎出口又拒绝更换时会指明国家,编排器关掉该国家的连接,下一个包只能重新拨。承载着已注册兄弟线路的出口绝不会被动。
-
极短的通话可能永远停在「拨号中」。 拨号方案用后台进程分别上报通话的开始和结果,两者没有顺序保证。通话不到一秒结束时,结果可能早于它要关闭的那条记录到达,然后被静默丢弃。服务码在 BYE 上被应答正是这种情况,问题因此暴露,但这个竞态从来不是服务码特有的,对任何短通话都潜伏着。
-
以
#开头的服务码不产生任何通话记录。 拨号方案经由 shell 上报通话,而词首的未加引号#会开启注释:拨#225#因此丢掉了号码和其后所有参数,记录根本没有创建——不是状态不对,是完全不存在。*#21#因为#在词中间而幸免,这正是故障看起来毫无规律的原因。 -
普通 USB 读卡器里的 SIM 可能报 NO_CARD,而隧道明明已经建立(issue #8)。模块桥把一张 SIM 呈现为三个逻辑槽位,普通读卡器只有一个槽位。但引擎契约仍用模块的布局去填未设置的槽位号,把 IMS-AKA 发到了槽位 2——单读卡器机器上并不存在,于是 SIM 读作「不存在」,而同一张卡在隧道那边应答完美。现在每个角色都跟随本线路实际绑定的读卡器。两个及以上读卡器的机器不受影响。
-
读取线路号码不再危及刚建立的连接(issue #8)。号码此前是靠开启 SIP 抓包、再额外发一次 REGISTER 制造出可读响应来获得的,而某些运营商 IMS 核心网会在刚接受注册几秒后拒绝这次主动的重新注册,返回 503。Asterisk 把它报成注册被拒,健康策略据此把线路拆掉——一条健康的连接,被「询问它自己的号码」这个动作杀掉了。运营商在本来就会做的那次注册里已通过
P-Associated-URI告知了号码,现在直接从那里读,不再额外发送任何东西,也不再打开会把认证头写进日志的 SIP 抓包。
升级说明
需要重建引擎镜像。沿用旧引擎则:服务码回复不显示、二进制短信按内容判定会有漏网、号码学不了新的(手动填写 MSISDN 可覆盖)。
Added
-
The dialler now accepts carrier service codes such as
*21*<number>#(divert),*#21#
(check divert) and#225#(balance). Three separate layers rejected them before, each
looking like the last: the browser validated the input as either a short numeric code or an
E.164 number, JsSIP refused to build a request URI because#is not a legal user character
in a SIP URI, and the dialplan's outgoing extension pattern matched only+or a digit in
first position. All three now pass the code through untouched, escaping#for transport and
restoring it on the way out, so what reaches the network is what was dialled. What a code
then means is the carrier's IMS to decide, not this gateway's: supplementary-service codes
are the ones a TAS normally answers, whereas USSD codes need a USSI gateway the carrier may
no longer operate, and a carrier that has moved self-service into an app may simply decline.
Codes a handset answers by itself, currently*#06#, are answered from the line's own
provisioning instead of being dialled, since no network ever replies to those. Service codes
are refused on the cellular-modem transport rather than dialled as a voice call, which is all
that path can do; reaching them through a modem would needAT+CUSD, which is not built. -
A dialled service code now reports whether the carrier served it. Because the code travels
as a call, the outcome used to arrive on a call's vocabulary — "declined", "no answer", a
running duration timer — none of which describe a request that is answered and torn down in
the same second, and all of which hide the one thing worth knowing: whether this carrier
supports the code at all. The Q.850 cause already reaching the control plane distinguishes
the cases, so codes are now scored on their own scale. An unknown code (404 Not Found),
a malformed one (484) and an unimplemented service (501/488) read as not supported; a 403
or 603 reads as refused, which is a different problem with a different fix — the request
reached the carrier and was declined by account policy rather than missing from the network.
Silence stays "no response" instead of being reported as unsupported, since nothing came
back to justify that claim. -
A service code now shows what the carrier actually replied. The answer to
#225#or*#21#
is not audio: the carrier puts it in the body of a SIP request inside the established dialog
— T-Mobile US uses the BYE — which is why such a "call" is silent and over in under a second.
That body was discarded, so an accepted code could confirm only that the carrier acted, never
what it said. The engine now copies theapplication/vnd.3gpp.ussd+xmlpayload onto the
channel and the control plane parses it (3GPP TS 24.390), storing the text with the call so
the balance or divert status appears where the code was dialled. The payload is bounded on
both sides — the engine refuses a body larger than 4 KB before copying it onto the stack, and
the parser caps the text it will store — and a body that is not decodable text is logged and
ignored rather than stored. Carriers that namespace the XML differently are handled; one that
returns no payload at all still reports the outcome as before.
Fixed
-
A line could retransmit forever against an exit connection that had already died. When the
exit is blamed but cannot be moved — strikes still short, pool exhausted, or the node pinned
— holding is the right call for node selection, but it used to leave one recoverable failure
unattended. sing-box keys a UDP session on its 5-tuple and retires it on an idle timer, and a
line rebuilding its tunnel refreshes that timer with every IKE retransmit: a session whose
outbound is dead is held open by the very retries meant to recover it, every later packet
goes to the same dead connection, no new session is ever created, and nothing is logged.
Rebuilding the container does not help, because it produces the same 5-tuple and lands on the
same dead session. Seen after a self-update restarted the orchestrator: two lines sharing the
only GB exit dialled before the route was up, got "no route to host", and stayed stuck for
twenty minutes while their tunnels reported CONNECTING and the exit itself tested fine. The
control plane now names the country when it blames an exit and declines to move it, and the
orchestrator closes that country's connections so the next packet has to dial afresh. This is
deliberately the weaker sibling of switching nodes: it changes no node, respects a pin, and
is gated on the same check, so an exit carrying a registered sibling line is never touched. -
A service code beginning with
#produced no call-log entry at all. The dialplan reports a
call through a shell, where an unquoted#at the start of a word opens a comment: dialling
#225#therefore discarded the number and every argument after it, and the record was never
created — not stuck in a wrong state, simply absent.*#21#was unaffected, its#falling
mid-word, which is why the failure looked arbitrary. The argument is now quoted. -
A very short call could stay on "dialing" in the call log forever. The dialplan reports a
call's start and its outcome from separate backgrounded processes, so nothing orders the two:
when a call ends in under a second, the outcome can reach the manager before the record it is
meant to close, and it was then dropped silently — the call never left "dialing" even though
it had completed. A dialled service code answered on the BYE does exactly that, which is how
this surfaced, but the race was never specific to service codes and had been latent for any
short call. The outcome now waits briefly for the record it belongs to instead of being
discarded. -
A SIM in an ordinary USB smart-card reader could report NO_CARD with the tunnel already up
(issue #8). A modem bridge presents one SIM on three logical slots so PIN keeping, tunnel
authentication and IMS-AKA can work independently; an ordinary reader has a single slot and
does all three through it. The engine contract nevertheless filled the unset slot numbers with
the modem layout, sending IMS-AKA to slot 2 — which on a one-reader gateway does not exist, so
the SIM read as absent while the same card answered the tunnel perfectly. Each role now
follows the reader the line is actually bound to, and the engine falls back to the only reader
present rather than refusing a slot number that names nothing. A modem line keeps its
dedicated channels. Gateways with two or more readers were unaffected: the stable USB-port
binding already resolved the right one there. -
Learning a line's phone number no longer risks the connection it just made (issue #8). WiFi
Calling came up, held for a few seconds and was then torn down and re-established, with the
carrier answering "503 Service Unavailable" — the number was learned by enabling SIP tracing
and sending an extra REGISTER purely to produce a response that could be read, and some
carrier IMS cores decline an unsolicited re-registration seconds after accepting one. Asterisk
reports that as a rejected registration, and the health policy acts on rejections. The carrier
already announces the number in the registration the line makes anyway, so the engine now
records it from that response and the control plane reads it from the log: nothing extra is
sent, and SIP tracing — which also writes authentication headers into the container log — is
no longer switched on to ask a line for its own number. The same applies to the six-hourly
ported-number check. Every public identity the carrier lists is recorded, so a network that
puts an IMSI-derived identity ahead of the dialable number is read correctly. Requires a
rebuilt engine image; an older engine keeps a number already learned but cannot learn a new
one, which a manually entered number covers. -
Binary SMS no longer appear in your conversations as walls of mojibake. Not every message is
meant for a person: carriers and services also send machine payloads — SIM data-download,
silent app pushes — whose content is arbitrary bytes rather than characters. The PDU says so
in its header, but Asterisk unpacks 8-bit data one byte per character and hands back a
string, so these landed in the message list looking like a text with a broken encoding, and
raised a notification each time. The engine now reports the message's TP-PID, TP-DCS, user
data header and raw PDU, and the control plane files anything that is 8-bit, addressed to the
SIM (message class 2) or marked as SIM data-download into a separate store instead of showing
it. Payloads already in the database are moved there on startup — nothing is discarded, and
the bytes are kept verbatim, since identifying an encrypted payload needs the PDU as it
arrived rather than a decode of it. Messages gains a collapsed "Non-text payloads" section
listing what was filed, with the reason it was filed and the raw bytes: the classification
reads the PDU header, so a carrier that mislabels a real text's data-coding scheme would
otherwise hide it for good with no way to notice. A rebuilt engine image is what supplies the
header fields; until then the control plane falls back to recognising a payload by its
content, which catches most but not all of them.