Skip to content

Commit 813ac3b

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 61e9657 commit 813ac3b

File tree

5 files changed

+527
-39
lines changed

5 files changed

+527
-39
lines changed

debug.go

Lines changed: 11 additions & 39 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,47 +22,20 @@ 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
25+
Log *log.Logger
26+
Level int
27+
Format string
2828
}
2929

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
30+
// panicf is a helper to panic with formatted text.
31+
func panicf(format string, a ...interface{}) {
32+
panic(fmt.Sprintf(format, a...))
5733
}
5834

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...)
35+
// ternary returns iftrue if cond is true, else iffalse.
36+
func ternary(cond bool, iftrue string, iffalse string) string {
37+
if cond {
38+
return iftrue
6439
}
65-
}
66-
67-
func panicf(format string, a ...interface{}) {
68-
panic(fmt.Sprintf(format, a...))
40+
return iffalse
6941
}

debug_linux.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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+
for _, a := range args {
27+
kv := strings.Split(a, "=")
28+
if len(kv) != 2 {
29+
continue
30+
}
31+
switch kv[0] {
32+
case "level":
33+
level, err := strconv.Atoi(kv[1])
34+
if err != nil {
35+
panicf("netlink: invalid NLDEBUG level: %q", a)
36+
}
37+
d.Level = level
38+
case "format":
39+
d.Format = kv[1]
40+
}
41+
}
42+
return d
43+
}
44+
45+
// debugf prints debugging information at the specified level, if d.Level is high enough to print the message.
46+
func (d *debugger) debugf(level int, format string, v ...interface{}) {
47+
if d.Level < level {
48+
return
49+
}
50+
switch d.Format {
51+
case "mnl":
52+
colorize := true
53+
_, err := unix.IoctlGetWinsize(int(os.Stdout.Fd()), unix.TIOCGWINSZ)
54+
if err != nil {
55+
colorize = false
56+
}
57+
for _, iface := range v {
58+
if msg, ok := iface.(Message); ok {
59+
nlmsgFprintf(d.Log.Writer(), msg, colorize)
60+
} else {
61+
d.Log.Printf(format, v...)
62+
}
63+
}
64+
default:
65+
d.Log.Printf(format, v...)
66+
}
67+
}
68+
69+
// nlmsgFprintfHeader prints the netlink message header to fd.
70+
func nlmsgFprintfHeader(fd io.Writer, nlh Header) {
71+
fmt.Fprintf(fd, "----------------\t------------------\n")
72+
fmt.Fprintf(fd, "| %010d |\t| message length |\n", nlh.Length)
73+
fmt.Fprintf(fd, "| %05d | %s%s%s%s |\t| type | flags |\n",
74+
nlh.Type,
75+
ternary(nlh.Flags&Request != 0, "R", "-"),
76+
ternary(nlh.Flags&Multi != 0, "M", "-"),
77+
ternary(nlh.Flags&Acknowledge != 0, "A", "-"),
78+
ternary(nlh.Flags&Echo != 0, "E", "-"),
79+
)
80+
fmt.Fprintf(fd, "| %010d |\t| sequence number|\n", nlh.Sequence)
81+
fmt.Fprintf(fd, "| %010d |\t| port ID |\n", nlh.PID)
82+
fmt.Fprintf(fd, "----------------\t------------------\n")
83+
}
84+
85+
// nlmsgFprintf prints a single Message for netlink errors and attributes.
86+
func nlmsgFprintf(fd io.Writer, m Message, colorize bool) {
87+
var hasHeader bool
88+
nlmsgFprintfHeader(fd, m.Header)
89+
switch {
90+
case m.Header.Type == Error:
91+
hasHeader = true
92+
case m.Header.Type == Done && m.Header.Flags&Multi != 0:
93+
if len(m.Data) == 0 {
94+
return
95+
}
96+
default:
97+
// Neither, nothing to do.
98+
}
99+
// Errno occupies 4 bytes.
100+
const endErrno = 4
101+
if len(m.Data) < endErrno {
102+
return
103+
}
104+
105+
c := nlenc.Int32(m.Data[:endErrno])
106+
if c != 0 {
107+
b := m.Data[0:4]
108+
fmt.Fprintf(fd, "| %.2x %.2x %.2x %.2x |\t",
109+
0xff&b[0], 0xff&b[1],
110+
0xff&b[2], 0xff&b[3])
111+
fmt.Fprintf(fd, "| extra header |\n")
112+
}
113+
114+
// Flags indicate an extended acknowledgement. The type/flags combination
115+
// checked above determines the offset where the TLVs occur.
116+
var off int
117+
if hasHeader {
118+
// There is an nlmsghdr preceding the TLVs.
119+
if len(m.Data) < endErrno+nlmsgHeaderLen {
120+
return
121+
}
122+
// The TLVs should be at the offset indicated by the nlmsghdr.length,
123+
// plus the offset where the header began. But make sure the calculated
124+
// offset is still in-bounds.
125+
h := *(*Header)(unsafe.Pointer(&m.Data[endErrno : endErrno+nlmsgHeaderLen][0]))
126+
off = endErrno + int(h.Length)
127+
if len(m.Data) < off {
128+
return
129+
}
130+
} else {
131+
// There is no nlmsghdr preceding the TLVs, parse them directly.
132+
off = endErrno
133+
}
134+
135+
data := m.Data[off:]
136+
for i := 0; i < len(data); {
137+
// Make sure there's at least a header's worth of data to read on each iteration.
138+
if len(data[i:]) < nlaHeaderLen {
139+
break
140+
}
141+
// Extract the length of the attribute.
142+
l := int(nlenc.Uint16(data[i : i+2]))
143+
// extract the type
144+
t := nlenc.Uint16(data[i+2 : i+4])
145+
// print attribute header
146+
if colorize {
147+
fmt.Fprintf(fd, "|\033[1;31m%05d|\033[1;32m%s%s|\033[1;34m%05d\033[0m|\t",
148+
l,
149+
ternary(t&syscall.NLA_F_NESTED != 0, "N", "-"),
150+
ternary(t&syscall.NLA_F_NET_BYTEORDER != 0, "B", "-"),
151+
t&attrTypeMask)
152+
fmt.Fprintf(fd, "|len |flags| type|\n")
153+
} else {
154+
fmt.Fprintf(fd, "|%05d|%s%s|%05d|\t",
155+
l,
156+
ternary(t&syscall.NLA_F_NESTED != 0, "N", "-"),
157+
ternary(t&syscall.NLA_F_NET_BYTEORDER != 0, "B", "-"),
158+
t&attrTypeMask)
159+
fmt.Fprintf(fd, "|len |flags| type|\n")
160+
}
161+
162+
nextAttr := i + nlaAlign(l)
163+
i += nlaHeaderLen
164+
165+
// Ignore zero-length attributes.
166+
if l == 0 {
167+
continue
168+
}
169+
// If nested check the next attribute
170+
if t&syscall.NLA_F_NESTED != 0 {
171+
continue
172+
}
173+
// Print the remaining attributes bytes
174+
for ; i < nextAttr; i += 4 {
175+
fmt.Fprintf(fd, "| %.2x %.2x %.2x %.2x |\t",
176+
0xff&data[i], 0xff&data[i+1],
177+
0xff&data[i+2], 0xff&data[i+3])
178+
fmt.Fprintf(fd, "| data |")
179+
fmt.Fprintf(fd, "\t %s %s %s %s\n",
180+
ternary(strconv.IsPrint(rune(data[i])), string(data[i]), " "),
181+
ternary(strconv.IsPrint(rune(data[i+1])), string(data[i+1]), " "),
182+
ternary(strconv.IsPrint(rune(data[i+2])), string(data[i+2]), " "),
183+
ternary(strconv.IsPrint(rune(data[i+3])), string(data[i+3]), " "),
184+
)
185+
}
186+
}
187+
fmt.Fprintf(fd, "----------------\t------------------\n")
188+
}

0 commit comments

Comments
 (0)