Skip to content

Add no-NAS analyzer and no-diag UI alert - #1132

Merged
untitaker merged 8 commits into
mainfrom
no-nas-analyzer-2
Sep 2, 2026
Merged

Add no-NAS analyzer and no-diag UI alert#1132
untitaker merged 8 commits into
mainfrom
no-nas-analyzer-2

Conversation

@untitaker

@untitaker untitaker commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Fix #882

An alternative to #1107 where the no-NAS analyzer is stripped down, and the analyzer interface is kept minimal to not use system time. This simplifies code but prevents the analyzer from emitting alerts if there are no packets at all.

Instead, we add a bespoke alerting component to the UI that compares packet timestamps with current system time. If the two are far apart, there is a warning in the UI. The warning goes away if we see packets again. Display or ntfy is not implemented.

Pull Request Checklist

  • The Rayhunter team has recently expressed interest in reviewing a PR for this.
    • If not, this PR may be closed due our limited resources and need to prioritize how we spend them.
  • Added or updated any documentation as needed to support the changes in this PR.
  • Code has been linted and run through cargo fmt.
  • If any new functionality has been added, unit tests were also added.
  • CONTRIBUTING.md has been read.
  • Your pull request is fewer than ~400 lines of code.

You must check one of:

  • No generative AI (including LLMs) tools were used to create this PR.
  • Generative AI was used to create this PR. I certify that I have read and understand the code, and that all comments and descriptions were authored by myself and are not the product of generative AI.

@untitaker
untitaker marked this pull request as ready for review August 31, 2026 22:08
@untitaker
untitaker requested a review from bmw August 31, 2026 22:08
Fix #882

An alternative to #1107 where
the no-NAS analyzer is stripped down, and the analyzer interface is kept
minimal to not use system time. This simplifies code but prevents the
analyzer from emitting alerts if there are no packets at all.

Instead, we add a bespoke alerting component to the UI that compares
packet timestamps with current system time. If the two are far apart,
there is a warning in the UI. The warning goes away if we see packets
again. Display or ntfy is not implemented.
@bmw bmw self-assigned this Sep 1, 2026

@bmw bmw left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like this approach a lot!

after thinking about this more yesterday, i was feeling worried about changing rayhunter's status line on the device in response to not seeing any traffic as i thought it'd create confusing false positives if users happened to even temporarily go somewhere without any traffic. i think not doing that and instead displaying the warning prominently in the web UI where we can provide more context makes a lot of sense

i also tested this on my orbic with its sim card removed and the no-NAS analyzer successfully fired after 5 mins and 2 seconds

Comment thread lib/src/analysis/analyzer.rs Outdated
row.events = self.update_timestamp(timestamp);
}

let (timestamp, gsmtap_msg) = match gsmtap_parser::parse(qmdl_message) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let (timestamp, gsmtap_msg) = match gsmtap_parser::parse(qmdl_message) {
let (_, gsmtap_msg) = match gsmtap_parser::parse(qmdl_message) {

GH's web UI isn't letting me write the multiline suggestion i want, but i think we should at least ignore the timestamp here and remove the row.packet_timestamp = Some(timestamp.to_datetime()); line below

in this or another PR, i also think it'd be nice to refactor gsmtap_parser::parse to stop returning the timestamp since we'll no longer be using it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your refactor is fine, but i somehow previously missed the other calls to parse that were actually using the timestamp and not seeing any is what made me suggest the change in the first place. my bad. sorry for creating more work with questionable benefit here

Comment thread lib/src/analysis/analyzer.rs Outdated
.collect()
}

fn update_timestamp(&mut self, timestamp: DateTime<FixedOffset>) -> Vec<Option<Event>> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it'd be nice to append self.packet_num to any event strings here like we currently do in analyze_information_element

Comment thread lib/src/analysis/analyzer.rs Outdated
packet_timestamp: Some(packet_timestamp),
skipped_message_reason: None,
events: Vec::new(),
events: self.update_timestamp(packet_timestamp),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one thing that's slightly awkward with this design is that since we call update_timestamp on every packet before calling analyze_information_element, a NAS message could cause the no-NAS analyzer to alert on the call to update_timestamp since it won't know it's a NAS message until analyze_information_element is called

i wanted to flag this, but i'll leave it up to you on whether or not you think it's worth doing something about. fwiw, i avoided this in my gist by only calling update_timestamp on skipped packets and also passing in the timestamp to analyze_information_element, but that requires additional refactoring and is a little annoying bc most analyzers wouldn't use a timestamp passed to analyze_information_element

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intended to fix that by calling update_timestamp after analyze_information_element, but seems like it got lost somewhere. Pushed it now.

Comment thread lib/src/analysis/no_nas_messages.rs Outdated
None
}

fn update_timestamp(&mut self, timestamp: DateTime<FixedOffset>) -> Option<Event> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you want to do anything special if we see a big jump forward in time? if so and if it'd be useful, i can take a look at the captures sent to EFF and see how common this is and/or how big of jumps users are seeing

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am now resetting the timestamps when there is a gap between packets of more than 5 mins. Will check later what this does on my captures

@bmw bmw mentioned this pull request Sep 1, 2026
8 tasks
@bmw

bmw commented Sep 1, 2026

Copy link
Copy Markdown
Member

also, if you test this with some of your devices with the sim card inserted, does the no-NAS analyzer fire for you? i just tested this with my same orbic with an inactive sim card for about an hour and it never saw any NAS messages 🙁

@untitaker

Copy link
Copy Markdown
Collaborator Author

I've addressed the review comments but still have to test this PR properly to check that it doesn't trigger anything on my captures.

