Auteur Sujet: Orange DHCP conformité protocolaire 2023 - lire depuis le début du sujet  (Lu 310191 fois)

0 Membres et 4 Invités sur ce sujet

nonolk

  • Abonné Free fibre
  • *
  • Messages: 58
  • Duppigheim (67)
Durcissement du contrôle de l’option 90/11 et de la conformité protocolaire
« Réponse #132 le: 08 décembre 2022 à 14:52:48 »
Bonjour Vivien

Y'a plein de chose bien sur la LiveBox (test de vie montant descendant , CoS L2/L3, respect des règles ARCEP, Remote Management, 3P) qui sont très très bien.

Publier une conf DHCHP qui marche sort du mandat que j'ai et surtout c'est très incomplet par rapport à ce qui est nécessaire pour avoir un lien de qualité avec les reprises de cycle DHCP4/6 en cas de perte de connectivité (vous seriez surpris du nombre de cas totalement indépendant de Orange où cela se produit ...)  et de respect de la specs DHCP (là aussi vous seriez surpris du délabrement de certain stack DHCP 4/6 ...).

Autant vous aider sur les plus gros problèmes, oui. Mais une conf qui sera incomplète par définition sauf à vous mettre l'ensemble de toutes les mécaniques à valeur ajoutées de la box, je suis pas pour.

Y'a 10 ans (aller 5 ...) la stabilité de la boxe était pas au rdv complètement et tenter de mettre autre chose me paraissait légitime. Sur les LB4 LB6, on est largement au niveau qualité. Et avec un connectivité IPv6 Full avec délégation de préfix qui commence à se faire plus que correctement.

LeVieux
Pour la délégation ipv6 si on pouvait avoir récupérer plus qu'un /64 par exemple un /60, car pour moi c'est ce qui me dérange le plus.

levieuxatorange

  • Expert Orange
  • Expert
  • *
  • Messages: 244
Durcissement du contrôle de l’option 90/11 et de la conformité protocolaire
« Réponse #133 le: 08 décembre 2022 à 15:31:40 »
Pour la délégation ipv6 si on pouvait avoir récupérer plus qu'un /64 par exemple un /60, car pour moi c'est ce qui me dérange le plus.
Après relecture de la spec, on limite effectivement à un /64 par device le demandant.
En commençant par le dernier des dispos
Sans avoir testé l'implémentation, rien ne t'empêche d'avoir plusieurs device qui demande chacun un /64.
Tu prends un routeur pour ta DMZ, un routeur pour ton LAN clients, un routeur pour un autre.
MAIS je n'ai pas testé ce que cela fait sur une LB4/6 ....

nonolk

  • Abonné Free fibre
  • *
  • Messages: 58
  • Duppigheim (67)
Durcissement du contrôle de l’option 90/11 et de la conformité protocolaire
« Réponse #134 le: 08 décembre 2022 à 15:36:30 »
Après relecture de la spec, on limite effectivement à un /64 par device le demandant.
En commençant par le dernier des dispos
Sans avoir testé l'implémentation, rien ne t'empêche d'avoir plusieurs device qui demande chacun un /64.
Tu prends un routeur pour ta DMZ, un routeur pour ton LAN clients, un routeur pour un autre.
MAIS je n'ai pas testé ce que cela fait sur une LB4/6 ....

Oui, c'est bien le souci, car si comme dans mon cas, j'ai un seul routeur avec vlans: Bureautique/ IOT/ DMZ, je ne peux configurer l'ipv6 que pour un, et c'est bien domage.

pegounet

  • Abonné Orange Fibre
  • *
  • Messages: 12
  • Rennes 35000
Durcissement du contrôle de l’option 90/11 et de la conformité protocolaire
« Réponse #135 le: 08 décembre 2022 à 17:35:02 »
Hello Le Vieux,

Mais si la Livebox reste le meilleur moyen de se connecter au réseau d'Orange, est-ce qu'il serait possible au moins d'avoir un mode bridge ? Ca suffirait à pas mal d'entre nous :)

Harold
:)

Fibroberto

  • Abonné Sosh fibre
  • *
  • Messages: 20
  • 06110
Durcissement du contrôle de l’option 90/11 et de la conformité protocolaire
« Réponse #136 le: 10 décembre 2022 à 20:20:44 »
Pour info, avec le bon password dans l'option 90 et l'adresse MAC en 61 j'ai réussi à retrouver ma connexion sur mon routeur openwrt (config du tuto ici : https://lafibre.info/remplacer-livebox/remplacement-de-la-livebox-par-un-routeur-openwrt-18-dhcp-v4v6-tv/ ).
En IPv4 tout du moins, IPv6 j'y comprends rien :)

Strangelovian

  • Abonné Orange Fibre
  • *
  • Messages: 58
Durcissement du contrôle de l’option 90/11 et de la conformité protocolaire
« Réponse #137 le: 11 décembre 2022 à 16:30:46 »
Ne faites pas comme moi les djeuns.
Avec ISC dhclient, les formats corrects pour le client id DHCP et DHCP6 sont les suivants.
DHCP client id (option 61): 7 octets en tout. 1 octet hardware type 01 ("ethernet") + 6 octets mac address livebox
DHCP6 client id (option 1): DUID de 10 octets en tout. 2 octets DUID type 0003 ("link layer") + 2 octets hardware type 0001 ("ethernet") + 6 octets mac address livebox

A titre d'exemple, les valeurs ci-dessous corrigées:

option dhcp.auth code 90 = string;
option dhcp6.auth code 11 = string;
option dhcp6.userclass code 15 = string;
option dhcp6.vendorclass code 16 = string;

interface "enp1s0.832"
{
    send dhcp-client-identifier 01:08:3E:5D:01:02:03; // ceci n'est pas la MAC de ma livebox
    send vendor-class-identifier "sagem";
    send user-class "+FSVDSL_livebox.Internet.softathome.Livebox3";
    send dhcp.auth = generate("/etc/orange/auth");

    send dhcp6.vendorclass 00:00:04:0e:00:05:73:61:67:65:6d;
    send dhcp6.userclass 00:2b:46:53:56:44:53:4c:5f:6c:69:76:65:62:6f:78:2e:49:6e:74:65:72:6e:65:74:2e:73:6f:66:74:61:74:68:6f:6d:65:2e:6c:69:76:65:62:6f:78:33;
    send dhcp6.client-id 00:03:00:01:08:3E:5D:01:02:03; // ceci n'est pas non plus la MAC de ma livebox
    send dhcp6.auth = generate("/etc/orange/auth");
}

