Skip to content

[14.5-stable] Implement user-configurable LTE Attach configuration #4857

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codespellrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

[codespell]
skip = ./.git/*,*/vendor/*,./conf/v2tlsbaseroot-certificates.pem,./pkg/gpt-tools/patches/*,./pkg/new-kernel/*,./pkg/kernel/*,./pkg/fw/*
ignore-words-list = uptodate,synopsys,crate
ignore-words-list = uptodate,synopsys,crate,UE
17 changes: 17 additions & 0 deletions docs/WIRELESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,23 @@ toggling roaming on or off, designating preferred network operators, and establi
of preference for Radio Access Technologies (RATs). This comprehensive API empowers users to tailor
cellular connectivity settings to suit various network scenarios and SIM card configurations.

#### LTE Attach configuration

LTE connection consists of two IP bearers, the initial (aka attach) EPS bearer and the default
EPS bearer. Device must first establish the initial bearer (LTE attach procedure, which
ModemManager shows as a transition from the `enabled`/`searching` to `registered` modem state)
and then it connects to a default bearer (transition from `registered` to `connected`).
Both bearers require PDP (Packet Data Protocol) context settings (`APN`, `ip-type`, potentially
`username`/`password`, etc.). Settings for the initial bearer are typically provided by the modem
profiles or the network, while settings for the default bearer are user-configured.
However, there are cases, especially with private cellular networks or when changing SIM card
and moving to another operator for which the modem was not pre-configured, where the modem
may not provide the correct APN settings for the attach procedure and it will fail to register
into the network. In these cases, the user can configure PDP settings within the `CellularAccessPoint`
for the attach bearer (see the `attach_*` protobuf fields). All of these fields are optional.
If they are not specified, EVE will not send any attach bearer configuration into the modem,
leaving it to the modem profiles or the network to determine the appropriate settings.

### Cellular info and metrics

The list of all cellular modems visible to the host (incl. the unused ones, without network config attached),
Expand Down
2 changes: 2 additions & 0 deletions pkg/pillar/cipher/cipher.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ func getEncryptionBlock(
decBlock.WifiPassword = zconfigDecBlockPtr.WifiPassword
decBlock.CellularNetUsername = zconfigDecBlockPtr.CellularNetUsername
decBlock.CellularNetPassword = zconfigDecBlockPtr.CellularNetPassword
decBlock.CellularNetAttachUsername = zconfigDecBlockPtr.CellularNetAttachUsername
decBlock.CellularNetAttachPassword = zconfigDecBlockPtr.CellularNetAttachPassword
decBlock.ProtectedUserData = zconfigDecBlockPtr.ProtectedUserData
decBlock.ClusterToken = zconfigDecBlockPtr.ClusterToken
return decBlock
Expand Down
15 changes: 8 additions & 7 deletions pkg/pillar/cmd/zedagent/handlecipherconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,13 @@ func parseCipherContext(ctx *getconfigContext,
// parseCipherBlock : will collate all the relevant information
// ciphercontext will be used to get the certs and encryption schemes
// should be run after parseCipherContext
func parseCipherBlock(ctx *getconfigContext, key string, cfgCipherBlock *zcommon.CipherBlock) types.CipherBlockStatus {
func parseCipherBlock(ctx *getconfigContext, key string,
cfgCipherBlock *zcommon.CipherBlock) (types.CipherBlockStatus, error) {

log.Functionf("parseCipherBlock(%s) started", key)
if cfgCipherBlock == nil {
log.Functionf("parseCipherBlock(%s) nil cipher block", key)
return types.CipherBlockStatus{CipherBlockID: key}
return types.CipherBlockStatus{CipherBlockID: key}, nil
}
cipherBlock := types.CipherBlockStatus{
CipherBlockID: key,
Expand All @@ -118,10 +119,10 @@ func parseCipherBlock(ctx *getconfigContext, key string, cfgCipherBlock *zcommon
// should contain valid cipher data
if len(cipherBlock.CipherData) == 0 ||
len(cipherBlock.CipherContextID) == 0 {
errStr := fmt.Sprintf("%s, block contains incomplete data", key)
log.Error(errStr)
cipherBlock.SetErrorNow(errStr)
return cipherBlock
err := fmt.Errorf("%s, block contains incomplete data", key)
log.Error(err)
cipherBlock.SetErrorNow(err.Error())
return cipherBlock, err
}
log.Functionf("%s, marking cipher as true", key)
cipherBlock.IsCipher = true
Expand All @@ -142,7 +143,7 @@ func parseCipherBlock(ctx *getconfigContext, key string, cfgCipherBlock *zcommon
}

log.Functionf("parseCipherBlock(%s) done", key)
return cipherBlock
return cipherBlock, nil
}

func publishCipherContext(ctx *getconfigContext,
Expand Down
148 changes: 112 additions & 36 deletions pkg/pillar/cmd/zedagent/parseconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,16 @@ func parseAppInstanceConfig(getconfigCtx *getconfigContext,
appInstance.CloudInitUserData = &userData
}
appInstance.RemoteConsole = cfgApp.GetRemoteConsole()
appInstance.CipherBlockStatus = parseCipherBlock(getconfigCtx, appInstance.Key(), cfgApp.GetCipherData())

var err error
appInstance.CipherBlockStatus, err = parseCipherBlock(
getconfigCtx, appInstance.Key(), cfgApp.GetCipherData())
if err != nil {
log.Errorf("Failed to parse application %s cipher data: %v",
cfgApp.Displayname, err)
appInstance.SetErrorNow(err.Error())
}

appInstance.ProfileList = cfgApp.ProfileList

// Add config submitted via local profile server.
Expand Down Expand Up @@ -1915,7 +1924,13 @@ func publishDatastoreConfig(ctx *getconfigContext,

datastore.DsCertPEM = ds.GetDsCertPEM()

datastore.CipherBlockStatus = parseCipherBlock(ctx, datastore.Key(), ds.GetCipherData())
var err error
datastore.CipherBlockStatus, err = parseCipherBlock(
ctx, datastore.Key(), ds.GetCipherData())
if err != nil {
log.Error(err)
datastore.SetErrorNow(err.Error())
}
ctx.pubDatastoreConfig.Publish(datastore.Key(), *datastore)
}
}
Expand Down Expand Up @@ -2069,7 +2084,12 @@ func parseOneNetworkXObjectConfig(ctx *getconfigContext, netEnt *zconfig.Network
}

// wireless property configuration
config.WirelessCfg = parseNetworkWirelessConfig(ctx, config.Key(), netEnt)
config.WirelessCfg, err = parseNetworkWirelessConfig(ctx, config.Key(), netEnt)
if err != nil {
log.Errorf("parseOneNetworkXObjectConfig (%s): %v", config.Key(), err)
config.SetErrorNow(err.Error())
return config
}

ipspec := netEnt.GetIp()
if ipspec == nil && config.Type != types.NetworkTypeNOOP {
Expand Down Expand Up @@ -2135,34 +2155,36 @@ func parseOneNetworkXObjectConfig(ctx *getconfigContext, netEnt *zconfig.Network
return config
}

func parseNetworkWirelessConfig(ctx *getconfigContext, key string, netEnt *zconfig.NetworkConfig) types.WirelessConfig {
var wconfig types.WirelessConfig
func parseNetworkWirelessConfig(ctx *getconfigContext,
key string, netEnt *zconfig.NetworkConfig) (wconfig types.WirelessConfig, err error) {

netWireless := netEnt.GetWireless()
if netWireless == nil {
return wconfig
return wconfig, nil
}
log.Functionf("parseNetworkWirelessConfig: Wireless of network present in %s, config %v", netEnt.Id, netWireless)
log.Functionf("parseNetworkWirelessConfig: Wireless of network present in %s, config %v",
netEnt.Id, netWireless)

wType := netWireless.GetType()
switch wType {
case zconfig.WirelessType_TypeNOOP:
// This is not a wireless network adapter.
// Return an empty wireless configuration.
return wconfig, nil
case zconfig.WirelessType_Cellular:
wconfig.WType = types.WirelessTypeCellular
cellNetConfigs := netWireless.GetCellularCfg()
if len(cellNetConfigs) == 0 {
log.Errorf("parseNetworkWirelessConfig: missing cellular config in: %v",
netWireless)
return wconfig
err = errors.New("missing cellular config")
return wconfig, err
}
// CellularCfg should really have been defined in the EVE API as a single entry
// rather than as a list (for multiple SIM cards and APNs there is AccessPoints list
// underneath). However, marking this field as deprecated and creating a new non-list
// field seems unnecessary - let's instead expect single entry.
if len(cellNetConfigs) > 1 {
log.Errorf(
"parseNetworkWirelessConfig: unexpected multiple cellular configs in: %v",
netWireless)
return wconfig
err = errors.New("unexpected multiple cellular configs")
return wconfig, err
}
cellNetConfig := cellNetConfigs[0]
for _, accessPoint := range cellNetConfig.AccessPoints {
Expand All @@ -2173,19 +2195,9 @@ func parseNetworkWirelessConfig(ctx *getconfigContext, key string, netEnt *zconf
// should be activated.
ap.Activated = cellNetConfig.ActivatedSimSlot == 0 ||
cellNetConfig.ActivatedSimSlot == accessPoint.SimSlot
switch accessPoint.AuthProtocol {
case zconfig.CellularAuthProtocol_CELLULAR_AUTH_PROTOCOL_PAP:
ap.AuthProtocol = types.WwanAuthProtocolPAP
case zconfig.CellularAuthProtocol_CELLULAR_AUTH_PROTOCOL_CHAP:
ap.AuthProtocol = types.WwanAuthProtocolCHAP
case zconfig.CellularAuthProtocol_CELLULAR_AUTH_PROTOCOL_PAP_AND_CHAP:
ap.AuthProtocol = types.WwanAuthProtocolPAPAndCHAP
default:
log.Errorf("parseNetworkWirelessConfig: unrecognized AuthProtocol: %+v",
accessPoint)
}
if ap.AuthProtocol != types.WwanAuthProtocolNone {
ap.EncryptedCredentials = parseCipherBlock(ctx, key, accessPoint.GetCipherData())
ap.AuthProtocol, err = parseCellularAuthProtocol(accessPoint.AuthProtocol)
if err != nil {
return wconfig, err
}
for _, plmn := range accessPoint.PreferredPlmns {
ap.PreferredPLMNs = append(ap.PreferredPLMNs, plmn)
Expand All @@ -2201,11 +2213,32 @@ func parseNetworkWirelessConfig(ctx *getconfigContext, key string, netEnt *zconf
case zevecommon.RadioAccessTechnology_RADIO_ACCESS_TECHNOLOGY_5GNR:
ap.PreferredRATs = append(ap.PreferredRATs, types.WwanRAT5GNR)
default:
log.Errorf("parseNetworkWirelessConfig: unrecognized RAT: %+v",
accessPoint)
return wconfig, fmt.Errorf("unrecognized RAT: %+v", accessPoint)
}
}
ap.ForbidRoaming = accessPoint.ForbidRoaming
ap.IPType, err = parseCellularIPType(accessPoint.IpType)
if err != nil {
return wconfig, err
}
ap.AttachAPN = accessPoint.AttachApn
ap.AttachIPType, err = parseCellularIPType(accessPoint.AttachIpType)
if err != nil {
return wconfig, err
}
ap.AttachAuthProtocol, err = parseCellularAuthProtocol(
accessPoint.AttachAuthProtocol)
if err != nil {
return wconfig, err
}
if ap.AuthProtocol != types.WwanAuthProtocolNone ||
ap.AttachAuthProtocol != types.WwanAuthProtocolNone {
ap.EncryptedCredentials, err = parseCipherBlock(
ctx, key, accessPoint.GetCipherData())
if err != nil {
return wconfig, err
}
}
wconfig.CellularV2.AccessPoints = append(wconfig.CellularV2.AccessPoints, ap)
}
// For backward compatibility.
Expand All @@ -2219,7 +2252,7 @@ func parseNetworkWirelessConfig(ctx *getconfigContext, key string, netEnt *zconf
probeCfg := cellNetConfig.Probe
customProbe, err := parseConnectivityProbe(probeCfg.GetCustomProbe())
if err != nil {
log.Errorf("parseNetworkWirelessConfig: %v", err)
return wconfig, err
}
if customProbe.Method == types.ConnectivityProbeMethodNone || err != nil {
// For backward compatibility.
Expand Down Expand Up @@ -2252,21 +2285,59 @@ func parseNetworkWirelessConfig(ctx *getconfigContext, key string, netEnt *zconf
case zconfig.WiFiKeyScheme_WPAEAP:
wifi.KeyScheme = types.KeySchemeWpaEap
default:
log.Errorf("parseNetworkWirelessConfig: unrecognized WiFi Key scheme: %+v",
wificfg)
return wconfig, fmt.Errorf("unrecognized WiFi Key scheme: %+v",
wificfg.GetKeyScheme())
}
wifi.Identity = wificfg.GetIdentity()
wifi.Password = wificfg.GetPassword()
wifi.Priority = wificfg.GetPriority()
wifiKey := fmt.Sprintf("%s-%s", key, wifi.SSID)
wifi.CipherBlockStatus = parseCipherBlock(ctx, wifiKey, wificfg.GetCipherData())
wifi.CipherBlockStatus, err = parseCipherBlock(
ctx, wifiKey, wificfg.GetCipherData())
if err != nil {
return wconfig, err
}
wconfig.Wifi = append(wconfig.Wifi, wifi)
}
log.Functionf("parseNetworkWirelessConfig: Wireless of type Wifi, %v", wconfig.Wifi)
default:
log.Errorf("parseNetworkWirelessConfig: unsupported wireless configure type %d", wType)
return wconfig, fmt.Errorf("unsupported wireless type: %d", wType)
}
return wconfig, nil
}

func parseCellularAuthProtocol(
authProtocol zconfig.CellularAuthProtocol) (types.WwanAuthProtocol, error) {
switch authProtocol {
case zconfig.CellularAuthProtocol_CELLULAR_AUTH_PROTOCOL_NONE:
return types.WwanAuthProtocolNone, nil
case zconfig.CellularAuthProtocol_CELLULAR_AUTH_PROTOCOL_PAP:
return types.WwanAuthProtocolPAP, nil
case zconfig.CellularAuthProtocol_CELLULAR_AUTH_PROTOCOL_CHAP:
return types.WwanAuthProtocolCHAP, nil
case zconfig.CellularAuthProtocol_CELLULAR_AUTH_PROTOCOL_PAP_AND_CHAP:
return types.WwanAuthProtocolPAPAndCHAP, nil
default:
return types.WwanAuthProtocolNone, fmt.Errorf(
"unrecognized cellular AuthProtocol: %+v", authProtocol)
}
}

func parseCellularIPType(
ipType zevecommon.CellularIPType) (types.WwanIPType, error) {
switch ipType {
case zevecommon.CellularIPType_CELLULAR_IP_TYPE_UNSPECIFIED:
return types.WwanIPTypeUnspecified, nil
case zevecommon.CellularIPType_CELLULAR_IP_TYPE_IPV4:
return types.WwanIPTypeIPv4, nil
case zevecommon.CellularIPType_CELLULAR_IP_TYPE_IPV4_AND_IPV6:
return types.WwanIPTypeIPv4AndIPv6, nil
case zevecommon.CellularIPType_CELLULAR_IP_TYPE_IPV6:
return types.WwanIPTypeIPv6, nil
default:
return types.WwanIPTypeUnspecified, fmt.Errorf(
"unrecognized cellular IP type: %+v", ipType)
}
return wconfig
}

func parseIpspecNetworkXObject(ipspec *zconfig.Ipspec, config *types.NetworkXObjectConfig) error {
Expand Down Expand Up @@ -3288,8 +3359,13 @@ func parseEdgeNodeClusterConfig(getconfigCtx *getconfigContext,
BootstrapNode: isJoinNode,
// XXX EncryptedClusterToken is only for gcp config
}
enClusterConfig.CipherToken = parseCipherBlock(getconfigCtx,
enClusterConfig.CipherToken, err = parseCipherBlock(getconfigCtx,
enClusterConfig.Key(), zcfgCluster.GetEncryptedClusterToken())
if err != nil {
// TODO: Flag enClusterConfig with an error and propagate it to the controller.
log.Errorf("parseEdgeNodeClusterConfig: failed to parse encrypted cluster token: %v", err)
return
}
log.Functionf("parseEdgeNodeClusterConfig: ENCluster API, Config %+v, %v", zcfgCluster, enClusterConfig)
ctx.pubEdgeNodeClusterConfig.Publish("global", enClusterConfig)
}
26 changes: 20 additions & 6 deletions pkg/pillar/cmd/zedagent/parsepatchenvelopes.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@ func addBinaryBlobToPatchEnvelope(ctx *getconfigContext, pe *types.PatchEnvelope
return err
}
volumeRef.ArtifactMetadata = artifact.GetArtifactMetaData()
volumeRef.EncArtifactMeta = getEncArtifactMetadata(ctx, artifact)
volumeRef.EncArtifactMeta, err = getEncArtifactMetadata(ctx, artifact)
if err != nil {
return err
}
pe.VolumeRefs = append(pe.VolumeRefs, *volumeRef)
return nil
case *zconfig.EveBinaryArtifact_Inline:
Expand All @@ -112,7 +115,10 @@ func addBinaryBlobToPatchEnvelope(ctx *getconfigContext, pe *types.PatchEnvelope
return err
}
binaryBlob.ArtifactMetadata = artifact.GetArtifactMetaData()
binaryBlob.EncArtifactMeta = getEncArtifactMetadata(ctx, artifact)
binaryBlob.EncArtifactMeta, err = getEncArtifactMetadata(ctx, artifact)
if err != nil {
return err
}
pe.BinaryBlobs = append(pe.BinaryBlobs, *binaryBlob)

return nil
Expand Down Expand Up @@ -144,10 +150,11 @@ func addBinaryBlobToPatchEnvelope(ctx *getconfigContext, pe *types.PatchEnvelope
return errors.New("Unknown EveBinaryArtifact format")
}

func getEncArtifactMetadata(ctx *getconfigContext, artifact *zconfig.EveBinaryArtifact) types.CipherBlockStatus {
func getEncArtifactMetadata(ctx *getconfigContext,
artifact *zconfig.EveBinaryArtifact) (types.CipherBlockStatus, error) {
data := artifact.GetMetadataCipherData()
if data == nil {
return types.CipherBlockStatus{}
return types.CipherBlockStatus{}, nil
}
if len(data.CipherData) < 16 {
log.Errorf("Failed to get metadata cipher data, cipherData is nil or less than 16 bytes")
Expand All @@ -169,10 +176,14 @@ func getEncryptedCipherBlock(ctx *getconfigContext,
persistCacheFilepath string) (*types.BinaryCipherBlob, error) {
var cipherData *zcommon.CipherBlock
var typeStr string
encArtifactMeta, err := getEncArtifactMetadata(ctx, artifact)
if err != nil {
return nil, err
}
cipherBlob := types.BinaryCipherBlob{
EncType: enctype,
ArtifactMetaData: artifact.GetArtifactMetaData(),
EncArtifactMeta: getEncArtifactMetadata(ctx, artifact),
EncArtifactMeta: encArtifactMeta,
}
switch enctype {
case types.BlobEncrytedTypeInline:
Expand All @@ -197,7 +208,10 @@ func getEncryptedCipherBlock(ctx *getconfigContext,
// we save the cipher block data to the cache file, and read it back in msrv side to decrypt,
// to avoid publishing the cipher block data which can be too big in size
key := fmt.Sprintf("%s-%s", typeStr, hex.EncodeToString(cipherData.CipherData[:16]))
EncBinaryArtifact := parseCipherBlock(ctx, key, cipherData)
EncBinaryArtifact, err := parseCipherBlock(ctx, key, cipherData)
if err != nil {
return nil, err
}
url, err := saveCipherBlockStatusToFile(EncBinaryArtifact, key, persistCacheFilepath)
if err != nil {
return nil, err
Expand Down
Loading
Loading