mirror of
https://github.com/MengMengCode/VoCat.git
synced 2026-08-13 19:33:42 +08:00
Merge pull request #7 from jiuliking/fix/plmn-23410-ims-interoperability
fix: support PLMN 234-10 IMS registration
This commit is contained in:
+6
-2
@@ -715,9 +715,13 @@ func newVoWiFiOrchestrator(
|
||||
return nil, fmt.Errorf("device %q IKE provider: %w", deviceConfig.ID, err)
|
||||
}
|
||||
imsProvider, err := ims.NewProvider(adapter, ims.Config{
|
||||
// The userspace SWu data plane currently carries the protected P-CSCF
|
||||
// signalling path over TCP.
|
||||
// The userspace SWu data plane carries protected P-CSCF signalling over
|
||||
// TCP by default. UK PLMN 234-10 exposes its P-CSCF over UDP/5060 on SWu.
|
||||
Transport: "tcp",
|
||||
TransportByPLMN: map[string]string{
|
||||
"23410": "udp",
|
||||
"234010": "udp",
|
||||
},
|
||||
// Some Vodafone UK SIM profiles leave AT+CSCA empty; Vodafone publishes
|
||||
// this service-centre number for manual SMS setup.
|
||||
SMSCenter: "+447785016005",
|
||||
|
||||
@@ -38,6 +38,7 @@ type Config struct {
|
||||
PCSCF string
|
||||
LocalAddress string
|
||||
Transport string
|
||||
TransportByPLMN map[string]string
|
||||
Port int
|
||||
RegistrationExpiry time.Duration
|
||||
TransactionTimeout time.Duration
|
||||
@@ -106,6 +107,19 @@ func normalizeConfig(config Config) (Config, error) {
|
||||
if config.Transport != "" && config.Transport != "udp" && config.Transport != "tcp" {
|
||||
return Config{}, fmt.Errorf("ims: unsupported SIP transport %q", config.Transport)
|
||||
}
|
||||
transportByPLMN := make(map[string]string, len(config.TransportByPLMN))
|
||||
for plmn, transport := range config.TransportByPLMN {
|
||||
plmn = strings.TrimSpace(plmn)
|
||||
transport = strings.ToLower(strings.TrimSpace(transport))
|
||||
if !digitsBetween(plmn, 5, 6) {
|
||||
return Config{}, fmt.Errorf("ims: invalid transport override PLMN %q", plmn)
|
||||
}
|
||||
if transport != "udp" && transport != "tcp" {
|
||||
return Config{}, fmt.Errorf("ims: unsupported SIP transport %q for PLMN %s", transport, plmn)
|
||||
}
|
||||
transportByPLMN[plmn] = transport
|
||||
}
|
||||
config.TransportByPLMN = transportByPLMN
|
||||
if strings.TrimSpace(config.UserAgent) == "" {
|
||||
config.UserAgent = "vocat/1"
|
||||
}
|
||||
@@ -185,7 +199,7 @@ func (provider *Provider) Start(ctx context.Context, request vowifi.IMSRequest)
|
||||
if provider.config.PCSCF != "" && !pcscfProvenByTunnel(endpoint, tunnel.PCSCF, provider.config.Port) {
|
||||
return nil, errors.New("ims: configured P-CSCF is not proven by the SWu tunnel")
|
||||
}
|
||||
transport := provider.config.Transport
|
||||
transport := transportForIdentity(provider.config, request.Identity)
|
||||
if transport == "" {
|
||||
transport = transportHint
|
||||
}
|
||||
@@ -227,6 +241,15 @@ func (provider *Provider) Start(ctx context.Context, request vowifi.IMSRequest)
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func transportForIdentity(config Config, identity vowifi.SIMIdentity) string {
|
||||
mcc := strings.TrimSpace(identity.HomeMCC)
|
||||
mnc := strings.TrimSpace(identity.HomeMNC)
|
||||
if transport := config.TransportByPLMN[mcc+mnc]; transport != "" {
|
||||
return transport
|
||||
}
|
||||
return config.Transport
|
||||
}
|
||||
|
||||
type identitySet struct {
|
||||
domain string
|
||||
private string
|
||||
|
||||
@@ -26,6 +26,53 @@ func (evidenceTunnel) Close(context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestTransportForIdentityUsesPLMNOverride(t *testing.T) {
|
||||
t.Parallel()
|
||||
config := Config{
|
||||
Transport: "tcp",
|
||||
TransportByPLMN: map[string]string{
|
||||
"23410": "udp",
|
||||
"234010": "udp",
|
||||
},
|
||||
}
|
||||
|
||||
if got := transportForIdentity(config, vowifi.SIMIdentity{HomeMCC: "234", HomeMNC: "10"}); got != "udp" {
|
||||
t.Fatalf("PLMN 234-10 transport = %q, want udp", got)
|
||||
}
|
||||
if got := transportForIdentity(config, vowifi.SIMIdentity{HomeMCC: "234", HomeMNC: "010"}); got != "udp" {
|
||||
t.Fatalf("zero-padded PLMN 234-010 transport = %q, want udp", got)
|
||||
}
|
||||
if got := transportForIdentity(config, vowifi.SIMIdentity{HomeMCC: "234", HomeMNC: "15"}); got != "tcp" {
|
||||
t.Fatalf("non-overridden transport = %q, want tcp", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransportForIdentityPreservesLeadingZeroMNCs(t *testing.T) {
|
||||
t.Parallel()
|
||||
config := Config{
|
||||
TransportByPLMN: map[string]string{
|
||||
"31001": "udp",
|
||||
"310001": "tcp",
|
||||
"31000": "udp",
|
||||
"310000": "tcp",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range []struct {
|
||||
mnc string
|
||||
want string
|
||||
}{
|
||||
{mnc: "01", want: "udp"},
|
||||
{mnc: "001", want: "tcp"},
|
||||
{mnc: "00", want: "udp"},
|
||||
{mnc: "000", want: "tcp"},
|
||||
} {
|
||||
if got := transportForIdentity(config, vowifi.SIMIdentity{HomeMCC: "310", HomeMNC: test.mnc}); got != test.want {
|
||||
t.Errorf("PLMN 310-%s transport = %q, want %q", test.mnc, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderRegisterAKAParseEvidenceAndClose(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
|
||||
@@ -28,8 +28,9 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIPSecAgreementRequired = errors.New("ims: a supported ipsec-3gpp security agreement is required")
|
||||
ErrIPSecInstall = errors.New("ims: install ipsec-3gpp security associations")
|
||||
ErrIPSecAgreementRequired = errors.New("ims: a supported ipsec-3gpp security agreement is required")
|
||||
ErrIPSecInstall = errors.New("ims: install ipsec-3gpp security associations")
|
||||
errIncompleteSecurityOffer = errors.New("ims: incomplete ipsec-3gpp security offer")
|
||||
)
|
||||
|
||||
// IPSecSAConfig is the complete, evidence-derived 3GPP transport-mode SA set.
|
||||
@@ -186,6 +187,9 @@ func parseSecurityAgreement(values []string, proposal securityProposal) (securit
|
||||
if err != nil {
|
||||
name := strings.ToLower(strings.TrimSpace(strings.SplitN(item, ";", 2)[0]))
|
||||
if name == "ipsec-3gpp" {
|
||||
if errors.Is(err, errIncompleteSecurityOffer) {
|
||||
continue
|
||||
}
|
||||
return securityAgreement{}, fmt.Errorf(
|
||||
"ims: malformed ipsec-3gpp Security-Server: %w",
|
||||
err,
|
||||
@@ -263,6 +267,19 @@ func parseSecurityMechanism(value string) (securityMechanism, error) {
|
||||
if value := parameters["ealg"]; value != "" {
|
||||
mechanism.encryption = strings.ToLower(value)
|
||||
}
|
||||
saParameterKeys := []string{"spi-c", "spi-s", "port-c", "port-s"}
|
||||
presentSAParameters := 0
|
||||
for _, key := range saParameterKeys {
|
||||
if parameters[key] != "" {
|
||||
presentSAParameters++
|
||||
}
|
||||
}
|
||||
if presentSAParameters == 0 {
|
||||
return securityMechanism{}, errIncompleteSecurityOffer
|
||||
}
|
||||
if presentSAParameters != len(saParameterKeys) {
|
||||
return securityMechanism{}, errors.New("ims: partially specified Security-Server SA parameters")
|
||||
}
|
||||
var err error
|
||||
if mechanism.spiClient, err = decimalUint32(parameters["spi-c"]); err != nil {
|
||||
return securityMechanism{}, err
|
||||
|
||||
@@ -36,6 +36,31 @@ func TestParseSecurityAgreementSelectsSupportedIPSec(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSecurityAgreementSkipsIncompleteCarrierAlternatives(t *testing.T) {
|
||||
proposal := securityProposal{
|
||||
spiClient: 1001,
|
||||
spiServer: 1002,
|
||||
portClient: 40666,
|
||||
portServer: 55610,
|
||||
}
|
||||
incomplete := []string{
|
||||
"ipsec-3gpp;q=0.100;alg=hmac-md5-96;mod=trans",
|
||||
"ipsec-3gpp;q=0.200;alg=hmac-sha-1-96;ealg=des-ede3-cbc;mod=trans",
|
||||
"ipsec-3gpp;q=0.300;alg=hmac-sha-1-96;ealg=aes-cbc;mod=trans",
|
||||
}
|
||||
selected := "ipsec-3gpp;q=1.000;alg=hmac-sha-1-96;prot=esp;mod=trans;" +
|
||||
"ealg=aes-cbc;spi-c=2001;spi-s=2002;port-c=50601;port-s=50600"
|
||||
values := []string{strings.Join(append(incomplete, selected), ", ")}
|
||||
|
||||
agreement, err := parseSecurityAgreement(values, proposal)
|
||||
if err != nil {
|
||||
t.Fatalf("parseSecurityAgreement() error = %v", err)
|
||||
}
|
||||
if agreement.selected.spiClient != 2001 || agreement.selected.spiServer != 2002 {
|
||||
t.Fatalf("selected mechanism = %#v", agreement.selected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSecurityAgreementFailsClosed(t *testing.T) {
|
||||
proposal := securityProposal{
|
||||
spiClient: 1001,
|
||||
@@ -73,6 +98,12 @@ func TestParseSecurityAgreementFailsClosed(t *testing.T) {
|
||||
valid + ", ipsec-3gpp;q=0.200;alg=hmac-sha-1-96;alg=hmac-sha-1-96",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "partially specified SA parameters poison otherwise valid list",
|
||||
values: []string{
|
||||
valid + ", ipsec-3gpp;q=0.200;alg=hmac-sha-1-96;spi-c=3001",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no offer",
|
||||
values: nil,
|
||||
|
||||
Reference in New Issue
Block a user