Release 1.5.29.
[wine] / programs / netstat / netstat.c
1 /*
2  * Copyright 2011-2013 AndrĂ© Hentschel
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18
19 #define NONAMELESSUNION
20 #include "netstat.h"
21 #include <winsock2.h>
22 #include <iphlpapi.h>
23 #include "wine/unicode.h"
24 #include "wine/debug.h"
25
26 WINE_DEFAULT_DEBUG_CHANNEL(netstat);
27
28 static const WCHAR ipW[] = {'I', 'P', 0};
29 static const WCHAR ipv6W[] = {'I', 'P', 'v', '6', 0};
30 static const WCHAR icmpW[] = {'I', 'C', 'M', 'P', 0};
31 static const WCHAR icmpv6W[] = {'I', 'C', 'M', 'P', 'v', '6', 0};
32 static const WCHAR tcpW[] = {'T', 'C', 'P', 0};
33 static const WCHAR tcpv6W[] = {'T', 'C', 'P', 'v', '6', 0};
34 static const WCHAR udpW[] = {'U', 'D', 'P', 0};
35 static const WCHAR udpv6W[] = {'U', 'D', 'P', 'v', '6', 0};
36
37 static const WCHAR fmtport[] = {'%', 'd', 0};
38 static const WCHAR fmtip[] = {'%', 'd', '.', '%', 'd', '.', '%', 'd', '.', '%', 'd', 0};
39 static const WCHAR fmtn[] = {'\n', 0};
40 static const WCHAR fmtnn[] = {'\n', '%', 's', '\n', 0};
41 static const WCHAR fmtcolon[] = {'%', 's', ':', '%', 's', 0};
42 static const WCHAR fmttcpout[] = {' ', ' ', '%', '-', '6', 's', ' ', '%', '-', '2', '2', 's', ' ', '%', '-', '2', '2', 's', ' ', '%', 's', '\n', 0};
43 static const WCHAR fmtudpout[] = {' ', ' ', '%', '-', '6', 's', ' ', '%', '-', '2', '2', 's', ' ', '*', ':', '*', '\n', 0};
44 static const WCHAR fmtethout[] = {'%', '-', '2', '0', 's', ' ', '%', '1', '4', 'l', 'u', ' ', '%', '1', '5', 'l', 'u', '\n', 0};
45 static const WCHAR fmtethoutu[] = {'%', '-', '2', '0', 's', ' ', '%', '1', '4', 'l', 'u', '\n', '\n', 0};
46 static const WCHAR fmtethheader[] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
47                                      ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
48                                      ' ', '%', '-', '1', '9', 's', ' ', '%', 's', '\n', '\n', 0};
49 static const WCHAR fmttcpstat[] = {' ', ' ', '%', '-', '3', '5', 's', ' ', '=', ' ', '%', 'l', 'u', '\n', 0};
50 static const WCHAR fmtudpstat[] = {' ', ' ', '%', '-', '2', '1', 's', ' ', '=', ' ', '%', 'l', 'u', '\n', 0};
51
52 static const WCHAR tcpstatesW[][16] = {
53     {'?', '?', '?', 0},
54     {'C', 'L', 'O', 'S', 'E', 'D', 0},
55     {'L', 'I', 'S', 'T', 'E', 'N', 'I', 'N', 'G', 0},
56     {'S', 'Y', 'N', '_', 'S', 'E', 'N', 'T', 0},
57     {'S', 'Y', 'N', '_', 'R', 'C', 'V', 'D', 0},
58     {'E', 'S', 'T', 'A', 'B', 'L', 'I', 'S', 'H', 'E', 'D', 0},
59     {'F', 'I', 'N', '_', 'W', 'A', 'I', 'T', '1', 0},
60     {'F', 'I', 'N', '_', 'W', 'A', 'I', 'T', '2', 0},
61     {'C', 'L', 'O', 'S', 'E', '_', 'W', 'A', 'I', 'T', 0},
62     {'C', 'L', 'O', 'S', 'I', 'N', 'G', 0},
63     {'L', 'A', 'S', 'T', '_', 'A', 'C', 'K', 0},
64     {'T', 'I', 'M', 'E', '_', 'W', 'A', 'I', 'T', 0},
65     {'D', 'E', 'L', 'E', 'T', 'E', '_', 'T', 'C', 'B', 0},
66 };
67
68 /* =========================================================================
69  *  Output a unicode string. Ideally this will go to the console
70  *  and hence required WriteConsoleW to output it, however if file i/o is
71  *  redirected, it needs to be WriteFile'd using OEM (not ANSI) format
72  * ========================================================================= */
73 static int __cdecl NETSTAT_wprintf(const WCHAR *format, ...)
74 {
75     static WCHAR *output_bufW = NULL;
76     static char  *output_bufA = NULL;
77     static BOOL  toConsole    = TRUE;
78     static BOOL  traceOutput  = FALSE;
79 #define MAX_WRITECONSOLE_SIZE 65535
80
81     __ms_va_list parms;
82     DWORD   nOut;
83     int len;
84     DWORD   res = 0;
85
86     /*
87      * Allocate buffer to use when writing to console
88      * Note: Not freed - memory will be allocated once and released when
89      *         xcopy ends
90      */
91
92     if (!output_bufW) output_bufW = HeapAlloc(GetProcessHeap(), 0,
93                                               MAX_WRITECONSOLE_SIZE);
94     if (!output_bufW) {
95         WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
96         return 0;
97     }
98
99     __ms_va_start(parms, format);
100     len = wvsprintfW(output_bufW, format, parms);
101     __ms_va_end(parms);
102
103     /* Try to write as unicode all the time we think its a console */
104     if (toConsole) {
105         res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
106                             output_bufW, len, &nOut, NULL);
107     }
108
109     /* If writing to console has failed (ever) we assume its file
110        i/o so convert to OEM codepage and output                  */
111     if (!res) {
112         BOOL usedDefaultChar = FALSE;
113         DWORD convertedChars;
114
115         toConsole = FALSE;
116
117         /*
118          * Allocate buffer to use when writing to file. Not freed, as above
119          */
120         if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
121                                                   MAX_WRITECONSOLE_SIZE);
122         if (!output_bufA) {
123             WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
124             return 0;
125         }
126
127         /* Convert to OEM, then output */
128         convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, output_bufW,
129                                              len, output_bufA, MAX_WRITECONSOLE_SIZE,
130                                              "?", &usedDefaultChar);
131         WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
132                   &nOut, FALSE);
133     }
134
135     /* Trace whether screen or console */
136     if (!traceOutput) {
137         WINE_TRACE("Writing to console? (%d)\n", toConsole);
138         traceOutput = TRUE;
139     }
140     return nOut;
141 }
142
143 static WCHAR *NETSTAT_load_message(UINT id) {
144     static WCHAR msg[2048];
145     static const WCHAR failedW[]  = {'F','a','i','l','e','d','!','\0'};
146
147     if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
148         WINE_FIXME("LoadString failed with %d\n", GetLastError());
149         strcpyW(msg, failedW);
150     }
151     return msg;
152 }
153
154 static WCHAR *NETSTAT_port_name(UINT port, WCHAR name[])
155 {
156     /* FIXME: can we get the name? */
157     sprintfW(name, fmtport, htons((WORD)port));
158     return name;
159 }
160
161 static WCHAR *NETSTAT_host_name(UINT ip, WCHAR name[])
162 {
163     UINT nip;
164
165     /* FIXME: can we get the name? */
166     nip = htonl(ip);
167     sprintfW(name, fmtip, (nip >> 24) & 0xFF, (nip >> 16) & 0xFF, (nip >> 8) & 0xFF, (nip) & 0xFF);
168     return name;
169 }
170
171 static void NETSTAT_conn_header(void)
172 {
173     WCHAR local[22], remote[22], state[22];
174     NETSTAT_wprintf(fmtnn, NETSTAT_load_message(IDS_TCP_ACTIVE_CONN));
175     NETSTAT_wprintf(fmtn);
176     strcpyW(local, NETSTAT_load_message(IDS_TCP_LOCAL_ADDR));
177     strcpyW(remote, NETSTAT_load_message(IDS_TCP_REMOTE_ADDR));
178     strcpyW(state, NETSTAT_load_message(IDS_TCP_STATE));
179     NETSTAT_wprintf(fmttcpout, NETSTAT_load_message(IDS_TCP_PROTO), local, remote, state);
180 }
181
182 static void NETSTAT_eth_stats(void)
183 {
184     PMIB_IFTABLE table;
185     DWORD err, size, i;
186     DWORD octets[2], ucastpkts[2], nucastpkts[2], discards[2], errors[2], unknown;
187     WCHAR recv[19];
188
189     size = sizeof(MIB_IFTABLE);
190     do
191     {
192         table = (PMIB_IFTABLE)HeapAlloc(GetProcessHeap(), 0, size);
193         err = GetIfTable(table, &size, FALSE);
194         if (err != NO_ERROR) HeapFree(GetProcessHeap(), 0, table);
195     } while (err == ERROR_INSUFFICIENT_BUFFER);
196
197     if (err) return;
198
199     NETSTAT_wprintf(NETSTAT_load_message(IDS_ETH_STAT));
200     NETSTAT_wprintf(fmtn);
201     NETSTAT_wprintf(fmtn);
202     strcpyW(recv, NETSTAT_load_message(IDS_ETH_RECV));
203     NETSTAT_wprintf(fmtethheader, recv, NETSTAT_load_message(IDS_ETH_SENT));
204
205     octets[0] = octets[1] = 0;
206     ucastpkts[0] = ucastpkts[1] = 0;
207     nucastpkts[0] = nucastpkts[1] = 0;
208     discards[0] = discards[1] = 0;
209     errors[0] = errors[1] = 0;
210     unknown = 0;
211
212     for (i = 0; i < table->dwNumEntries; i++)
213     {
214         octets[0] += table->table[i].dwInOctets;
215         octets[1] += table->table[i].dwOutOctets;
216         ucastpkts[0] += table->table[i].dwInUcastPkts;
217         ucastpkts[1] += table->table[i].dwOutUcastPkts;
218         nucastpkts[0] += table->table[i].dwInNUcastPkts;
219         nucastpkts[1] += table->table[i].dwOutNUcastPkts;
220         discards[0] += table->table[i].dwInDiscards;
221         discards[1] += table->table[i].dwOutDiscards;
222         errors[0] += table->table[i].dwInErrors;
223         errors[1] += table->table[i].dwOutErrors;
224         unknown += table->table[i].dwInUnknownProtos;
225     }
226
227     NETSTAT_wprintf(fmtethout, NETSTAT_load_message(IDS_ETH_BYTES), octets[0], octets[1]);
228     NETSTAT_wprintf(fmtethout, NETSTAT_load_message(IDS_ETH_UNICAST), ucastpkts[0], ucastpkts[1]);
229     NETSTAT_wprintf(fmtethout, NETSTAT_load_message(IDS_ETH_NUNICAST), nucastpkts[0], nucastpkts[1]);
230     NETSTAT_wprintf(fmtethout, NETSTAT_load_message(IDS_ETH_DISCARDS), discards[0], discards[1]);
231     NETSTAT_wprintf(fmtethout, NETSTAT_load_message(IDS_ETH_ERRORS), errors[0], errors[1]);
232     NETSTAT_wprintf(fmtethoutu, NETSTAT_load_message(IDS_ETH_UNKNOWN), unknown);
233
234     HeapFree(GetProcessHeap(), 0, table);
235 }
236
237 static void NETSTAT_tcp_table(void)
238 {
239     PMIB_TCPTABLE table;
240     DWORD err, size, i;
241     WCHAR HostIp[MAX_HOSTNAME_LEN], HostPort[32];
242     WCHAR RemoteIp[MAX_HOSTNAME_LEN], RemotePort[32];
243     WCHAR Host[MAX_HOSTNAME_LEN + 32];
244     WCHAR Remote[MAX_HOSTNAME_LEN + 32];
245
246     size = sizeof(MIB_TCPTABLE);
247     do
248     {
249         table = (PMIB_TCPTABLE)HeapAlloc(GetProcessHeap(), 0, size);
250         err = GetTcpTable(table, &size, TRUE);
251         if (err != NO_ERROR) HeapFree(GetProcessHeap(), 0, table);
252     } while (err == ERROR_INSUFFICIENT_BUFFER);
253
254     if (err) return;
255
256     for (i = 0; i < table->dwNumEntries; i++)
257     {
258         if ((table->table[i].u.dwState ==  MIB_TCP_STATE_CLOSE_WAIT) ||
259             (table->table[i].u.dwState ==  MIB_TCP_STATE_ESTAB) ||
260             (table->table[i].u.dwState ==  MIB_TCP_STATE_TIME_WAIT))
261         {
262             NETSTAT_host_name(table->table[i].dwLocalAddr, HostIp);
263             NETSTAT_port_name(table->table[i].dwLocalPort, HostPort);
264             NETSTAT_host_name(table->table[i].dwRemoteAddr, RemoteIp);
265             NETSTAT_port_name(table->table[i].dwRemotePort, RemotePort);
266
267             sprintfW(Host, fmtcolon, HostIp, HostPort);
268             sprintfW(Remote, fmtcolon, RemoteIp, RemotePort);
269             NETSTAT_wprintf(fmttcpout, tcpW, Host, Remote, tcpstatesW[table->table[i].u.dwState]);
270         }
271     }
272     HeapFree(GetProcessHeap(), 0, table);
273 }
274
275 static void NETSTAT_tcp_stats(void)
276 {
277     PMIB_TCPSTATS stats;
278
279     stats = (PMIB_TCPSTATS)HeapAlloc(GetProcessHeap(), 0, sizeof(MIB_TCPSTATS));
280
281     if (GetTcpStatistics(stats) == NO_ERROR)
282     {
283         NETSTAT_wprintf(fmtnn, NETSTAT_load_message(IDS_TCP_STAT));
284         NETSTAT_wprintf(fmtn);
285         NETSTAT_wprintf(fmttcpstat, NETSTAT_load_message(IDS_TCP_ACTIVE_OPEN), stats->dwActiveOpens);
286         NETSTAT_wprintf(fmttcpstat, NETSTAT_load_message(IDS_TCP_PASSIV_OPEN), stats->dwPassiveOpens);
287         NETSTAT_wprintf(fmttcpstat, NETSTAT_load_message(IDS_TCP_FAILED_CONN), stats->dwAttemptFails);
288         NETSTAT_wprintf(fmttcpstat, NETSTAT_load_message(IDS_TCP_RESET_CONN),  stats->dwEstabResets);
289         NETSTAT_wprintf(fmttcpstat, NETSTAT_load_message(IDS_TCP_CURR_CONN),   stats->dwCurrEstab);
290         NETSTAT_wprintf(fmttcpstat, NETSTAT_load_message(IDS_TCP_SEGM_RECV),   stats->dwInSegs);
291         NETSTAT_wprintf(fmttcpstat, NETSTAT_load_message(IDS_TCP_SEGM_SENT),   stats->dwOutSegs);
292         NETSTAT_wprintf(fmttcpstat, NETSTAT_load_message(IDS_TCP_SEGM_RETRAN), stats->dwRetransSegs);
293     }
294
295     HeapFree(GetProcessHeap(), 0, stats);
296 }
297
298 static void NETSTAT_udp_table(void)
299 {
300     PMIB_UDPTABLE table;
301     DWORD err, size, i;
302     WCHAR HostIp[MAX_HOSTNAME_LEN], HostPort[32];
303     WCHAR Host[MAX_HOSTNAME_LEN + 32];
304
305     size = sizeof(MIB_UDPTABLE);
306     do
307     {
308         table = (PMIB_UDPTABLE)HeapAlloc(GetProcessHeap(), 0, size);
309         err = GetUdpTable(table, &size, TRUE);
310         if (err != NO_ERROR) HeapFree(GetProcessHeap(), 0, table);
311     } while (err == ERROR_INSUFFICIENT_BUFFER);
312
313     if (err) return;
314
315     for (i = 0; i < table->dwNumEntries; i++)
316     {
317         NETSTAT_host_name(table->table[i].dwLocalAddr, HostIp);
318         NETSTAT_port_name(table->table[i].dwLocalPort, HostPort);
319
320         sprintfW(Host, fmtcolon, HostIp, HostPort);
321         NETSTAT_wprintf(fmtudpout, udpW, Host);
322     }
323     HeapFree(GetProcessHeap(), 0, table);
324 }
325
326 static void NETSTAT_udp_stats(void)
327 {
328     PMIB_UDPSTATS stats;
329
330     stats = (PMIB_UDPSTATS)HeapAlloc(GetProcessHeap(), 0, sizeof(MIB_UDPSTATS));
331
332     if (GetUdpStatistics(stats) == NO_ERROR)
333     {
334         NETSTAT_wprintf(fmtnn, NETSTAT_load_message(IDS_UDP_STAT));
335         NETSTAT_wprintf(fmtn);
336         NETSTAT_wprintf(fmtudpstat, NETSTAT_load_message(IDS_UDP_DGRAMS_RECV), stats->dwInDatagrams);
337         NETSTAT_wprintf(fmtudpstat, NETSTAT_load_message(IDS_UDP_NO_PORTS), stats->dwNoPorts);
338         NETSTAT_wprintf(fmtudpstat, NETSTAT_load_message(IDS_UDP_RECV_ERRORS), stats->dwInErrors);
339         NETSTAT_wprintf(fmtudpstat, NETSTAT_load_message(IDS_UDP_DGRAMS_SENT),  stats->dwOutDatagrams);
340     }
341
342     HeapFree(GetProcessHeap(), 0, stats);
343 }
344
345 static NETSTATPROTOCOLS NETSTAT_get_protocol(WCHAR name[])
346 {
347     if (!strcmpiW(name, ipW)) return PROT_IP;
348     if (!strcmpiW(name, ipv6W)) return PROT_IPV6;
349     if (!strcmpiW(name, icmpW)) return PROT_ICMP;
350     if (!strcmpiW(name, icmpv6W)) return PROT_ICMPV6;
351     if (!strcmpiW(name, tcpW)) return PROT_TCP;
352     if (!strcmpiW(name, tcpv6W)) return PROT_TCPV6;
353     if (!strcmpiW(name, udpW)) return PROT_UDP;
354     if (!strcmpiW(name, udpv6W)) return PROT_UDPV6;
355     return PROT_UNKNOWN;
356 }
357
358 int wmain(int argc, WCHAR *argv[])
359 {
360     WSADATA wsa_data;
361     BOOL output_stats = FALSE;
362
363     if (WSAStartup(MAKEWORD(2, 2), &wsa_data))
364     {
365         WINE_ERR("WSAStartup failed: %d\n", WSAGetLastError());
366         return 1;
367     }
368
369     if (argc == 1)
370     {
371         /* No options */
372         NETSTAT_conn_header();
373         NETSTAT_tcp_table();
374         return 0;
375     }
376
377     while (argv[1] && argv[1][0] == '-')
378     {
379         switch (argv[1][1])
380         {
381         case 'a':
382             NETSTAT_conn_header();
383             NETSTAT_tcp_table();
384             NETSTAT_udp_table();
385             return 0;
386         case 'e':
387             NETSTAT_eth_stats();
388             return 0;
389         case 's':
390             output_stats = TRUE;
391             break;
392         case 'p':
393             argv++; argc--;
394             if (argc == 1) return 1;
395             switch (NETSTAT_get_protocol(argv[1]))
396             {
397                 case PROT_TCP:
398                     if (output_stats)
399                         NETSTAT_tcp_stats();
400                     NETSTAT_conn_header();
401                     NETSTAT_tcp_table();
402                     break;
403                 case PROT_UDP:
404                     if (output_stats)
405                         NETSTAT_udp_stats();
406                     NETSTAT_conn_header();
407                     NETSTAT_udp_table();
408                     break;
409                 default:
410                     WINE_FIXME("Protocol not yet implemented: %s\n", debugstr_w(argv[1]));
411             }
412             return 0;
413         default:
414             WINE_FIXME("Unknown option: %s\n", debugstr_w(argv[1]));
415             return 1;
416         }
417         argv++; argc--;
418     }
419
420     if (output_stats)
421     {
422         NETSTAT_tcp_stats();
423         NETSTAT_udp_stats();
424     }
425
426     return 0;
427 }