Skip to content

Count IPv6 traffic in network protocol statistics - #969

Merged
JaewonHur merged 3 commits into
apple:mainfrom
JaewonHur:support-ipv6
Oct 16, 2026
Merged

JaewonHur merged 3 commits into
apple:mainfrom
JaewonHur:support-ipv6

Conversation

@JaewonHur

@JaewonHur JaewonHur commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

The ip, udp and icmp protocol counters were read only from /proc/net/snmp, which is IPv4-only, so IPv6 traffic was never counted. In a dual-stack setup, TCP over IPv6 didn't move the ip byte counters, and IPv6 UDP datagrams and ping6 didn't move the udp and icmp ones.

Also read /proc//net/snmp6 and add the Ip6, Udp6 and Icmp6 counters to their IPv4 counterparts: packets, ip octets (Ip6InOctets/Ip6OutOctets), and the matching drop and error counters so they cover the same traffic as the packet counts. TCP is unchanged because the kernel's Tcp counters already cover both families. If snmp6 is missing (IPv6 disabled in the guest), only the IPv4 counters are used.

Add unit tests for merging IPv4 and IPv6 counters, IPv6-only input and a missing snmp6.

The ip, udp and icmp protocol counters were read only from
/proc/net/snmp, which is IPv4-only, so IPv6 traffic was never counted.
In a dual-stack pod, TCP over IPv6 didn't move the ip byte counters,
and IPv6 UDP datagrams and ping6 didn't move the udp and icmp ones.

Also read /proc/<pid>/net/snmp6 and add the Ip6, Udp6 and Icmp6
counters to their IPv4 counterparts: packets, ip octets
(Ip6InOctets/Ip6OutOctets), and the matching drop and error counters
so they cover the same traffic as the packet counts. TCP is unchanged
because the kernel's Tcp counters already cover both families. If
snmp6 is missing (IPv6 disabled in the guest), only the IPv4 counters
are used.

Add unit tests for merging IPv4 and IPv6 counters, IPv6-only input and
a missing snmp6.
Comment on lines +70 to +74
/// `snmp` and `netstat` alternate a header line (`Proto: name name ...`) with a value line
/// (`Proto: 1 2 ...`). `snmp6` has one `Name value` pair per line. Fields are matched by
/// name so new kernel columns don't break parsing. IPv6 counters from `snmp6` are kept apart
/// from the IPv4 ones in `udp6`, `icmp6` and `ip6`, and stay `nil` when `snmp6` is absent.
public init(snmp: String, netstat: String? = nil, snmp6: String? = nil) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: This comment wording is very confusing to me

Comment on lines +137 to +141
ip6.receivedPackets = ip6Table["InReceives"] ?? 0
ip6.transmittedPackets = ip6Table["OutRequests"] ?? 0
ip6.receivedBytes = ip6Table["InOctets"] ?? 0
ip6.transmittedBytes = ip6Table["OutOctets"] ?? 0
ip6.receivedDrops = sum(ip6Table, "InDiscards", "InNoRoutes")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why are some of these optional but when we sum they are not?

Comment on lines +163 to +169
/// ```
/// Ip6InReceives 50
/// Ip6InOctets 1000
/// Udp6InDatagrams 60
/// Udp6NoPorts 5
/// Icmp6InMsgs 6
/// ```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nice!

// The same mapping as IPv4, but every IPv6 counter is in snmp6, including octets and InNoRoutes.
let udp6Table = Self.snmp6Fields(snmp6, prefix: "Udp6")
var udp6 = Counters()
udp6.receivedPackets = udp6Table["InDatagrams"] ?? 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think in the future, we should make keyed constants like this enum values

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agree!

Comment on lines +126 to +130
let s = ProcNetSnmp(snmp: Self.snmp, netstat: Self.netstat, snmp6: Self.snmp6)
// The IPv4 counters are unchanged by snmp6.
#expect(s.ip == ProcNetSnmp(snmp: Self.snmp, netstat: Self.netstat).ip)
#expect(s.udp == ProcNetSnmp(snmp: Self.snmp, netstat: Self.netstat).udp)
#expect(s.icmp == ProcNetSnmp(snmp: Self.snmp, netstat: Self.netstat).icmp)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Unchanged why? Do we change anything in this test?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It was trying to say passing snmp6: Self.snmp6 above should not change ipv4 values like s.ip below. Let me make it clear.

@JaewonHur
JaewonHur merged commit 6adea56 into apple:main Oct 16, 2026
5 checks passed
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.

2 participants