1- // Query tethering offload statistics from the hardware offload HAL .
1+ // Query tethering and network interface status via INetworkManagementService .
22//
3- // When SoftAP is active, the tethering offload HAL accelerates packet
4- // forwarding in hardware. This example shows how to query forwarded
5- // traffic statistics for each upstream interface.
3+ // Uses the "network_management" system service to list network interfaces,
4+ // check tethering status, and query interface configurations.
5+ // Replaces the previous vendor offload HAL approach with system-level APIs.
6+ //
7+ // Note: Some tethering methods were removed in Android API 36+.
68//
79// Build:
810//
9- // GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o build/softap_tether_offload ./examples/softap_tether_offload/
11+ // GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o build/softap_tether_offload ./examples/softap_tether_offload/
1012// adb push softap_tether_offload /data/local/tmp/ && adb shell /data/local/tmp/softap_tether_offload
1113package main
1214
@@ -15,10 +17,9 @@ import (
1517 "fmt"
1618 "os"
1719
20+ genOs "github.com/xaionaro-go/binder/android/os"
1821 "github.com/xaionaro-go/binder/binder"
1922 "github.com/xaionaro-go/binder/binder/versionaware"
20- genOs "github.com/xaionaro-go/binder/android/os"
21- "github.com/xaionaro-go/binder/android/hardware/tetheroffload"
2223 "github.com/xaionaro-go/binder/kernelbinder"
2324 "github.com/xaionaro-go/binder/servicemanager"
2425)
@@ -41,43 +42,123 @@ func main() {
4142
4243 sm := servicemanager .New (transport )
4344
44- // First list network interfaces to know which upstreams exist.
45- netSvc , err := sm .GetService (ctx , servicemanager .NetworkmanagementService )
45+ svc , err := sm .GetService (ctx , servicemanager .NetworkmanagementService )
4646 if err != nil {
47- fmt .Fprintf (os .Stderr , "get network_management: %v\n " , err )
48- } else {
49- netMgr := genOs .NewNetworkManagementServiceProxy (netSvc )
47+ fmt .Fprintf (os .Stderr , "get network_management service: %v\n " , err )
48+ os .Exit (1 )
49+ }
50+
51+ netMgr := genOs .NewNetworkManagementServiceProxy (svc )
5052
51- ifaces , err := netMgr .ListInterfaces (ctx )
52- if err != nil {
53- fmt .Fprintf (os .Stderr , "ListInterfaces: %v\n " , err )
54- } else {
55- fmt .Printf ("Network interfaces: %v\n \n " , ifaces )
53+ // List all network interfaces.
54+ ifaces , err := netMgr .ListInterfaces (ctx )
55+ if err != nil {
56+ fmt .Fprintf (os .Stderr , "ListInterfaces: %v\n " , err )
57+ } else {
58+ fmt .Printf ("Network interfaces (%d):\n " , len (ifaces ))
59+ for _ , iface := range ifaces {
60+ fmt .Printf (" %s\n " , iface )
5661 }
5762 }
5863
59- // Try to access the tethering offload HAL.
60- offloadSvc , err := sm .GetService (ctx , servicemanager .ServiceName ("android.hardware.tetheroffload.IOffload/default" ))
64+ // Check tethering status (may be removed in API 36+).
65+ fmt .Println ()
66+ tethering , err := netMgr .IsTetheringStarted (ctx )
67+ if err != nil {
68+ fmt .Fprintf (os .Stderr , "IsTetheringStarted: %v\n " , err )
69+ fmt .Fprintf (os .Stderr , " (this method was removed in Android API 36)\n " )
70+ } else {
71+ fmt .Printf ("Tethering active: %v\n " , tethering )
72+ }
73+
74+ // List tethered interfaces (may be removed in API 36+).
75+ tethered , err := netMgr .ListTetheredInterfaces (ctx )
76+ if err != nil {
77+ fmt .Fprintf (os .Stderr , "ListTetheredInterfaces: %v\n " , err )
78+ fmt .Fprintf (os .Stderr , " (this method was removed in Android API 36)\n " )
79+ } else if len (tethered ) == 0 {
80+ fmt .Println ("Tethered interfaces: (none)" )
81+ } else {
82+ fmt .Printf ("Tethered interfaces: %v\n " , tethered )
83+ }
84+
85+ // Check bandwidth control.
86+ fmt .Println ()
87+ bwCtrl , err := netMgr .IsBandwidthControlEnabled (ctx )
88+ if err != nil {
89+ fmt .Fprintf (os .Stderr , "IsBandwidthControlEnabled: %v\n " , err )
90+ } else {
91+ fmt .Printf ("Bandwidth control enabled: %v\n " , bwCtrl )
92+ }
93+
94+ // Check IP forwarding (may be removed in API 36+).
95+ fwd , err := netMgr .GetIpForwardingEnabled (ctx )
96+ if err != nil {
97+ fmt .Fprintf (os .Stderr , "GetIpForwardingEnabled: %v\n " , err )
98+ } else {
99+ fmt .Printf ("IP forwarding enabled: %v\n " , fwd )
100+ }
101+
102+ // Try to read network statistics from procfs as a complement.
103+ fmt .Println ()
104+ printNetStats (ifaces )
105+ }
106+
107+ // printNetStats reads basic traffic counters from /proc/net/dev for the
108+ // given interfaces.
109+ func printNetStats (ifaces []string ) {
110+ data , err := os .ReadFile ("/proc/net/dev" )
61111 if err != nil {
62- fmt .Fprintf (os .Stderr , "get tether offload HAL: %v\n " , err )
63- fmt .Fprintf (os .Stderr , "(offload HAL not available — no hardware offload support or SELinux denial)\n " )
64- fmt .Fprintf (os .Stderr , "\n On devices with offload support, this queries forwarded traffic stats:\n " )
65- fmt .Fprintf (os .Stderr , " offload.GetForwardedStats(ctx, \" wlan0\" ) → {RxBytes, TxBytes}\n " )
66- os .Exit (0 )
112+ fmt .Fprintf (os .Stderr , "read /proc/net/dev: %v\n " , err )
113+ return
67114 }
68115
69- offload := tetheroffload .NewOffloadProxy (offloadSvc )
116+ // Build a set of interfaces we care about.
117+ ifaceSet := make (map [string ]bool , len (ifaces ))
118+ for _ , iface := range ifaces {
119+ ifaceSet [iface ] = true
120+ }
70121
71- // Query forwarded stats for common upstream interfaces.
72- upstreams := []string {"wlan0" , "eth0" , "rmnet0" , "rmnet_data0" }
122+ fmt .Println ("Network traffic statistics:" )
123+ fmt .Printf (" %-15s %15s %15s\n " , "Interface" , "RX bytes" , "TX bytes" )
124+ fmt .Printf (" %-15s %15s %15s\n " , "---------" , "--------" , "--------" )
73125
74- fmt .Println ("Tethering offload forwarded traffic:" )
75- for _ , iface := range upstreams {
76- stats , err := offload .GetForwardedStats (ctx , iface )
77- if err != nil {
126+ lines := splitLines (string (data ))
127+ for _ , line := range lines [2 :] { // skip header lines
128+ var iface string
129+ var rxBytes , txBytes int64
130+ var dummy int64
131+ // Format: iface: rx_bytes rx_packets ... tx_bytes tx_packets ...
132+ n , _ := fmt .Sscanf (line , " %s %d %d %d %d %d %d %d %d %d" ,
133+ & iface , & rxBytes , & dummy , & dummy , & dummy , & dummy , & dummy , & dummy , & dummy , & txBytes )
134+ if n < 10 {
78135 continue
79136 }
80- fmt .Printf (" %-15s RX: %d bytes TX: %d bytes\n " ,
81- iface , stats .RxBytes , stats .TxBytes )
137+ // Remove trailing colon from iface name.
138+ if len (iface ) > 0 && iface [len (iface )- 1 ] == ':' {
139+ iface = iface [:len (iface )- 1 ]
140+ }
141+ if ! ifaceSet [iface ] {
142+ continue
143+ }
144+ if rxBytes == 0 && txBytes == 0 {
145+ continue
146+ }
147+ fmt .Printf (" %-15s %15d %15d\n " , iface , rxBytes , txBytes )
148+ }
149+ }
150+
151+ func splitLines (s string ) []string {
152+ var lines []string
153+ start := 0
154+ for i := 0 ; i < len (s ); i ++ {
155+ if s [i ] == '\n' {
156+ lines = append (lines , s [start :i ])
157+ start = i + 1
158+ }
159+ }
160+ if start < len (s ) {
161+ lines = append (lines , s [start :])
82162 }
163+ return lines
83164}
0 commit comments