|  | 
|  | 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