Investigating the India–Telegram BGP Hijack with monocle

· Mingwei Zhang
monocle case-study tutorial rpki

On June 16, 2026, Indian carrier Reliance Communications (AS18101) began originating Telegram’s IP prefixes in BGP, disrupting access for users inside and briefly outside India. The event has been covered from a policy angle (Anurag Bhatia’s analysis, the IPv6 blackholing follow-up) and a traffic-impact angle (Kentik’s post). This post does something different: it shows how to investigate the forensic evidence yourself using monocle and the BGPKIT API - open-source tools, real commands, real output. No screenshots of a vendor portal, no proprietary telemetry. Everything here you can run on your own machine.

The goal is forensics methodology, not opinion. We won’t speculate about intent or mitigation policy. We’ll establish ground truth, find the hijack in the data, measure how it propagated, and check the RPKI timeline - the same steps you’d follow for any BGP origin-conflict incident.

Prerequisites

brew install monocle  # monocle 1.3.0 used in this post
# cargo install monocle          # also works, but takes longer to build from source

All timestamps below are UTC. monocle accepts RFC 3339 strings and converts them internally:

$ monocle time "2026-06-16T16:14:19Z" -s
2026-06-16T16:14:19+00:00

Step 1: Establish ground truth - who should originate this prefix?

Before touching any MRT data, establish the expected state of the prefix. This is free and instant, and it gives you the baseline to compare against.

Telegram operates five ASNs: AS62041, AS62014, AS59930, AS44907, and AS211157. A single monocle inspect call pulls all of them:

monocle inspect 62041 62014 59930 44907 211157
ASN | Name | Country | Org
--- | --- | --- | ---
AS62041 | Telegram Messenger EUR | VG | Telegram Messenger Inc
AS62014 | Telegram Messenger APAC | VG | Telegram Messenger Inc
AS59930 | Telegram Messenger NA  | VG | Telegram Messenger Inc
AS44907 | Telegram Messenger Inc | VG | Telegram Messenger Inc
AS211157| Telegram Messenger Inc | VG | Telegram Messenger Inc

Now check who currently announces the prefix that was reported hijacked, 91.105.192.0/23:

monocle pfx2as 91.105.192.0/23 --show-name

The legitimate origin is AS211157 (Telegram). The suspect, AS18101 (Reliance Communications), should not appear here in a clean table.

The single most important check: RPKI validation

$ monocle rpki validate 91.105.192.0/23 18101 --format json
{
  "prefix": "91.105.192.0/23",
  "asn": 18101,
  "state": "invalid",
  "reason": "ASN 18101 not authorized; authorized ASNs: 211157",
  "covering_roas": [
    {"prefix": "91.105.192.0/23", "max_length": 23, "origin_asn": 211157, "ta": "RIPENCC"}
  ]
}

The prefix is covered by a ROA authorizing AS211157 only. AS18101 originating it is RPKI-invalid by definition. Any network enforcing Route Origin Validation (ROV) should have rejected this announcement. Confirm the legitimate origin validates clean:

$ monocle rpki validate 91.105.192.0/23 211157 --format json
{"prefix":"91.105.192.0/23","asn":211157,"state":"valid",...}

You now know: the prefix is signed, the legitimate origin is valid, and the suspect origin is invalid - before downloading a single MRT file.

List Telegram’s signed prefixes

To understand what address space was protected by RPKI, pull every Telegram ROA across all five ASNs:

for asn in 62041 62014 59930 44907 211157; do
  monocle rpki roas $asn --format json-line
done | grep '^{' \
  | jq -r '"\(.prefix) AS\(.origin_asn)"' \
  | sort -u

This returns 55 ROAs - Telegram’s signed prefix set, from 91.105.192.0/23 up through 149.154.160.0/22 and the 91.108.x.0/22 blocks. Every one of these prefixes, if originated by AS18101, would be RPKI-invalid.

Understand the suspect’s connectivity

monocle as2rel 18101 --is-downstream --show-name  # upstreams
monocle as2rel 18101 15412 --format json | jq '.results[0]'  # relationship with FLAG

