Skip to content

Commit

Permalink
Set region to unknown on startup (#413)
Browse files Browse the repository at this point in the history
* Set region to unknown on startup

This sets the current/default region  to a new “UNKNOWN” region to avoid invalid uplink packet reports, region parameters (and thus invalid downlinks) when the gateway can’t or has not received it’s asserted region from the config service yet

* Return region as null when UNKNOWN on CLI

* Update config/settings.toml

---------

Co-authored-by: Jeff Grunewald <jeff.grunewald@gmail.com>
  • Loading branch information
madninja and jeffgrunewald authored Apr 7, 2023
1 parent 3ad4f7e commit b0218dc
Show file tree
Hide file tree
Showing 14 changed files with 364 additions and 347 deletions.
508 changes: 252 additions & 256 deletions Cargo.lock

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,17 @@ helium-gateway pre-installed, manual installation requires you to:
Supported values are `stdio` or `syslog`. Note you may need to configure
the `syslog` service on your device to accept the logs.

6. Configure the region if required. The default region of the gateway is
`US915`, if your region is different you can set the right one in the
`settings.toml` file.
6. Configure the region if required. The default region of the gateway is set to
`UKNOWN`, and fetched based on the asserted location of the gateway. Setting
the region to a known region or caching the last fetched region and using
the `GW_REGION` environment variable on startup will allow the gateway to use
the correct region for uplinks immediately, while the region parameters are
retrieved.

The supported region values are listed in the [region protobuf definition](https://github.com/helium/proto/blob/master/src/region.proto)

**NOTE**: This regional setting is only used for uplinks. Due to TX power
regulations, the gateway location needs to be asserted on the blockchain to
be able to send downlinks.
**NOTE**: Due to TX power regulations, the gateway location needs to be
asserted on the blockchain to be able to send downlinks.

7. Start the service by either starting it manually or hooking it into the
`init.d`, `systemd`, or equivalent system services for your platform. Consult
Expand Down
4 changes: 1 addition & 3 deletions beacon/src/beacon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ impl Beacon {
) -> Result<Self> {
match remote_entropy.version {
0 | 1 => {
if region_params.params.is_empty() {
return Err(Error::no_region_params());
}
region_params.check_valid()?;

let seed_data = {
let mut hasher = Sha256::new();
Expand Down
24 changes: 23 additions & 1 deletion beacon/src/region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,25 @@ impl From<&Region> for i32 {
}
}

impl Default for Region {
fn default() -> Self {
Region(ProtoRegion::Unknown)
}
}

impl Region {
pub fn from_i32(v: i32) -> Result<Self> {
ProtoRegion::from_i32(v)
.map(Self)
.ok_or_else(|| Error::unsupported_region(v))
}

pub fn is_unknown(&self) -> bool {
self.0 == ProtoRegion::Unknown
}
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
pub struct RegionParams {
pub gain: Decimal,
pub region: Region,
Expand Down Expand Up @@ -164,10 +174,22 @@ impl RegionParams {
params,
})
}

pub fn is_unknown(&self) -> bool {
self.region.is_unknown()
}
}

impl RegionParams {
pub fn check_valid(&self) -> Result {
if self.is_unknown() || self.params.is_empty() {
return Err(Error::no_region_params());
}
Ok(())
}

pub fn max_eirp(&self) -> Result<Decimal> {
self.check_valid()?;
self.params
.iter()
.max_by_key(|p| p.max_eirp)
Expand Down
6 changes: 4 additions & 2 deletions config/settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ api = 4467

# The default region to use until a region is received from the Helium network.
# This value should line up with the configured region of the semtech packet
# forwarder.
region = "US915"
# forwarder. Note: Not setting this here or with a GW_REGION env var will stop
# packet and poc beaconing until the correct asserted region is retrieved.
#
# region = "US915"

[log]
# The logging level to assume on startup
Expand Down
9 changes: 2 additions & 7 deletions src/beaconer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! This module provides proof-of-coverage (PoC) beaconing support.
use crate::{
error::RegionError,
gateway::{self, BeaconResp},
impl_msg_sign, region_watcher,
service::{entropy::EntropyService, poc::PocIotService},
Expand Down Expand Up @@ -133,9 +132,7 @@ impl Beaconer {
}

pub async fn mk_beacon(&mut self) -> Result<beacon::Beacon> {
if self.region_params.params.is_empty() {
return Err(RegionError::no_region_params());
}
self.region_params.check_valid()?;

let mut entropy_service = EntropyService::new(self.entropy_uri.clone());
let remote_entropy = entropy_service.get_entropy().await?;
Expand Down Expand Up @@ -225,8 +222,6 @@ impl Beaconer {
}

async fn handle_received_beacon(&mut self, packet: PacketUp) {
info!("received possible PoC payload: {packet:?}");

if let Some(last_beacon) = &self.last_beacon {
if packet.payload() == last_beacon.data {
info!("ignoring last self beacon witness");
Expand Down Expand Up @@ -266,7 +261,7 @@ impl Beaconer {
}

async fn handle_secondary_beacon(&mut self, report: poc_lora::LoraWitnessReportReqV1) {
if self.region_params.params.is_empty() {
if self.region_params.check_valid().is_err() {
warn!("no region params for secondary beacon");
return;
};
Expand Down
20 changes: 13 additions & 7 deletions src/cmd/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl fmt::Display for InfoKey {
struct InfoCache {
port: u16,
public_keys: Option<(PublicKey, PublicKey)>,
region: Option<Region>,
region: Option<Option<Region>>,
router: Option<RouterStatus>,
}

Expand Down Expand Up @@ -92,14 +92,19 @@ impl InfoCache {
Ok(onboarding_key)
}

async fn region(&mut self) -> Result<Region> {
if let Some(region) = self.region {
return Ok(region);
async fn region(&mut self) -> Result<Option<Region>> {
if let Some(maybe_region) = self.region {
return Ok(maybe_region);
}
let mut client = LocalClient::new(self.port).await?;
let region = client.region().await?;
self.region = Some(region);
Ok(region)
let maybe_region = if region.is_unknown() {
None
} else {
Some(region)
};
self.region = Some(maybe_region);
Ok(maybe_region)
}

pub async fn router(&mut self) -> Result<RouterStatus> {
Expand Down Expand Up @@ -132,7 +137,8 @@ impl InfoKey {
json!(name)
}
Self::Region => {
json!(cache.region().await?.to_string())
let region = cache.region().await?;
json!(region.map(|region| region.to_string()))
}
Self::Router => {
json!(cache.router().await?)
Expand Down
2 changes: 1 addition & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub enum Error {
Service(#[from] ServiceError),
#[error("semtech udp error: {0}")]
Semtech(#[from] Box<semtech_udp::server_runtime::Error>),
#[error("beacon error: {0}")]
#[error("{0}")]
Beacon(#[from] beacon::Error),
#[error("gateway error: {0}")]
Gateway(#[from] crate::gateway::GatewayError),
Expand Down
36 changes: 24 additions & 12 deletions src/gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ use semtech_udp::{
tx_ack::Error as TxAckErr,
CodingRate, MacAddress, Modulation,
};
use std::{
convert::TryFrom,
time::{Duration, Instant},
};
use std::time::{Duration, Instant};
use tracing::{debug, info, warn};

pub const DOWNLINK_TIMEOUT: Duration = Duration::from_secs(5);
Expand Down Expand Up @@ -135,15 +132,17 @@ impl Gateway {
Event::ClientDisconnected((mac, addr)) => {
info!(%mac, %addr, "disconnected packet forwarder")
}
Event::PacketReceived(rxpk, _gateway_mac) => match PacketUp::try_from(rxpk) {
Ok(packet) if packet.is_potential_beacon() => {
self.beacons.received_beacon(packet).await
}
Ok(packet) => self.handle_uplink(packet, Instant::now()).await,
Err(err) => {
warn!(%err, "ignoring push_data");
Event::PacketReceived(rxpk, _gateway_mac) => {
match PacketUp::from_rxpk(rxpk, self.region_params.region) {
Ok(packet) if packet.is_potential_beacon() => {
self.handle_potential_beacon(packet).await;
}
Ok(packet) => self.handle_uplink(packet, Instant::now()).await,
Err(err) => {
warn!(%err, "ignoring push_data");
}
}
},
}
Event::NoClientWithMac(_packet, mac) => {
info!(%mac, "ignoring send to client with unknown MAC")
}
Expand All @@ -154,7 +153,20 @@ impl Gateway {
Ok(())
}

async fn handle_potential_beacon(&mut self, packet: PacketUp) {
if self.region_params.is_unknown() {
info!(downlink_mac = %self.downlink_mac, uplink = %packet, "ignored potential beacon, no region");
return;
}
info!(downlink_mac = %self.downlink_mac, uplink = %packet, "received potential beacon");
self.beacons.received_beacon(packet).await
}

async fn handle_uplink(&mut self, packet: PacketUp, received: Instant) {
if self.region_params.is_unknown() {
info!(downlink_mac = %self.downlink_mac, uplink = %packet, "ignored uplink, no region");
return;
}
info!(downlink_mac = %self.downlink_mac, uplink = %packet, "received uplink");
self.uplinks.uplink(packet, received).await;
}
Expand Down
52 changes: 24 additions & 28 deletions src/packet.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{error::DecodeError, Error, Result};
use crate::{error::DecodeError, Error, Region, Result};
use helium_proto::services::{
poc_lora,
router::{PacketRouterPacketDownV1, PacketRouterPacketUpV1},
Expand Down Expand Up @@ -57,33 +57,6 @@ impl fmt::Display for PacketUp {
}
}

impl TryFrom<push_data::RxPk> for PacketUp {
type Error = Error;

fn try_from(rxpk: push_data::RxPk) -> Result<Self> {
if rxpk.get_crc_status() != &CRC::OK {
return Err(DecodeError::invalid_crc());
}
let rssi = rxpk
.get_signal_rssi()
.unwrap_or_else(|| rxpk.get_channel_rssi());

let packet = PacketRouterPacketUpV1 {
rssi,
timestamp: *rxpk.get_timestamp() as u64,
payload: rxpk.get_data().to_vec(),
frequency: to_hz(*rxpk.get_frequency()) as u32,
datarate: datarate::to_proto(rxpk.get_datarate())? as i32,
snr: rxpk.get_snr(),
region: 0,
hold_time: 0,
gateway: vec![],
signature: vec![],
};
Ok(Self(packet))
}
}

impl TryFrom<PacketUp> for poc_lora::LoraWitnessReportReqV1 {
type Error = Error;
fn try_from(value: PacketUp) -> Result<Self> {
Expand All @@ -110,6 +83,29 @@ impl TryFrom<PacketUp> for poc_lora::LoraWitnessReportReqV1 {
}

impl PacketUp {
pub fn from_rxpk(rxpk: push_data::RxPk, region: Region) -> Result<Self> {
if rxpk.get_crc_status() != &CRC::OK {
return Err(DecodeError::invalid_crc());
}
let rssi = rxpk
.get_signal_rssi()
.unwrap_or_else(|| rxpk.get_channel_rssi());

let packet = PacketRouterPacketUpV1 {
rssi,
timestamp: *rxpk.get_timestamp() as u64,
payload: rxpk.get_data().to_vec(),
frequency: to_hz(*rxpk.get_frequency()) as u32,
datarate: datarate::to_proto(rxpk.get_datarate())? as i32,
snr: rxpk.get_snr(),
region: region.into(),
hold_time: 0,
gateway: vec![],
signature: vec![],
};
Ok(Self(packet))
}

pub fn is_potential_beacon(&self) -> bool {
Self::parse_header(self.payload())
.map(|header| header.mtype() == lorawan::MType::Proprietary)
Expand Down
14 changes: 1 addition & 13 deletions src/packet_router/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use crate::{
gateway,
message_cache::{CacheMessage, MessageCache},
region_watcher,
service::packet_router::PacketRouterService,
sync, Base64, Keypair, MsgSign, PacketUp, RegionParams, Result, Settings,
sync, Base64, Keypair, MsgSign, PacketUp, Result, Settings,
};
use exponential_backoff::Backoff;
use helium_proto::services::router::{PacketRouterPacketDownV1, PacketRouterPacketUpV1};
Expand Down Expand Up @@ -53,11 +52,9 @@ impl MessageSender {

pub struct PacketRouter {
messages: MessageReceiver,
region_watch: region_watcher::MessageReceiver,
transmit: gateway::MessageSender,
service: PacketRouterService,
reconnect_retry: u32,
region_params: RegionParams,
keypair: Arc<Keypair>,
store: MessageCache<PacketUp>,
}
Expand All @@ -66,18 +63,14 @@ impl PacketRouter {
pub fn new(
settings: &Settings,
messages: MessageReceiver,
region_watch: region_watcher::MessageReceiver,
transmit: gateway::MessageSender,
) -> Self {
let router_settings = &settings.router;
let service =
PacketRouterService::new(router_settings.uri.clone(), settings.keypair.clone());
let store = MessageCache::new(router_settings.queue);
let region_params = region_watcher::current_value(&region_watch);
Self {
service,
region_params,
region_watch,
keypair: settings.keypair.clone(),
transmit,
messages,
Expand Down Expand Up @@ -118,10 +111,6 @@ impl PacketRouter {
}
None => warn!("ignoring closed message channel"),
},
region_change = self.region_watch.changed() => match region_change {
Ok(()) => self.region_params = region_watcher::current_value(&self.region_watch),
Err(_) => warn!("region watch disconnected")
},
_ = time::sleep_until(reconnect_sleep) => {
reconnect_sleep = self.handle_reconnect(&reconnect_backoff).await;
},
Expand Down Expand Up @@ -182,7 +171,6 @@ impl PacketRouter {
packet: CacheMessage<PacketUp>,
) -> Result<PacketRouterPacketUpV1> {
let mut uplink: PacketRouterPacketUpV1 = packet.into_inner().into();
uplink.region = self.region_params.region.into();
uplink.gateway = self.keypair.public_key().into();
uplink.signature = uplink.sign(self.keypair.clone()).await?;
Ok(uplink)
Expand Down
Loading

0 comments on commit b0218dc

Please sign in to comment.