Coverage Report

Created: 2025-12-12 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/frr/zebra/tc_netlink.c
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
/*
3
 * Zebra Traffic Control (TC) interaction with the kernel using netlink.
4
 *
5
 * Copyright (C) 2022 Shichu Yang
6
 */
7
8
#include <zebra.h>
9
10
#ifdef HAVE_NETLINK
11
12
#include <linux/pkt_cls.h>
13
#include <linux/pkt_sched.h>
14
#include <netinet/if_ether.h>
15
#include <sys/socket.h>
16
17
#include "if.h"
18
#include "prefix.h"
19
#include "vrf.h"
20
21
#include "zebra/zserv.h"
22
#include "zebra/zebra_ns.h"
23
#include "zebra/rt.h"
24
#include "zebra/interface.h"
25
#include "zebra/debug.h"
26
#include "zebra/kernel_netlink.h"
27
#include "zebra/tc_netlink.h"
28
#include "zebra/zebra_errors.h"
29
#include "zebra/zebra_dplane.h"
30
#include "zebra/zebra_tc.h"
31
#include "zebra/zebra_trace.h"
32
33
0
#define TC_FREQ_DEFAULT (100)
34
35
/* some magic number */
36
0
#define TC_QDISC_MAJOR_ZEBRA (0xbeef0000u)
37
0
#define TC_MINOR_NOCLASS (0xffffu)
38
39
0
#define TIME_UNITS_PER_SEC (1000000)
40
0
#define xmittime(r, s) (TIME_UNITS_PER_SEC * ((double)(s) / (double)(r)))
41
42
static uint32_t tc_get_freq(void)
43
0
{
44
0
  int freq = 0;
45
0
  FILE *fp = fopen("/proc/net/psched", "r");
46
47
0
  if (fp) {
48
0
    uint32_t nom, denom;
49
50
0
    if (fscanf(fp, "%*08x%*08x%08x%08x", &nom, &denom) == 2) {
51
0
      if (nom == 1000000)
52
0
        freq = denom;
53
0
    }
54
0
    fclose(fp);
55
0
  }
56
57
0
  return freq == 0 ? TC_FREQ_DEFAULT : freq;
58
0
}
59
60
static void tc_calc_rate_table(struct tc_ratespec *ratespec, uint32_t *table,
61
             uint32_t mtu)
62
0
{
63
0
  if (mtu == 0)
64
0
    mtu = 2047;
65
66
0
  int cell_log = -1;
67
68
0
  if (cell_log < 0) {
69
0
    cell_log = 0;
70
0
    while ((mtu >> cell_log) > 255)
71
0
      cell_log++;
72
0
  }
73
74
0
  for (int i = 0; i < 256; i++)
75
0
    table[i] = xmittime(ratespec->rate, (i + 1) << cell_log);
76
77
0
  ratespec->cell_align = -1;
78
0
  ratespec->cell_log = cell_log;
79
0
  ratespec->linklayer = TC_LINKLAYER_ETHERNET;
80
0
}
81
82
static int tc_flower_get_inet_prefix(const struct prefix *prefix,
83
             struct inet_prefix *addr)
84
0
{
85
0
  addr->family = prefix->family;
86
87
0
  if (addr->family == AF_INET) {
88
0
    addr->bytelen = 4;
89
0
    addr->bitlen = prefix->prefixlen;
90
0
    addr->flags = 0;
91
0
    addr->flags |= PREFIXLEN_SPECIFIED;
92
0
    addr->flags |= ADDRTYPE_INET;
93
0
    memcpy(addr->data, prefix->u.val32, sizeof(prefix->u.val32));
94
0
  } else if (addr->family == AF_INET6) {
95
0
    addr->bytelen = 16;
96
0
    addr->bitlen = prefix->prefixlen;
97
0
    addr->flags = 0;
98
0
    addr->flags |= PREFIXLEN_SPECIFIED;
99
0
    addr->flags |= ADDRTYPE_INET;
100
0
    memcpy(addr->data, prefix->u.val, sizeof(prefix->u.val));
101
0
  } else {
102
0
    return -1;
103
0
  }
104
105
0
  return 0;
106
0
}
107
108
static int tc_flower_get_inet_mask(const struct prefix *prefix,
109
           struct inet_prefix *addr)