Reliance’s primary upstreams are AS15412 (FLAG Telecom) and AS4755 (Tata Communications). Keep these in mind - they’re the transits that would carry (or filter) any announcement Reliance originates.

Step 2: Find the hijack in the data

Now look for the actual BGP announcements. The reported time is 16:14:19 UTC on June 16. Start with a narrow 5-minute window and a single small collector.

Why a small collector?

rrc01 (London) and route-views2 are large collectors with hundreds of peers. Searching them downloads and parses a lot of data. For a first confirmatory search, a small collector is much faster - and if the event is real, a small collector that has a peer seeing the path will confirm it just as well.

Use the BGPKIT API to find collector peer counts:

$ curl -s "https://api.bgpkit.com/v3/broker/peers?collector=rrc14" | jq '.data | length'
21

rrc14 (Palo Alto, 21 peers) is small. Let’s search there:

monocle search -p 91.105.192.0/23 -s -S -o 18101 \
  -t 1781626459 -d 5m -c rrc14 \
  --cache-dir ~/.cache/monocle \
  --format json-line \
  | jq -c '.timestamp |= todateiso8601'
{"timestamp":"2026-06-16T16:14:49Z","type":"ANNOUNCE","prefix":"91.105.192.0/23",
 "as_path":"19151 15412 18101","peer_asn":19151,"collector":"rrc14",...}

Confirmed. At 16:14:49 UTC, rrc14’s peer AS19151 received 91.105.192.0/23 originated by AS18101, via the path 19151 15412 18101 - meaning FLAG (AS15412) was the transit carrying the hijack to the global table.

Why -s -S?

The -p prefix filter is exact-only by default. Hijacks routinely involve more-specifics - the victim announces /24s to fight back, the hijacker follows with even more specific routes. A bare -p 91.105.192.0/23 misses both the covering aggregate and any more-specifics. -s (include-super) and -S (include-sub) expand the match. Get in the habit of adding both to any hijack search.

The prefix arms race

Widen the search slightly (still one collector, still cached) to see the full set of prefixes Reliance originated in the escalation wave:

monocle search -p "91.108.4.0/22,149.154.160.0/22,91.105.192.0/23,95.161.64.0/20" \
  -s -S -o 18101 -t 1781594400 -T 1781630400 -c rrc14 \
  --cache-dir ~/.cache/monocle --format json-line \
  | jq -r '"\(.timestamp | todateiso8601) \(.prefix)"' | sort -u
2026-06-16T16:14:49Z  149.154.160.0/24
2026-06-16T16:14:49Z  149.154.161.0/24
2026-06-16T16:14:49Z  149.154.162.0/24
2026-06-16T16:14:49Z  149.154.163.0/24
2026-06-16T16:14:49Z  91.105.192.0/23
2026-06-16T16:14:49Z  91.108.4.0/23
2026-06-16T16:14:49Z  91.108.6.0/23
2026-06-16T16:14:49Z  95.161.64.0/21
2026-06-16T16:14:49Z  95.161.72.0/21

Nine prefixes, all announced simultaneously at 16:14:49. Note the specificity: Reliance originated /23s under Telegram’s legitimate /22s, and /24s under the legitimate /23s. In BGP, a more-specific route wins. This is the prefix arms race Kentik described visually - and here it is in the raw update stream, with exact prefixes and timestamps.

Verify every hijacked prefix was RPKI-invalid

Validate each against AS18101:

for pf in 91.108.4.0/23 149.154.160.0/24 91.105.192.0/23; do
  monocle rpki validate $pf 18101 --format json-line 2>/dev/null \
    | grep '^{' \
    | jq -r --arg pf "$pf" '"\($pf) → \(.state): \(.reason)"'
done
91.108.4.0/23 invalid: ASN 18101 not authorized; authorized ASNs: 62041
149.154.160.0/24 invalid: ASN 18101 not authorized; authorized ASNs: 62041
91.105.192.0/23 invalid: ASN 18101 not authorized; authorized ASNs: 211157

