The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
ethernet.c
Go to the documentation of this file.
1 /*
2  * This program is is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License, version 2 of the
4  * License as published by the Free Software Foundation.
5  *
6  * This program is distributed in the hope that it will be useful,
7  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9  * GNU General Public License for more details.
10  *
11  * You should have received a copy of the GNU General Public License
12  * along with this program; if not, write to the Free Software
13  * Foundation, inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
14  */
15 
16 /**
17  * $Id: 45774a04e8cb082659d1382098d822e65fc40410 $
18  * @file ethernet.c
19  * @brief Functions to parse and construct ethernet headers.
20  *
21  * @copyright 2017 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
22  */
23 #include "ethernet.h"
24 #include <string.h>
25 #include <arpa/inet.h>
26 #include <freeradius-devel/io/proto.h>
27 
28 /** Decodes an ethernet header with up to two levels of VLAN nesting
29  *
30  * Technically this should be a .1Q protocol, but because of how .1Q subsumes the
31  * ether_type field, it's just easier to munge it together with the ethernet
32  * decoder.
33  *
34  * @param[out] proto_ctx Header information extracted from the ethernet frame,
35  * and any additional VLAN tags discovered.
36  * Must point to memory of the size indicated by the
37  * #fr_proto_lib_t struct exported by this library.
38  * @param[in] data Start of packet data.
39  * @param[in] data_len of the data.
40  * @return
41  * - >0 Length of the header.
42  * - <=0 on failure.
43  */
44 static ssize_t fr_ethernet_decode(void *proto_ctx, uint8_t const *data, size_t data_len)
45 {
46  uint8_t const *p = data, *end = p + data_len;
47  ethernet_header_t const *ether_hdr = (void const *)p;
48  vlan_header_t const *vlan_hdr;
49  int i = 0;
50  uint16_t ether_type;
51  fr_ethernet_proto_ctx_t *ether_ctx = proto_ctx;
52 
53  p += sizeof(*ether_hdr);
54  if (unlikely(p >= end)) {
55  ood:
56  fr_strerror_printf("Ethernet header length (%zu bytes) is greater than remaining "
57  "data in buffer (%zu bytes)", sizeof(*ether_hdr), data_len);
58  return 0;
59  }
60 
61  memcpy(ether_ctx->dst_addr.addr, ether_hdr->dst_addr, sizeof(ether_ctx->dst_addr));
62  memcpy(ether_ctx->src_addr.addr, ether_hdr->src_addr, sizeof(ether_ctx->src_addr));
63  ether_type = ntohs(ether_hdr->ether_type);
64 
65  p -= sizeof(ether_hdr->ether_type); /* reverse */
66  vlan_hdr = (void const *)p;
67  for (i = 0; i < 3; i++) {
68  switch (ether_type) {
69  /*
70  * There are a number of devices out there which
71  * double tag with 0x8100 *sigh*
72  */
73  case 0x8100: /* CVLAN */
74  case 0x9100: /* SVLAN */
75  case 0x9200: /* SVLAN */
76  case 0x9300: /* SVLAN */
77  if ((uint8_t const *)(++vlan_hdr) >= end) goto ood;
78  ether_type = ntohs(vlan_hdr->tag_type);
79  continue;
80 
81  default:
82  break;
83  }
84 
85  break;
86  }
87  vlan_hdr = (void const *)p; /* reset */
88 
89  /*
90  * We don't explicitly memset the ctx
91  * so se these to zero now.
92  */
93  ether_ctx->svlan_tpid = 0;
94  ether_ctx->cvlan_tpid = 0;
95 
96  switch (i) {
97  /*
98  * SVLAN
99  */
100  case 2:
101  ether_ctx->svlan_tpid = ntohs(vlan_hdr->tag_type);
102  ether_ctx->svlan_pcp = VLAN_PCP_UNPACK(vlan_hdr);
103  ether_ctx->svlan_dei = VLAN_DEI_UNPACK(vlan_hdr);
104  ether_ctx->svlan_vid = VLAN_VID_UNPACK(vlan_hdr);
105  vlan_hdr++;
106  FALL_THROUGH;
107 
108  /*
109  * CVLAN
110  */
111  case 1:
112  ether_ctx->cvlan_tpid = ntohs(vlan_hdr->tag_type);
113  ether_ctx->cvlan_pcp = VLAN_PCP_UNPACK(vlan_hdr);
114  ether_ctx->cvlan_dei = VLAN_DEI_UNPACK(vlan_hdr);
115  ether_ctx->cvlan_vid = VLAN_VID_UNPACK(vlan_hdr);
116  vlan_hdr++;
117  FALL_THROUGH;
118 
119  /*
120  * Naked
121  */
122  case 0:
123  ether_ctx->ether_type = ether_type; /* Always ends up being the payload type */
124  break;
125 
126  default:
127  fr_strerror_const("Exceeded maximum level of VLAN tag nesting (2)");
128  break;
129  }
130  p = ((uint8_t const *)vlan_hdr) + sizeof(ether_hdr->ether_type);
131 
132  ether_ctx->payload_len = data_len - (p - data);
133 
134  return p - data;
135 }
136 
137 /** Encodes an ethernet header and up to two levels of VLAN nesting
138  *
139  * @param[in] proto_ctx produced by #fr_ethernet_decode, or by the code
140  * creating a new packet.
141  * @param[out] data Where to write output data.
142  * @param[in] data_len Length of the output buffer.
143  * @return
144  * - >0 The length of data written to the buffer.
145  * - 0 an error occurred.
146  * - <0 The amount of buffer space we would have needed (as a negative integer).
147  */
148 static ssize_t fr_ethernet_encode(void *proto_ctx, uint8_t *data, size_t data_len)
149 {
150  fr_ethernet_proto_ctx_t *ether_ctx = proto_ctx;
151 
152  uint8_t *p = data, *end = p + data_len;
153 
154  ethernet_header_t *ether_hdr = (void *)p;
155 
156  p += sizeof(ether_hdr->src_addr) + sizeof(ether_hdr->dst_addr);
157  if (unlikely(p >= end)) {
158  oob:
159  fr_strerror_printf("insufficient buffer space, needed %zu bytes, have %zu bytes",
160  p - data, data_len);
161  return data - p;
162  }
163 
164  memcpy(ether_hdr->dst_addr, ether_ctx->dst_addr.addr, sizeof(ether_hdr->dst_addr));
165  memcpy(ether_hdr->src_addr, ether_ctx->src_addr.addr, sizeof(ether_hdr->src_addr));
166 
167  /*
168  * Encode the SVLAN, CVLAN and ether type.
169  */
170  if (ether_ctx->svlan_tpid) {
171  vlan_header_t *svlan_hdr, *cvlan_hdr;
172  uint16_t *ether_type;
173 
174  svlan_hdr = (void *)p;
175  p += sizeof(*svlan_hdr);
176 
177  cvlan_hdr = (void *)p;
178  p += sizeof(*cvlan_hdr);
179 
180  ether_type = (void *)p;
181  p += sizeof(*ether_type);
182 
183  if (unlikely(p >= end)) goto oob;
184 
185  svlan_hdr->tag_type = htons(ether_ctx->svlan_tpid);
186  svlan_hdr->tag_control = VLAN_TCI_PACK(ether_ctx->svlan_pcp, ether_ctx->svlan_dei,
187  ether_ctx->svlan_vid);
188 
189  cvlan_hdr->tag_type = htons(ether_ctx->cvlan_tpid);
190  cvlan_hdr->tag_control = VLAN_TCI_PACK(ether_ctx->cvlan_pcp, ether_ctx->cvlan_dei,
191  ether_ctx->cvlan_vid);
192 
193  *ether_type = htons(ether_ctx->ether_type);
194 
195  return p - data;
196  }
197 
198  /*
199  * Just encode the CVLAN and ether type.
200  */
201  if (ether_ctx->cvlan_tpid) {
202  vlan_header_t *cvlan_hdr;
203  uint16_t *ether_type;
204 
205  cvlan_hdr = (void *)p;
206  p += sizeof(*cvlan_hdr);
207 
208  ether_type = (void *)p;
209  p += sizeof(*ether_type);
210 
211  if (unlikely(p >= end)) goto oob;
212 
213  cvlan_hdr->tag_type = htons(ether_ctx->cvlan_tpid);
214  cvlan_hdr->tag_control = VLAN_TCI_PACK(ether_ctx->cvlan_pcp, ether_ctx->cvlan_dei,
215  ether_ctx->cvlan_vid);
216 
217  *ether_type = htons(ether_ctx->ether_type);
218 
219  return p - data;
220  }
221 
222  /*
223  * Just encode the ether type.
224  */
225  p += sizeof(ether_hdr->ether_type);
226  if (unlikely(p >= end)) goto oob;
227 
228  ether_hdr->ether_type = htons(ether_ctx->ether_type);
229 
230  return p - data;
231 }
232 
233 /** Inverts addresses, so that a decoder proto_ctx can be used for encoding
234  *
235  * @param[in] proto_ctx created by the user or decoder.
236  */
237 static void fr_ethernet_invert(void *proto_ctx)
238 {
239  fr_ethernet_proto_ctx_t *ether_ctx = proto_ctx;
240  uint8_t tmp_addr[ETHER_ADDR_LEN];
241 
242  /*
243  * VLANs stay the same, we just need to swap the mac addresses
244  */
245  memcpy(tmp_addr, ether_ctx->dst_addr.addr, sizeof(tmp_addr));
246  memcpy(&ether_ctx->dst_addr, &ether_ctx->src_addr, sizeof(ether_ctx->dst_addr));
247  memcpy(ether_ctx->src_addr.addr, tmp_addr, sizeof(ether_ctx->src_addr));
248 }
249 
250 /** Retrieve an option value from the proto_ctx
251  *
252  * @param[out] out value box to place option value into.
253  * @param[in] proto_ctx to retrieve value from.
254  * @param[in] group Option group. Which collection of options to query.
255  * @param[in] opt Option to retrieve.
256  * @return
257  * - 0 on success.
258  * - -1 on failure.
259  */
260 static int fr_ethernet_get_option(fr_value_box_t *out, void const *proto_ctx, fr_proto_opt_group_t group, int opt)
261 {
262  fr_ethernet_proto_ctx_t const *ether_ctx = proto_ctx;
263 
264  switch (group) {
266  switch (opt) {
268  return fr_value_box(out, ether_ctx->svlan_tpid, true);
269 
271  return fr_value_box(out, ether_ctx->svlan_pcp, true);
272 
274  return fr_value_box(out, ether_ctx->svlan_dei, true);
275 
277  return fr_value_box(out, ether_ctx->svlan_vid, true);
278 
280  return fr_value_box(out, ether_ctx->cvlan_tpid, true);
281 
283  return fr_value_box(out, ether_ctx->cvlan_pcp, true);
284 
286  return fr_value_box(out, ether_ctx->cvlan_dei, true);
287 
289  return fr_value_box(out, ether_ctx->cvlan_vid, true);
290 
291  default:
292  fr_strerror_printf("Option %i group %i not implemented", opt, group);
293  return -1;
294  }
295 
296  case PROTO_OPT_GROUP_L2:
297  switch (opt) {
299  fr_value_box_init(out, FR_TYPE_SIZE, NULL, true);
300  out->vb_size = ether_ctx->payload_len;
301  return 0;
302 
304  return fr_value_box_ethernet_addr(out, NULL, &ether_ctx->src_addr, true);
305 
307  return fr_value_box_ethernet_addr(out, NULL, &ether_ctx->dst_addr, true);
308 
310  return fr_value_box(out, ether_ctx->ether_type, true);
311 
312  default:
313  fr_strerror_printf("Option %i group %i not implemented", opt, group);
314  return -1;
315  }
316 
317  default:
318  fr_strerror_printf("Option group %i not implemented", group);
319  return -1;
320  }
321 }
322 
323 /** Set an option in the proto_ctx
324  *
325  * @param[in] proto_ctx to set value in.
326  * @param[in] group Option group. Which collection of options opt exists in.
327  * @param[in] opt Option to set.
328  * @param[in] in value to set.
329  * @return
330  * - 0 on success.
331  * - -1 on failure.
332  */
333 static int fr_ethernet_set_option(void *proto_ctx, fr_proto_opt_group_t group, int opt, fr_value_box_t *in)
334 {
335  fr_ethernet_proto_ctx_t *ether_ctx = proto_ctx;
336 
337  switch (group) {
339  switch (opt) {
341  return fr_value_unbox_shallow(&ether_ctx->svlan_tpid, in);
342 
344  return fr_value_unbox_shallow(&ether_ctx->svlan_pcp, in);
345 
347  return fr_value_unbox_shallow(&ether_ctx->svlan_dei, in);
348 
350  return fr_value_unbox_shallow(&ether_ctx->svlan_vid, in);
351 
353  return fr_value_unbox_shallow(&ether_ctx->cvlan_tpid, in);
354 
356  return fr_value_unbox_shallow(&ether_ctx->cvlan_pcp, in);
357 
359  return fr_value_unbox_shallow(&ether_ctx->cvlan_dei, in);
360 
362  return fr_value_unbox_shallow(&ether_ctx->cvlan_vid, in);
363 
364  default:
365  fr_strerror_printf("Option %i group %i not implemented", opt, group);
366  return -1;
367  }
368 
369  case PROTO_OPT_GROUP_L2:
370  switch (opt) {
372  if (in->type != FR_TYPE_SIZE) {
373  fr_strerror_printf("Unboxing failed. Needed type %s, had type %s",
375  fr_type_to_str(in->type));
376  return -1;
377  }
378  ether_ctx->payload_len = in->vb_size;
379  return 0;
380 
382  return fr_value_unbox_ethernet_addr(&ether_ctx->src_addr, in);
383 
385  return fr_value_unbox_ethernet_addr(&ether_ctx->dst_addr, in);
386 
388  return fr_value_unbox_shallow(&ether_ctx->ether_type, in);
389 
390  default:
391  fr_strerror_printf("Option %i group %i not implemented", opt, group);
392  return -1;
393  }
394 
395  default:
396  fr_strerror_printf("Option group %i not implemented", group);
397  return -1;
398  }
399 
400 }
401 
404  .magic = MODULE_MAGIC_INIT,
405  .name = "ethernet",
407 
408  .decode = fr_ethernet_decode,
409  .encode = fr_ethernet_encode,
410  .invert = fr_ethernet_invert,
411  .get_option = fr_ethernet_get_option,
412  .set_option = fr_ethernet_set_option
413 };
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition: build.h:320
#define unlikely(_x)
Definition: build.h:379
static fr_slen_t in
Definition: dict.h:821
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition: dl_module.h:63
static ssize_t fr_ethernet_decode(void *proto_ctx, uint8_t const *data, size_t data_len)
Decodes an ethernet header with up to two levels of VLAN nesting.
Definition: ethernet.c:44
static int fr_ethernet_get_option(fr_value_box_t *out, void const *proto_ctx, fr_proto_opt_group_t group, int opt)
Retrieve an option value from the proto_ctx.
Definition: ethernet.c:260
static ssize_t fr_ethernet_encode(void *proto_ctx, uint8_t *data, size_t data_len)
Encodes an ethernet header and up to two levels of VLAN nesting.
Definition: ethernet.c:148
static void fr_ethernet_invert(void *proto_ctx)
Inverts addresses, so that a decoder proto_ctx can be used for encoding.
Definition: ethernet.c:237
static int fr_ethernet_set_option(void *proto_ctx, fr_proto_opt_group_t group, int opt, fr_value_box_t *in)
Set an option in the proto_ctx.
Definition: ethernet.c:333
fr_proto_lib_t const libfreeradius_ethernet
Definition: ethernet.c:403
Structures and functions for parsing ethernet headers.
uint16_t ether_type
Ether type. Usually 0x0800 (IPv4) 0x086DD (IPv6).
Definition: ethernet.h:90
fr_ethernet_t src_addr
Definition: ethernet.h:88
uint8_t dst_addr[ETHER_ADDR_LEN]
Definition: ethernet.h:79
uint16_t svlan_tpid
SVLAN tag type. If 0, no SVLAN present.
Definition: ethernet.h:97
uint16_t cvlan_tpid
CVLAN tag type. If 0, no CVLAN/SVLAN present.
Definition: ethernet.h:92
uint16_t cvlan_vid
CVLAN vlan ID.
Definition: ethernet.h:95
fr_ethernet_t dst_addr
Definition: ethernet.h:89
uint16_t svlan_vid
SVLAN vlan ID.
Definition: ethernet.h:100
#define VLAN_VID_UNPACK(_vlan)
Unpack the VLAN ID from the TCI.
Definition: ethernet.h:48
uint8_t cvlan_dei
CVLAN drop eligible indicator.
Definition: ethernet.h:94
size_t payload_len
Remaining bytes after the ethernet header has been parsed.
Definition: ethernet.h:102
uint8_t svlan_dei
SVLAN drop eligible indicator.
Definition: ethernet.h:99
uint16_t tag_control
Definition: ethernet.h:70
#define VLAN_DEI_UNPACK(_vlan)
Unpack the Drop Eligible Indicator from the TCI.
Definition: ethernet.h:43
#define VLAN_TCI_PACK(_pcp, _dei, _vid)
Pack the PCP (Priority Code Point) DEI (Drop Eligible Indicator) and VID (VLAN ID)
Definition: ethernet.h:61
@ PROTO_OPT_ETHERNET_SVLAN_PCP
Outer VLAN priority code point.
Definition: ethernet.h:110
@ PROTO_OPT_ETHERNET_CVLAN_PCP
Inner VLAN priority code point.
Definition: ethernet.h:114
@ PROTO_OPT_ETHERNET_CVLAN_VID
Inner VLAN ID.
Definition: ethernet.h:116
@ PROTO_OPT_ETHERNET_SVLAN_VID
Outer VLAN ID.
Definition: ethernet.h:112
@ PROTO_OPT_ETHERNET_CVLAN_DEI
Inner VLAN drop eligible indicator.
Definition: ethernet.h:115
@ PROTO_OPT_ETHERNET_SVLAN_DEI
Outer VLAN drop eligible indicator.
Definition: ethernet.h:111
@ PROTO_OPT_ETHERNET_SVLAN_TPID
Outer VLAN tag type.
Definition: ethernet.h:109
@ PROTO_OPT_ETHERNET_CVLAN_TPID
Inner VLAN tag type.
Definition: ethernet.h:113
uint16_t tag_type
Tag type.
Definition: ethernet.h:68
#define VLAN_PCP_UNPACK(_vlan)
Unpack the Priority Code Point from the TCI.
Definition: ethernet.h:38
uint16_t ether_type
Definition: ethernet.h:81
uint8_t svlan_pcp
SVLAN priority code point 0-6.
Definition: ethernet.h:98
uint8_t src_addr[ETHER_ADDR_LEN]
Definition: ethernet.h:80
uint8_t cvlan_pcp
CVLAN priority code point 0-6.
Definition: ethernet.h:93
Structure of a DEC/Intel/Xerox or 802.3 Ethernet header.
Definition: ethernet.h:78
Src/dst link layer information.
Definition: ethernet.h:87
A VLAN header.
Definition: ethernet.h:67
uint8_t addr[6]
Ethernet address.
Definition: inet.h:46
fr_proto_opt_group_t
Option contexts.
Definition: proto.h:36
@ PROTO_OPT_GROUP_L2
Generic layer 2 options.
Definition: proto.h:39
@ PROTO_OPT_GROUP_CUSTOM
Custom options exported by the library.
Definition: proto.h:37
@ PROTO_OPT_L2_NEXT_PROTOCOL
Next protocol (if available).
Definition: proto.h:52
@ PROTO_OPT_L2_SRC_ADDRESS
Source address.
Definition: proto.h:50
@ PROTO_OPT_L2_DST_ADDRESS
Destination address.
Definition: proto.h:51
@ PROTO_OPT_L2_PAYLOAD_LEN
Definition: proto.h:49
The public structure exported by protocol encoding/decoding libraries.
Definition: proto.h:157
unsigned short uint16_t
Definition: merged_model.c:31
@ FR_TYPE_SIZE
Unsigned integer capable of representing any memory address on the local system.
Definition: merged_model.c:115
long int ssize_t
Definition: merged_model.c:24
unsigned char uint8_t
Definition: merged_model.c:30
#define ETHER_ADDR_LEN
Definition: net.h:64
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition: strerror.h:64
#define fr_strerror_const(_msg)
Definition: strerror.h:223
static char const * fr_type_to_str(fr_type_t type)
Return a static string containing the type name.
Definition: types.h:433
static fr_slen_t data
Definition: value.h:1265
#define fr_value_box(_box, _var, _tainted)
Automagically fill in a box, determining the value type from the type of the C variable.
Definition: value.h:871
static always_inline int fr_value_box_ethernet_addr(fr_value_box_t *dst, fr_dict_attr_t const *enumv, fr_ethernet_t const *src, bool tainted)
Definition: value.h:823
static int fr_value_unbox_ethernet_addr(fr_ethernet_t *dst, fr_value_box_t *src)
Unbox an ethernet value (6 bytes, network byte order)
Definition: value.h:915
#define fr_value_unbox_shallow(_var, _box)
Unbox simple types performing type checks.
Definition: value.h:960
#define fr_value_box_init(_vb, _type, _enumv, _tainted)
Initialise a fr_value_box_t.
Definition: value.h:587
static size_t char ** out
Definition: value.h:997