110
0
{
111
0
  addr->family = prefix->family;
112
113
0
  if (addr->family == AF_INET) {
114
0
    addr->bytelen = 4;
115
0
    addr->bitlen = prefix->prefixlen;
116
0
    addr->flags = 0;
117
0
    addr->flags |= PREFIXLEN_SPECIFIED;
118
0
    addr->flags |= ADDRTYPE_INET;
119
0
  } else if (addr->family == AF_INET6) {
120
0
    addr->bytelen = 16;
121
0
    addr->bitlen = prefix->prefixlen;
122
0
    addr->flags = 0;
123
0
    addr->flags |= PREFIXLEN_SPECIFIED;
124
0
    addr->flags |= ADDRTYPE_INET;
125
0
  } else {
126
0
    return -1;
127
0
  }
128
129
0
  memset(addr->data, 0xff, addr->bytelen);
130
131
0
  int rest = prefix->prefixlen;
132
133
0
  for (int i = 0; i < addr->bytelen / 4; i++) {
134
0
    if (!rest) {
135
0
      addr->data[i] = 0;
136
0
    } else if (rest / 32 >= 1) {
137
0
      rest -= 32;
138
0
    } else {
139
0
      addr->data[i] <<= 32 - rest;
140
0
      addr->data[i] = htonl(addr->data[i]);
141
0
      rest = 0;
142
0
    }
143
0
  }
144
145
0
  return 0;
146
0
}
147
148
/*
149
 * Traffic control queue discipline encoding (only "htb" supported)
150
 */
151
static ssize_t netlink_qdisc_msg_encode(int cmd, struct zebra_dplane_ctx *ctx,
152
          void *data, size_t datalen)
153
0
{
154
0
  struct nlsock *nl;
155
0
  const char *kind_str = NULL;
156
157
0
  struct rtattr *nest;
158
159
0
  struct {
160
0
    struct nlmsghdr n;
161
0
    struct tcmsg t;
162
0
    char buf[0];
163
0
  } *req = (void *)data;
164
165
0
  if (datalen < sizeof(*req))
166
0
    return 0;
167
168
0
  nl = kernel_netlink_nlsock_lookup(dplane_ctx_get_ns_sock(ctx));
169
170
0
  memset(req, 0, sizeof(*req));
171
172
0
  req->n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
173
0
  req->n.nlmsg_flags = NLM_F_CREATE | NLM_F_REQUEST;
174
175
0
  req->n.nlmsg_flags |= NLM_F_REPLACE;
176
177
0
  req->n.nlmsg_type = cmd;
178
179
0
  req->n.nlmsg_pid = nl->snl.nl_pid;
180
181
0
  req->t.tcm_family = AF_UNSPEC;
182
0
  req->t.tcm_ifindex = dplane_ctx_get_ifindex(ctx);
183
0
  req->t.tcm_info = 0;
184
0
  req->t.tcm_handle = 0;
185
0
  req->t.tcm_parent = TC_H_ROOT;
186
187
0
  if (cmd == RTM_NEWQDISC) {
188
0
    req->t.tcm_handle = TC_H_MAKE(TC_QDISC_MAJOR_ZEBRA, 0);
189
190
0
    kind_str = dplane_ctx_tc_qdisc_get_kind_str(ctx);
191
192
0
    nl_attr_put(&req->n, datalen, TCA_KIND, kind_str,
193
0
          strlen(kind_str) + 1);
194
195
0
    nest = nl_attr_nest(&req->n, datalen, TCA_OPTIONS);
196
197
0
    switch (dplane_ctx_tc_qdisc_get_kind(ctx)) {
198
0
    case TC_QDISC_HTB: {
199
0
      struct tc_htb_glob htb_glob = {
200
0
        .rate2quantum = 10,
201
0
        .version = 3,
202
0
        .defcls = TC_MINOR_NOCLASS};
203
0
      nl_attr_put(&req->n, datalen, TCA_HTB_INIT, &htb_glob,
204
0
            sizeof(htb_glob));
205
0
      break;
206
0
    }
207
0
    case TC_QDISC_NOQUEUE:
208
0
      break;
209
0
    default:
210
0
      break;
211
      /* not implemented */
212
0
    }
213
214
0
    nl_attr_nest_end(&req->n, nest);
215
0
  } else {
216
    /* ifindex are enough for del/get qdisc */
217
0
  }
218
219
0
  return NLMSG_ALIGN(req->n.nlmsg_len);
220
0
}
221
222
/*
223
 * Traffic control class encoding
224
 */
225
static ssize_t netlink_tclass_msg_encode(int cmd, struct zebra_dplane_ctx *ctx,
226
           void *data, size_t datalen)