Every hijacked prefix was RPKI-invalid. This is machine-verified evidence, not an inference. Any transit that carried these was, by definition, not enforcing ROV.

Step 3: Was the hijacked route actually selected in the routing table?

Announcements in BGP updates tell you what was advertised. To see what was actually installed in a router’s RIB at a given moment, use monocle rib. This is more expensive than search - always pass a collector filter.

$ monocle rib 1781627400 -p "91.105.192.0/23,91.108.4.0/22,149.154.160.0/22" \
    -s -S -c rrc14 --format json-line \
    | jq -r '"\(.prefix) origin=AS\(.origin_asns[0]) via \(.as_path)"' | sort -u
149.154.160.0/24 origin=AS18101 via 19151 15412 18101
149.154.161.0/24 origin=AS18101 via 19151 15412 18101
149.154.162.0/24 origin=AS18101 via 19151 15412 18101
149.154.163.0/24 origin=AS18101 via 19151 15412 18101
91.105.192.0/23  origin=AS18101 via 19151 15412 18101
91.108.4.0/23   origin=AS18101 via 19151 15412 18101
91.108.6.0/23   origin=AS18101 via 19151 15412 18101

At 16:30 UTC - sixteen minutes after the hijack began - rrc14’s peer AS19151 had AS18101 installed as the origin for all seven prefixes, all via FLAG. The hijacked route wasn’t just announced; it was the selected route in the RIB.

When did it end?

Check a later RIB snapshot:

$ monocle rib 1781683200 -p "91.105.192.0/23,91.108.4.0/22,149.154.160.0/22" \
    -s -S -c rrc14 --format json-line \
    | jq -r '"\(.prefix) origin=AS\(.origin_asns[0]) via \(.as_path)"' | sort -u
91.105.192.0/23 origin=AS211157 via 19151 9002 211157 211157

By 2026-06-17 08:00 UTC, the prefix is back to origin AS211157 (Telegram) - and notice the path changed from ... 15412 18101 to ... 9002 211157. The prefix is now reaching this vantage point via AS9002, not FLAG. The hijack was withdrawn sometime between 20:00 UTC on June 16 and 08:00 UTC on June 17. (Anurag’s update noted the prefix fading behind FLAG around 01:47 IST / 20:17 UTC on June 16.)

Step 4: Who propagated the invalid routes?

From the RIB and update data, the propagation path at rrc14 is consistently 19151 15412 18101 - FLAG (AS15412) was the transit. Confirm the relationship:

$ monocle as2rel 18101 15412 --format json | jq '.results[0]'
{"asn1":18101,"asn2":15412,"connected":"86.5%","peer":"61.6%","as2_upstream":"24.9%"}

FLAG is both a peer and an upstream of Reliance at high visibility. Since the announcement was RPKI-invalid (Step 1) yet FLAG carried it into the global table, FLAG was not enforcing ROV on this session. The same logic applies to Tata (AS4755), Reliance’s other transit, which appeared in paths at other collectors.

Hurricane Electric (AS6939) also appeared briefly in observed AS paths. In the UPDATE stream, HE propagated the invalid route for about 40 seconds and then withdrew it, consistent with an RPKI-invalid rejection path that was slower than the initial propagation. That is useful timing evidence, but it should not be read as a statement about traffic volume or broad customer impact.

Step 5: The RPKI timeline - did Telegram have ROAs before the incident?

A common pattern in BGP hijack history is reactive ROA creation: Twitter created ROAs for its routes after the 2021 Myanmar hijack, and that protection limited damage during the 2022 Russia incident. Was Telegram in the same reactive position, or did it already have ROAs?

The BGPKIT Alpha API provides historical ROA data with date_ranges arrays showing when each ROA was active:

$ curl -s "https://alpha.api.bgpkit.com/roas?asn=211157&date=2026-06-16" \
    | jq '.data[] | {prefix, date_ranges}'
{
  "prefix": "91.105.192.0/23",
  "date_ranges": [
    ["2023-09-23", "2025-05-04"],
    ["2025-05-06", "2026-06-12"],
    ["2026-06-14", "2026-06-20"]
  ]
}