cyayon

  • Abonné Orange Fibre
  • *
  • Messages: 764
  • Cordon 74 - Orange Fibre Pro
Durcissement du contrôle de l’option 90/11 et de la conformité protocolaire
« Réponse #138 le: 11 décembre 2022 à 18:56:58 »
Ne faites pas comme moi les djeuns.
Avec ISC dhclient, les formats corrects pour le client id DHCP et DHCP6 sont les suivants.
DHCP client id (option 61): 7 octets en tout. 1 octet hardware type 01 ("ethernet") + 6 octets mac address livebox
DHCP6 client id (option 1): DUID de 10 octets en tout. 2 octets DUID type 0003 ("link layer") + 2 octets hardware type 0001 ("ethernet") + 6 octets mac address livebox

A titre d'exemple, les valeurs ci-dessous corrigées:

Hello,

Merci pour le rappel.
Je ne connaissais pas le generate(). Ça retourne quoi ? As tu les sources ?
Merci

zoc

  • Abonné Orange Fibre
  • *
  • Messages: 4 750
  • Antibes (06) / Mercury (73)
Durcissement du contrôle de l’option 90/11 et de la conformité protocolaire
« Réponse #139 le: 11 décembre 2022 à 19:51:58 »
Normal, generate ca n’existe pas dans le client isc officiel. C’est un patch écrit par un membre du forum qui rajoute la fonction.

cyayon

  • Abonné Orange Fibre
  • *
  • Messages: 764
  • Cordon 74 - Orange Fibre Pro
Durcissement du contrôle de l’option 90/11 et de la conformité protocolaire
« Réponse #140 le: 11 décembre 2022 à 20:03:58 »
Normal, generate ca n’existe pas dans le client isc officiel. C’est un patch écrit par un membre du forum qui rajoute la fonction.

A oui, je me disais bien :)
Je pensais également qu’on ne pouvait pas mixer ipv4 et ipv6 avec la meme instance dhclient. Avant de migrer vers systemd-networkd, je faisais tourner 2 instances dhclient (ipv4 et ipv6).
Je note en cas de rollback…

zoc

  • Abonné Orange Fibre
  • *
  • Messages: 4 750
  • Antibes (06) / Mercury (73)

cyayon

  • Abonné Orange Fibre
  • *
  • Messages: 764
  • Cordon 74 - Orange Fibre Pro

Strangelovian

  • Abonné Orange Fibre
  • *
  • Messages: 58
Durcissement du contrôle de l’option 90/11 et de la conformité protocolaire
« Réponse #143 le: 11 décembre 2022 à 20:35:29 »
Hello,