227
0
{
228
0
  enum dplane_op_e op = dplane_ctx_get_op(ctx);
229
230
0
  struct nlsock *nl;
231
0
  const char *kind_str = NULL;
232
233
0
  struct rtattr *nest;
234
235
0
  struct {
236
0
    struct nlmsghdr n;
237
0
    struct tcmsg t;
238
0
    char buf[0];
239
0
  } *req = (void *)data;
240
241
0
  if (datalen < sizeof(*req))
242
0
    return 0;
243
244
0
  nl = kernel_netlink_nlsock_lookup(dplane_ctx_get_ns_sock(ctx));
245
246
0
  memset(req, 0, sizeof(*req));
247
248
0
  req->n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
249
0
  req->n.nlmsg_flags = NLM_F_CREATE | NLM_F_REQUEST;
250
251
0
  if (op == DPLANE_OP_TC_CLASS_UPDATE)
252
0
    req->n.nlmsg_flags |= NLM_F_REPLACE;
253
254
0
  req->n.nlmsg_type = cmd;
255
256
0
  req->n.nlmsg_pid = nl->snl.nl_pid;
257
258
0
  req->t.tcm_family = AF_UNSPEC;
259
0
  req->t.tcm_ifindex = dplane_ctx_get_ifindex(ctx);
260
261
0
  req->t.tcm_handle = TC_H_MAKE(TC_QDISC_MAJOR_ZEBRA,
262
0
              dplane_ctx_tc_class_get_handle(ctx));
263
0
  req->t.tcm_parent = TC_H_MAKE(TC_QDISC_MAJOR_ZEBRA, 0);
264
0
  req->t.tcm_info = 0;
265
266
0
  kind_str = dplane_ctx_tc_class_get_kind_str(ctx);
267
268
0
  if (op == DPLANE_OP_TC_CLASS_ADD || op == DPLANE_OP_TC_CLASS_UPDATE) {
269
0
    zlog_debug("netlink tclass encoder: op: %s kind: %s handle: %u",
270
0
         op == DPLANE_OP_TC_CLASS_UPDATE ? "update" : "add",
271
0
         kind_str, dplane_ctx_tc_class_get_handle(ctx));
272
273
0
    nl_attr_put(&req->n, datalen, TCA_KIND, kind_str,
274
0
          strlen(kind_str) + 1);
275
276
0
    nest = nl_attr_nest(&req->n, datalen, TCA_OPTIONS);
277
278
0
    switch (dplane_ctx_tc_class_get_kind(ctx)) {
279
0
    case TC_QDISC_HTB: {
280
0
      struct tc_htb_opt htb_opt = {};
281
282
0
      uint64_t rate = dplane_ctx_tc_class_get_rate(ctx),
283
0
         ceil = dplane_ctx_tc_class_get_ceil(ctx);
284
285
0
      uint64_t buffer, cbuffer;
286
287
      /* TODO: fetch mtu from interface */
288
0
      uint32_t mtu = 1500;
289
290
0
      uint32_t rtab[256];
291
0
      uint32_t ctab[256];
292
293
0
      ceil = MAX(rate, ceil);
294
295
0
      htb_opt.rate.rate = (rate >> 32 != 0) ? ~0U : rate;
296
0
      htb_opt.ceil.rate = (ceil >> 32 != 0) ? ~0U : ceil;
297
298
0
      buffer = rate / tc_get_freq() + mtu;
299
0
      cbuffer = ceil / tc_get_freq() + mtu;
300
301
0
      htb_opt.buffer = buffer;
302
0
      htb_opt.cbuffer = cbuffer;
303
304
0
      tc_calc_rate_table(&htb_opt.rate, rtab, mtu);
305
0
      tc_calc_rate_table(&htb_opt.ceil, ctab, mtu);
306
307
0
      htb_opt.ceil.mpu = htb_opt.rate.mpu = 0;
308
0
      htb_opt.ceil.overhead = htb_opt.rate.overhead = 0;
309
310
0
      if (rate >> 32 != 0) {
311
0
        nl_attr_put(&req->n, datalen, TCA_HTB_RATE64,
312
0
              &rate, sizeof(rate));
313
0
      }
314
315
0
      if (ceil >> 32 != 0) {
316
0
        nl_attr_put(&req->n, datalen, TCA_HTB_CEIL64,
317
0
              &ceil, sizeof(ceil));
318
0
      }
319
320
0
      nl_attr_put(&req->n, datalen, TCA_HTB_PARMS, &htb_opt,
321
0
            sizeof(htb_opt));
322
323
0
      nl_attr_put(&req->n, datalen, TCA_HTB_RTAB, rtab,
324
0
            sizeof(rtab));
325
0
      nl_attr_put(&req->n, datalen, TCA_HTB_CTAB, ctab,
326
0
            sizeof(ctab));
327
0
      break;
328
0
    }
329
0
    default:
330
0
      break;
331
0
    }
332
333
0
    nl_attr_nest_end(&req->n, nest);
334
0
  }
335
336
0
  return NLMSG_ALIGN(req->n.nlmsg_len);
337
0
}
338
339
static int netlink_tfilter_flower_port_type(uint8_t ip_proto, bool src)
340
0
{
341
0
  if (ip_proto == IPPROTO_TCP)
342
0
    return src ? TCA_FLOWER_KEY_TCP_SRC : TCA_FLOWER_KEY_TCP_DST;
343
0
  else if (ip_proto == IPPROTO_UDP)
344
0
    return src ? TCA_FLOWER_KEY_UDP_SRC : TCA_FLOWER_KEY_UDP_DST;
345
0
  else if (ip_proto == IPPROTO_SCTP)
346
0
    return src ? TCA_FLOWER_KEY_SCTP_SRC : TCA_FLOWER_KEY_SCTP_DST;
347
0
  else
348
0
    return -1;
349
0
}
350
351
static void netlink_tfilter_flower_put_options(struct nlmsghdr *n,
352
                 size_t datalen,
353
                 struct zebra_dplane_ctx *ctx)
