@@ -384,6 +384,7 @@ The `SEC(".struct_ops")` block at the end registers the callbacks as a `Qdisc_op
384384#include <linux/pkt_sched.h>
385385#include <net/if.h>
386386#include <signal.h>
387+ #include <spawn.h>
387388#include <stdbool.h>
388389#include <stdint.h>
389390#include <stdio.h>
@@ -396,6 +397,8 @@ The `SEC(".struct_ops")` block at the end registers the callbacks as a `Qdisc_op
396397#include "egress_pacer.h"
397398#include "egress_pacer.skel.h"
398399
400+ extern char **environ;
401+
399402static volatile sig_atomic_t exiting;
400403
401404static struct env {
@@ -576,38 +579,137 @@ static int wait_for_duration(void)
576579 return 0;
577580}
578581
582+ static const char *find_tc_binary(void)
583+ {
584+ static const char *const candidates[] = {
585+ "/usr/sbin/tc",
586+ "/sbin/tc",
587+ };
588+ size_t index;
589+
590+ for (index = 0; index < sizeof(candidates) / sizeof(candidates[0]); index++) {
591+ if (!access(candidates[index], X_OK))
592+ return candidates[index];
593+ }
594+ return NULL;
595+ }
596+
579597static void print_current_qdisc(void)
580598{
599+ const char *tc_binary = find_tc_binary();
600+ char *const arguments[] = {
601+ (char *)"tc", (char *)"qdisc", (char *)"show", (char *)"dev",
602+ (char *)env.interface, NULL,
603+ };
604+ posix_spawn_file_actions_t actions;
581605 pid_t child;
582606 pid_t waited;
607+ int error;
583608 int status;
584609
585610 fprintf(stderr, "current qdisc state:\n");
586- child = fork();
587- if (child < 0) {
588- fprintf(stderr, "failed to run tc: %s\n", strerror(errno));
589- return;
590- }
591- if (child == 0) {
592- if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0)
593- _exit(127);
594- execlp("tc", "tc", "qdisc", "show", "dev", env.interface,
595- (char *)NULL);
596- _exit(127);
611+ if (!tc_binary) {
612+ fprintf(stderr, "tc was not found in /usr/sbin or /sbin\n");
613+ goto manual;
597614 }
598615
616+ error = posix_spawn_file_actions_init(&actions);
617+ if (error)
618+ goto spawn_failed;
619+ error = posix_spawn_file_actions_adddup2(&actions, STDERR_FILENO,
620+ STDOUT_FILENO);
621+ if (!error)
622+ error = posix_spawn(&child, tc_binary, &actions, NULL, arguments,
623+ environ);
624+ posix_spawn_file_actions_destroy(&actions);
625+ if (error)
626+ goto spawn_failed;
627+
599628 do {
600629 waited = waitpid(child, &status, 0);
601630 } while (waited < 0 && errno == EINTR);
602- if (waited < 0 || !WIFEXITED(status) || WEXITSTATUS(status))
603- fprintf(stderr, "run manually: tc qdisc show dev %s\n",
631+ if (waited >= 0 && WIFEXITED(status) && !WEXITSTATUS(status))
632+ return;
633+ if (waited < 0)
634+ error = errno;
635+ else
636+ error = EIO;
637+
638+ spawn_failed:
639+ fprintf(stderr, "failed to run tc: %s\n", strerror(error));
640+ manual:
641+ fprintf(stderr, "run manually: tc qdisc show dev %s\n", env.interface);
642+ }
643+
644+ static int install_pacer(struct egress_pacer_bpf *skel,
645+ struct bpf_tc_hook *hook)
646+ {
647+ int error;
648+
649+ error = egress_pacer_bpf__attach(skel);
650+ if (error) {
651+ if (error == -EEXIST) {
652+ fprintf(stderr,
653+ "bpf_pacer is already registered; a stale root qdisc may own it\n");
654+ print_current_qdisc();
655+ fprintf(stderr,
656+ "if it is stale, recover with: sudo tc qdisc del dev %s root\n",
657+ env.interface);
658+ } else {
659+ fprintf(stderr, "failed to register bpf_pacer qdisc: %s\n",
660+ strerror(-error));
661+ }
662+ return error;
663+ }
664+
665+ error = bpf_tc_hook_create(hook);
666+ if (!error)
667+ return 0;
668+ if (error == -EEXIST) {
669+ fprintf(stderr, "refusing to replace the existing root qdisc on %s\n",
604670 env.interface);
671+ print_current_qdisc();
672+ fprintf(stderr,
673+ "if it is stale, recover with: sudo tc qdisc del dev %s root\n",
674+ env.interface);
675+ } else {
676+ fprintf(stderr, "failed to attach bpf_pacer to %s: %s\n",
677+ env.interface, strerror(-error));
678+ }
679+ return error;
680+ }
681+
682+ static int cleanup_pacer(struct egress_pacer_bpf *skel,
683+ struct bpf_tc_hook *hook, bool qdisc_created, int error)
684+ {
685+ struct pacer_stats final_stats = {};
686+ int cleanup_error;
687+
688+ if (qdisc_created) {
689+ cleanup_error = bpf_tc_hook_destroy(hook);
690+ if (cleanup_error) {
691+ fprintf(stderr, "failed to remove bpf_pacer from %s: %s\n",
692+ env.interface, strerror(-cleanup_error));
693+ if (!error)
694+ error = cleanup_error;
695+ }
696+ }
697+ if (skel && skel->bss)
698+ final_stats = skel->bss->stats;
699+ if (qdisc_created) {
700+ printf("SUMMARY enqueued=%llu dequeued=%llu policy_dropped=%llu "
701+ "cleanup_dropped=%llu bytes_dequeued=%llu max_qlen=%llu\n",
702+ final_stats.enqueued, final_stats.dequeued,
703+ final_stats.policy_dropped, final_stats.cleanup_dropped,
704+ final_stats.bytes_dequeued, final_stats.max_qlen);
705+ }
706+ egress_pacer_bpf__destroy(skel);
707+ return error != 0;
605708}
606709
607710int main(int argc, char **argv)
608711{
609712 struct egress_pacer_bpf *skel = NULL;
610- struct pacer_stats final_stats = {};
611713 struct bpf_tc_hook hook = {
612714 .sz = sizeof(hook),
613715 .attach_point = BPF_TC_QDISC,
@@ -617,7 +719,6 @@ int main(int argc, char **argv)
617719 };
618720 bool qdisc_created = false;
619721 unsigned int ifindex;
620- int cleanup_err;
621722 int err;
622723
623724 err = parse_args(argc, argv);
@@ -655,38 +756,9 @@ int main(int argc, char **argv)
655756 goto cleanup;
656757 }
657758
658- err = egress_pacer_bpf__attach(skel);
659- if (err) {
660- if (err == -EEXIST) {
661- fprintf(stderr,
662- "bpf_pacer is already registered; a stale root qdisc may own it\n");
663- print_current_qdisc();
664- fprintf(stderr,
665- "if it is stale, recover with: sudo tc qdisc del dev %s root\n",
666- env.interface);
667- } else {
668- fprintf(stderr, "failed to register bpf_pacer qdisc: %s\n",
669- strerror(-err));
670- }
671- goto cleanup;
672- }
673-
674- err = bpf_tc_hook_create(&hook);
675- if (err) {
676- if (err == -EEXIST) {
677- fprintf(stderr,
678- "refusing to replace the existing root qdisc on %s\n",
679- env.interface);
680- print_current_qdisc();
681- fprintf(stderr,
682- "if it is stale, recover with: sudo tc qdisc del dev %s root\n",
683- env.interface);
684- } else {
685- fprintf(stderr, "failed to attach bpf_pacer to %s: %s\n",
686- env.interface, strerror(-err));
687- }
759+ err = install_pacer(skel, &hook);
760+ if (err)
688761 goto cleanup;
689- }
690762 qdisc_created = true;
691763
692764 printf("READY interface=%s rate_kbps=%llu queue_limit=%u duration=%u\n",
@@ -696,26 +768,7 @@ int main(int argc, char **argv)
696768 err = wait_for_duration();
697769
698770cleanup:
699- if (qdisc_created) {
700- cleanup_err = bpf_tc_hook_destroy(&hook);
701- if (cleanup_err) {
702- fprintf(stderr, "failed to remove bpf_pacer from %s: %s\n",
703- env.interface, strerror(-cleanup_err));
704- if (!err)
705- err = cleanup_err;
706- }
707- }
708- if (skel && skel->bss)
709- final_stats = skel->bss->stats;
710- if (qdisc_created) {
711- printf("SUMMARY enqueued=%llu dequeued=%llu policy_dropped=%llu "
712- "cleanup_dropped=%llu bytes_dequeued=%llu max_qlen=%llu\n",
713- final_stats.enqueued, final_stats.dequeued,
714- final_stats.policy_dropped, final_stats.cleanup_dropped,
715- final_stats.bytes_dequeued, final_stats.max_qlen);
716- }
717- egress_pacer_bpf__destroy(skel);
718- return err != 0;
771+ return cleanup_pacer(skel, &hook, qdisc_created, err);
719772}
720773```
721774
0 commit comments