View Javadoc
1   /*
2    * Copyright 2020-2022 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.driver.unix;
6   
7   import java.net.InetAddress;
8   import java.net.UnknownHostException;
9   import java.util.ArrayList;
10  import java.util.List;
11  
12  import oshi.annotation.concurrent.ThreadSafe;
13  import oshi.software.os.InternetProtocolStats.IPConnection;
14  import oshi.software.os.InternetProtocolStats.TcpState;
15  import oshi.software.os.InternetProtocolStats.TcpStats;
16  import oshi.software.os.InternetProtocolStats.UdpStats;
17  import oshi.util.ExecutingCommand;
18  import oshi.util.ParseUtil;
19  import oshi.util.tuples.Pair;
20  
21  /**
22   * Utility to query TCP connections
23   */
24  @ThreadSafe
25  public final class NetStat {
26  
27      private NetStat() {
28      }
29  
30      /**
31       * Query netstat to obtain number of established TCP connections
32       *
33       * @return A pair with number of established IPv4 and IPv6 connections
34       */
35      public static Pair<Long, Long> queryTcpnetstat() {
36          long tcp4 = 0L;
37          long tcp6 = 0L;
38          List<String> activeConns = ExecutingCommand.runNative("netstat -n -p tcp");
39          for (String s : activeConns) {
40              if (s.endsWith("ESTABLISHED")) {
41                  if (s.startsWith("tcp4")) {
42                      tcp4++;
43                  } else if (s.startsWith("tcp6")) {
44                      tcp6++;
45                  }
46              }
47          }
48          return new Pair<>(tcp4, tcp6);
49      }
50  
51      /**
52       * Query netstat to all TCP and UDP connections
53       *
54       * @return A list of TCP and UDP connections
55       */
56      public static List<IPConnection> queryNetstat() {
57          List<IPConnection> connections = new ArrayList<>();
58          List<String> activeConns = ExecutingCommand.runNative("netstat -n");
59          for (String s : activeConns) {
60              String[] split = null;
61              if (s.startsWith("tcp") || s.startsWith("udp")) {
62                  split = ParseUtil.whitespaces.split(s);
63                  if (split.length >= 5) {
64                      String state = (split.length == 6) ? split[5] : null;
65                      // Substitution if required
66                      if ("SYN_RCVD".equals(state)) {
67                          state = "SYN_RECV";
68                      }
69                      String type = split[0];
70                      Pair<byte[], Integer> local = parseIP(split[3]);
71                      Pair<byte[], Integer> foreign = parseIP(split[4]);
72                      connections.add(new IPConnection(type, local.getA(), local.getB(), foreign.getA(), foreign.getB(),
73                              state == null ? TcpState.NONE : TcpState.valueOf(state),
74                              ParseUtil.parseIntOrDefault(split[2], 0), ParseUtil.parseIntOrDefault(split[1], 0), -1));
75                  }
76              }
77          }
78          return connections;
79      }
80  
81      private static Pair<byte[], Integer> parseIP(String s) {
82          // 73.169.134.6.9599 to 73.169.134.6 port 9599
83          // or
84          // 2001:558:600a:a5.123 to 2001:558:600a:a5 port 123
85          int portPos = s.lastIndexOf('.');
86          if (portPos > 0 && s.length() > portPos) {
87              int port = ParseUtil.parseIntOrDefault(s.substring(portPos + 1), 0);
88              String ip = s.substring(0, portPos);
89              try {
90                  // Try to parse existing IP
91                  return new Pair<>(InetAddress.getByName(ip).getAddress(), port);
92              } catch (UnknownHostException e) {
93                  try {
94                      // Try again with trailing ::
95                      if (ip.endsWith(":") && ip.contains("::")) {
96                          ip = ip + "0";
97                      } else if (ip.endsWith(":") || ip.contains("::")) {
98                          ip = ip + ":0";
99                      } else {
100                         ip = ip + "::0";
101                     }
102                     return new Pair<>(InetAddress.getByName(ip).getAddress(), port);
103                 } catch (UnknownHostException e2) {
104                     return new Pair<>(new byte[0], port);
105                 }
106             }
107         }
108         return new Pair<>(new byte[0], 0);
109     }
110 
111     /**
112      * Gets TCP stats via {@code netstat -s}. Used for Linux and OpenBSD formats
113      *
114      * @param netstatStr The command string
115      * @return The statistics
116      */
117     public static TcpStats queryTcpStats(String netstatStr) {
118         long connectionsEstablished = 0;
119         long connectionsActive = 0;
120         long connectionsPassive = 0;
121         long connectionFailures = 0;
122         long connectionsReset = 0;
123         long segmentsSent = 0;
124         long segmentsReceived = 0;
125         long segmentsRetransmitted = 0;
126         long inErrors = 0;
127         long outResets = 0;
128         List<String> netstat = ExecutingCommand.runNative(netstatStr);
129         for (String s : netstat) {
130             String[] split = s.trim().split(" ", 2);
131             if (split.length == 2) {
132                 switch (split[1]) {
133                 case "connections established":
134                 case "connection established (including accepts)":
135                 case "connections established (including accepts)":
136                     connectionsEstablished = ParseUtil.parseLongOrDefault(split[0], 0L);
137                     break;
138                 case "active connection openings":
139                     connectionsActive = ParseUtil.parseLongOrDefault(split[0], 0L);
140                     break;
141                 case "passive connection openings":
142                     connectionsPassive = ParseUtil.parseLongOrDefault(split[0], 0L);
143                     break;
144                 case "failed connection attempts":
145                 case "bad connection attempts":
146                     connectionFailures = ParseUtil.parseLongOrDefault(split[0], 0L);
147                     break;
148                 case "connection resets received":
149                 case "dropped due to RST":
150                     connectionsReset = ParseUtil.parseLongOrDefault(split[0], 0L);
151                     break;
152                 case "segments sent out":
153                 case "packet sent":
154                 case "packets sent":
155                     segmentsSent = ParseUtil.parseLongOrDefault(split[0], 0L);
156                     break;
157                 case "segments received":
158                 case "packet received":
159                 case "packets received":
160                     segmentsReceived = ParseUtil.parseLongOrDefault(split[0], 0L);
161                     break;
162                 case "segments retransmitted":
163                     segmentsRetransmitted = ParseUtil.parseLongOrDefault(split[0], 0L);
164                     break;
165                 case "bad segments received":
166                 case "discarded for bad checksum":
167                 case "discarded for bad checksums":
168                 case "discarded for bad header offset field":
169                 case "discarded for bad header offset fields":
170                 case "discarded because packet too short":
171                 case "discarded for missing IPsec protection":
172                     inErrors += ParseUtil.parseLongOrDefault(split[0], 0L);
173                     break;
174                 case "resets sent":
175                     outResets = ParseUtil.parseLongOrDefault(split[0], 0L);
176                     break;
177                 default:
178                     // handle special case variable strings
179                     if (split[1].contains("retransmitted") && split[1].contains("data packet")) {
180                         segmentsRetransmitted += ParseUtil.parseLongOrDefault(split[0], 0L);
181                     }
182                     break;
183                 }
184 
185             }
186 
187         }
188         return new TcpStats(connectionsEstablished, connectionsActive, connectionsPassive, connectionFailures,
189                 connectionsReset, segmentsSent, segmentsReceived, segmentsRetransmitted, inErrors, outResets);
190     }
191 
192     /**
193      * Gets UDP stats via {@code netstat -s}. Used for Linux and OpenBSD formats
194      *
195      * @param netstatStr The command string
196      * @return The statistics
197      */
198     public static UdpStats queryUdpStats(String netstatStr) {
199         long datagramsSent = 0;
200         long datagramsReceived = 0;
201         long datagramsNoPort = 0;
202         long datagramsReceivedErrors = 0;
203         List<String> netstat = ExecutingCommand.runNative(netstatStr);
204         for (String s : netstat) {
205             String[] split = s.trim().split(" ", 2);
206             if (split.length == 2) {
207                 switch (split[1]) {
208                 case "packets sent":
209                 case "datagram output":
210                 case "datagrams output":
211                     datagramsSent = ParseUtil.parseLongOrDefault(split[0], 0L);
212                     break;
213                 case "packets received":
214                 case "datagram received":
215                 case "datagrams received":
216                     datagramsReceived = ParseUtil.parseLongOrDefault(split[0], 0L);
217                     break;
218                 case "packets to unknown port received":
219                 case "dropped due to no socket":
220                 case "broadcast/multicast datagram dropped due to no socket":
221                 case "broadcast/multicast datagrams dropped due to no socket":
222                     datagramsNoPort += ParseUtil.parseLongOrDefault(split[0], 0L);
223                     break;
224                 case "packet receive errors":
225                 case "with incomplete header":
226                 case "with bad data length field":
227                 case "with bad checksum":
228                 case "woth no checksum":
229                     datagramsReceivedErrors += ParseUtil.parseLongOrDefault(split[0], 0L);
230                     break;
231                 default:
232                     break;
233                 }
234             }
235         }
236         return new UdpStats(datagramsSent, datagramsReceived, datagramsNoPort, datagramsReceivedErrors);
237     }
238 }