354
0
{
355
0
  struct inet_prefix addr;
356
0
  uint32_t flags = 0, classid;
357
0
  uint8_t protocol = htons(dplane_ctx_tc_filter_get_eth_proto(ctx));
358
0
  uint32_t filter_bm = dplane_ctx_tc_filter_get_filter_bm(ctx);
359
360
0
  if (filter_bm & TC_FLOWER_SRC_IP) {
361
0
    const struct prefix *src_p =
362
0
      dplane_ctx_tc_filter_get_src_ip(ctx);
363
364
0
    if (tc_flower_get_inet_prefix(src_p, &addr) != 0)
365
0
      return;
366
367
0
    nl_attr_put(n, datalen,
368
0
          (addr.family == AF_INET) ? TCA_FLOWER_KEY_IPV4_SRC
369
0
                 : TCA_FLOWER_KEY_IPV6_SRC,
370
0
          addr.data, addr.bytelen);
371
372
0
    if (tc_flower_get_inet_mask(src_p, &addr) != 0)
373
0
      return;
374
375
0
    nl_attr_put(n, datalen,
376
0
          (addr.family == AF_INET)
377
0
            ? TCA_FLOWER_KEY_IPV4_SRC_MASK
378
0
            : TCA_FLOWER_KEY_IPV6_SRC_MASK,
379
0
          addr.data, addr.bytelen);
380
0
  }
381
382
0
  if (filter_bm & TC_FLOWER_DST_IP) {
383
0
    const struct prefix *dst_p =
384
0
      dplane_ctx_tc_filter_get_dst_ip(ctx);
385
386
0
    if (tc_flower_get_inet_prefix(dst_p, &addr) != 0)
387
0
      return;
388
389
0
    nl_attr_put(n, datalen,
390
0
          (addr.family == AF_INET) ? TCA_FLOWER_KEY_IPV4_DST
391
0
                 : TCA_FLOWER_KEY_IPV6_DST,
392
0
          addr.data, addr.bytelen);
393
394
0
    if (tc_flower_get_inet_mask(dst_p, &addr) != 0)
395
0
      return;
396
397
0
    nl_attr_put(n, datalen,
398
0
          (addr.family == AF_INET)
399
0
            ? TCA_FLOWER_KEY_IPV4_DST_MASK
400
0
            : TCA_FLOWER_KEY_IPV6_DST_MASK,
401
0
          addr.data, addr.bytelen);
402
0
  }
403
404
0
  if (filter_bm & TC_FLOWER_IP_PROTOCOL) {
405
0
    nl_attr_put8(n, datalen, TCA_FLOWER_KEY_IP_PROTO,
406
0
           dplane_ctx_tc_filter_get_ip_proto(ctx));
407
0
  }
408
409
0
  if (filter_bm & TC_FLOWER_SRC_PORT) {
410
0
    uint16_t min, max;
411
412
0
    min = dplane_ctx_tc_filter_get_src_port_min(ctx);
413
0
    max = dplane_ctx_tc_filter_get_src_port_max(ctx);
414
415
0
    if (max > min) {
416
0
      nl_attr_put16(n, datalen, TCA_FLOWER_KEY_PORT_SRC_MIN,
417
0
              htons(min));
418
419
0
      nl_attr_put16(n, datalen, TCA_FLOWER_KEY_PORT_SRC_MAX,
420
0
              htons(max));
421
0
    } else {
422
0
      int type = netlink_tfilter_flower_port_type(
423
0
        dplane_ctx_tc_filter_get_ip_proto(ctx), true);
424
425
0
      if (type < 0)
426
0
        return;
427
428
0
      nl_attr_put16(n, datalen, type, htons(min));
429
0
    }
430
0
  }
431
432
0
  if (filter_bm & TC_FLOWER_DST_PORT) {
433
0
    uint16_t min = dplane_ctx_tc_filter_get_dst_port_min(ctx),
434
0
       max = dplane_ctx_tc_filter_get_dst_port_max(ctx);
435
436
0
    if (max > min) {
437
0
      nl_attr_put16(n, datalen, TCA_FLOWER_KEY_PORT_DST_MIN,
438
0
              htons(min));
439
440
0
      nl_attr_put16(n, datalen, TCA_FLOWER_KEY_PORT_DST_MAX,
441
0
              htons(max));
442
0
    } else {
443
0
      int type = netlink_tfilter_flower_port_type(
444
0
        dplane_ctx_tc_filter_get_ip_proto(ctx), false);
445
446
0
      if (type < 0)
447
0
        return;
448
449
0
      nl_attr_put16(n, datalen, type, htons(min));
450
0
    }
451
0
  }
452
453
0
  if (filter_bm & TC_FLOWER_DSFIELD) {
454
0
    nl_attr_put8(n, datalen, TCA_FLOWER_KEY_IP_TOS,
455
0
           dplane_ctx_tc_filter_get_dsfield(ctx));
456
0
    nl_attr_put8(n, datalen, TCA_FLOWER_KEY_IP_TOS_MASK,
457
0
           dplane_ctx_tc_filter_get_dsfield_mask(ctx));
458
0
  }
459
460
0
  classid = TC_H_MAKE(TC_QDISC_MAJOR_ZEBRA,
461
0
          dplane_ctx_tc_filter_get_classid(ctx));
462
0
  nl_attr_put32(n, datalen, TCA_FLOWER_CLASSID, classid);
463
464
0
  nl_attr_put32(n, datalen, TCA_FLOWER_FLAGS, flags);
465
466
0
  nl_attr_put16(n, datalen, TCA_FLOWER_KEY_ETH_TYPE, protocol);
467
0
}
468
469
/*
470
 * Traffic control filter encoding
471
 */
