mirror of
https://github.com/MengMengCode/VoCat.git
synced 2026-08-17 05:13:43 +08:00
fix: deliver Vodafone UK MT SMS over WiFi Calling (#39)
- Read ipsec-3gpp UDP server port even when REGISTER used TCP - Advertise Allow MESSAGE and smsip Accept-Contact for all carriers - Do not require P-CSCF port-c (50601) on inbound UE-server XFRM - Log rejected inbound TCP/UDP sources Verified on VOXI/Vodafone UK 23415 with Quectel EC25. Closes #38 Co-authored-by: wl77vv <[email protected]>
This commit is contained in:
@@ -946,7 +946,7 @@ func (session *Session) buildRegister(
|
||||
}
|
||||
o2Germany := usesO2GermanyIMSProfile(session.request.Identity)
|
||||
supported := "path, gruu"
|
||||
allow := "REGISTER, INVITE, ACK, CANCEL, BYE, OPTIONS"
|
||||
allow := "REGISTER, INVITE, ACK, CANCEL, BYE, OPTIONS, MESSAGE, SUBSCRIBE, NOTIFY"
|
||||
if o2Germany {
|
||||
// Match the complete IMS capability set used by the previously working
|
||||
// VoHive client. O2 validates more of the initial UE security profile
|
||||
@@ -987,6 +987,12 @@ func (session *Session) buildRegister(
|
||||
"Accept-Contact: *;+g.3gpp.smsip",
|
||||
`Accept-Contact: *;+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel"`,
|
||||
)
|
||||
} else {
|
||||
lines = append(lines,
|
||||
"P-Access-Network-Info: IEEE-802.11;i-wlan-node-id=000000000000;network-provided",
|
||||
"Accept-Contact: *;+g.3gpp.smsip",
|
||||
`Accept-Contact: *;+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel"`,
|
||||
)
|
||||
}
|
||||
if session.securityOffered() {
|
||||
lines = append(lines,
|
||||
|
||||
@@ -534,25 +534,7 @@ func buildXFRMInstallPlan(config IPSecSAConfig) ([]xfrmOperation, error) {
|
||||
for _, protocol := range flow.protocols {
|
||||
operations = append(operations, xfrmOperation{
|
||||
description: flow.description + " " + protocol + " policy",
|
||||
arguments: []string{
|
||||
flow.family,
|
||||
"xfrm", "policy", "add",
|
||||
"src", flow.sourcePrefix,
|
||||
"dst", flow.destinationPrefix,
|
||||
"proto", protocol,
|
||||
"sport", strconv.Itoa(flow.sourcePort),
|
||||
"dport", strconv.Itoa(flow.destinationPort),
|
||||
"dir", flow.direction,
|
||||
"priority", "100",
|
||||
"tmpl",
|
||||
"src", flow.templateSource.String(),
|
||||
"dst", flow.templateDestination.String(),
|
||||
"proto", "esp",
|
||||
"spi", fmt.Sprintf("0x%08x", flow.spi),
|
||||
"reqid", strconv.FormatUint(uint64(flow.reqid), 10),
|
||||
"mode", "transport",
|
||||
"level", "required",
|
||||
},
|
||||
arguments: xfrmPolicyArgs(flow, protocol, false),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -568,16 +550,7 @@ func buildXFRMCleanupPlan(config IPSecSAConfig) []xfrmOperation {
|
||||
protocol := flow.protocols[protocolIndex]
|
||||
operations = append(operations, xfrmOperation{
|
||||
description: "delete " + flow.description + " " + protocol + " policy",
|
||||
arguments: []string{
|
||||
flow.family,
|
||||
"xfrm", "policy", "delete",
|
||||
"src", flow.sourcePrefix,
|
||||
"dst", flow.destinationPrefix,
|
||||
"proto", protocol,
|
||||
"sport", strconv.Itoa(flow.sourcePort),
|
||||
"dport", strconv.Itoa(flow.destinationPort),
|
||||
"dir", flow.direction,
|
||||
},
|
||||
arguments: xfrmPolicyArgs(flow, protocol, true),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -606,6 +579,45 @@ func buildXFRMCleanupPlan(config IPSecSAConfig) []xfrmOperation {
|
||||
return operations
|
||||
}
|
||||
|
||||
|
||||
func xfrmPolicyArgs(flow xfrmFlow, protocol string, delete bool) []string {
|
||||
args := []string{
|
||||
flow.family,
|
||||
"xfrm", "policy",
|
||||
}
|
||||
if delete {
|
||||
args = append(args, "delete")
|
||||
} else {
|
||||
args = append(args, "add")
|
||||
}
|
||||
args = append(args,
|
||||
"src", flow.sourcePrefix,
|
||||
"dst", flow.destinationPrefix,
|
||||
"proto", protocol,
|
||||
)
|
||||
if flow.sourcePort > 0 {
|
||||
args = append(args, "sport", strconv.Itoa(flow.sourcePort))
|
||||
}
|
||||
if flow.destinationPort > 0 {
|
||||
args = append(args, "dport", strconv.Itoa(flow.destinationPort))
|
||||
}
|
||||
args = append(args, "dir", flow.direction)
|
||||
if delete {
|
||||
return args
|
||||
}
|
||||
return append(args,
|
||||
"priority", "100",
|
||||
"tmpl",
|
||||
"src", flow.templateSource.String(),
|
||||
"dst", flow.templateDestination.String(),
|
||||
"proto", "esp",
|
||||
"spi", fmt.Sprintf("0x%08x", flow.spi),
|
||||
"reqid", strconv.FormatUint(uint64(flow.reqid), 10),
|
||||
"mode", "transport",
|
||||
"level", "required",
|
||||
)
|
||||
}
|
||||
|
||||
type xfrmFlow struct {
|
||||
description string
|
||||
family string
|
||||
@@ -650,7 +662,7 @@ func xfrmFlows(config IPSecSAConfig) []xfrmFlow {
|
||||
{
|
||||
description: "P-CSCF-client to UE-server", family: family,
|
||||
sourcePrefix: remotePrefix, destinationPrefix: localPrefix,
|
||||
sourcePort: config.PCSCFClientPort, destinationPort: config.UEServerPort,
|
||||
sourcePort: 0, destinationPort: config.UEServerPort,
|
||||
direction: "in", templateSource: config.RemoteIP, templateDestination: config.LocalIP,
|
||||
spi: config.UEServerSPI, reqid: serverPairReqID(config),
|
||||
protocols: []string{"tcp", "udp"},
|
||||
@@ -916,9 +928,7 @@ func (session *Session) validProtectedUDPSource(remote *net.UDPAddr) bool {
|
||||
return false
|
||||
}
|
||||
expectedIP := addressIP(session.conn.RemoteAddr())
|
||||
return expectedIP != nil &&
|
||||
expectedIP.Equal(remote.IP) &&
|
||||
remote.Port == session.securityAgreement.selected.portClient
|
||||
return expectedIP != nil && expectedIP.Equal(remote.IP)
|
||||
}
|
||||
|
||||
func (session *Session) effectiveSecurityMode() string {
|
||||
|
||||
@@ -91,11 +91,14 @@ func (session *Session) startRuntimeReceivers() error {
|
||||
|
||||
session.receiveDone.Add(1)
|
||||
go session.readMainConnection()
|
||||
if session.securityActive && session.transport == "tcp" && session.protectedTCP != nil {
|
||||
// Vodafone UK (and others) deliver MT SMS as SIP MESSAGE to the
|
||||
// ipsec-3gpp UE server port over UDP even when REGISTER used TCP.
|
||||
// Always read both sockets when they were reserved.
|
||||
if session.securityActive && session.protectedTCP != nil {
|
||||
session.receiveDone.Add(1)
|
||||
go session.acceptProtectedTCP()
|
||||
}
|
||||
if session.securityActive && session.transport == "udp" && session.protectedUDP != nil {
|
||||
if session.securityActive && session.protectedUDP != nil {
|
||||
session.receiveDone.Add(1)
|
||||
go session.readProtectedUDP()
|
||||
}
|
||||
@@ -140,6 +143,8 @@ func (session *Session) acceptProtectedTCP() {
|
||||
return
|
||||
}
|
||||
if !session.validProtectedTCPSource(connection.RemoteAddr()) {
|
||||
session.logInboundSMS(slog.LevelWarn, "IMS inbound TCP rejected", nil,
|
||||
"stage", "source_filter", "remote", connection.RemoteAddr().String())
|
||||
_ = connection.Close()
|
||||
continue
|
||||
}
|
||||
@@ -186,6 +191,8 @@ func (session *Session) readProtectedUDP() {
|
||||
return
|
||||
}
|
||||
if !session.validProtectedUDPSource(remote) {
|
||||
session.logInboundSMS(slog.LevelWarn, "IMS inbound UDP rejected", nil,
|
||||
"stage", "source_filter", "remote", remote.String(), "packet_bytes", count)
|
||||
continue
|
||||
}
|
||||
packet, err := parseSIPPacket(buffer[:count])
|
||||
@@ -208,8 +215,9 @@ func (session *Session) validProtectedTCPSource(address net.Addr) bool {
|
||||
return false
|
||||
}
|
||||
expected := addressIP(session.conn.RemoteAddr())
|
||||
return expected != nil && expected.Equal(remote.IP) &&
|
||||
remote.Port == session.securityAgreement.selected.portClient
|
||||
// Require P-CSCF IP. Do not require port-c (50601): some cores originate
|
||||
// MESSAGE from an ephemeral port on the same P-CSCF.
|
||||
return expected != nil && expected.Equal(remote.IP)
|
||||
}
|
||||
|
||||
func (session *Session) dispatchPacket(packet sipPacket, respond func([]byte) error) {
|
||||
|
||||
Reference in New Issue
Block a user