Skip to content

Commit 1c052f9

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 565febb commit 1c052f9

File tree

5 files changed

+517
-225
lines changed

5 files changed

+517
-225
lines changed

debug.go

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

33
import (
44
"fmt"
5-
"io"
65
"log"
76
"os"
8-
"strconv"
97
"strings"
10-
"syscall"
11-
"unsafe"
12-
13-
"github.com/mdlayher/netlink/nlenc"
148
)
159

1610
// Arguments used to create a debugger.
@@ -33,230 +27,15 @@ type debugger struct {
3327
Format string
3428
}
3529

36-
// newDebugger creates a debugger by parsing key=value arguments.
37-
func newDebugger(args []string) *debugger {
38-
d := &debugger{
39-
Log: log.New(os.Stderr, "nl: ", 0),
40-
Level: 1,
41-
}
42-
43-
for _, a := range args {
44-
kv := strings.Split(a, "=")
45-
if len(kv) != 2 {
46-
// Ignore malformed pairs and assume callers wants defaults.
47-
continue
48-
}
49-
50-
switch kv[0] {
51-
// Select the log level for the debugger.
52-
case "level":
53-
level, err := strconv.Atoi(kv[1])
54-
if err != nil {
55-
panicf("netlink: invalid NLDEBUG level: %q", a)
56-
}
57-
58-
d.Level = level
59-
case "format":
60-
d.Format = kv[1]
61-
}
62-
}
63-
64-
return d
65-
}
66-
67-
// debugf prints debugging information at the specified level, if d.Level is
68-
// high enough to print the message.
69-
func (d *debugger) debugf(level int, format string, v ...interface{}) {
70-
if d.Level >= level {
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-
}
80-
}
81-
}
82-
30+
// panicf is a helper to panic with formatted text.
8331
func panicf(format string, a ...interface{}) {
8432
panic(fmt.Sprintf(format, a...))
8533
}
8634

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-
35+
// ternary returns iftrue if cond is true, else iffalse.
25636
func ternary(cond bool, iftrue string, iffalse string) string {
25737
if cond {
25838
return iftrue
259-
} else {
260-
return iffalse
26139
}
40+
return iffalse
26241
}

0 commit comments

Comments
 (0)