View Javadoc
1   /*
2    * Copyright 2020-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.software.os.mac;
6   
7   import static com.sun.jna.platform.mac.SystemB.INT_SIZE;
8   import static com.sun.jna.platform.mac.SystemB.PROC_ALL_PIDS;
9   import static oshi.jna.platform.mac.SystemB.AF_INET;
10  import static oshi.jna.platform.mac.SystemB.AF_INET6;
11  import static oshi.jna.platform.mac.SystemB.PROC_PIDFDSOCKETINFO;
12  import static oshi.jna.platform.mac.SystemB.PROC_PIDLISTFDS;
13  import static oshi.jna.platform.mac.SystemB.PROX_FDTYPE_SOCKET;
14  import static oshi.jna.platform.mac.SystemB.SOCKINFO_IN;
15  import static oshi.jna.platform.mac.SystemB.SOCKINFO_TCP;
16  import static oshi.software.os.InternetProtocolStats.TcpState.CLOSED;
17  import static oshi.software.os.InternetProtocolStats.TcpState.CLOSE_WAIT;
18  import static oshi.software.os.InternetProtocolStats.TcpState.CLOSING;
19  import static oshi.software.os.InternetProtocolStats.TcpState.ESTABLISHED;
20  import static oshi.software.os.InternetProtocolStats.TcpState.FIN_WAIT_1;
21  import static oshi.software.os.InternetProtocolStats.TcpState.FIN_WAIT_2;
22  import static oshi.software.os.InternetProtocolStats.TcpState.LAST_ACK;
23  import static oshi.software.os.InternetProtocolStats.TcpState.LISTEN;
24  import static oshi.software.os.InternetProtocolStats.TcpState.NONE;
25  import static oshi.software.os.InternetProtocolStats.TcpState.SYN_RECV;
26  import static oshi.software.os.InternetProtocolStats.TcpState.SYN_SENT;
27  import static oshi.software.os.InternetProtocolStats.TcpState.TIME_WAIT;
28  import static oshi.software.os.InternetProtocolStats.TcpState.UNKNOWN;
29  import static oshi.util.Memoizer.defaultExpiration;
30  import static oshi.util.Memoizer.memoize;
31  
32  import java.util.ArrayList;
33  import java.util.List;
34  import java.util.function.Supplier;
35  
36  import com.sun.jna.Memory;
37  
38  import oshi.annotation.concurrent.ThreadSafe;
39  import oshi.driver.unix.NetStat;
40  import oshi.jna.platform.mac.SystemB;
41  import oshi.jna.platform.mac.SystemB.InSockInfo;
42  import oshi.jna.platform.mac.SystemB.ProcFdInfo;
43  import oshi.jna.platform.mac.SystemB.SocketFdInfo;
44  import oshi.jna.platform.unix.CLibrary.BsdIp6stat;
45  import oshi.jna.platform.unix.CLibrary.BsdIpstat;
46  import oshi.jna.platform.unix.CLibrary.BsdTcpstat;
47  import oshi.jna.platform.unix.CLibrary.BsdUdpstat;
48  import oshi.software.common.AbstractInternetProtocolStats;
49  import oshi.util.ParseUtil;
50  import oshi.util.platform.mac.SysctlUtil;
51  import oshi.util.tuples.Pair;
52  
53  /**
54   * Internet Protocol Stats implementation
55   */
56  @ThreadSafe
57  public class MacInternetProtocolStats extends AbstractInternetProtocolStats {
58  
59      private boolean isElevated;
60  
61      public MacInternetProtocolStats(boolean elevated) {
62          this.isElevated = elevated;
63      }
64  
65      private Supplier<Pair<Long, Long>> establishedv4v6 = memoize(NetStat::queryTcpnetstat, defaultExpiration());
66      private Supplier<BsdTcpstat> tcpstat = memoize(MacInternetProtocolStats::queryTcpstat, defaultExpiration());
67      private Supplier<BsdUdpstat> udpstat = memoize(MacInternetProtocolStats::queryUdpstat, defaultExpiration());
68      // With elevated permissions use tcpstat only
69      // Backup estimate get ipstat and subtract off udp
70      private Supplier<BsdIpstat> ipstat = memoize(MacInternetProtocolStats::queryIpstat, defaultExpiration());
71      private Supplier<BsdIp6stat> ip6stat = memoize(MacInternetProtocolStats::queryIp6stat, defaultExpiration());
72  
73      @Override
74      public TcpStats getTCPv4Stats() {
75          BsdTcpstat tcp = tcpstat.get();
76          if (this.isElevated) {
77              return new TcpStats(establishedv4v6.get().getA(), ParseUtil.unsignedIntToLong(tcp.tcps_connattempt),
78                      ParseUtil.unsignedIntToLong(tcp.tcps_accepts), ParseUtil.unsignedIntToLong(tcp.tcps_conndrops),
79                      ParseUtil.unsignedIntToLong(tcp.tcps_drops), ParseUtil.unsignedIntToLong(tcp.tcps_sndpack),
80                      ParseUtil.unsignedIntToLong(tcp.tcps_rcvpack), ParseUtil.unsignedIntToLong(tcp.tcps_sndrexmitpack),
81                      ParseUtil.unsignedIntToLong(
82                              tcp.tcps_rcvbadsum + tcp.tcps_rcvbadoff + tcp.tcps_rcvmemdrop + tcp.tcps_rcvshort),
83                      0L);
84          }
85          BsdIpstat ip = ipstat.get();
86          BsdUdpstat udp = udpstat.get();
87          return new TcpStats(establishedv4v6.get().getA(), ParseUtil.unsignedIntToLong(tcp.tcps_connattempt),
88                  ParseUtil.unsignedIntToLong(tcp.tcps_accepts), ParseUtil.unsignedIntToLong(tcp.tcps_conndrops),
89                  ParseUtil.unsignedIntToLong(tcp.tcps_drops),
90                  Math.max(0L, ParseUtil.unsignedIntToLong(ip.ips_delivered - udp.udps_opackets)),
91                  Math.max(0L, ParseUtil.unsignedIntToLong(ip.ips_total - udp.udps_ipackets)),
92                  ParseUtil.unsignedIntToLong(tcp.tcps_sndrexmitpack),
93                  Math.max(0L, ParseUtil.unsignedIntToLong(ip.ips_badsum + ip.ips_tooshort + ip.ips_toosmall
94                          + ip.ips_badhlen + ip.ips_badlen - udp.udps_hdrops + udp.udps_badsum + udp.udps_badlen)),
95                  0L);
96      }
97  
98      @Override
99      public TcpStats getTCPv6Stats() {
100         BsdIp6stat ip6 = ip6stat.get();
101         BsdUdpstat udp = udpstat.get();
102         return new TcpStats(establishedv4v6.get().getB(), 0L, 0L, 0L, 0L,
103                 ip6.ip6s_localout - ParseUtil.unsignedIntToLong(udp.udps_snd6_swcsum),
104                 ip6.ip6s_total - ParseUtil.unsignedIntToLong(udp.udps_rcv6_swcsum), 0L, 0L, 0L);
105     }
106 
107     @Override
108     public UdpStats getUDPv4Stats() {
109         BsdUdpstat stat = udpstat.get();
110         return new UdpStats(ParseUtil.unsignedIntToLong(stat.udps_opackets),
111                 ParseUtil.unsignedIntToLong(stat.udps_ipackets), ParseUtil.unsignedIntToLong(stat.udps_noportmcast),
112                 ParseUtil.unsignedIntToLong(stat.udps_hdrops + stat.udps_badsum + stat.udps_badlen));
113     }
114 
115     @Override
116     public UdpStats getUDPv6Stats() {
117         BsdUdpstat stat = udpstat.get();
118         return new UdpStats(ParseUtil.unsignedIntToLong(stat.udps_snd6_swcsum),
119                 ParseUtil.unsignedIntToLong(stat.udps_rcv6_swcsum), 0L, 0L);
120     }
121 
122     @Override
123     public List<IPConnection> getConnections() {
124         List<IPConnection> conns = new ArrayList<>();
125         int[] pids = new int[1024];
126         int numberOfProcesses = SystemB.INSTANCE.proc_listpids(PROC_ALL_PIDS, 0, pids, pids.length * INT_SIZE)
127                 / INT_SIZE;
128         for (int i = 0; i < numberOfProcesses; i++) {
129             // Handle off-by-one bug in proc_listpids where the size returned
130             // is: SystemB.INT_SIZE * (pids + 1)
131             if (pids[i] > 0) {
132                 for (Integer fd : queryFdList(pids[i])) {
133                     IPConnection ipc = queryIPConnection(pids[i], fd);
134                     if (ipc != null) {
135                         conns.add(ipc);
136                     }
137                 }
138             }
139         }
140         return conns;
141     }
142 
143     private static List<Integer> queryFdList(int pid) {
144         List<Integer> fdList = new ArrayList<>();
145         int bufferSize = SystemB.INSTANCE.proc_pidinfo(pid, PROC_PIDLISTFDS, 0, null, 0);
146         if (bufferSize > 0) {
147             ProcFdInfo fdInfo = new ProcFdInfo();
148             int numStructs = bufferSize / fdInfo.size();
149             ProcFdInfo[] fdArray = (ProcFdInfo[]) fdInfo.toArray(numStructs);
150             bufferSize = SystemB.INSTANCE.proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fdArray[0], bufferSize);
151             numStructs = bufferSize / fdInfo.size();
152             for (int i = 0; i < numStructs; i++) {
153                 if (fdArray[i].proc_fdtype == PROX_FDTYPE_SOCKET) {
154                     fdList.add(fdArray[i].proc_fd);
155                 }
156             }
157         }
158         return fdList;
159     }
160 
161     private static IPConnection queryIPConnection(int pid, int fd) {
162         try (SocketFdInfo si = new SocketFdInfo()) {
163             int ret = SystemB.INSTANCE.proc_pidfdinfo(pid, fd, PROC_PIDFDSOCKETINFO, si, si.size());
164             if (si.size() == ret && si.psi.soi_family == AF_INET || si.psi.soi_family == AF_INET6) {
165                 InSockInfo ini;
166                 String type;
167                 TcpState state;
168                 if (si.psi.soi_kind == SOCKINFO_TCP) {
169                     si.psi.soi_proto.setType("pri_tcp");
170                     si.psi.soi_proto.read();
171                     ini = si.psi.soi_proto.pri_tcp.tcpsi_ini;
172                     state = stateLookup(si.psi.soi_proto.pri_tcp.tcpsi_state);
173                     type = "tcp";
174                 } else if (si.psi.soi_kind == SOCKINFO_IN) {
175                     si.psi.soi_proto.setType("pri_in");
176                     si.psi.soi_proto.read();
177                     ini = si.psi.soi_proto.pri_in;
178                     state = NONE;
179                     type = "udp";
180                 } else {
181                     return null;
182                 }
183 
184                 byte[] laddr;
185                 byte[] faddr;
186                 if (ini.insi_vflag == 1) {
187                     laddr = ParseUtil.parseIntToIP(ini.insi_laddr[3]);
188                     faddr = ParseUtil.parseIntToIP(ini.insi_faddr[3]);
189                     type += "4";
190                 } else if (ini.insi_vflag == 2) {
191                     laddr = ParseUtil.parseIntArrayToIP(ini.insi_laddr);
192                     faddr = ParseUtil.parseIntArrayToIP(ini.insi_faddr);
193                     type += "6";
194                 } else if (ini.insi_vflag == 3) {
195                     laddr = ParseUtil.parseIntToIP(ini.insi_laddr[3]);
196                     faddr = ParseUtil.parseIntToIP(ini.insi_faddr[3]);
197                     type += "46";
198                 } else {
199                     return null;
200                 }
201                 int lport = ParseUtil.bigEndian16ToLittleEndian(ini.insi_lport);
202                 int fport = ParseUtil.bigEndian16ToLittleEndian(ini.insi_fport);
203                 return new IPConnection(type, laddr, lport, faddr, fport, state, si.psi.soi_qlen, si.psi.soi_incqlen,
204                         pid);
205             }
206         }
207         return null;
208     }
209 
210     private static TcpState stateLookup(int state) {
211         switch (state) {
212         case 0:
213             return CLOSED;
214         case 1:
215             return LISTEN;
216         case 2:
217             return SYN_SENT;
218         case 3:
219             return SYN_RECV;
220         case 4:
221             return ESTABLISHED;
222         case 5:
223             return CLOSE_WAIT;
224         case 6:
225             return FIN_WAIT_1;
226         case 7:
227             return CLOSING;
228         case 8:
229             return LAST_ACK;
230         case 9:
231             return FIN_WAIT_2;
232         case 10:
233             return TIME_WAIT;
234         default:
235             return UNKNOWN;
236         }
237     }
238 
239     /*
240      * There are multiple versions of some tcp/udp/ip stats structures in macOS. Since we only need a few of the
241      * hundreds of fields, we can improve performance by selectively reading the ints from the appropriate offsets,
242      * which are consistent across the structure.
243      */
244 
245     private static BsdTcpstat queryTcpstat() {
246         BsdTcpstat mt = new BsdTcpstat();
247         try (Memory m = SysctlUtil.sysctl("net.inet.tcp.stats")) {
248             if (m != null && m.size() >= 128) {
249                 mt.tcps_connattempt = m.getInt(0);
250                 mt.tcps_accepts = m.getInt(4);
251                 mt.tcps_drops = m.getInt(12);
252                 mt.tcps_conndrops = m.getInt(16);
253                 mt.tcps_sndpack = m.getInt(64);
254                 mt.tcps_sndrexmitpack = m.getInt(72);
255                 mt.tcps_rcvpack = m.getInt(104);
256                 mt.tcps_rcvbadsum = m.getInt(112);
257                 mt.tcps_rcvbadoff = m.getInt(116);
258                 mt.tcps_rcvmemdrop = m.getInt(120);
259                 mt.tcps_rcvshort = m.getInt(124);
260             }
261         }
262         return mt;
263     }
264 
265     private static BsdIpstat queryIpstat() {
266         BsdIpstat mi = new BsdIpstat();
267         try (Memory m = SysctlUtil.sysctl("net.inet.ip.stats")) {
268             if (m != null && m.size() >= 60) {
269                 mi.ips_total = m.getInt(0);
270                 mi.ips_badsum = m.getInt(4);
271                 mi.ips_tooshort = m.getInt(8);
272                 mi.ips_toosmall = m.getInt(12);
273                 mi.ips_badhlen = m.getInt(16);
274                 mi.ips_badlen = m.getInt(20);
275                 mi.ips_delivered = m.getInt(56);
276             }
277         }
278         return mi;
279     }
280 
281     private static BsdIp6stat queryIp6stat() {
282         BsdIp6stat mi6 = new BsdIp6stat();
283         try (Memory m = SysctlUtil.sysctl("net.inet6.ip6.stats")) {
284             if (m != null && m.size() >= 96) {
285                 mi6.ip6s_total = m.getLong(0);
286                 mi6.ip6s_localout = m.getLong(88);
287             }
288         }
289         return mi6;
290     }
291 
292     public static BsdUdpstat queryUdpstat() {
293         BsdUdpstat ut = new BsdUdpstat();
294         try (Memory m = SysctlUtil.sysctl("net.inet.udp.stats")) {
295             if (m != null && m.size() >= 1644) {
296                 ut.udps_ipackets = m.getInt(0);
297                 ut.udps_hdrops = m.getInt(4);
298                 ut.udps_badsum = m.getInt(8);
299                 ut.udps_badlen = m.getInt(12);
300                 ut.udps_opackets = m.getInt(36);
301                 ut.udps_noportmcast = m.getInt(48);
302                 ut.udps_rcv6_swcsum = m.getInt(64);
303                 ut.udps_snd6_swcsum = m.getInt(80);
304             }
305         }
306         return ut;
307     }
308 }