Skip to content

Commit 4c5347c

Browse files
committed
debug: option to print using mnl format
nftables uses the libmnl library that has a particular debugging format. This format is also used for nftables --debug=all. google/nftables golang library to interact with nftables uses this library to interface with netlink directly. Since these libraries are not well documented, is common to have to dump the netlink bits to compare. This commit implements an option to choose the same format used in nftables by adding a new option to the NLDEBUG environmenta variable and using the format=mnl key value. For Linux systems mnl is the default format. Change-Id: I5f63753b3ada4d25565fe0bddac35e010510a34f
1 parent 33463fe commit 4c5347c

File tree

5 files changed

+588
-40
lines changed

5 files changed

+588
-40
lines changed

debug.go

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"fmt"
55
"log"
66
"os"
7-
"strconv"
87
"strings"
98
)
109

@@ -23,45 +22,9 @@ func init() {
2322

2423
// A debugger is used to provide debugging information about a netlink connection.
2524
type debugger struct {
26-
Log *log.Logger
27-
Level int
28-
}
29-
30-
// newDebugger creates a debugger by parsing key=value arguments.
31-
func newDebugger(args []string) *debugger {
32-
d := &debugger{
33-
Log: log.New(os.Stderr, "nl: ", 0),
34-
Level: 1,
35-
}
36-
37-
for _, a := range args {
38-
kv := strings.Split(a, "=")
39-
if len(kv) != 2 {
40-
// Ignore malformed pairs and assume callers wants defaults.
41-
continue
42-
}
43-
44-
switch kv[0] {
45-
// Select the log level for the debugger.
46-
case "level":
47-
level, err := strconv.Atoi(kv[1])
48-
if err != nil {
49-
panicf("netlink: invalid NLDEBUG level: %q", a)
50-
}
51-
52-
d.Level = level
53-
}
54-
}
55-
56-
return d
57-
}
58-
59-
// debugf prints debugging information at the specified level, if d.Level is
60-
// high enough to print the message.
61-
func (d *debugger) debugf(level int, format string, v ...interface{}) {
62-
if d.Level >= level {
63-
d.Log.Printf(format, v...)
64-
}
25+
Log *log.Logger
26+
Level int
27+
Format string
6528
}
6629

6730
func panicf(format string, a ...interface{}) {

debug_linux.go

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
//go:build linux
2+
3+
package netlink
4+
5+
import (
6+
"fmt"
7+
"io"
8+
"log"
9+
"os"
10+
"strconv"
11+
"strings"
12+
"syscall"
13+
"unsafe"
14+
15+
"github.com/mdlayher/netlink/nlenc"
16+
"golang.org/x/sys/unix"
17+
)
18+
19+
// newDebugger creates a debugger by parsing key=value arguments.
20+
func newDebugger(args []string) *debugger {
21+
d := &debugger{
22+
Log: log.New(os.Stderr, "nl: ", 0),
23+
Level: 1,
24+
Format: "mnl",
25+
}
26+
27+
for _, a := range args {
28+
kv := strings.Split(a, "=")
29+
if len(kv) != 2 {
30+
// Ignore malformed pairs and assume callers wants defaults.
31+
continue
32+
}
33+
34+
switch kv[0] {
35+
// Select the log level for the debugger.
36+
case "level":
37+
level, err := strconv.Atoi(kv[1])
38+
if err != nil {
39+
panicf("netlink: invalid NLDEBUG level: %q", a)
40+
}
41+
42+
d.Level = level
43+
case "format":
44+
d.Format = kv[1]
45+
}
46+
}
47+
48+
return d
49+
}
50+
51+
// debugf prints debugging information at the specified level, if d.Level is
52+
// high enough to print the message.
53+
func (d *debugger) debugf(level int, format string, v ...interface{}) {
54+
if d.Level < level {
55+
return
56+
}
57+
58+
switch d.Format {
59+
case "mnl":
60+
colorize := true
61+
_, err := unix.IoctlGetWinsize(int(os.Stdout.Fd()), unix.TIOCGWINSZ)
62+
if err != nil {
63+
colorize = false
64+
}
65+
66+
for _, iface := range v {
67+
if msg, ok := iface.(Message); ok {
68+
nlmsgFprintf(d.Log.Writer(), msg, colorize)
69+
} else {
70+
d.Log.Printf(format, v...)
71+
}
72+
}
73+
default:
74+
d.Log.Printf(format, v...)
75+
}
76+
}
77+
78+
/*
79+
nlmsgFprintf - print netlink message to file
80+
- Based on https://git.netfilter.org/libmnl/tree/src/nlmsg.c
81+
- This function prints the netlink header to a file handle.
82+
- It may be useful for debugging purposes. One example of the output
83+
- is the following:
84+
85+
---------------- ------------------
86+
| 0000000040 | | message length |
87+
| 00016 | R-A- | | type | flags |
88+
| 1289148991 | | sequence number|
89+
| 0000000000 | | port ID |
90+
---------------- ------------------
91+
| 00 00 00 00 | | extra header |
92+
| 00 00 00 00 | | extra header |
93+
| 01 00 00 00 | | extra header |
94+
| 01 00 00 00 | | extra header |
95+
|00008|--|00003| |len |flags| type|
96+
| 65 74 68 30 | | data | e t h 0
97+
---------------- ------------------
98+
99+
*
100+
* This example above shows the netlink message that is send to kernel-space
101+
* to set up the link interface eth0. The netlink and attribute header data
102+
* are displayed in base 10 whereas the extra header and the attribute payload
103+
* are expressed in base 16. The possible flags in the netlink header are:
104+
*
105+
* - R, that indicates that NLM_F_REQUEST is set.
106+
* - M, that indicates that NLM_F_MULTI is set.
107+
* - A, that indicates that NLM_F_ACK is set.
108+
* - E, that indicates that NLM_F_ECHO is set.
109+
*
110+
* The lack of one flag is displayed with '-'. On the other hand, the possible
111+
* attribute flags available are:
112+
*
113+
* - N, that indicates that NLA_F_NESTED is set.
114+
* - B, that indicates that NLA_F_NET_BYTEORDER is set.
115+
*/
116+
func nlmsgFprintfHeader(fd io.Writer, nlh Header) {
117+
fmt.Fprintf(fd, "----------------\t------------------\n")
118+
fmt.Fprintf(fd, "| %010d |\t| message length |\n", nlh.Length)
119+
fmt.Fprintf(fd, "| %05d | %s%s%s%s |\t| type | flags |\n",
120+
nlh.Type,
121+
ternary(nlh.Flags&Request != 0, "R", "-"),
122+
ternary(nlh.Flags&Multi != 0, "M", "-"),
123+
ternary(nlh.Flags&Acknowledge != 0, "A", "-"),
124+
ternary(nlh.Flags&Echo != 0, "E", "-"),
125+
)
126+
fmt.Fprintf(fd, "| %010d |\t| sequence number|\n", nlh.Sequence)
127+
fmt.Fprintf(fd, "| %010d |\t| port ID |\n", nlh.PID)
128+
fmt.Fprintf(fd, "----------------\t------------------\n")
129+
}
130+
131+
// nlmsgFprintf checks a single Message for netlink errors.
132+
func nlmsgFprintf(fd io.Writer, m Message, colorize bool) {
133+
var hasHeader bool
134+
nlmsgFprintfHeader(fd, m.Header)
135+
switch {
136+
case m.Header.Type == Error:
137+
hasHeader = true
138+
case m.Header.Type == Done && m.Header.Flags&Multi != 0:
139+
if len(m.Data) == 0 {
140+
return
141+
}
142+
default:
143+
// Neither, nothing to do.
144+
}
145+
146+
// Errno occupies 4 bytes.
147+
const endErrno = 4
148+
if len(m.Data) < endErrno {
149+
return
150+
}
151+
152+
c := nlenc.Int32(m.Data[:endErrno])
153+
if c != 0 {
154+
b := m.Data[0:4]
155+
fmt.Fprintf(fd, "| %.2x %.2x %.2x %.2x |\t",
156+
0xff&b[0], 0xff&b[1],
157+
0xff&b[2], 0xff&b[3])
158+
fmt.Fprintf(fd, "| extra header |\n")
159+
}
160+
161+
// Flags indicate an extended acknowledgement. The type/flags combination
162+
// checked above determines the offset where the TLVs occur.
163+
var off int
164+
if hasHeader {
165+
// There is an nlmsghdr preceding the TLVs.
166+
if len(m.Data) < endErrno+nlmsgHeaderLen {
167+
return
168+
}
169+
170+
// The TLVs should be at the offset indicated by the nlmsghdr.length,
171+
// plus the offset where the header began. But make sure the calculated
172+
// offset is still in-bounds.
173+
h := *(*Header)(unsafe.Pointer(&m.Data[endErrno : endErrno+nlmsgHeaderLen][0]))
174+
off = endErrno + int(h.Length)
175+
176+
if len(m.Data) < off {
177+
return
178+
}
179+
} else {
180+
// There is no nlmsghdr preceding the TLVs, parse them directly.
181+
off = endErrno
182+
}
183+
184+
data := m.Data[off:]
185+
for i := 0; i < len(data); {
186+
// Make sure there's at least a header's worth
187+
// of data to read on each iteration.
188+
if len(data[i:]) < nlaHeaderLen {
189+
break
190+
}
191+
192+
// Extract the length of the attribute.
193+
l := int(nlenc.Uint16(data[i : i+2]))
194+
// extract the type
195+
t := nlenc.Uint16(data[i+2 : i+4])
196+
// print attribute header
197+
if colorize {
198+
fmt.Fprintf(fd, "|\033[1;31m%05d|\033[1;32m%s%s|\033[1;34m%05d\033[0m|\t",
199+
l,
200+
ternary(t&syscall.NLA_F_NESTED != 0, "N", "-"),
201+
ternary(t&syscall.NLA_F_NET_BYTEORDER != 0, "B", "-"),
202+
t&attrTypeMask)
203+
fmt.Fprintf(fd, "|len |flags| type|\n")
204+
} else {
205+
fmt.Fprintf(fd, "|%05d|%s%s|%05d|\t",
206+
l,
207+
ternary(t&syscall.NLA_F_NESTED != 0, "N", "-"),
208+
ternary(t&syscall.NLA_F_NET_BYTEORDER != 0, "B", "-"),
209+
t&attrTypeMask)
210+
fmt.Fprintf(fd, "|len |flags| type|\n")
211+
}
212+
213+
nextAttr := i + nlaAlign(l)
214+
215+
// advance the pointer to the bytes after the header
216+
i += nlaHeaderLen
217+
218+
// Ignore zero-length attributes.
219+
if l == 0 {
220+
continue
221+
}
222+
// If nested check the next attribute
223+
if t&syscall.NLA_F_NESTED != 0 {
224+
continue
225+
}
226+
227+
// Print the remaining attributes bytes
228+
for ; i < nextAttr; i += 4 {
229+
fmt.Fprintf(fd, "| %.2x %.2x %.2x %.2x |\t",
230+
0xff&data[i], 0xff&data[i+1],
231+
0xff&data[i+2], 0xff&data[i+3])
232+
233+
fmt.Fprintf(fd, "| data |")
234+
235+
fmt.Fprintf(fd, "\t %s %s %s %s\n",
236+
ternary(strconv.IsPrint(rune(data[i])), string(data[i]), " "),
237+
ternary(strconv.IsPrint(rune(data[i+1])), string(data[i+1]), " "),
238+
ternary(strconv.IsPrint(rune(data[i+2])), string(data[i+2]), " "),
239+
ternary(strconv.IsPrint(rune(data[i+3])), string(data[i+3]), " "),
240+
)
241+
}
242+
}
243+
fmt.Fprintf(fd, "----------------\t------------------\n")
244+
}
245+
246+
func ternary(cond bool, iftrue string, iffalse string) string {
247+
if cond {
248+
return iftrue
249+
} else {
250+
return iffalse
251+
}
252+
}

0 commit comments

Comments
 (0)