472
static ssize_t netlink_tfilter_msg_encode(int cmd, struct zebra_dplane_ctx *ctx,
473
            void *data, size_t datalen)
474
0
{
475
0
  enum dplane_op_e op = dplane_ctx_get_op(ctx);
476
477
0
  struct nlsock *nl;
478
0
  const char *kind_str = NULL;
479
480
0
  struct rtattr *nest;
481
482
0
  uint16_t priority;
483
0
  uint16_t protocol;
484
485
0
  struct {
486
0
    struct nlmsghdr n;
487
0
    struct tcmsg t;
488
0
    char buf[0];
489
0
  } *req = (void *)data;
490
491
0
  if (datalen < sizeof(*req))
492
0
    return 0;
493
494
0
  nl = kernel_netlink_nlsock_lookup(dplane_ctx_get_ns_sock(ctx));
495
496
0
  memset(req, 0, sizeof(*req));
497
498
0
  req->n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
499
0
  req->n.nlmsg_flags = NLM_F_CREATE | NLM_F_REQUEST;
500
501
0
  if (op == DPLANE_OP_TC_FILTER_UPDATE)
502
0
    req->n.nlmsg_flags |= NLM_F_REPLACE;
503
504
0
  req->n.nlmsg_type = cmd;
505
506
0
  req->n.nlmsg_pid = nl->snl.nl_pid;
507
508
0
  req->t.tcm_family = AF_UNSPEC;
509
0
  req->t.tcm_ifindex = dplane_ctx_get_ifindex(ctx);
510
511
0
  priority = dplane_ctx_tc_filter_get_priority(ctx);
512
0
  protocol = htons(dplane_ctx_tc_filter_get_eth_proto(ctx));
513
514
0
  req->t.tcm_info = TC_H_MAKE(priority << 16, protocol);
515
0
  req->t.tcm_handle = dplane_ctx_tc_filter_get_handle(ctx);
516
0
  req->t.tcm_parent = TC_H_MAKE(TC_QDISC_MAJOR_ZEBRA, 0);
517
518
0
  kind_str = dplane_ctx_tc_filter_get_kind_str(ctx);
519
520
0
  if (op == DPLANE_OP_TC_FILTER_ADD || op == DPLANE_OP_TC_FILTER_UPDATE) {
521
0
    nl_attr_put(&req->n, datalen, TCA_KIND, kind_str,
522
0
          strlen(kind_str) + 1);
523
524
0
    zlog_debug(
525
0
      "netlink tfilter encoder: op: %s priority: %u protocol: %u kind: %s handle: %u filter_bm: %u ip_proto: %u",
526
0
      op == DPLANE_OP_TC_FILTER_UPDATE ? "update" : "add",
527
0
      priority, protocol, kind_str,
528
0
      dplane_ctx_tc_filter_get_handle(ctx),
529
0
      dplane_ctx_tc_filter_get_filter_bm(ctx),
530
0
      dplane_ctx_tc_filter_get_ip_proto(ctx));
531
532
0
    nest = nl_attr_nest(&req->n, datalen, TCA_OPTIONS);
533
0
    switch (dplane_ctx_tc_filter_get_kind(ctx)) {
534
0
    case TC_FILTER_FLOWER: {
535
0
      netlink_tfilter_flower_put_options(&req->n, datalen,
536
0
                 ctx);
537
0
      break;
538
0
    }
539
0
    default:
540
0
      break;
541
0
    }
542
0
    nl_attr_nest_end(&req->n, nest);
543
0
  }
544
545
0
  return NLMSG_ALIGN(req->n.nlmsg_len);
546
0
}
547
548
static ssize_t netlink_newqdisc_msg_encoder(struct zebra_dplane_ctx *ctx,
549
              void *buf, size_t buflen)