Merci pour le rappel.
Je ne connaissais pas le generate(). Ça retourne quoi ? As tu les sources ?
Merci
Oui, c'est un patch de Xavier G / kindwolf ici, sources et procédure de patch ici: https://xavier.kindwolf.org/2018-orange-dhcp/
Un backup de tout ça sur github:
#!/usr/bin/env python3
import os
import sys
import struct
import string
import random
import hashlib
ORANGE_VENDOR_TYPE = 0x1A
ORANGE_USER_TYPE = 0x01
ORANGE_SALT_TYPE = 0x3C
ORANGE_HASH_TYPE = 0x03
def load_credentials(filepath):
'''
Read credentials from the given file. Empty lines and comments are ignored.
The first username:password line found will be used.
And do not forget to chmod o-rwx that damn file.
'''
with open(filepath, 'r') as filedesc:
for line in filedesc.readlines():
line = line.strip()
if line and not line.startswith('#') and ':' in line:
parts = line.split(':', 1)
return (parts[0], parts[1])
raise Exception('no credentials found in %s' % filepath)
def to_bytes(string, encoding='ascii'):
'''
Convert the given string to bytes, assuming ASCII encoding by default.
'''
return bytes(string, encoding)
def tlv(id, value):
'''
Poor man's implementation of TLV (Type, Length, Value) encoding.
'''
length = len(value) + 2
if length > 253:
raise Exception('Unable to deal with length > 253')
return struct.pack('BB', id, length) + value
def make_salt(length=16):
'''
Return a binary string made of <length> random bytes.
'''
return os.urandom(length)
def make_ascii_salt(length=16):
'''
Return a string made of <length> random printable ASCII characters
(excluding tabs).
'''
binary_salt = make_salt(length)
lowest_value = ord(' ')
highest_value = ord('~')
interval = highest_value - lowest_value + 1
ascii_salt = []
for byte in binary_salt:
ascii_salt.append(lowest_value + (byte % interval))
return bytes(ascii_salt)
def make_orange_hash(salt, password, byte=None):
'''
Return byte + MD5(byte, password, salt).
'''
random_byte = os.urandom(1) if byte is None else byte
md5_hasher = hashlib.md5()
md5_hasher.update(random_byte)
md5_hasher.update(password)
md5_hasher.update(salt)
return random_byte + md5_hasher.digest()
def make_orange_authentication(credentials='/etc/orange/credentials'):
'''
Generate and return the Orange-specific DCHP authentication value.
'''
(username, password) = load_credentials(credentials)
salt = make_ascii_salt()
random_char = to_bytes(random.choice(string.ascii_letters))
hash = make_orange_hash(salt, to_bytes(password), random_char)
# Strive to imitate what a LiveBox would generate, starting with 11 null
# bytes:
# - 1 for the authentication protocol: 0 means "configuration token"
# - 1 for the algorithm: 0 means "none"
# - 1 for RDM (Replay Detection method) type: 0 means "monotonically-increasing counter"
# - 8 for RDM (Replay Detection method) value (here, 0, simply)
auth = bytes(11)
# Then, according to [1], we have a sequence of Type-Length-Value fields:
# - Vendor information field: type 0x1A, length 9 (1+1+7), value 00:00:05:58:01:03:41,
# where 0x0558 (1368) is the IANA enterprise number for Orange
auth += tlv(ORANGE_VENDOR_TYPE, bytes([0x00, 0x00, 0x05, 0x58, 0x01, 0x03, 0x41]))
# - Username field: type 0x01, length 13 (1+1+11), the Orange PPP login:
auth += tlv(ORANGE_USER_TYPE, to_bytes(username))
# - Salt field: type 0x3C, length 18 (1+1+16), 16-byte random salt
auth += tlv(ORANGE_SALT_TYPE, salt)
# - Hash field: type 0x03, length 19 (1+1+17), 1 random byte followed by the 16-byte MD5 hash of:
# - that random byte
# - the Orange PPP password
# - the salt field
auth += tlv(ORANGE_HASH_TYPE, hash)
return auth
# References:
# [1] https://lafibre.info/remplacer-livebox/cacking-nouveau-systeme-de-generation-de-loption-90-dhcp/
if __name__ == '__main__':
auth = make_orange_authentication()
sys.stdout.buffer.write(auth)
sys.exit(0)
view raw auth.py hosted with ❤ by GitHub
# /etc/orange/auth: PPP credentials for Orange
# Do not forget to chmod o-rwx this file before storing your credentials in it.
fti/abcd123:wxyz987
view raw credentials hosted with ❤ by GitHub
isc-dhcp-client/oldstable 4.4.1-2+deb10u2 amd64 [upgradable from: 4.4.1-2+deb10u1kindwolf1]
isc-dhcp-common/oldstable 4.4.1-2+deb10u2 amd64 [upgradable from: 4.4.1-2+deb10u1kindwolf1]
#!/bin/sh
# apt install devscripts fakeroot
# apt build-dep isc-dhcp-client
apt source isc-dhcp-client
cd isc-dhcp-4.3.5
patch -p1 < ../../isc-dhcp-generate.diff
dch --local kindwolf
dpkg-source --commit
debuild -us -uc
sudo dpkg -i isc-dhcp-common_4.4.1-2+deb10u1kindwolf1_amd64.deb isc-dhcp-client_4.4.1-2+deb10u1kindwolf1_amd64.deb
credentials: simple storage for your Orange PPP credentials; intended absolute path: /etc/orange/credentials
auth: Python 3 script that reads /etc/orange/credentials and output a suitable value for DHCP option 90; do not forget to chmod +x that file
debian-repository.txt: instructions to get patched isc-dhcp* packages.
isc-dhcp-generate-master.diff: same as isc-dhcp-generate.diff, but adjusted for the upstream master branch.
isc-dchp-generate-for-tag-v4_1_esv_r8.diff: same as isc-dhcp-generate.diff, but adjusted for tag v4_1_esv_r8.
isc-dhcp-generate.diff: patch to implement the generate() function in isc-dhcp-client
specifically for isc-dhcp-client 4.3.5-4+b1 as found on Debian stretch:
# apt install devscripts fakeroot
# apt build-dep isc-dhcp-client
$ apt source isc-dhcp-client
$ cd isc-dhcp-4.3.5
$ patch -p1 < /path/to/isc-dhcp-generate.diff
$ dch --local kindwolf
$ dpkg-source --commit
$ debuild -us -uc
# dpkg -i isc-dhcp-common_4.3.5-3+deb9u1kindwolf1_amd64.deb isc-dhcp-client_4.3.5-3+deb9u1kindwolf1_amd64.deb
That said, this patch also applies correctly to the official Git repository ( https://source.isc.org/git/dhcp.git ) as of commit a5b21e16ff3a58d2262eddfffc4e8937379b016f.
Configuration in dhclient.conf:
option authentication code 90 = string;
send authentication = generate("/etc/orange/auth");
Note: you can pass extra arguments if needed, e.g. generate("/path/to/exec", "arg1", "arg2")
view raw gistfile1.txt hosted with ❤ by GitHub
diff --git a/common/conflex.c b/common/conflex.c
index 045b655d..439bc30c 100644
--- a/common/conflex.c
+++ b/common/conflex.c
@@ -1028,6 +1028,8 @@ intern(char *atom, enum dhcp_token dfv) {
return GETHOSTNAME;
break;
}
+ if (!strncasecmp (atom + 1, "enerate", 8))
+ return GENERATE;
if (!strcasecmp (atom + 1, "iaddr"))
return GIADDR;
if (!strcasecmp (atom + 1, "roup"))
diff --git a/common/dhcp-eval.5 b/common/dhcp-eval.5
index 7dd52613..4a5568bd 100644
--- a/common/dhcp-eval.5
+++ b/common/dhcp-eval.5
@@ -239,6 +239,17 @@ Several of the boolean expressions above depend on the results of
evaluating data expressions. A list of these expressions is provided
here.
.PP
+.B generate (\fIexecutable\fB[, \fIargument\fB[, ...]])\fR
+.PP
+.RS 0.25i
+The \fBgenerate\fR operator expects an executable as first parameter. It
+can be either an absolute path or just a name that will be looked up in
+the current PATH. Other arguments, if any, are passed as command line
+arguments to the executable. \fIexecutable\fR and \fIargument\fR are data
+expressions. The command is executed and generate() evaluates to its
+standard output. If this output exceeds 1024 bytes, it will be truncated.
+.RE
+.PP
.B substring (\fIdata-expr\fB, \fIoffset\fB, \fIlength\fB)\fR
.PP
.RS 0.25i
diff --git a/common/parse.c b/common/parse.c
index 3ac4ebf7..60ed7183 100644
--- a/common/parse.c
+++ b/common/parse.c
@@ -3485,6 +3485,8 @@ int parse_boolean (cfile)
* data_expression :== SUBSTRING LPAREN data-expression COMMA
* numeric-expression COMMA
* numeric-expression RPAREN |
+ * GENERATE LPAREN data-expression
+ * [COMMA data-expression, ...] RPAREN |
* CONCAT LPAREN data-expression COMMA
* data-expression RPAREN
* SUFFIX LPAREN data_expression COMMA
@@ -3668,6 +3670,60 @@ int parse_non_binary (expr, cfile, lose, context)
(*expr) -> op = expr_known;
break;
+ case GENERATE:
+#ifdef ENABLE_GENERATE
+#ifndef GENERATE_ARGV_PREALLOC
+#define GENERATE_ARGV_PREALLOC 10
+#endif
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_generate;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN) goto nolparen;
+ if (!parse_data_expression (&(*expr) -> data.generate.exec_path,
+ cfile, lose)) goto nodata;
+ /* Parse 0 to n comma-separated arguments: */
+ int argv_size = GENERATE_ARGV_PREALLOC * sizeof(struct expression *);
+ int argv_size_increment = argv_size;
+ struct expression **argv = dmalloc(argv_size, MDL);
+ int argc = 0;
+ int comma_expected = 1;
+ for (;;) {
+ if (comma_expected) {
+ token = next_token(&val, (unsigned *)0, cfile);
+ if (token == RPAREN) break;
+ if (token != COMMA) parse_warn(cfile, "expected comma");
+ comma_expected = 0;
+ continue;
+ }
+ /* Neither a right parenthesis nor a comma? We expect
+ * an argument: */
+ if (!parse_data_expression(&(argv[argc]), cfile, lose)) {
+ parse_warn(cfile, "expected data expression");
+ }
+ ++ argc;
+ comma_expected = 1;
+ /* Is it time to extend argv? */
+ if (!(argc % GENERATE_ARGV_PREALLOC)) {
+ argv_size += argv_size_increment;
+ argv = realloc(argv, argv_size);
+ }
+ }
+ (*expr) -> data.generate.argc = argc;
+ (*expr) -> data.generate.argv = argv;
+#if defined (DEBUG_EXPRESSION_PARSE)
+ print_expression ("generate expression", *expr);
+#endif
+#else /* ! ENABLE_GENERATE */
+ parse_warn(cfile, "define ENABLE_GENERATE in site.h to "
+ "enable generate(); expressions.");
+ skip_to_semi(cfile);
+ *lose = 1;
+ return 0;
+#endif /* ENABLE_GENERATE */
+ break;
case SUBSTRING:
skip_token(&val, (unsigned *)0, cfile);
if (!expression_allocate (expr, MDL))
diff --git a/common/print.c b/common/print.c
index b42e7bc5..832deadd 100644
--- a/common/print.c
+++ b/common/print.c
@@ -546,6 +546,7 @@ static unsigned print_subexpression (expr, buf, len)
{
unsigned rv, left;
const char *s;
+ int argc, i;
switch (expr -> op) {
case expr_none:
@@ -616,6 +617,24 @@ static unsigned print_subexpression (expr, buf, len)
}
break;
+ case expr_generate:
+ argc = expr -> data.generate.argc;
+ if (len > 11) {
+ rv = 10;
+ strcpy(buf, "(generate ");
+ rv += print_subexpression(expr -> data.generate.exec_path,
+ buf + rv, len - rv - 1 - argc);
+ for (i = 0; i < argc; ++ i) {
+ buf[rv++] = ' ';
+ rv += print_subexpression(expr -> data.generate.argv[i],
+ buf + rv,
+ len - rv - 1 - (argc - i - 1));
+ }
+ buf[rv++] = ')';
+ buf[rv] = 0;
+ return rv;
+ }
+ break;
case expr_substring:
if (len > 11) {
rv = 8;
diff --git a/common/tree.c b/common/tree.c
index ea787543..a931b221 100644
--- a/common/tree.c
+++ b/common/tree.c
@@ -1079,6 +1079,7 @@ int evaluate_boolean_expression (result, packet, lease, client_state,
case expr_gethostname:
case expr_v6relay:
case expr_concat_dclist:
+ case expr_generate:
log_error ("Data opcode in evaluate_boolean_expression: %d",
expr -> op);
return 0;
@@ -1123,6 +1124,86 @@ int evaluate_boolean_expression (result, packet, lease, client_state,
return 0;
}
+int exec_read(exec_path, argv, argc, result)
+ struct data_string *exec_path;
+ struct data_string *argv;
+ int argc;
+ struct data_string *result;
+{
+ int i;
+ char **exec_argv;
+ int pipe_fd[2];
+ int success = 1;
+ /* Amount of data read from the child process: */
+ int data_read = 0;
+
+ /* Prepare execvp()'s 2nd parameter: */
+ exec_argv = dmalloc((argc + 2) * sizeof(char *), MDL);
+ exec_argv[0] = (char *)exec_path->data;
+ for (i = 0; i < argc; ++ i) {
+ exec_argv[i + 1] = (char *)argv[i].data;
+ }
+ exec_argv[argc + 1] = NULL;
+
+ if (pipe(pipe_fd) == -1) {
+ log_error("exec_read(): pipe() failed, errno == %d", errno);
+ return 0;
+ }
+#ifndef GENERATE_BUFSIZE
+#define GENERATE_BUFSIZE 1024
+#endif
+ result->data = dmalloc(GENERATE_BUFSIZE, MDL);
+ result->len = GENERATE_BUFSIZE;
+ if (fork()) {
+ /* Close unused side of the pipe: */
+ close(pipe_fd[1]);
+ // Read the child process's stdout:
+ for (result->len = 0; result->len < GENERATE_BUFSIZE; ) {
+ data_read = read(pipe_fd[0],
+ (void *)result->data + result->len,
+ GENERATE_BUFSIZE - result->len);
+ /* Handle errors: */
+ if (data_read == -1) {
+ log_error("exec_read(): read() failed, "
+ "errno == %d (%s)",
+ errno, strerror(errno));
+ success = 0;
+ break;
+ }
+ /* Handle end of file: */
+ if (!data_read) break;
+ result->len += data_read;
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug("exec_read(): read %4d bytes (total: %4d, "
+ "max: %4d) from %s",
+ data_read, result->len, GENERATE_BUFSIZE,
+ exec_path->data);
+#endif
+ }
+ // Read only one buffer worth of data then close the file
+ // descriptor:
+ close(pipe_fd[0]);
+ }
+ else {
+ /* Close unused side of the pipe: */
+ close(pipe_fd[0]);
+ /* No stdin: */
+ close(STDIN_FILENO);
+ /* Pipe stdout to the parent process: */
+ dup2(pipe_fd[1], STDOUT_FILENO);
+ execvp((const char *)exec_path->data, (char * const *)exec_argv);
+ /* Still there? execvp must have failed. */
+ log_error("exec_read(): execvp(%s) failed, errno == %d (%s)",
+ exec_path->data,
+ errno, strerror(errno));
+ close(pipe_fd[1]);
+ close(STDOUT_FILENO);
+ exit(1);
+ }
+ free(exec_argv);
+ return success;
+}
+
int evaluate_data_expression (result, packet, lease, client_state,
in_options, cfg_options, scope, expr, file, line)
struct data_string *result;
@@ -1145,8 +1226,45 @@ int evaluate_data_expression (result, packet, lease, client_state,
struct binding_value *bv;
struct packet *relay_packet;
struct option_state *relay_options;
+#ifdef ENABLE_GENERATE
+ struct data_string *argv_data;
+ int argc;
+#endif
switch (expr -> op) {
+#ifdef ENABLE_GENERATE
+ case expr_generate:
+ memset(&data, 0, sizeof data);
+ /* Evaluate exec_path into a static string: */
+ s0 = evaluate_data_expression(&data, packet, lease,
+ client_state, in_options,
+ cfg_options, scope,
+ expr -> data.generate.exec_path,
+ MDL);
+ /* Do we have arguments? */
+ argv_data = 0;
+ argc = expr->data.generate.argc;
+ if (argc) {
+ argv_data = dmalloc(argc * sizeof(struct data_string), MDL);
+ /* Evaluate all arguments into static strings: */
+ for (i = 0; i < argc; ++ i) {
+ s1 = evaluate_data_expression(&argv_data[i], packet, lease,
+ client_state, in_options,
+ cfg_options, scope,
+ expr -> data.generate.argv[i],
+ MDL);
+ }
+ }
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug("data: generate: about to fork %s with %d args", data.data, argc);
+ for (i = 0; i < argc; ++ i) {
+ log_debug("data: generate: arg #%lu: %s", i, argv_data[i].data);
+ }
+#endif
+ int success = exec_read(&data, argv_data, argc, result);
+ if (argv_data) free(argv_data);
+ return success;
+#endif /* ENABLE_GENERATE */
/* Extract N bytes starting at byte M of a data string. */
case expr_substring:
memset (&data, 0, sizeof data);
@@ -2289,6 +2407,7 @@ int evaluate_numeric_expression (result, packet, lease, client_state,
case expr_null:
case expr_gethostname:
case expr_v6relay:
+ case expr_generate:
log_error ("Data opcode in evaluate_numeric_expression: %d",
expr -> op);
return 0;
@@ -2865,6 +2984,21 @@ void expression_dereference (eptr, file, line)
file, line);
break;
+ case expr_generate:
+ log_debug("expression_dereference(): case expr_generate");
+ if (expr -> data.generate.exec_path)
+ expression_dereference (&expr -> data.generate.exec_path,
+ file, line);
+ for (int i = 0; i < expr -> data.generate.argc; ++ i) {
+ expression_dereference(&expr -> data.generate.argv[i],
+ file, line);
+ }
+ /* Do not forget to free the array of arguments itself: */
+ if (expr -> data.generate.argv) {
+ free(expr -> data.generate.argv);
+ expr -> data.generate.argv = 0;
+ }
+ break;
case expr_substring:
if (expr -> data.substring.expr)
expression_dereference (&expr -> data.substring.expr,
@@ -3072,6 +3206,7 @@ int is_data_expression (expr)
expr->op == expr_config_option ||
expr->op == expr_null ||
expr->op == expr_gethostname ||
+ expr->op == expr_generate ||
expr->op == expr_v6relay);
}
@@ -3111,6 +3246,7 @@ int is_compound_expression (expr)
expr -> op == expr_extract_int8 ||
expr -> op == expr_extract_int16 ||
expr -> op == expr_extract_int32 ||
+ expr -> op == expr_generate ||
expr -> op == expr_v6relay);
}
@@ -3173,6 +3309,7 @@ static int op_val (op)
case expr_gethostname:
case expr_v6relay:
case expr_concat_dclist:
+ case expr_generate:
return 100;
case expr_equal:
@@ -3267,6 +3404,7 @@ enum expression_context op_context (op)
case expr_gethostname:
case expr_v6relay:
case expr_concat_dclist:
+ case expr_generate:
return context_any;
case expr_equal:
@@ -3365,6 +3503,21 @@ int write_expression (file, expr, col, indent, firstp)
col = token_print_indent (file, col, indent, "", "", ")");
break;
+ case expr_generate:
+ col = token_print_indent(file, col, indent, "", "", "generate");
+ col = token_print_indent(file, col, indent, " ", "", "(");
+ scol = col;
+ col = write_expression(file, expr -> data.generate.exec_path,
+ col, scol, 1);
+ for (int i = 0; i < expr -> data.generate.argc; ++ i) {
+ col = token_print_indent(file, col, scol, "", " ", ",");
+ col = write_expression(file,
+ expr -> data.generate.argv[i],
+ col, scol, 0);
+ }
+ col = token_print_indent (file, col, indent, "", "", ")");
+ break;
+
case expr_suffix:
col = token_print_indent (file, col, indent, "", "", "suffix");
col = token_print_indent (file, col, indent, " ", "", "(");
diff --git a/configure.ac b/configure.ac
index a7974385..f1ac7a39 100644
--- a/configure.ac
+++ b/configure.ac
@@ -133,6 +133,16 @@ if test "$enable_execute" != "no" ; then
[Define to include execute() config language support.])
fi
+# generate() support.
+AC_ARG_ENABLE(generate,
+ AS_HELP_STRING([--enable-generate],[enable support for generate() in config (default is yes)]))
+# generate() is on by default, so define if it is not explicitly disabled.
+if test "$enable_generate" != "no" ; then
+ enable_generate="yes"
+ AC_DEFINE([ENABLE_GENERATE], [1],
+ [Define to include generate() config language support.])
+fi
+
# Server tracing support.
AC_ARG_ENABLE(tracing,
AS_HELP_STRING([--enable-tracing],[enable support for server activity tracing (default is yes)]))
@@ -1051,6 +1061,7 @@ Features:
debug: $enable_debug
failover: $enable_failover
execute: $enable_execute
+ generate: $enable_generate
binary-leases: $enable_binary_leases
dhcpv6: $enable_dhcpv6
delayed-ack: $enable_delayed_ack
diff --git a/includes/config.h.in b/includes/config.h.in
index 1608f671..f20ad4dc 100644
--- a/includes/config.h.in
+++ b/includes/config.h.in
@@ -28,6 +28,9 @@
/* Define to include execute() config language support. */
#undef ENABLE_EXECUTE
+/* Define to include generate() config language support. */
+#undef ENABLE_GENERATE
+
/* Define to include Failover Protocol support. */
#undef FAILOVER_PROTOCOL
diff --git a/includes/dhctoken.h b/includes/dhctoken.h
index 5920f4ff..76b492db 100644
--- a/includes/dhctoken.h
+++ b/includes/dhctoken.h
@@ -181,6 +181,7 @@ enum dhcp_token {
TOKEN_FDDI = 379,
AUTHORITATIVE = 380,
TOKEN_NOT = 381,
+ GENERATE = 382,
AUTHENTICATION = 383,
IGNORE = 384,
ACCEPT = 385,
diff --git a/includes/site.h b/includes/site.h
index 2ef69e41..d088388d 100644
--- a/includes/site.h
+++ b/includes/site.h
@@ -212,6 +212,11 @@
/* #define ENABLE_EXECUTE */
+/* Define this if you want to be able to fetch the output of external commands
+ during conditional evaluation. */
+
+/* #define ENABLE_GENERATE */
+
/* Define this if you aren't debugging and you want to save memory
(potentially a _lot_ of memory) by allocating leases in chunks rather
than one at a time. */
diff --git a/includes/tree.h b/includes/tree.h
index c8cce4b2..9014a197 100644
--- a/includes/tree.h
+++ b/includes/tree.h
@@ -191,7 +191,8 @@ enum expr_op {
expr_iregex_match,
expr_gethostname,
expr_v6relay,
- expr_concat_dclist
+ expr_concat_dclist,
+ expr_generate,
};
struct expression {
@@ -203,6 +204,13 @@ struct expression {
struct expression *offset;
struct expression *len;
} substring;
+ struct {
+ /* generate(): path to the executable file to run: */
+ struct expression *exec_path;
+ /* generate(): arguments to pass to the executable: */
+ int argc;
+ struct expression **argv;
+ } generate;
struct expression *equal [2];
struct expression *and [2];
struct expression *or [2];
diff --git a/common/conflex.c b/common/conflex.c
index fe994ac..3100ec6 100644
--- a/common/conflex.c
+++ b/common/conflex.c
@@ -1028,6 +1028,8 @@ intern(char *atom, enum dhcp_token dfv) {
return GETHOSTNAME;
break;
}
+ if (!strncasecmp (atom + 1, "enerate", 8))
+ return GENERATE;
if (!strcasecmp (atom + 1, "iaddr"))
return GIADDR;
if (!strcasecmp (atom + 1, "roup"))
diff --git a/common/dhcp-eval.5 b/common/dhcp-eval.5
index 7dd5261..4a5568b 100644
--- a/common/dhcp-eval.5
+++ b/common/dhcp-eval.5
@@ -239,6 +239,17 @@ Several of the boolean expressions above depend on the results of
evaluating data expressions. A list of these expressions is provided
here.
.PP
+.B generate (\fIexecutable\fB[, \fIargument\fB[, ...]])\fR
+.PP
+.RS 0.25i
+The \fBgenerate\fR operator expects an executable as first parameter. It
+can be either an absolute path or just a name that will be looked up in
+the current PATH. Other arguments, if any, are passed as command line
+arguments to the executable. \fIexecutable\fR and \fIargument\fR are data
+expressions. The command is executed and generate() evaluates to its
+standard output. If this output exceeds 1024 bytes, it will be truncated.
+.RE
+.PP
.B substring (\fIdata-expr\fB, \fIoffset\fB, \fIlength\fB)\fR
.PP
.RS 0.25i
diff --git a/common/parse.c b/common/parse.c
index cfc02ce..3bff832 100644
--- a/common/parse.c
+++ b/common/parse.c
@@ -3481,6 +3481,8 @@ int parse_boolean (cfile)
* data_expression :== SUBSTRING LPAREN data-expression COMMA
* numeric-expression COMMA
* numeric-expression RPAREN |
+ * GENERATE LPAREN data-expression
+ * [COMMA data-expression, ...] RPAREN |
* CONCAT LPAREN data-expression COMMA
* data-expression RPAREN
* SUFFIX LPAREN data_expression COMMA
@@ -3664,6 +3666,60 @@ int parse_non_binary (expr, cfile, lose, context)
(*expr) -> op = expr_known;
break;
+ case GENERATE:
+#ifdef ENABLE_GENERATE
+#ifndef GENERATE_ARGV_PREALLOC
+#define GENERATE_ARGV_PREALLOC 10
+#endif
+ skip_token(&val, (unsigned *)0, cfile);
+ if (!expression_allocate (expr, MDL))
+ log_fatal ("can't allocate expression");
+ (*expr) -> op = expr_generate;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LPAREN) goto nolparen;
+ if (!parse_data_expression (&(*expr) -> data.generate.exec_path,
+ cfile, lose)) goto nodata;
+ /* Parse 0 to n comma-separated arguments: */
+ int argv_size = GENERATE_ARGV_PREALLOC * sizeof(struct expression *);
+ int argv_size_increment = argv_size;
+ struct expression **argv = dmalloc(argv_size, MDL);
+ int argc = 0;
+ int comma_expected = 1;
+ for (;;) {
+ if (comma_expected) {
+ token = next_token(&val, (unsigned *)0, cfile);
+ if (token == RPAREN) break;
+ if (token != COMMA) parse_warn(cfile, "expected comma");
+ comma_expected = 0;
+ continue;
+ }
+ /* Neither a right parenthesis nor a comma? We expect
+ * an argument: */
+ if (!parse_data_expression(&(argv[argc]), cfile, lose)) {
+ parse_warn(cfile, "expected data expression");
+ }
+ ++ argc;
+ comma_expected = 1;
+ /* Is it time to extend argv? */
+ if (!(argc % GENERATE_ARGV_PREALLOC)) {
+ argv_size += argv_size_increment;
+ argv = realloc(argv, argv_size);
+ }
+ }
+ (*expr) -> data.generate.argc = argc;
+ (*expr) -> data.generate.argv = argv;
+#if defined (DEBUG_EXPRESSION_PARSE)
+ print_expression ("generate expression", *expr);
+#endif
+#else /* ! ENABLE_GENERATE */
+ parse_warn(cfile, "define ENABLE_GENERATE in site.h to "
+ "enable generate(); expressions.");
+ skip_to_semi(cfile);
+ *lose = 1;
+ return 0;
+#endif /* ENABLE_GENERATE */
+ break;
case SUBSTRING:
skip_token(&val, (unsigned *)0, cfile);
if (!expression_allocate (expr, MDL))
diff --git a/common/print.c b/common/print.c
index ce368c4..3989917 100644
--- a/common/print.c
+++ b/common/print.c
@@ -546,6 +546,7 @@ static unsigned print_subexpression (expr, buf, len)
{
unsigned rv, left;
const char *s;
+ int argc, i;
switch (expr -> op) {
case expr_none:
@@ -616,6 +617,24 @@ static unsigned print_subexpression (expr, buf, len)
}
break;
+ case expr_generate:
+ argc = expr -> data.generate.argc;
+ if (len > 11) {
+ rv = 10;
+ strcpy(buf, "(generate ");
+ rv += print_subexpression(expr -> data.generate.exec_path,
+ buf + rv, len - rv - 1 - argc);
+ for (i = 0; i < argc; ++ i) {
+ buf[rv++] = ' ';
+ rv += print_subexpression(expr -> data.generate.argv[i],
+ buf + rv,
+ len - rv - 1 - (argc - i - 1));
+ }
+ buf[rv++] = ')';
+ buf[rv] = 0;
+ return rv;
+ }
+ break;
case expr_substring:
if (len > 11) {
rv = 8;
diff --git a/common/tree.c b/common/tree.c
index e364b2a..8500a00 100644
--- a/common/tree.c
+++ b/common/tree.c
@@ -1079,6 +1079,7 @@ int evaluate_boolean_expression (result, packet, lease, client_state,
case expr_gethostname:
case expr_v6relay:
case expr_concat_dclist:
+ case expr_generate:
log_error ("Data opcode in evaluate_boolean_expression: %d",
expr -> op);
return 0;
@@ -1123,6 +1124,86 @@ int evaluate_boolean_expression (result, packet, lease, client_state,
return 0;
}
+int exec_read(exec_path, argv, argc, result)
+ struct data_string *exec_path;
+ struct data_string *argv;
+ int argc;
+ struct data_string *result;
+{
+ int i;
+ char **exec_argv;
+ int pipe_fd[2];
+ int success = 1;
+ /* Amount of data read from the child process: */
+ int data_read = 0;
+
+ /* Prepare execvp()'s 2nd parameter: */
+ exec_argv = dmalloc((argc + 2) * sizeof(char *), MDL);
+ exec_argv[0] = (char *)exec_path->data;
+ for (i = 0; i < argc; ++ i) {
+ exec_argv[i + 1] = (char *)argv[i].data;
+ }
+ exec_argv[argc + 1] = NULL;
+
+ if (pipe(pipe_fd) == -1) {
+ log_error("exec_read(): pipe() failed, errno == %d", errno);
+ return 0;
+ }
+#ifndef GENERATE_BUFSIZE
+#define GENERATE_BUFSIZE 1024
+#endif
+ result->data = dmalloc(GENERATE_BUFSIZE, MDL);
+ result->len = GENERATE_BUFSIZE;
+ if (fork()) {
+ /* Close unused side of the pipe: */
+ close(pipe_fd[1]);
+ // Read the child process's stdout:
+ for (result->len = 0; result->len < GENERATE_BUFSIZE; ) {
+ data_read = read(pipe_fd[0],
+ (void *)result->data + result->len,
+ GENERATE_BUFSIZE - result->len);
+ /* Handle errors: */
+ if (data_read == -1) {
+ log_error("exec_read(): read() failed, "
+ "errno == %d (%s)",
+ errno, strerror(errno));
+ success = 0;
+ break;
+ }
+ /* Handle end of file: */
+ if (!data_read) break;
+ result->len += data_read;
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug("exec_read(): read %4d bytes (total: %4d, "
+ "max: %4d) from %s",
+ data_read, result->len, GENERATE_BUFSIZE,
+ exec_path->data);
+#endif
+ }
+ // Read only one buffer worth of data then close the file
+ // descriptor:
+ close(pipe_fd[0]);
+ }
+ else {
+ /* Close unused side of the pipe: */
+ close(pipe_fd[0]);
+ /* No stdin: */
+ close(STDIN_FILENO);
+ /* Pipe stdout to the parent process: */
+ dup2(pipe_fd[1], STDOUT_FILENO);
+ execvp((const char *)exec_path->data, (char * const *)exec_argv);
+ /* Still there? execvp must have failed. */
+ log_error("exec_read(): execvp(%s) failed, errno == %d (%s)",
+ exec_path->data,
+ errno, strerror(errno));
+ close(pipe_fd[1]);
+ close(STDOUT_FILENO);
+ exit(1);
+ }
+ free(exec_argv);
+ return success;
+}
+
int evaluate_data_expression (result, packet, lease, client_state,
in_options, cfg_options, scope, expr, file, line)
struct data_string *result;
@@ -1145,8 +1226,45 @@ int evaluate_data_expression (result, packet, lease, client_state,
struct binding_value *bv;
struct packet *relay_packet;
struct option_state *relay_options;
+#ifdef ENABLE_GENERATE
+ struct data_string *argv_data;
+ int argc;
+#endif
switch (expr -> op) {
+#ifdef ENABLE_GENERATE
+ case expr_generate:
+ memset(&data, 0, sizeof data);
+ /* Evaluate exec_path into a static string: */
+ s0 = evaluate_data_expression(&data, packet, lease,
+ client_state, in_options,
+ cfg_options, scope,
+ expr -> data.generate.exec_path,
+ MDL);
+ /* Do we have arguments? */
+ argv_data = 0;
+ argc = expr->data.generate.argc;
+ if (argc) {
+ argv_data = dmalloc(argc * sizeof(struct data_string), MDL);
+ /* Evaluate all arguments into static strings: */
+ for (i = 0; i < argc; ++ i) {
+ s1 = evaluate_data_expression(&argv_data[i], packet, lease,
+ client_state, in_options,
+ cfg_options, scope,
+ expr -> data.generate.argv[i],
+ MDL);
+ }
+ }
+#if defined (DEBUG_EXPRESSIONS)
+ log_debug("data: generate: about to fork %s with %d args", data.data, argc);
+ for (i = 0; i < argc; ++ i) {
+ log_debug("data: generate: arg #%lu: %s", i, argv_data[i].data);
+ }
+#endif
+ int success = exec_read(&data, argv_data, argc, result);
+ if (argv_data) free(argv_data);
+ return success;
+#endif /* ENABLE_GENERATE */
/* Extract N bytes starting at byte M of a data string. */
case expr_substring:
memset (&data, 0, sizeof data);
@@ -2289,6 +2407,7 @@ int evaluate_numeric_expression (result, packet, lease, client_state,
case expr_null:
case expr_gethostname:
case expr_v6relay:
+ case expr_generate:
log_error ("Data opcode in evaluate_numeric_expression: %d",
expr -> op);
return 0;
@@ -2865,6 +2984,21 @@ void expression_dereference (eptr, file, line)
file, line);
break;
+ case expr_generate:
+ log_debug("expression_dereference(): case expr_generate");
+ if (expr -> data.generate.exec_path)
+ expression_dereference (&expr -> data.generate.exec_path,
+ file, line);
+ for (int i = 0; i < expr -> data.generate.argc; ++ i) {
+ expression_dereference(&expr -> data.generate.argv[i],
+ file, line);
+ }
+ /* Do not forget to free the array of arguments itself: */
+ if (expr -> data.generate.argv) {
+ free(expr -> data.generate.argv);
+ expr -> data.generate.argv = 0;
+ }
+ break;
case expr_substring:
if (expr -> data.substring.expr)
expression_dereference (&expr -> data.substring.expr,
@@ -3072,6 +3206,7 @@ int is_data_expression (expr)
expr->op == expr_config_option ||
expr->op == expr_null ||
expr->op == expr_gethostname ||
+ expr->op == expr_generate ||
expr->op == expr_v6relay);
}
@@ -3111,6 +3246,7 @@ int is_compound_expression (expr)
expr -> op == expr_extract_int8 ||
expr -> op == expr_extract_int16 ||
expr -> op == expr_extract_int32 ||
+ expr -> op == expr_generate ||
expr -> op == expr_v6relay);
}
@@ -3173,6 +3309,7 @@ static int op_val (op)
case expr_gethostname:
case expr_v6relay:
case expr_concat_dclist:
+ case expr_generate:
return 100;
case expr_equal:
@@ -3267,6 +3404,7 @@ enum expression_context op_context (op)
case expr_gethostname:
case expr_v6relay:
case expr_concat_dclist:
+ case expr_generate:
return context_any;
case expr_equal:
@@ -3365,6 +3503,21 @@ int write_expression (file, expr, col, indent, firstp)
col = token_print_indent (file, col, indent, "", "", ")");
break;
+ case expr_generate:
+ col = token_print_indent(file, col, indent, "", "", "generate");
+ col = token_print_indent(file, col, indent, " ", "", "(");
+ scol = col;
+ col = write_expression(file, expr -> data.generate.exec_path,
+ col, scol, 1);
+ for (int i = 0; i < expr -> data.generate.argc; ++ i) {
+ col = token_print_indent(file, col, scol, "", " ", ",");
+ col = write_expression(file,
+ expr -> data.generate.argv[i],
+ col, scol, 0);
+ }
+ col = token_print_indent (file, col, indent, "", "", ")");
+ break;
+
case expr_suffix:
col = token_print_indent (file, col, indent, "", "", "suffix");
col = token_print_indent (file, col, indent, " ", "", "(");
diff --git a/configure.ac b/configure.ac
index aef0b5d..8bf4615 100644
--- a/configure.ac
+++ b/configure.ac
@@ -120,6 +120,16 @@ if test "$enable_execute" != "no" ; then
[Define to include execute() config language support.])
fi
+# generate() support.
+AC_ARG_ENABLE(generate,
+ AS_HELP_STRING([--enable-generate],[enable support for generate() in config (default is yes)]))
+# generate() is on by default, so define if it is not explicitly disabled.
+if test "$enable_generate" != "no" ; then
+ enable_generate="yes"
+ AC_DEFINE([ENABLE_GENERATE], [1],
+ [Define to include generate() config language support.])
+fi
+
# Server tracing support.
AC_ARG_ENABLE(tracing,
AS_HELP_STRING([--enable-tracing],[enable support for server activity tracing (default is yes)]))
@@ -860,6 +870,7 @@ Features:
debug: $enable_debug
failover: $enable_failover
execute: $enable_execute
+ generate: $enable_generate
binary-leases: $enable_binary_leases
dhcpv6: $enable_dhcpv6
delayed-ack: $enable_delayed_ack
diff --git a/includes/config.h.in b/includes/config.h.in
index 02f5dbc..56b026d 100644
--- a/includes/config.h.in
+++ b/includes/config.h.in
@@ -28,6 +28,9 @@
/* Define to include execute() config language support. */
#undef ENABLE_EXECUTE
+/* Define to include generate() config language support. */
+#undef ENABLE_GENERATE
+
/* Define to include Failover Protocol support. */
#undef FAILOVER_PROTOCOL
diff --git a/includes/dhctoken.h b/includes/dhctoken.h
index 15bbd1c..fc5c87c 100644
--- a/includes/dhctoken.h
+++ b/includes/dhctoken.h
@@ -179,6 +179,7 @@ enum dhcp_token {
TOKEN_FDDI = 379,
AUTHORITATIVE = 380,
TOKEN_NOT = 381,
+ GENERATE = 382,
AUTHENTICATION = 383,
IGNORE = 384,
ACCEPT = 385,
diff --git a/includes/site.h b/includes/site.h
index 52482e9..7f11c16 100644
--- a/includes/site.h
+++ b/includes/site.h
@@ -206,6 +206,11 @@
/* #define ENABLE_EXECUTE */
+/* Define this if you want to be able to fetch the output of external commands
+ during conditional evaluation. */
+
+/* #define ENABLE_GENERATE */
+
/* Define this if you aren't debugging and you want to save memory
(potentially a _lot_ of memory) by allocating leases in chunks rather
than one at a time. */
diff --git a/includes/tree.h b/includes/tree.h
index 8a285a5..7b0b4ad 100644
--- a/includes/tree.h
+++ b/includes/tree.h
@@ -192,7 +192,8 @@ enum expr_op {
expr_iregex_match,
expr_gethostname,
expr_v6relay,
- expr_concat_dclist
+ expr_concat_dclist,
+ expr_generate,
};
struct expression {
@@ -204,6 +205,13 @@ struct expression {
struct expression *offset;
struct expression *len;
} substring;
+ struct {
+ /* generate(): path to the executable file to run: */
+ struct expression *exec_path;
+ /* generate(): arguments to pass to the executable: */
+ int argc;
+ struct expression **argv;
+ } generate;
struct expression *equal [2];
struct expression *and [2];
struct expression *or [2];

A priori ce patch s'applique toujours sans erreurs sur les derniereres versions isc dhcp.

Il faut bien lancer deux process isc dhclient pour ipv4 et ipv6, mais ils peuvent partager le même fichier de configuration.