538 lines
11 KiB
C
538 lines
11 KiB
C
/*
|
|
* ni-dhcp-iface-helper.c
|
|
* This file is part of Network Inador
|
|
*
|
|
* Copyright (C) 2021 - Félix Arreola Rodríguez
|
|
*
|
|
* Network Inador is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Network Inador is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Network Inador; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include "network-inador-manager.h"
|
|
|
|
#define COMMAND_SOCKET_PATH "/tmp/network-inador.socket"
|
|
|
|
extern char** environ;
|
|
|
|
int debug (int argc, char *argv[]) {
|
|
char **item;
|
|
int fd;
|
|
|
|
fd = open ("/tmp/var_dhcp.env", O_CREAT | O_WRONLY | O_TRUNC, 0644);
|
|
|
|
if (fd < 0) {
|
|
return -1;
|
|
}
|
|
|
|
for (item = environ; *item; item++) {
|
|
char *name, *val, **p;
|
|
|
|
/* Split on the = */
|
|
name = strdup (*item);
|
|
val = strchr (name, '=');
|
|
if (!val || val == name) {
|
|
free (name);
|
|
continue;
|
|
}
|
|
*val++ = '\0';
|
|
|
|
/* Ignore non-DCHP-related environment variables
|
|
for (p = (char **) ignore; *p; p++) {
|
|
if (strncmp (name, *p, strlen (*p)) == 0)
|
|
goto next;
|
|
}*/
|
|
|
|
write (fd, "Name: <", 7);
|
|
write (fd, name, strlen (name));
|
|
write (fd, ">, Value: <", 11);
|
|
write (fd, val, strlen (val));
|
|
write (fd, ">\n", 2);
|
|
}
|
|
|
|
close (fd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
enum {
|
|
DHCP_CLIENT_SELECTING,
|
|
DHCP_CLIENT_BOUND,
|
|
DHCP_CLIENT_RENEWED,
|
|
|
|
DHCP_CLIENT_EXPIRED,
|
|
DHCP_CLIENT_FAILED,
|
|
|
|
DHCP_CLIENT_KILLED,
|
|
};
|
|
|
|
uint32_t utils_ip4_netmask_to_prefix (uint32_t netmask) {
|
|
uint32_t prefix;
|
|
uint8_t v;
|
|
const uint8_t *p = (uint8_t *) &netmask;
|
|
|
|
if (p[3]) {
|
|
prefix = 24;
|
|
v = p[3];
|
|
} else if (p[2]) {
|
|
prefix = 16;
|
|
v = p[2];
|
|
} else if (p[1]) {
|
|
prefix = 8;
|
|
v = p[1];
|
|
} else {
|
|
prefix = 0;
|
|
v = p[0];
|
|
}
|
|
|
|
while (v) {
|
|
prefix++;
|
|
v <<= 1;
|
|
}
|
|
|
|
return prefix;
|
|
}
|
|
|
|
char *retrieve_env (const char *name) {
|
|
char *bus, *dup, *get;
|
|
|
|
get = getenv (name);
|
|
|
|
if (get == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
bus = strchr (get, '=');
|
|
|
|
if (!bus || bus == get) {
|
|
return NULL;
|
|
}
|
|
|
|
dup = strdup (&get[1]);
|
|
|
|
if (dup == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return dup;
|
|
}
|
|
|
|
int wait_for_ack_or_error (int s) {
|
|
unsigned char buffer[128];
|
|
int ret;
|
|
|
|
ret = recv (s, buffer, sizeof (buffer), 0);
|
|
|
|
if (ret < 3) {
|
|
return -1;
|
|
}
|
|
|
|
if (buffer[0] == NET_INADOR_TYPE_RESPONSE_ERROR) {
|
|
return -1;
|
|
}
|
|
|
|
if (buffer[0] == NET_INADOR_TYPE_RESPONSE && buffer[1] == NET_INADOR_RESPONSE_EXECUTED) {
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void send_basic (int s, char *interface, int tipo) {
|
|
unsigned char buffer[128];
|
|
|
|
buffer[0] = NET_INADOR_TYPE_COMMAND;
|
|
buffer[1] = NET_INADOR_COMMAND_DHCP_CLIENT_FEED;
|
|
|
|
buffer[2] = AF_INET;
|
|
if (tipo == DHCP_CLIENT_SELECTING) {
|
|
buffer[3] = NET_INADOR_DHCP_STATUS_SELECTING;
|
|
} else if (tipo == DHCP_CLIENT_EXPIRED) {
|
|
buffer[3] = NET_INADOR_DHCP_STATUS_EXPIRED;
|
|
} else if (tipo == DHCP_CLIENT_FAILED) {
|
|
buffer[3] = NET_INADOR_DHCP_STATUS_FAILED;
|
|
}
|
|
buffer[4] = strlen (interface);
|
|
memset (&buffer[5], 0, 11);
|
|
|
|
strncpy (&buffer[16], interface, buffer[4]);
|
|
|
|
send (s, buffer, 16 + buffer[4], 0);
|
|
|
|
if (wait_for_ack_or_error (s) < 0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
void send_bound_renew (int s, int is_bound, char *interface, struct in_addr *ip, int prefix, struct in_addr *gateways, int gw_c, struct in_addr *broadcast, struct in_addr *dhcp_server, uint32_t lease_time, struct in_addr *dns, int dns_c, char *domain_name) {
|
|
unsigned char buffer[128];
|
|
struct in_addr empty;
|
|
int pos, g;
|
|
|
|
memset (&empty, 0, sizeof (empty));
|
|
|
|
buffer[0] = NET_INADOR_TYPE_COMMAND;
|
|
buffer[1] = NET_INADOR_COMMAND_DHCP_CLIENT_FEED;
|
|
|
|
buffer[2] = AF_INET;
|
|
if (is_bound) {
|
|
buffer[3] = NET_INADOR_DHCP_STATUS_BOUND;
|
|
} else {
|
|
buffer[3] = NET_INADOR_DHCP_STATUS_RENEWED;
|
|
}
|
|
buffer[4] = strlen (interface);
|
|
memset (&buffer[5], 0, 11);
|
|
|
|
buffer[6] = prefix;
|
|
|
|
if (lease_time != 0) {
|
|
memcpy (&buffer[12], &lease_time, 4);
|
|
}
|
|
|
|
pos = 16;
|
|
|
|
/* Copiar el nombre de la interfaz */
|
|
strncpy (&buffer[16], interface, buffer[4]);
|
|
pos = 16 + buffer[4];
|
|
|
|
if (memcmp (&empty, ip, sizeof (empty)) != 0) {
|
|
/* Tenemos una IP */
|
|
buffer[5] = 4;
|
|
memcpy (&buffer[pos], ip, 4);
|
|
pos += 4;
|
|
}
|
|
|
|
/* Procesar las múltiples rutas posibles */
|
|
buffer[7] = 4 * gw_c;
|
|
for (g = 0; g < gw_c; g++) {
|
|
memcpy (&buffer[pos], &gateways[g], 4);
|
|
pos += 4;
|
|
}
|
|
|
|
if (memcmp (&empty, broadcast, sizeof (empty)) != 0) {
|
|
/* Tenemos broadcast */
|
|
buffer[8] = 4;
|
|
memcpy (&buffer[pos], broadcast, 4);
|
|
pos += 4;
|
|
}
|
|
|
|
if (memcmp (&empty, dhcp_server, sizeof (empty)) != 0) {
|
|
/* Tenemos IP del servidor DHCP */
|
|
buffer[9] = 4;
|
|
memcpy (&buffer[pos], dhcp_server, 4);
|
|
pos += 4;
|
|
}
|
|
|
|
/* Procesar los múltiples dns */
|
|
buffer[10] = 4 * dns_c;
|
|
for (g = 0; g < dns_c; g++) {
|
|
memcpy (&buffer[pos], &dns[g], 4);
|
|
pos += 4;
|
|
}
|
|
|
|
/* Procesar el nombre de dominio */
|
|
if (domain_name != NULL && domain_name[0] != 0) {
|
|
buffer[11] = strlen (domain_name);
|
|
memcpy (&buffer[pos], domain_name, buffer[11]);
|
|
pos += buffer[11];
|
|
}
|
|
send (s, buffer, pos, 0);
|
|
|
|
if (wait_for_ack_or_error (s) < 0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
int parse_ip (char *var, struct in_addr *addr) {
|
|
int res;
|
|
|
|
if (var == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
res = inet_pton (AF_INET, var, addr);
|
|
|
|
if (res <= 0) {
|
|
memset (addr, 0, sizeof (*addr));
|
|
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int parse_multiple_ip (char *var, struct in_addr *addr, int max) {
|
|
int res;
|
|
char buffer[512];
|
|
char *inicio, *bus;
|
|
int c = 0;
|
|
size_t offset;
|
|
|
|
if (var == NULL) return 0;
|
|
|
|
inicio = var;
|
|
while (inicio[0] != 0) {
|
|
bus = strchr (inicio, ' ');
|
|
|
|
if (bus == NULL) break;
|
|
|
|
/* Desboardamiento */
|
|
offset = bus - inicio;
|
|
|
|
if (offset + 1 > sizeof (buffer)) {
|
|
return 0;
|
|
}
|
|
strncpy (buffer, inicio, offset);
|
|
buffer[offset] = 0;
|
|
inicio = &var[offset + 1];
|
|
|
|
res = parse_ip (buffer, &addr[c]);
|
|
if (res < 0) continue;
|
|
|
|
c++;
|
|
if (c == max) return c;
|
|
}
|
|
|
|
/* Parsear lo último, si es que queda algo */
|
|
if (inicio[0] != 0) {
|
|
res = parse_ip (inicio, &addr[c]);
|
|
if (res < 0) {
|
|
return c;
|
|
}
|
|
|
|
c++;
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
int parse_int (char *var, int *entero) {
|
|
int res;
|
|
|
|
if (var == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
res = sscanf (var, "%i", entero);
|
|
|
|
if (res <= 0) {
|
|
*entero = 0;
|
|
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
enum {
|
|
IFACE_ISC_DHCLIENT = 1,
|
|
IFACE_BUSYBOX_UDHCPC
|
|
};
|
|
|
|
const char *isc_dhcp_reasons[] = {
|
|
"MEDIUM",
|
|
"PREINIT",
|
|
"BOUND",
|
|
"RENEW",
|
|
"REBIND",
|
|
"REBOOT",
|
|
"EXPIRE",
|
|
"FAIL",
|
|
"STOP",
|
|
"RELEASE",
|
|
"NBI",
|
|
"TIMEOUT",
|
|
NULL
|
|
};
|
|
|
|
const char *busybox_dhcp_argv[] = {
|
|
"deconfig",
|
|
"bound",
|
|
"renew",
|
|
"nak"
|
|
};
|
|
|
|
int main (int argc, char *argv[]) {
|
|
char *reason, *interface;
|
|
int s, ret, tipo;
|
|
struct sockaddr_un path_dest;
|
|
struct in_addr ip, netmask, broadcast, dhcp_server;
|
|
struct in_addr gateways[7];
|
|
struct in_addr dns[7];
|
|
int gw_c, dns_c;
|
|
|
|
int prefix, lease_time;
|
|
|
|
debug (argc, argv);
|
|
tipo = 0;
|
|
|
|
/* Tratar de determinar si estamos siendo corridos por un el DHCP del ISC o por el udhcpc busybox */
|
|
reason = getenv ("reason");
|
|
|
|
if (reason != NULL) {
|
|
s = 0;
|
|
while (isc_dhcp_reasons[s] != NULL) {
|
|
if (strcmp (reason, isc_dhcp_reasons[s]) == 0) {
|
|
/* Es un ISC DHCP Client */
|
|
tipo = IFACE_ISC_DHCLIENT;
|
|
break;
|
|
}
|
|
s++;
|
|
}
|
|
}
|
|
|
|
if (tipo == 0 && argc > 1) {
|
|
/* Intentar revisar si es un busybox udhcpc */
|
|
s = 0;
|
|
while (busybox_dhcp_argv[s] != NULL) {
|
|
if (strcmp (argv[1], busybox_dhcp_argv[s]) == 0) {
|
|
tipo = IFACE_BUSYBOX_UDHCPC;
|
|
reason = argv[1];
|
|
break;
|
|
}
|
|
s++;
|
|
}
|
|
}
|
|
|
|
if (tipo == 0) {
|
|
return 1;
|
|
}
|
|
|
|
interface = getenv ("interface");
|
|
|
|
if (reason == NULL || interface == NULL) {
|
|
return 1;
|
|
}
|
|
|
|
/* Intentar abrir el socket a la escucha */
|
|
s = socket (AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
|
|
|
|
if (s < 0) {
|
|
return 1;
|
|
}
|
|
|
|
path_dest.sun_family = AF_UNIX;
|
|
strncpy (path_dest.sun_path, COMMAND_SOCKET_PATH, sizeof (path_dest.sun_path));
|
|
ret = connect (s, (struct sockaddr *) &path_dest, sizeof (path_dest));
|
|
|
|
if (ret < 0) {
|
|
perror ("Connect");
|
|
close (s);
|
|
return 1;
|
|
}
|
|
|
|
if (strcmp (reason, "PREINIT") == 0 || // ISC DHCP
|
|
strcmp (reason, "deconfig") == 0 // Busybox udhcpc
|
|
) {
|
|
send_basic (s, interface, DHCP_CLIENT_SELECTING);
|
|
} if (strcmp (reason, "FAIL") == 0) {
|
|
send_basic (s, interface, DHCP_CLIENT_FAILED);
|
|
} else if (strcmp (reason, "BOUND") == 0 || strcmp (reason, "REBOOT") == 0 ||
|
|
strcmp (reason, "RENEW") == 0 || strcmp (reason, "REBIND") == 0 || // 4 de ISC DHCP
|
|
strcmp (reason, "bound") == 0 || strcmp (reason, "renew") == 0 // 2 de Busybox udhcpc
|
|
) {
|
|
/*
|
|
* Los valores más importantes recibidos:
|
|
* new_domain_name_servers -> los DNS
|
|
* new_expiry -> el timestamp de la hora
|
|
* new_domain_name -> DNS search name
|
|
* new_routers -> Lista de routers
|
|
* new_subnet_mask -> Máscara de red en formato largo
|
|
* new_broadcast_address -> Dirección broadcast
|
|
* new_ip_address -> Dirección IP
|
|
* new_dhcp_lease_time -> Tiempo en segundos
|
|
*/
|
|
/* Parsear y revisar las direcciones IP recibidas */
|
|
char *s_ip, *s_mask, *s_gateway, *s_broadcast, *s_lease_time, *s_dhcp_server, *s_dns, *s_domain_name;
|
|
if (tipo == IFACE_ISC_DHCLIENT) {
|
|
s_ip = getenv ("new_ip_address");
|
|
s_mask = getenv ("new_subnet_mask");
|
|
s_gateway = getenv ("new_routers");
|
|
s_broadcast = getenv ("new_broadcast_address");
|
|
s_lease_time = getenv ("new_dhcp_lease_time");
|
|
s_dhcp_server = getenv ("new_dhcp_server_identifier");
|
|
s_dns = getenv ("new_domain_name_servers");
|
|
s_domain_name = getenv ("new_domain_name");
|
|
} else if (tipo == IFACE_BUSYBOX_UDHCPC) {
|
|
s_ip = getenv ("ip");
|
|
s_mask = getenv ("subnet");
|
|
s_gateway = getenv ("router");
|
|
s_broadcast = getenv ("broadcast");
|
|
s_lease_time = getenv ("lease");
|
|
s_dhcp_server = getenv ("serverid");
|
|
s_dns = getenv ("dns");
|
|
s_domain_name = getenv ("domain");
|
|
}
|
|
/* TODO: Faltan los DNS */
|
|
|
|
if (parse_ip (s_ip, &ip) < 0) {
|
|
close (s);
|
|
return -1;
|
|
}
|
|
|
|
if (parse_ip (s_mask, &netmask) < 0) {
|
|
close (s);
|
|
return -1;
|
|
}
|
|
|
|
prefix = utils_ip4_netmask_to_prefix (netmask.s_addr);
|
|
gw_c = parse_multiple_ip (s_gateway, gateways, 7);
|
|
|
|
if (parse_ip (s_broadcast, &broadcast) < 0) {
|
|
memset (&broadcast, 0, sizeof (broadcast));
|
|
}
|
|
|
|
if (parse_ip (s_dhcp_server, &dhcp_server) < 0) {
|
|
memset (&dhcp_server, 0, sizeof (dhcp_server));
|
|
}
|
|
|
|
if (parse_int (s_lease_time, &lease_time) < 0) {
|
|
lease_time = 0;
|
|
}
|
|
|
|
dns_c = parse_multiple_ip (s_dns, dns, 7);
|
|
int is_bound = 0;
|
|
if (strcmp (reason, "BOUND") == 0 || strcmp (reason, "REBOOT") == 0 || // Del ISC dhcp
|
|
strcmp (reason, "bound") == 0 // del busybox
|
|
) {
|
|
is_bound = 1;
|
|
}
|
|
send_bound_renew (s, is_bound, interface, &ip, prefix, gateways, gw_c, &broadcast, &dhcp_server, lease_time, dns, dns_c, s_domain_name);
|
|
} else if (strcmp (reason, "EXPIRE") == 0 || strcmp (reason, "STOP") == 0 || strcmp (reason, "RELEASE") == 0) {
|
|
/* Los mismos valores, pero con old_ */
|
|
send_basic (s, interface, DHCP_CLIENT_EXPIRED);
|
|
} else {
|
|
send_basic (s, interface, DHCP_CLIENT_FAILED);
|
|
close (s);
|
|
|
|
return -1;
|
|
}
|
|
close (s);
|
|
|
|
return 0;
|
|
}
|