550
0
{
551
0
  return netlink_qdisc_msg_encode(RTM_NEWQDISC, ctx, buf, buflen);
552
0
}
553
554
static ssize_t netlink_delqdisc_msg_encoder(struct zebra_dplane_ctx *ctx,
555
              void *buf, size_t buflen)
556
0
{
557
0
  return netlink_qdisc_msg_encode(RTM_DELQDISC, ctx, buf, buflen);
558
0
}
559
560
static ssize_t netlink_newtclass_msg_encoder(struct zebra_dplane_ctx *ctx,
561
               void *buf, size_t buflen)
562
0
{
563
0
  return netlink_tclass_msg_encode(RTM_NEWTCLASS, ctx, buf, buflen);
564
0
}
565
566
static ssize_t netlink_deltclass_msg_encoder(struct zebra_dplane_ctx *ctx,
567
               void *buf, size_t buflen)
568
0
{
569
0
  return netlink_tclass_msg_encode(RTM_DELTCLASS, ctx, buf, buflen);
570
0
}
571
572
static ssize_t netlink_newtfilter_msg_encoder(struct zebra_dplane_ctx *ctx,
573
                void *buf, size_t buflen)
574
0
{
575
0
  return netlink_tfilter_msg_encode(RTM_NEWTFILTER, ctx, buf, buflen);
576
0
}
577
578
static ssize_t netlink_deltfilter_msg_encoder(struct zebra_dplane_ctx *ctx,
579
                void *buf, size_t buflen)
580
0
{
581
0
  return netlink_tfilter_msg_encode(RTM_DELTFILTER, ctx, buf, buflen);
582
0
}
583
584
enum netlink_msg_status
585
netlink_put_tc_qdisc_update_msg(struct nl_batch *bth,
586
        struct zebra_dplane_ctx *ctx)
587
0
{
588
0
  enum dplane_op_e op;
589
0
  enum netlink_msg_status ret;
590
591
0
  op = dplane_ctx_get_op(ctx);
592
593
0
  if (op == DPLANE_OP_TC_QDISC_INSTALL) {
594
0
    ret = netlink_batch_add_msg(
595
0
      bth, ctx, netlink_newqdisc_msg_encoder, false);
596
0
  } else if (op == DPLANE_OP_TC_QDISC_UNINSTALL) {
597
0
    ret = netlink_batch_add_msg(
598
0
      bth, ctx, netlink_delqdisc_msg_encoder, false);
599
0
  } else {
600
0
    return FRR_NETLINK_ERROR;
601
0
  }
602
603
0
  return ret;
604
0
}
605
606
enum netlink_msg_status
607
netlink_put_tc_class_update_msg(struct nl_batch *bth,
608
        struct zebra_dplane_ctx *ctx)
609
0
{
610
0
  enum dplane_op_e op;
611
0
  enum netlink_msg_status ret;
612
613
0
  op = dplane_ctx_get_op(ctx);
614
615
0
  if (op == DPLANE_OP_TC_CLASS_ADD || op == DPLANE_OP_TC_CLASS_UPDATE) {
616
0
    ret = netlink_batch_add_msg(
617
0
      bth, ctx, netlink_newtclass_msg_encoder, false);
618
0
  } else if (op == DPLANE_OP_TC_CLASS_DELETE) {
619
0
    ret = netlink_batch_add_msg(
620
0
      bth, ctx, netlink_deltclass_msg_encoder, false);
621
0
  } else {
622
0
    return FRR_NETLINK_ERROR;
623
0
  }
624
625
0
  return ret;
626
0
}
627
628
enum netlink_msg_status
629
netlink_put_tc_filter_update_msg(struct nl_batch *bth,
630
         struct zebra_dplane_ctx *ctx)
