Используйте libnm или popen(nmcli) для программной настройки сети?

Вопрос или проблема

У нас есть система, в которой пользователю необходимо иметь возможность постоянно изменять настройки сети через интерфейс (с помощью нашего кода на C). Ранее это достигалось путем генерации нового файла dhcpcd.conf и перезапуска сети, но теперь этого не существует – dhcpcd исчез, и Network manager (nmcli / libnm) – это новое решение.

Я изучал libnm, но, похоже, примеров очень мало, и это довольно сложно. “Ленивым” вариантом было бы использовать popen() для взаимодействия с nmcli, но это кажется немного хаком.

Так стоит ли мне принять решение и использовать вызовы libnm, или метод popen вполне приемлем? Или есть какой-то третий способ, который я упустил?

Я думаю, что оба варианта являются жизнеспособными и разумными; страница man для nmcli даже говорит:

Типичные применения включают:

  • Скрипты: Используйте NetworkManager через nmcli, вместо того чтобы управлять сетевыми подключениями вручную. nmcli поддерживает краткий формат вывода, который лучше подходит для обработки скриптами.

Таким образом, возможность вызова извне определенно является целевой задачей, и с помощью --terse вы получите вывод, над которым поддерживающие его подумали, чтобы сохранить стабильность для использования компьютерными программами, которые его обрабатывают.

Я думаю, что libnm в основном имеет больше смысла, если

  1. ваша программа на C уже использует GObject, и
  2. вы не хотите напрямую обрабатывать вызовы D-Bus, но
  3. также не хотите работать очень абстрактно от API D-Bus NetworkManager.

Поскольку описанные вами операции являются “большими”, что означает, что вещи, которые они вызывают, займут несколько миллисекунд, а может, и секунд, накладные расходы от одного popen (который внутренне создает каналы, создаёт форки, вызывает оболочку и говорит оболочке выполнить команду) действительно незначительны.

Поэтому я бы стал вызывать nmcli --terse {subcommand} из вашей программы на C. Будет ли это просто

FILE* output = popen("nmcli --terse foobar", "r");

что включает в себя запуск оболочки, или, более продвинутый вариант, но, возможно, выгодный, если вы хотите делать такие вещи, как использовать nmcli connection monitor,

char* args[] = {"nmcli", "--terse", "foobar"};
char* nmcli_path = "/usr/bin/nmcli";
struct io_pipe {
  int read_end;
  int write_end;
};
struct io_pipe pipe_fd;

int success = pipe(&pipe_fd); // проверьте успешность >= 0!
int child_pid = fork();       // проверьте new_pid >= 0!
if(child_pid > 0) {           // мы родитель!
  close(pipe_fd.write_end);   // нам не нужен конец записи
  /* Делайте то, что вы бы сделали с FILE*, выходящим из popen, здесь,
   * например, 
   * fd_set watched_fds;
   * FD_ZERO(&watched_fds);
   * FD_SET(io_pipe.read_end, &watched_fds);
   * fcntl(io_pipe.read_end, O_NONBLOCK);
   * struct timeval timeout = {.tv_sec = 5, .tv_usec = 0};
   * char* buffer = NULL;
   * while(select(1,            // сколько файловых дескрипторов?
                  &watched_fds, // какие дескрипторы для чтения
                  NULL, NULL,   // какие дескрипторы для записи, исключений
                  timeout       // timeval, указывающий тайм-аут.
                 )) {
   *   if(!buffer) {
   *     buffer = malloc(4096);
   *   }
   *   int howmuch = read(pipe_fd.read_end, buffer, 4096);
   *   // Делайте что-то умное с тем, что вы прочитали!
   * }
   * free(buffer);
   */
  int wstatus;
  waitpid(child_pid, &wstatus, 0); 
  /* смотрите `man 2 wait` для того, что можно делать с wstatus */
} else {                      // мы потомок!
  close(pipe_fd.read_end);    // нам не нужен конец чтения
                              // мы соединяем наш stdout с концом записи:
  dup2(STDOUT_FILENO, pipe_fd.write_end);
  execv(nmcli_path, args);    // мы заменяем себя на nmcli 
}

