1/*
2** Zabbix
3** Copyright (C) 2001-2021 Zabbix SIA
4**
5** This program is free software; you can redistribute it and/or modify
6** it under the terms of the GNU General Public License as published by
7** the Free Software Foundation; either version 2 of the License, or
8** (at your option) any later version.
9**
10** This program is distributed in the hope that it will be useful,
11** but WITHOUT ANY WARRANTY; without even the implied warranty of
12** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13** GNU General Public License for more details.
14**
15** You should have received a copy of the GNU General Public License
16** along with this program; if not, write to the Free Software
17** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18**/
19
20package netif
21
22import (
23	"encoding/json"
24	"errors"
25	"fmt"
26	"net"
27	"unsafe"
28
29	"golang.org/x/sys/windows"
30	"zabbix.com/pkg/plugin"
31	"zabbix.com/pkg/win32"
32)
33
34const (
35	errorEmptyIpTable = "Empty IP address table returned."
36	errorCannotFindIf = "Cannot obtain network interface information."
37	guidStringLen     = 38
38)
39
40func (p *Plugin) nToIP(addr uint32) net.IP {
41	b := (*[4]byte)(unsafe.Pointer(&addr))
42	return net.IPv4(b[0], b[1], b[2], b[3])
43}
44
45func (p *Plugin) getIpAddrTable() (addrs []win32.MIB_IPADDRROW, err error) {
46	var ipTable *win32.MIB_IPADDRTABLE
47	var sizeIn, sizeOut uint32
48	if sizeOut, err = win32.GetIpAddrTable(nil, 0, false); err != nil {
49		return
50	}
51	if sizeOut == 0 {
52		return
53	}
54	for sizeOut > sizeIn {
55		sizeIn = sizeOut
56		buf := make([]byte, sizeIn)
57		ipTable = (*win32.MIB_IPADDRTABLE)(unsafe.Pointer(&buf[0]))
58		if sizeOut, err = win32.GetIpAddrTable(ipTable, sizeIn, false); err != nil {
59			return
60		}
61	}
62	return (*win32.RGMIB_IPADDRROW)(unsafe.Pointer(&ipTable.Table[0]))[:ipTable.NumEntries:ipTable.NumEntries], nil
63}
64
65func (p *Plugin) getIfRowByIP(ipaddr string, ifs []win32.MIB_IF_ROW2) (row *win32.MIB_IF_ROW2) {
66	var ip net.IP
67	if ip = net.ParseIP(ipaddr); ip == nil {
68		return
69	}
70
71	var err error
72	var ips []win32.MIB_IPADDRROW
73	if ips, err = p.getIpAddrTable(); err != nil {
74		return
75	}
76
77	for i := range ips {
78		if ip.Equal(p.nToIP(ips[i].Addr)) {
79			for j := range ifs {
80				if ifs[j].InterfaceIndex == ips[i].Index {
81					return &ifs[j]
82				}
83			}
84		}
85	}
86	return
87}
88
89func (p *Plugin) getGuidString(winGuid win32.GUID) string {
90	return fmt.Sprintf("{%08X-%04X-%04X-%02X-%02X}", winGuid.Data1, winGuid.Data2, winGuid.Data3, winGuid.Data4[:2], winGuid.Data4[2:])
91}
92
93func (p *Plugin) getNetStats(networkIf string, statName string, dir dirFlag) (result uint64, err error) {
94	var ifTable *win32.MIB_IF_TABLE2
95	if ifTable, err = win32.GetIfTable2(); err != nil {
96		return
97	}
98	defer win32.FreeMibTable(ifTable)
99
100	ifs := (*win32.RGMIB_IF_ROW2)(unsafe.Pointer(&ifTable.Table[0]))[:ifTable.NumEntries:ifTable.NumEntries]
101
102	var row *win32.MIB_IF_ROW2
103	for i := range ifs {
104		if len(networkIf) == guidStringLen && networkIf[0] == '{' && networkIf[guidStringLen-1] == '}' &&
105			networkIf == p.getGuidString(ifs[i].InterfaceGuid) ||
106			networkIf == windows.UTF16ToString(ifs[i].Description[:]) {
107			row = &ifs[i]
108			break
109		}
110	}
111	if row == nil {
112		row = p.getIfRowByIP(networkIf, ifs)
113	}
114	if row == nil {
115		return 0, errors.New(errorCannotFindIf)
116	}
117
118	var value uint64
119	switch statName {
120	case "bytes":
121		if dir&dirIn != 0 {
122			value += row.InOctets
123		}
124		if dir&dirOut != 0 {
125			value += row.OutOctets
126		}
127	case "packets":
128		if dir&dirIn != 0 {
129			value += row.InUcastPkts + row.InNUcastPkts
130		}
131		if dir&dirOut != 0 {
132			value += row.OutUcastPkts + row.OutNUcastPkts
133		}
134	case "errors":
135		if dir&dirIn != 0 {
136			value += row.InErrors
137		}
138		if dir&dirOut != 0 {
139			value += row.OutErrors
140		}
141	case "dropped":
142		if dir&dirIn != 0 {
143			value += row.InDiscards + row.InUnknownProtos
144		}
145		if dir&dirOut != 0 {
146			value += row.OutDiscards
147		}
148	default:
149		return 0, errors.New(errorInvalidSecondParam)
150	}
151	return value, nil
152}
153
154func (p *Plugin) getDevDiscovery() (devices []msgIfDiscovery, err error) {
155	var table *win32.MIB_IF_TABLE2
156	if table, err = win32.GetIfTable2(); err != nil {
157		return
158	}
159	defer win32.FreeMibTable(table)
160
161	devices = make([]msgIfDiscovery, 0, table.NumEntries)
162	rows := (*win32.RGMIB_IF_ROW2)(unsafe.Pointer(&table.Table[0]))[:table.NumEntries:table.NumEntries]
163	for i := range rows {
164		guid := p.getGuidString(rows[i].InterfaceGuid)
165		devices = append(devices, msgIfDiscovery{windows.UTF16ToString(rows[i].Description[:]), &guid})
166	}
167	return
168}
169
170func (p *Plugin) getIfType(iftype uint32) string {
171	switch iftype {
172	case windows.IF_TYPE_OTHER:
173		return "Other"
174	case windows.IF_TYPE_ETHERNET_CSMACD:
175		return "Ethernet"
176	case windows.IF_TYPE_ISO88025_TOKENRING:
177		return "Token Ring"
178	case windows.IF_TYPE_PPP:
179		return "PPP"
180	case windows.IF_TYPE_SOFTWARE_LOOPBACK:
181		return "Software Loopback"
182	case windows.IF_TYPE_ATM:
183		return "ATM"
184	case windows.IF_TYPE_IEEE80211:
185		return "IEEE 802.11 Wireless"
186	case windows.IF_TYPE_TUNNEL:
187		return "Tunnel type encapsulation"
188	case windows.IF_TYPE_IEEE1394:
189		return "IEEE 1394 Firewire"
190	default:
191		return "unknown"
192	}
193}
194
195func (p *Plugin) getAdminStatus(status int32) string {
196	switch status {
197	case 0:
198		return "disabled"
199	case 1:
200		return "enabled"
201	default:
202		return "unknown"
203	}
204}
205
206func (p *Plugin) getIP(index uint32, ips []win32.MIB_IPADDRROW) string {
207	for i := range ips {
208		if ips[i].Index == index {
209			return fmt.Sprintf(" %-15s", p.nToIP(ips[i].Addr))
210		}
211	}
212	return " -"
213}
214
215func (p *Plugin) getDevList() (devices string, err error) {
216	var ifTable *win32.MIB_IF_TABLE2
217	if ifTable, err = win32.GetIfTable2(); err != nil {
218		return
219	}
220	defer win32.FreeMibTable(ifTable)
221	ifs := (*win32.RGMIB_IF_ROW2)(unsafe.Pointer(&ifTable.Table[0]))[:ifTable.NumEntries:ifTable.NumEntries]
222
223	var ips []win32.MIB_IPADDRROW
224	if ips, err = p.getIpAddrTable(); err != nil {
225		return
226	}
227
228	for i := range ifs {
229		devices += fmt.Sprintf("%-25s", p.getIfType(ifs[i].Type))
230		devices += fmt.Sprintf(" %-8s", p.getAdminStatus(ifs[i].AdminStatus))
231		devices += p.getIP(ifs[i].InterfaceIndex, ips)
232		devices += fmt.Sprintf(" %s\n", windows.UTF16ToString(ifs[i].Description[:]))
233	}
234
235	return
236}
237
238// Export -
239func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) (result interface{}, err error) {
240	var direction dirFlag
241	var mode string
242
243	switch key {
244	case "net.if.discovery":
245		if len(params) > 0 {
246			return nil, errors.New(errorParametersNotAllowed)
247		}
248		var devices []msgIfDiscovery
249		if devices, err = p.getDevDiscovery(); err != nil {
250			return
251		}
252		var b []byte
253		if b, err = json.Marshal(devices); err != nil {
254			return
255		}
256		return string(b), nil
257	case "net.if.list":
258		if len(params) > 0 {
259			return nil, errors.New(errorParametersNotAllowed)
260		}
261		return p.getDevList()
262	case "net.if.in":
263		direction = dirIn
264	case "net.if.out":
265		direction = dirOut
266	case "net.if.total":
267		direction = dirIn | dirOut
268	default:
269		/* SHOULD_NEVER_HAPPEN */
270		return nil, errors.New(errorUnsupportedMetric)
271	}
272
273	if len(params) < 1 || params[0] == "" {
274		return nil, errors.New(errorEmptyIfName)
275	}
276
277	if len(params) > 2 {
278		return nil, errors.New(errorTooManyParams)
279	}
280
281	if len(params) == 2 && params[1] != "" {
282		mode = params[1]
283	} else {
284		mode = "bytes"
285	}
286
287	return p.getNetStats(params[0], mode, direction)
288}
289
290func init() {
291	plugin.RegisterMetrics(&impl, "NetIf",
292		"net.if.list", "Returns a list of network interfaces in text format.",
293		"net.if.in", "Returns incoming traffic statistics on network interface.",
294		"net.if.out", "Returns outgoing traffic statistics on network interface.",
295		"net.if.total", "Returns sum of incoming and outgoing traffic statistics on network interface.",
296		"net.if.discovery", "Returns list of network interfaces. Used for low-level discovery.")
297
298}
299