631
0
{
632
0
  enum dplane_op_e op;
633
0
  enum netlink_msg_status ret;
634
635
0
  op = dplane_ctx_get_op(ctx);
636
637
0
  if (op == DPLANE_OP_TC_FILTER_ADD) {
638
0
    ret = netlink_batch_add_msg(
639
0
      bth, ctx, netlink_newtfilter_msg_encoder, false);
640
0
  } else if (op == DPLANE_OP_TC_FILTER_UPDATE) {
641
    /*
642
     * Replace will fail if either filter type or the number of
643
     * filter options is changed, so DEL then NEW
644
     *
645
     * TFILTER may have refs to TCLASS.
646
     */
647
648
0
    (void)netlink_batch_add_msg(
649
0
      bth, ctx, netlink_deltfilter_msg_encoder, false);
650
0
    ret = netlink_batch_add_msg(
651
0
      bth, ctx, netlink_newtfilter_msg_encoder, false);
652
0
  } else if (op == DPLANE_OP_TC_FILTER_DELETE) {
653
0
    ret = netlink_batch_add_msg(
654
0
      bth, ctx, netlink_deltfilter_msg_encoder, false);
655
0
  } else {
656
0
    return FRR_NETLINK_ERROR;
657
0
  }
658
659
0
  return ret;
660
0
}
661
662
/*
663
 * Request filters from the kernel
664
 */
665
static int netlink_request_filters(struct zebra_ns *zns, int family, int type,
666
           ifindex_t ifindex)
667
0
{
668
0
  struct {
669
0
    struct nlmsghdr n;
670
0
    struct tcmsg tc;
671
0
  } req;
672
673
0
  memset(&req, 0, sizeof(req));
674
0
  req.n.nlmsg_type = type;
675
0
  req.n.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
676
0
  req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
677
0
  req.tc.tcm_family = family;
678
0
  req.tc.tcm_ifindex = ifindex;
679
680
0
  return netlink_request(&zns->netlink_cmd, &req);
681
0
}
682
683
/*
684
 * Request queue discipline from the kernel
685
 */
686
static int netlink_request_qdiscs(struct zebra_ns *zns, int family, int type)
687
1
{
688
1
  struct {
689
1
    struct nlmsghdr n;
690
1
    struct tcmsg tc;
691
1
  } req;
692
693
1
  memset(&req, 0, sizeof(req));
694
1
  req.n.nlmsg_type = type;
695
1
  req.n.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
696
1
  req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
697
1
  req.tc.tcm_family = family;
698
699
1
  return netlink_request(&zns->netlink_cmd, &req);
700
1
}
701
702
int netlink_qdisc_change(struct nlmsghdr *h, ns_id_t ns_id, int startup)
703
0
{
704
0
  struct tcmsg *tcm;
705
0
  struct zebra_tc_qdisc qdisc = {};
706
0
  enum tc_qdisc_kind kind = TC_QDISC_UNSPEC;
707
0
  const char *kind_str = "Unknown";
708
709
0
  int len;
710
0
  struct rtattr *tb[TCA_MAX + 1];
711
712
0
  frrtrace(3, frr_zebra, netlink_tc_qdisc_change, h, ns_id, startup);
713
714
0
  len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct tcmsg));
715
716
0
  if (len < 0) {
717
0
    zlog_err(
718
0
      "%s: Message received from netlink is of a broken size %d %zu",
719
0
      __func__, h->nlmsg_len,
720
0
      (size_t)NLMSG_LENGTH(sizeof(struct tcmsg)));
721
0
    return -1;
722
0
  }
723
724
0
  tcm = NLMSG_DATA(h);
725
0
  netlink_parse_rtattr(tb, TCA_MAX, TCA_RTA(tcm), len);
726
727
0
  if (RTA_DATA(tb[TCA_KIND])) {
728
0
    kind_str = (const char *)RTA_DATA(tb[TCA_KIND]);
729
730
0
    kind = tc_qdisc_str2kind(kind_str);
731
0
  }
732
733
0
  qdisc.qdisc.ifindex = tcm->tcm_ifindex;
734
735
0
  switch (kind) {
736
0
  case TC_QDISC_NOQUEUE:
737
    /* "noqueue" is the default qdisc */
738
0
    break;
739
0
  case TC_QDISC_HTB:
740
0
  case TC_QDISC_UNSPEC:
741
0
    break;
742
0
  }
743
744
0
  if (tb[TCA_OPTIONS] != NULL) {
745
0
    struct rtattr *options[TCA_HTB_MAX + 1];
746
747
0
    netlink_parse_rtattr_nested(options, TCA_HTB_MAX,
748
0
              tb[TCA_OPTIONS]);
749
750
    /* TODO: more details */
751
    /* struct tc_htb_glob *glob = RTA_DATA(options[TCA_HTB_INIT]);
752
     */
753
0
  }