вполне зависит от вас.

Ответ или решение

Конфигурация сетевых настроек с использованием libnm или nmcli в C

В современных системах, где используетесь NetworkManager, программная конфигурация сетевых параметров представляется возможной через два основных подхода: использование библиотеки libnm или вызов командного интерфейса nmcli через popen(). В этом ответе мы рассмотрим оба метода, их преимущества и недостатки, а также определим, какой из них лучше подходит для поставленной задачи.

Вариант 1: Использование libnm

libnm — это библиотека встраиваемого программирования для работы с NetworkManager. Она предоставляет C API для сложного управления сетевыми соединениями и поддерживает все аспекты, которые доступны через D-Bus API.

Преимущества:

  • Глубокая интеграция: libnm предоставляет более тонкий контроль и гибкость в управлении сетевыми настройками. Вы можете непосредственно взаимодействовать с объектами NetworkManager и получать доступ ко всем их свойствам.
  • Ошибка безопасности: Прямые вызовы API более безопасны, так как снижают вероятность возможности инъекций или выполнения несанкционированных команд, которые могут произойти при использовании system() или popen().
  • Ошибки типа: При использовании C API вы получаете немедленное уведомление о любых ошибках.

Недостатки:

  • Сложность: Использование libnm может потребовать изучения большого объема документации. Для некоторых задач это может показаться излишним.
  • Зависимости: Ваш проект будет зависеть от структуры и версий библиотеки libnm, что может вызвать проблемы с совместимостью при обновлениях.

Вариант 2: Вызов nmcli через popen

Использование popen() для вызова nmcli — это подход, который позволяет быстро внедрять управляющие команды в ваше приложение, избегая необходимости глубокого понимания библиотек. nmcli поддерживает вывод в сжатом формате с параметром --terse, что упрощает разбор результата выполнения команды.

Преимущества:

  • Простота использования: Поскольку nmcli является встроенной утилитой, вам не нужно заботиться о внешних зависимостях и установках.
  • Гибкость в работе с командами: Команды можно рассматривать как текстовые строки, что делает их простыми для написания и отладки.
  • Скорость реализации: Для быстрого выполнения сетевых настроек без необходимости изучения API это отличный вариант.

Недостатки:

  • Надежность: Поскольку popen() вызывает команду в оболочке, у вас есть риск ошибок и проблем с синтаксисом команд, которые могут возникнуть при формировании строк.
  • Работа с выводом: Вам придется дополнительно обрабатывать вывод и ошибки, что может добавить сложности в код.

Рекомендации

Для разработчиков, ищущих быстрое решение без значительных временных затрат на обучение API, использование popen() с подачей команд nmcli вполне приемлемо. Однако, если вы планируете интеграцию системного уровня и более сложные сценарии работы с сетевыми настройками, стоит рассмотреть libnm.

Если вы все же решите использовать popen(), вам можно воспользоваться следующим базовым примером:

#include <stdio.h>
#include <stdlib.h>

void configure_network(const char *command) {
    FILE *output = popen(command, "r");
    if (output == NULL) {
        perror("Error executing command");
        return;
    }

    // Чтение и обработка вывода команды
    char buffer[128];
    while (fgets(buffer, sizeof(buffer), output) != NULL) {
        printf("%s", buffer);
    }

    pclose(output);
}

int main() {
    configure_network("nmcli --terse connection show");
    return 0;
}

Заключение

Оба подхода являются жизнеспособными и применимыми, но выбор между основан на ваших долгосрочных планах и потребностях в функциональности. Если важна скорость и простота, используйте nmcli через popen(). Если же вы стремитесь к более высокому уровню интеграции и контролю над сетевыми настройками, стоит рассмотреть использование libnm.

Оцените материал
Добавить комментарий

Капча загружается...