@untitaker

untitaker commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

also, if you test this with some of your devices with the sim card inserted, does the no-NAS analyzer fire for you? i just tested this with my same orbic with an inactive sim card for about an hour and it never saw any NAS messages 🙁

yep, here the no-NAS analyzer fired as well, using an orbic with verizon sim. i think this is expected, the orbic doesn't support the right LTE bands for EU at all.

is it a problem that the analyzer fires in your scenario? I think that is actually what we want, identify dead sims. in my experience there are two kinds of inactive sims, some of them work, some of them are totally dead

@bmw

bmw commented Sep 2, 2026

Copy link
Copy Markdown
Member

is it a problem that the analyzer fires in your scenario? I think that is actually what we want, identify dead sims. in my experience there are two kinds of inactive sims, some of them work, some of them are totally dead

not necessarily, however, i wasn't under the impression my sim was dead, just inactive

playing with it more, here's what i think happened. when i start my device with its sim and the no-NAS analyzer already enabled, it sees an AttachRequest followed by a message like LTE(NAS(EMMMessage(EMMAttachReject(EMMAttachReject { emm_cause: Type3V { inner: EPSServicesAndNonEPSServicesNotAllowed .... the tower is rejecting my device bc it has an inactive sim

if after this i stop the recording and start a new one, rayhunter doesn't see either of these messages, presumably bc my device doesn't try to reconnect to the tower that rejected it, so the no-NAS analyzer flags an event. i think i either restarted the recording like this in my previous testing or there was a race between rayhunter and the modem starting

i don't personally think this is a deal breaker with this heuristic we're flagging as experimental, but it is a limitation with this heuristic i hadn't really realized yet. what do you think? think it's worth documenting in doc/heuristics.md at least?

@untitaker

untitaker commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

I would argue the no-NAS analyzer is right to flag that second recording, as it's going to be useless. How can you catch IMSI catchers if the device won't try to connect to anything after the initial attempt?

@bmw

bmw commented Sep 2, 2026

Copy link
Copy Markdown
Member

maybe, but i imagine this is the normal behavior of devices and expect you'd see something similar with your tplink in this scenario too. alerting when everything is operating as it should feels bad/confusing to me personally. i also think the deviceshould attempt to attach to the tower again after some timeout or if another matching PLMN popped up. i'm not too worried about this point, but i did want to flag it

similarly, as part of only calling one of update_timestamp and analyze_information_element, i was imaging we'd also pass the timestamp to analyze_information_element so the no-NAS analyzer can respond to those messages too. i also think we should update the update_timestamp doc comment and maybe rename the function to something like report_skipped_packet. thoughts?

feel free to take your time on this and i'm sorry for any false sense of urgency i created. i have more than 48 hours before i disappear on vacation for a bit

@untitaker

Copy link
Copy Markdown
Collaborator Author

i also think the deviceshould attempt to attach to the tower again after some timeout or if another matching PLMN popped up

is it possible to verify this? if the device isn't permanently disabled, i think this would call for a "temporary warning" that clears itself, similar to when we don't receive any qmdl msgs in a while

@bmw

bmw commented Sep 2, 2026

Copy link
Copy Markdown
Member

i also think the deviceshould attempt to attach to the tower again after some timeout or if another matching PLMN popped up

is it possible to verify this?

probably through some combination of trawling the specs and letting a device in this state run for a long period of time. in my initial run where i saw this behavior tho it had been running for almost an hour with no NAS messages at all...

i'm not sure if you still want to do more testing first, but i'd personally be fine with getting this merged and playing with it more later since it's marked as experimental and not on by default

to that end and as a gentle nudge just in case you didn't notice yet, prettier is mad

@bmw bmw left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this lgtm!

feel free to merge when/as you wish

}
});
}
const events: Event[] = (row_json.events ?? []).map((event_json: any): Event | null => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for catching this! i unfortunately had missed it

@untitaker
untitaker merged commit 77515df into main Sep 2, 2026
40 checks passed
@untitaker
untitaker deleted the no-nas-analyzer-2 branch September 2, 2026 23:11
holdTheDoorHoid pushed a commit to holdTheDoorHoid/rayhunter that referenced this pull request Sep 3, 2026
Brings in EFForg/rayhunter up to b869b81 (eight commits):

- The no-NAS detector and the no-diag alert (EFForg#1132). The detector is off
  by default, as upstream ships it. Analyzers now receive each packet's
  timestamp, and packets that produce nothing to analyze still reach every
  analyzer through `report_skipped_packet`, so a detector that watches the
  clock sees time pass; this fork's five detectors take the new argument.
  Warnings raised on skipped packets are no longer dropped by
  rayhunter-check or the web report reader.
- The Rust toolchain pinned to 1.98.0 for CI and local builds.
- Help tooltips for the installer GUI's options, and three dependency bumps.

Resolved against the fork:

- The harness keeps its packet numbers and its list of traffic it never
  claimed to analyze; those packets now advance detector clocks like any
  other skipped packet.
- The settings page keeps its tabbed layout; the new detector appears in
  the Detection tab with an explanation like the others.
- The no-diag alert sits in the cell-site panel under the SIM verdict
  rather than at the top of the page, since it answers the same question:
  is the device saying anything at all. Its judgement is a tested helper
  and it is styled for dark mode.
- Upstream's test that builds a bare 2G element now builds one with the
  message bytes this fork's 2G element carries.
- A detector page, index row, configuration key, guide update and
  troubleshooting entry, in the fork's documentation style.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Warn when SIM card is suspected to not be working

2 participants