754
755
0
  if (h->nlmsg_type == RTM_NEWQDISC) {
756
0
    if (startup &&
757
0
        TC_H_MAJ(tcm->tcm_handle) == TC_QDISC_MAJOR_ZEBRA) {
758
0
      enum zebra_dplane_result ret;
759
760
0
      ret = dplane_tc_qdisc_uninstall(&qdisc);
761
762
0
      zlog_debug("%s: %s leftover qdisc: ifindex %d kind %s",
763
0
           __func__,
764
0
           ((ret == ZEBRA_DPLANE_REQUEST_FAILURE)
765
0
              ? "Failed to remove"
766
0
              : "Removed"),
767
0
           qdisc.qdisc.ifindex, kind_str);
768
0
    }
769
0
  }
770
771
0
  return 0;
772
0
}
773
774
int netlink_tclass_change(struct nlmsghdr *h, ns_id_t ns_id, int startup)
775
0
{
776
0
  struct tcmsg *tcm;
777
778
0
  int len;
779
0
  struct rtattr *tb[TCA_MAX + 1];
780
781
0
  frrtrace(3, frr_zebra, netlink_tc_class_change, h, ns_id, startup);
782
783
0
  len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct tcmsg));
784
785
0
  if (len < 0) {
786
0
    zlog_err(
787
0
      "%s: Message received from netlink is of a broken size %d %zu",
788
0
      __func__, h->nlmsg_len,
789
0
      (size_t)NLMSG_LENGTH(sizeof(struct tcmsg)));
790
0
    return -1;
791
0
  }
792
793
0
  tcm = NLMSG_DATA(h);
794
0
  netlink_parse_rtattr(tb, TCA_MAX, TCA_RTA(tcm), len);
795
796
797
0
  if (tb[TCA_OPTIONS] != NULL) {
798
0
    struct rtattr *options[TCA_HTB_MAX + 1];
799
800
0
    netlink_parse_rtattr_nested(options, TCA_HTB_MAX,
801
0
              tb[TCA_OPTIONS]);
802
803
    /* TODO: more details */
804
    /* struct tc_htb_opt *opt = RTA_DATA(options[TCA_HTB_PARMS]); */
805
0
  }
806
807
0
  return 0;
808
0
}
809
810
int netlink_tfilter_change(struct nlmsghdr *h, ns_id_t ns_id, int startup)
811
0
{
812
0
  struct tcmsg *tcm;
813
814
0
  int len;
815
0
  struct rtattr *tb[TCA_MAX + 1];
816
817
0
  frrtrace(3, frr_zebra, netlink_tc_filter_change, h, ns_id, startup);
818
819
0
  len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct tcmsg));
820
821
0
  if (len < 0) {
822
0
    zlog_err(
823
0
      "%s: Message received from netlink is of a broken size %d %zu",
824
0
      __func__, h->nlmsg_len,
825
0
      (size_t)NLMSG_LENGTH(sizeof(struct tcmsg)));
826
0
    return -1;
827
0
  }
828
829
0
  tcm = NLMSG_DATA(h);
830
0
  netlink_parse_rtattr(tb, TCA_MAX, TCA_RTA(tcm), len);
831
832
0
  return 0;
833
0
}
834
835
int netlink_qdisc_read(struct zebra_ns *zns)
836
1
{
837
1
  int ret;
838
1
  struct zebra_dplane_info dp_info;
839
840
1
  zebra_dplane_info_from_zns(&dp_info, zns, true);
841
842
1
  ret = netlink_request_qdiscs(zns, AF_UNSPEC, RTM_GETQDISC);
843
1
  if (ret < 0)
844
1
    return ret;
845
846
0
  ret = netlink_parse_info(netlink_qdisc_change, &zns->netlink_cmd,
847
0
         &dp_info, 0, true);
848
0
  if (ret < 0)
849
0
    return ret;
850
851
0
  return 0;
852
0
}
853
854
int netlink_tfilter_read_for_interface(struct zebra_ns *zns, ifindex_t ifindex)
855
0
{
856
0
  int ret;
857
0
  struct zebra_dplane_info dp_info;
858
859
0
  zebra_dplane_info_from_zns(&dp_info, zns, true);
860
861
0
  ret = netlink_request_filters(zns, AF_UNSPEC, RTM_GETTFILTER, ifindex);
862
0
  if (ret < 0)
863
0
    return ret;
864
865
0
  ret = netlink_parse_info(netlink_tfilter_change, &zns->netlink_cmd,
866
0
         &dp_info, 0, true);
867
0
  if (ret < 0)
868
0
    return ret;
869
870
0
  return 0;
871
0
}
872
873
#endif /* HAVE_NETLINK */