Skip to content

Commit 565febb

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=mnt key value. Change-Id: Ifcddb31d1561074942ad8d6af74b134540eca5fe Signed-off-by: Antonio Ojea <[email protected]>
1 parent 61e9657 commit 565febb

File tree

2 files changed

+199
-3
lines changed

2 files changed

+199
-3
lines changed

debug.go

Lines changed: 196 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ package netlink
22

33
import (
44
"fmt"
5+
"io"
56
"log"
67
"os"
78
"strconv"
89
"strings"
10+
"syscall"
11+
"unsafe"
12+
13+
"github.com/mdlayher/netlink/nlenc"
914
)
1015

1116
// Arguments used to create a debugger.
@@ -23,8 +28,9 @@ func init() {
2328

2429
// A debugger is used to provide debugging information about a netlink connection.
2530
type debugger struct {
26-
Log *log.Logger
27-
Level int
31+
Log *log.Logger
32+
Level int
33+
Format string
2834
}
2935

3036
// newDebugger creates a debugger by parsing key=value arguments.
@@ -50,6 +56,8 @@ func newDebugger(args []string) *debugger {
5056
}
5157

5258
d.Level = level
59+
case "format":
60+
d.Format = kv[1]
5361
}
5462
}
5563

@@ -60,10 +68,195 @@ func newDebugger(args []string) *debugger {
6068
// high enough to print the message.
6169
func (d *debugger) debugf(level int, format string, v ...interface{}) {
6270
if d.Level >= level {
63-
d.Log.Printf(format, v...)
71+
if d.Format == "mnl" {
72+
for _, iface := range v {
73+
if msg, ok := iface.(Message); ok {
74+
nlmsgFprintf(d.Log.Writer(), msg)
75+
}
76+
}
77+
} else {
78+
d.Log.Printf(format, v...)
79+
}
6480
}
6581
}
6682

6783
func panicf(format string, a ...interface{}) {
6884
panic(fmt.Sprintf(format, a...))
6985
}
86+
87+
/*
88+
nlmsgFprintf - print netlink message to file
89+
- Based on https://git.netfilter.org/libmnl/tree/src/nlmsg.c
90+
- This function prints the netlink header to a file handle.
91+
- It may be useful for debugging purposes. One example of the output
92+
- is the following:
93+
94+
---------------- ------------------
95+
| 0000000040 | | message length |
96+
| 00016 | R-A- | | type | flags |
97+
| 1289148991 | | sequence number|
98+
| 0000000000 | | port ID |
99+
---------------- ------------------
100+
| 00 00 00 00 | | extra header |
101+
| 00 00 00 00 | | extra header |
102+
| 01 00 00 00 | | extra header |
103+
| 01 00 00 00 | | extra header |
104+
|00008|--|00003| |len |flags| type|
105+
| 65 74 68 30 | | data | e t h 0
106+
---------------- ------------------
107+
108+
*
109+
* This example above shows the netlink message that is send to kernel-space
110+
* to set up the link interface eth0. The netlink and attribute header data
111+
* are displayed in base 10 whereas the extra header and the attribute payload
112+
* are expressed in base 16. The possible flags in the netlink header are:
113+
*
114+
* - R, that indicates that NLM_F_REQUEST is set.
115+
* - M, that indicates that NLM_F_MULTI is set.
116+
* - A, that indicates that NLM_F_ACK is set.
117+
* - E, that indicates that NLM_F_ECHO is set.
118+
*
119+
* The lack of one flag is displayed with '-'. On the other hand, the possible
120+
* attribute flags available are:
121+
*
122+
* - N, that indicates that NLA_F_NESTED is set.
123+
* - B, that indicates that NLA_F_NET_BYTEORDER is set.
124+
*/
125+
func nlmsgFprintfHeader(fd io.Writer, nlh Header) {
126+
fmt.Fprintf(fd, "----------------\t------------------\n")
127+
fmt.Fprintf(fd, "| %010d |\t| message length |\n", nlh.Length)
128+
fmt.Fprintf(fd, "| %05d | %s%s%s%s |\t| type | flags |\n",
129+
nlh.Type,
130+
ternary(nlh.Flags&Request != 0, "R", "-"),
131+
ternary(nlh.Flags&Multi != 0, "M", "-"),
132+
ternary(nlh.Flags&Acknowledge != 0, "A", "-"),
133+
ternary(nlh.Flags&Echo != 0, "E", "-"),
134+
)
135+
fmt.Fprintf(fd, "| %010d |\t| sequence number|\n", nlh.Sequence)
136+
fmt.Fprintf(fd, "| %010d |\t| port ID |\n", nlh.PID)
137+
fmt.Fprintf(fd, "----------------\t------------------\n")
138+
}
139+
140+
// nlmsgFprintf checks a single Message for netlink errors.
141+
func nlmsgFprintf(fd io.Writer, m Message) {
142+
colorize := true
143+
var hasHeader bool
144+
nlmsgFprintfHeader(fd, m.Header)
145+
switch {
146+
case m.Header.Type == Error:
147+
hasHeader = true
148+
case m.Header.Type == Done && m.Header.Flags&Multi != 0:
149+
if len(m.Data) == 0 {
150+
return
151+
}
152+
default:
153+
// Neither, nothing to do.
154+
}
155+
156+
// Errno occupies 4 bytes.
157+
const endErrno = 4
158+
if len(m.Data) < endErrno {
159+
return
160+
}
161+
162+
c := nlenc.Int32(m.Data[:endErrno])
163+
if c != 0 {
164+
b := m.Data[0:4]
165+
fmt.Fprintf(fd, "| %.2x %.2x %.2x %.2x |\t",
166+
0xff&b[0], 0xff&b[1],
167+
0xff&b[2], 0xff&b[3])
168+
fmt.Fprintf(fd, "| extra header |\n")
169+
}
170+
171+
// Flags indicate an extended acknowledgement. The type/flags combination
172+
// checked above determines the offset where the TLVs occur.
173+
var off int
174+
if hasHeader {
175+
// There is an nlmsghdr preceding the TLVs.
176+
if len(m.Data) < endErrno+nlmsgHeaderLen {
177+
return
178+
}
179+
180+
// The TLVs should be at the offset indicated by the nlmsghdr.length,
181+
// plus the offset where the header began. But make sure the calculated
182+
// offset is still in-bounds.
183+
h := *(*Header)(unsafe.Pointer(&m.Data[endErrno : endErrno+nlmsgHeaderLen][0]))
184+
off = endErrno + int(h.Length)
185+
186+
if len(m.Data) < off {
187+
return
188+
}
189+
} else {
190+
// There is no nlmsghdr preceding the TLVs, parse them directly.
191+
off = endErrno
192+
}
193+
194+
data := m.Data[off:]
195+
for i := 0; i < len(data); {
196+
// Make sure there's at least a header's worth
197+
// of data to read on each iteration.
198+
if len(data[i:]) < nlaHeaderLen {
199+
break
200+
}
201+
202+
// Extract the length of the attribute.
203+
l := int(nlenc.Uint16(data[i : i+2]))
204+
// extract the type
205+
t := nlenc.Uint16(data[i+2 : i+4])
206+
// print attribute header
207+
if colorize {
208+
fmt.Fprintf(fd, "|\033[1;31m%05d|\033[1;32m%s%s|\033[1;34m%05d\033[0m|\t",
209+
l,
210+
ternary(t&syscall.NLA_F_NESTED != 0, "N", "-"),
211+
ternary(t&syscall.NLA_F_NET_BYTEORDER != 0, "B", "-"),
212+
t&attrTypeMask)
213+
fmt.Fprintf(fd, "|len |flags| type|\n")
214+
} else {
215+
fmt.Fprintf(fd, "|%05d|%s%s|%05d|\t",
216+
l,
217+
ternary(t&syscall.NLA_F_NESTED != 0, "N", "-"),
218+
ternary(t&syscall.NLA_F_NET_BYTEORDER != 0, "B", "-"),
219+
t&attrTypeMask)
220+
fmt.Fprintf(fd, "|len |flags| type|\n")
221+
}
222+
223+
nextAttr := i + nlaAlign(l)
224+
225+
// advance the pointer to the bytes after the header
226+
i += nlaHeaderLen
227+
228+
// Ignore zero-length attributes.
229+
if l == 0 {
230+
continue
231+
}
232+
// If nested check the next attribute
233+
if t&syscall.NLA_F_NESTED != 0 {
234+
continue
235+
}
236+
237+
// Print the remaining attributes bytes
238+
for ; i < nextAttr; i += 4 {
239+
fmt.Fprintf(fd, "| %.2x %.2x %.2x %.2x |\t",
240+
0xff&data[i], 0xff&data[i+1],
241+
0xff&data[i+2], 0xff&data[i+3])
242+
243+
fmt.Fprintf(fd, "| data |")
244+
245+
fmt.Fprintf(fd, "\t %s %s %s %s\n",
246+
ternary(strconv.IsPrint(rune(data[i])), string(data[i]), " "),
247+
ternary(strconv.IsPrint(rune(data[i+1])), string(data[i+1]), " "),
248+
ternary(strconv.IsPrint(rune(data[i+2])), string(data[i+2]), " "),
249+
ternary(strconv.IsPrint(rune(data[i+3])), string(data[i+3]), " "),
250+
)
251+
}
252+
}
253+
fmt.Fprintf(fd, "----------------\t------------------\n")
254+
}
255+
256+
func ternary(cond bool, iftrue string, iffalse string) string {
257+
if cond {
258+
return iftrue
259+
} else {
260+
return iffalse
261+
}
262+
}

doc.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@
2727
//
2828
// $ NLDEBUG=level=1 ./nlctl
2929
//
30+
// $ NLDEBUG=level=1,format=mnl ./nlctl
31+
//
3032
// Available key/value debugger options include:
3133
//
3234
// level=N: specify the debugging level (only "1" is currently supported)
35+
// format=mnl: specify the same format used by libmnl (nftables --debug=all)
3336
package netlink

0 commit comments

Comments
 (0)