Telegram’s ROA for 91.105.192.0/23 dates back to September 2023 - nearly three years before this incident. Unlike Twitter’s reactive adoption, Telegram had protection in place well ahead of time. This is why RPKI-RoV was able to limit the hijack’s global impact (Kentik reported only ~1.6% of their BGP sources saw the hijacked route). The protection was already there; the question was whether transits enforced it.

Step 6: What about the IPv6 blackholing story?

Anurag’s second post describes a separate phenomenon: Indian ISPs (TTSL AS45820, Lightstorm AS152144/AS135709) originating Telegram’s IPv6 prefix 2a0a:f280::/48 and its aggregate 2a0a:f280::/32, which he hypothesizes is a misapplied remote-triggered blackhole configuration.

For this post, the important question is not whether the RFC 7999 BLACKHOLE community (65535:666) appears. A standards-compliant blackhole route should be tightly scoped: section 3.2 says receivers should add NO_ADVERTISE or NO_EXPORT, and section 3.3 says the neighboring network must be authorized to advertise the covering prefix. TTSL and Lightstorm are not authorized for Telegram’s IPv6 space, so the observable BGP fact remains the same either way: these are unauthorized origins.

Bryton Herdes’ False immunity: long prefixes that bypass ROV talk is useful background on how TE and RTBH exceptions can create loose handling for long prefixes. But the public BGP evidence here cannot prove the operators’ internal mechanism; it can only show the invalid origins.

What is observable is that these ISPs are originating prefixes they do not own - RPKI-invalid for those origins. This is the same routing anomaly pattern as the Reliance Communications hijack, regardless of the mechanism. You can verify it the same way:

monocle pfx2as 2a0a:f280::/48 --show-name          # who announces it?
monocle rpki validate 2a0a:f280::/48 45820 --format json  # is TTSL authorized?

A secondary puzzle remains: if this is not standard RFC 7999 RTBH, what mechanism caused these ISPs to originate Telegram’s IPv6 prefixes? That question is worth investigating, but it is not something BGP UPDATE data alone can answer - it requires off-band information about the operators’ configuration practices.

Summary

Here is the complete evidence chain, reproducible from the commands above:

  1. Ground truth: 91.105.192.0/23 is RPKI-signed for AS211157 (Telegram). AS18101 (Reliance Communications) originating it is RPKI-invalid. (monocle rpki validate)
  2. The hijack: At 16:14:49 UTC on June 16, Reliance originated 9 Telegram prefixes (/23s and /24s under the legitimate aggregates), all via FLAG (AS15412). (monocle search -s -S -c rrc14)
  3. RIB confirmation: At 16:30 UTC, the hijacked routes were installed in rrc14’s RIB with AS18101 as origin. By 08:00 UTC on June 17, the prefix was back to AS211157. (monocle rib -c rrc14)
  4. ROV failure: FLAG (AS15412) and Tata (AS4755) carried RPKI-invalid announcements, so neither enforced ROV on those sessions. (monocle as2rel + propagation data)
  5. ROA timeline: Telegram’s ROAs date to September 2023 - protection was proactive, not reactive. (BGPKIT Alpha API)
  6. IPv6 anomaly: TTSL and Lightstorm originated Telegram’s IPv6 prefixes (2a0a:f280::/48 and aggregate 2a0a:f280::/32), RPKI-invalid for those origins. RFC 7999 section 3.3 describes blackhole prefixes as “typically as specific as possible, /32 for IPv4 or /128 for IPv6,” and section 3.3’s MUST conditions require the origin to be authorized - neither fits aggregates originated by an unauthorized AS.

Every command in this post has been run against live public data. That’s the point: BGP forensics doesn’t require a proprietary platform. monocle, the BGPKIT API, and public MRT archives are sufficient to investigate - and verify - any routing incident.

References