Bläddra i källkod

ncd: major rework, make NCD into a much more general system

ambrop7 15 år sedan
förälder
incheckning
443def606f
44 ändrade filer med 3275 tillägg och 1850 borttagningar
  1. 8 0
      blog_channels.txt
  2. 20 3
      examples/ncd_parser_test.c
  3. 11 2
      examples/ncd_tokenizer_test.c
  4. 230 108
      generated/NCDConfigParser_parse.c
  5. 8 5
      generated/NCDConfigParser_parse.h
  6. 251 97
      generated/NCDConfigParser_parse.out
  7. 57 15
      generated/NCDConfigParser_parse.y
  8. 4 0
      generated/blog_channel_ncd_list.h
  9. 4 0
      generated/blog_channel_ncd_net_backend_badvpn.h
  10. 4 0
      generated/blog_channel_ncd_net_backend_physical.h
  11. 4 0
      generated/blog_channel_ncd_net_dns.h
  12. 4 0
      generated/blog_channel_ncd_net_ipv4_addr.h
  13. 4 0
      generated/blog_channel_ncd_net_ipv4_dhcp.h
  14. 4 0
      generated/blog_channel_ncd_net_ipv4_route.h
  15. 4 0
      generated/blog_channel_ncd_var.h
  16. 25 17
      generated/blog_channels_defines.h
  17. 8 0
      generated/blog_channels_list.h
  18. 21 3
      misc/ipaddr.h
  19. 10 3
      ncd/CMakeLists.txt
  20. 0 152
      ncd/NCDInterfaceModule.c
  21. 0 86
      ncd/NCDInterfaceModule.h
  22. 184 0
      ncd/NCDModule.c
  23. 89 0
      ncd/NCDModule.h
  24. 243 0
      ncd/NCDValue.c
  25. 60 0
      ncd/NCDValue.h
  26. 0 231
      ncd/interface_modules/interface_badvpn.c
  27. 92 0
      ncd/modules/list.c
  28. 49 0
      ncd/modules/modules.h
  29. 261 0
      ncd/modules/net_backend_badvpn.c
  30. 45 35
      ncd/modules/net_backend_physical.c
  31. 278 0
      ncd/modules/net_dns.c
  32. 112 0
      ncd/modules/net_ipv4_addr.c
  33. 211 0
      ncd/modules/net_ipv4_dhcp.c
  34. 137 0
      ncd/modules/net_ipv4_route.c
  35. 101 0
      ncd/modules/var.c
  36. 493 1018
      ncd/ncd.c
  37. 18 10
      ncd/ncd.conf.example
  38. 2 2
      ncd/ncd.h
  39. 99 32
      ncdconfig/NCDConfig.c
  40. 21 5
      ncdconfig/NCDConfig.h
  41. 15 3
      ncdconfig/NCDConfigParser.c
  42. 57 15
      ncdconfig/NCDConfigParser_parse.y
  43. 19 3
      ncdconfig/NCDConfigTokenizer.c
  44. 8 5
      ncdconfig/NCDConfigTokenizer.h

+ 8 - 0
blog_channels.txt

@@ -3,6 +3,14 @@ client 4
 flooder 4
 tun2socks 4
 ncd 4
+ncd_var 4
+ncd_list 4
+ncd_net_backend_physical 4
+ncd_net_backend_badvpn 4
+ncd_net_dns 4
+ncd_net_ipv4_addr 4
+ncd_net_ipv4_dhcp 4
+ncd_net_ipv4_route 4
 StreamPeerIO 4
 DatagramPeerIO 4
 BReactor 3

+ 20 - 3
examples/ncd_parser_test.c

@@ -50,7 +50,7 @@ int main (int argc, char **argv)
     // print
     struct NCDConfig_interfaces *iface = ast;
     while (iface) {
-        printf("Interface %s\n", iface->name);
+        printf("process %s\n", iface->name);
         
         struct NCDConfig_statements *st = iface->statements;
         while (st) {
@@ -66,9 +66,26 @@ int main (int argc, char **argv)
             
             printf("\n");
             
-            struct NCDConfig_strings *arg = st->args;
+            struct NCDConfig_arguments *arg = st->args;
             while (arg) {
-                printf("    %s\n", arg->value);
+                switch (arg->type) {
+                    case NCDCONFIG_ARG_STRING:
+                        printf("    string: %s\n", arg->string);
+                        break;
+                    case NCDCONFIG_ARG_VAR:
+                        printf("    var: ");
+                        struct NCDConfig_strings *n = arg->var;
+                        printf("%s", n->value);
+                        n = n->next;
+                        while (n) {
+                            printf(".%s", n->value);
+                            n = n->next;
+                        }
+                        printf("\n");
+                        break;
+                    default:
+                        ASSERT(0);
+                }
                 arg = arg->next;
             }
             

+ 11 - 2
examples/ncd_tokenizer_test.c

@@ -47,14 +47,23 @@ static int tokenizer_output (void *user, int token, char *value, size_t pos)
         case NCD_TOKEN_CURLY_CLOSE:
             printf("curly_close\n");
             break;
+        case NCD_TOKEN_ROUND_OPEN:
+            printf("round_open\n");
+            break;
+        case NCD_TOKEN_ROUND_CLOSE:
+            printf("round_close\n");
+            break;
         case NCD_TOKEN_SEMICOLON:
             printf("semicolon\n");
             break;
         case NCD_TOKEN_DOT:
             printf("dot\n");
             break;
-        case NCD_TOKEN_INTERFACE:
-            printf("interface\n");
+        case NCD_TOKEN_COMMA:
+            printf("comma\n");
+            break;
+        case NCD_TOKEN_PROCESS:
+            printf("process\n");
             break;
         case NCD_TOKEN_NAME:
             printf("name %s\n", value);

+ 230 - 108
generated/NCDConfigParser_parse.c

@@ -70,15 +70,16 @@ struct parser_out {
 **                       defined, then do no error processing.
 */
 #define YYCODETYPE unsigned char
-#define YYNOCODE 15
+#define YYNOCODE 18
 #define YYACTIONTYPE unsigned char
 #define ParseTOKENTYPE void *
 typedef union {
   int yyinit;
   ParseTOKENTYPE yy0;
-  struct NCDConfig_interfaces * yy10;
-  struct NCDConfig_statements * yy22;
-  struct NCDConfig_strings * yy24;
+  struct NCDConfig_strings * yy10;
+  struct NCDConfig_statements * yy18;
+  struct NCDConfig_interfaces * yy32;
+  struct NCDConfig_arguments * yy34;
 } YYMINORTYPE;
 #ifndef YYSTACKDEPTH
 #define YYSTACKDEPTH 100
@@ -87,8 +88,8 @@ typedef union {
 #define ParseARG_PDECL ,struct parser_out *parser_out
 #define ParseARG_FETCH struct parser_out *parser_out = yypParser->parser_out
 #define ParseARG_STORE yypParser->parser_out = parser_out
-#define YYNSTATE 19
-#define YYNRULE 11
+#define YYNSTATE 32
+#define YYNRULE 17
 #define YY_NO_ACTION      (YYNSTATE+YYNRULE+2)
 #define YY_ACCEPT_ACTION  (YYNSTATE+YYNRULE+1)
 #define YY_ERROR_ACTION   (YYNSTATE+YYNRULE)
@@ -158,29 +159,37 @@ static const YYMINORTYPE yyzerominor = { 0 };
 **  yy_default[]       Default action for each state.
 */
 static const YYACTIONTYPE yy_action[] = {
- /*     0 */     8,    9,   11,    4,   31,   15,    4,   17,    4,    2,
- /*    10 */    12,    7,   14,   13,    7,   19,   16,    1,   18,   10,
- /*    20 */     5,   32,   32,   32,    6,    3,
+ /*     0 */    18,   13,   23,   20,   11,   50,   18,   22,   23,   31,
+ /*    10 */    23,   30,   14,   22,   16,   17,   25,   17,   27,   17,
+ /*    20 */    28,   17,   29,   17,   19,   21,   18,   24,   26,    5,
+ /*    30 */     7,   32,   15,    9,   10,    4,   51,    2,    1,   51,
+ /*    40 */    51,    6,   12,   51,   51,    3,    8,
 };
 static const YYCODETYPE yy_lookahead[] = {
- /*     0 */     9,    1,   10,   11,   13,   10,   11,   10,   11,    5,
- /*    10 */     2,    7,    9,   12,    7,    0,   11,    3,   12,    2,
- /*    20 */     4,   14,   14,   14,    6,    5,
+ /*     0 */     2,   12,   14,   15,    6,   16,    2,    9,   14,   15,
+ /*    10 */    14,   15,    1,    9,   13,   14,   13,   14,   13,   14,
+ /*    20 */    13,   14,   13,   14,    2,    2,    2,   12,   14,    7,
+ /*    30 */     7,    0,    2,    4,    8,    3,   17,   10,    5,   17,
+ /*    40 */    17,    7,    6,   17,   17,   10,    7,
 };
-#define YY_SHIFT_USE_DFLT (-1)
-#define YY_SHIFT_MAX 13
+#define YY_SHIFT_USE_DFLT (-3)
+#define YY_SHIFT_MAX 23
 static const signed char yy_shift_ofst[] = {
- /*     0 */     0,    8,    8,    8,    4,    0,    8,    7,   15,   17,
- /*    10 */    14,   16,   18,   20,
+ /*     0 */    11,   -2,    4,    4,   24,   24,   24,   24,   24,   11,
+ /*    10 */    24,   22,   23,   31,   30,   32,   29,   33,   26,   34,
+ /*    20 */    36,   39,   27,   35,
 };
-#define YY_REDUCE_USE_DFLT (-10)
-#define YY_REDUCE_MAX 7
+#define YY_REDUCE_USE_DFLT (-13)
+#define YY_REDUCE_MAX 10
 static const signed char yy_reduce_ofst[] = {
- /*     0 */    -9,   -8,   -5,   -3,    1,    3,    5,    6,
+ /*     0 */   -11,  -12,   -6,   -4,    1,    3,    5,    7,    9,   15,
+ /*    10 */    14,
 };
 static const YYACTIONTYPE yy_default[] = {
- /*     0 */    30,   30,   22,   23,   30,   20,   30,   28,   30,   30,
- /*    10 */    30,   30,   26,   30,   21,   24,   27,   25,   29,
+ /*     0 */    49,   49,   49,   49,   49,   35,   39,   36,   40,   33,
+ /*    10 */    49,   49,   49,   49,   49,   49,   49,   49,   43,   49,
+ /*    20 */    49,   49,   45,   46,   34,   37,   44,   41,   38,   42,
+ /*    30 */    48,   47,
 };
 #define YY_SZ_ACTTAB (int)(sizeof(yy_action)/sizeof(yy_action[0]))
 
@@ -274,10 +283,11 @@ void ParseTrace(FILE *TraceFILE, char *zTracePrompt){
 /* For tracing shifts, the names of all terminals and nonterminals
 ** are required.  The following table supplies these names */
 static const char *const yyTokenName[] = { 
-  "$",             "INTERFACE",     "NAME",          "CURRLY_OPEN", 
-  "CURLY_CLOSE",   "SEMICOLON",     "DOT",           "STRING",      
-  "error",         "interfaces",    "statements",    "statement_names",
-  "statement_args",  "input",       
+  "$",             "PROCESS",       "NAME",          "CURLY_OPEN",  
+  "CURLY_CLOSE",   "ROUND_OPEN",    "ROUND_CLOSE",   "SEMICOLON",   
+  "DOT",           "STRING",        "COMMA",         "error",       
+  "interfaces",    "statements",    "statement_names",  "statement_args",
+  "input",       
 };
 #endif /* NDEBUG */
 
@@ -286,16 +296,22 @@ static const char *const yyTokenName[] = {
 */
 static const char *const yyRuleName[] = {
  /*   0 */ "input ::= interfaces",
- /*   1 */ "interfaces ::= INTERFACE NAME CURRLY_OPEN statements CURLY_CLOSE",
- /*   2 */ "interfaces ::= INTERFACE NAME CURRLY_OPEN statements CURLY_CLOSE interfaces",
- /*   3 */ "statements ::= statement_names SEMICOLON",
- /*   4 */ "statements ::= statement_names statement_args SEMICOLON",
- /*   5 */ "statements ::= statement_names SEMICOLON statements",
- /*   6 */ "statements ::= statement_names statement_args SEMICOLON statements",
- /*   7 */ "statement_names ::= NAME",
- /*   8 */ "statement_names ::= NAME DOT statement_names",
- /*   9 */ "statement_args ::= STRING",
- /*  10 */ "statement_args ::= STRING statement_args",
+ /*   1 */ "interfaces ::= PROCESS NAME CURLY_OPEN statements CURLY_CLOSE",
+ /*   2 */ "interfaces ::= PROCESS NAME CURLY_OPEN statements CURLY_CLOSE interfaces",
+ /*   3 */ "statements ::= statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON",
+ /*   4 */ "statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON",
+ /*   5 */ "statements ::= statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON statements",
+ /*   6 */ "statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON statements",
+ /*   7 */ "statements ::= statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON",
+ /*   8 */ "statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON",
+ /*   9 */ "statements ::= statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON statements",
+ /*  10 */ "statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON statements",
+ /*  11 */ "statement_names ::= NAME",
+ /*  12 */ "statement_names ::= NAME DOT statement_names",
+ /*  13 */ "statement_args ::= STRING",
+ /*  14 */ "statement_args ::= statement_names",
+ /*  15 */ "statement_args ::= STRING COMMA statement_args",
+ /*  16 */ "statement_args ::= statement_names COMMA statement_args",
 };
 #endif /* NDEBUG */
 
@@ -375,39 +391,48 @@ static void yy_destructor(
     ** inside the C code.
     */
       /* TERMINAL Destructor */
-    case 1: /* INTERFACE */
+    case 1: /* PROCESS */
     case 2: /* NAME */
-    case 3: /* CURRLY_OPEN */
+    case 3: /* CURLY_OPEN */
     case 4: /* CURLY_CLOSE */
-    case 5: /* SEMICOLON */
-    case 6: /* DOT */
-    case 7: /* STRING */
+    case 5: /* ROUND_OPEN */
+    case 6: /* ROUND_CLOSE */
+    case 7: /* SEMICOLON */
+    case 8: /* DOT */
+    case 9: /* STRING */
+    case 10: /* COMMA */
 {
 #line 43 "NCDConfigParser_parse.y"
  free((yypminor->yy0)); 
-#line 389 "NCDConfigParser_parse.c"
+#line 408 "NCDConfigParser_parse.c"
 }
       break;
-    case 9: /* interfaces */
+    case 12: /* interfaces */
 {
 #line 50 "NCDConfigParser_parse.y"
- NCDConfig_free_interfaces((yypminor->yy10)); 
-#line 396 "NCDConfigParser_parse.c"
+ NCDConfig_free_interfaces((yypminor->yy32)); 
+#line 415 "NCDConfigParser_parse.c"
 }
       break;
-    case 10: /* statements */
+    case 13: /* statements */
 {
 #line 51 "NCDConfigParser_parse.y"
- NCDConfig_free_statements((yypminor->yy22)); 
-#line 403 "NCDConfigParser_parse.c"
+ NCDConfig_free_statements((yypminor->yy18)); 
+#line 422 "NCDConfigParser_parse.c"
 }
       break;
-    case 11: /* statement_names */
-    case 12: /* statement_args */
+    case 14: /* statement_names */
 {
 #line 52 "NCDConfigParser_parse.y"
- NCDConfig_free_strings((yypminor->yy24)); 
-#line 411 "NCDConfigParser_parse.c"
+ NCDConfig_free_strings((yypminor->yy10)); 
+#line 429 "NCDConfigParser_parse.c"
+}
+      break;
+    case 15: /* statement_args */
+{
+#line 53 "NCDConfigParser_parse.y"
+ NCDConfig_free_arguments((yypminor->yy34)); 
+#line 436 "NCDConfigParser_parse.c"
 }
       break;
     default:  break;   /* If no destructor action specified: do nothing */
@@ -636,17 +661,23 @@ static const struct {
   YYCODETYPE lhs;         /* Symbol on the left-hand side of the rule */
   unsigned char nrhs;     /* Number of right-hand side symbols in the rule */
 } yyRuleInfo[] = {
-  { 13, 1 },
-  { 9, 5 },
-  { 9, 6 },
-  { 10, 2 },
-  { 10, 3 },
-  { 10, 3 },
-  { 10, 4 },
-  { 11, 1 },
-  { 11, 3 },
-  { 12, 1 },
-  { 12, 2 },
+  { 16, 1 },
+  { 12, 5 },
+  { 12, 6 },
+  { 13, 4 },
+  { 13, 5 },
+  { 13, 5 },
+  { 13, 6 },
+  { 13, 5 },
+  { 13, 6 },
+  { 13, 6 },
+  { 13, 7 },
+  { 14, 1 },
+  { 14, 3 },
+  { 15, 1 },
+  { 15, 1 },
+  { 15, 3 },
+  { 15, 3 },
 };
 
 static void yy_accept(yyParser*);  /* Forward Declaration */
@@ -704,115 +735,206 @@ static void yy_reduce(
       case 0: /* input ::= interfaces */
 #line 59 "NCDConfigParser_parse.y"
 {
-    parser_out->ast = yymsp[0].minor.yy10;
+    parser_out->ast = yymsp[0].minor.yy32;
 
-    if (!yymsp[0].minor.yy10) {
+    if (!yymsp[0].minor.yy32) {
         parser_out->out_of_memory = 1;
     }
 }
-#line 714 "NCDConfigParser_parse.c"
+#line 745 "NCDConfigParser_parse.c"
         break;
-      case 1: /* interfaces ::= INTERFACE NAME CURRLY_OPEN statements CURLY_CLOSE */
+      case 1: /* interfaces ::= PROCESS NAME CURLY_OPEN statements CURLY_CLOSE */
 #line 67 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy10 = NCDConfig_make_interfaces(yymsp[-3].minor.yy0, yymsp[-1].minor.yy22, 0, NULL);
-    if (!yygotominor.yy10) {
+    yygotominor.yy32 = NCDConfig_make_interfaces(yymsp[-3].minor.yy0, yymsp[-1].minor.yy18, 0, NULL);
+    if (!yygotominor.yy32) {
         parser_out->out_of_memory = 1;
     }
   yy_destructor(yypParser,1,&yymsp[-4].minor);
   yy_destructor(yypParser,3,&yymsp[-2].minor);
   yy_destructor(yypParser,4,&yymsp[0].minor);
 }
-#line 727 "NCDConfigParser_parse.c"
+#line 758 "NCDConfigParser_parse.c"
         break;
-      case 2: /* interfaces ::= INTERFACE NAME CURRLY_OPEN statements CURLY_CLOSE interfaces */
+      case 2: /* interfaces ::= PROCESS NAME CURLY_OPEN statements CURLY_CLOSE interfaces */
 #line 74 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy10 = NCDConfig_make_interfaces(yymsp[-4].minor.yy0, yymsp[-2].minor.yy22, 1, yymsp[0].minor.yy10);
-    if (!yygotominor.yy10) {
+    yygotominor.yy32 = NCDConfig_make_interfaces(yymsp[-4].minor.yy0, yymsp[-2].minor.yy18, 1, yymsp[0].minor.yy32);
+    if (!yygotominor.yy32) {
         parser_out->out_of_memory = 1;
     }
   yy_destructor(yypParser,1,&yymsp[-5].minor);
   yy_destructor(yypParser,3,&yymsp[-3].minor);
   yy_destructor(yypParser,4,&yymsp[-1].minor);
 }
-#line 740 "NCDConfigParser_parse.c"
+#line 771 "NCDConfigParser_parse.c"
         break;
-      case 3: /* statements ::= statement_names SEMICOLON */
+      case 3: /* statements ::= statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON */
 #line 81 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy22 = NCDConfig_make_statements(yymsp[-1].minor.yy24, 0, NULL, 0, NULL);
-    if (!yygotominor.yy22) {
+    yygotominor.yy18 = NCDConfig_make_statements(yymsp[-3].minor.yy10, NULL, NULL, NULL);
+    if (!yygotominor.yy18) {
         parser_out->out_of_memory = 1;
     }
-  yy_destructor(yypParser,5,&yymsp[0].minor);
+  yy_destructor(yypParser,5,&yymsp[-2].minor);
+  yy_destructor(yypParser,6,&yymsp[-1].minor);
+  yy_destructor(yypParser,7,&yymsp[0].minor);
 }
-#line 751 "NCDConfigParser_parse.c"
+#line 784 "NCDConfigParser_parse.c"
         break;
-      case 4: /* statements ::= statement_names statement_args SEMICOLON */
+      case 4: /* statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON */
 #line 88 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy22 = NCDConfig_make_statements(yymsp[-2].minor.yy24, 1, yymsp[-1].minor.yy24, 0, NULL);
-    if (!yygotominor.yy22) {
+    yygotominor.yy18 = NCDConfig_make_statements(yymsp[-4].minor.yy10, yymsp[-2].minor.yy34, NULL, NULL);
+    if (!yygotominor.yy18) {
         parser_out->out_of_memory = 1;
     }
-  yy_destructor(yypParser,5,&yymsp[0].minor);
+  yy_destructor(yypParser,5,&yymsp[-3].minor);
+  yy_destructor(yypParser,6,&yymsp[-1].minor);
+  yy_destructor(yypParser,7,&yymsp[0].minor);
 }
-#line 762 "NCDConfigParser_parse.c"
+#line 797 "NCDConfigParser_parse.c"
         break;
-      case 5: /* statements ::= statement_names SEMICOLON statements */
+      case 5: /* statements ::= statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON statements */
 #line 95 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy22 = NCDConfig_make_statements(yymsp[-2].minor.yy24, 0, NULL, 1, yymsp[0].minor.yy22);
-    if (!yygotominor.yy22) {
+    yygotominor.yy18 = NCDConfig_make_statements(yymsp[-4].minor.yy10, NULL, NULL, yymsp[0].minor.yy18);
+    if (!yygotominor.yy18) {
         parser_out->out_of_memory = 1;
     }
-  yy_destructor(yypParser,5,&yymsp[-1].minor);
+  yy_destructor(yypParser,5,&yymsp[-3].minor);
+  yy_destructor(yypParser,6,&yymsp[-2].minor);
+  yy_destructor(yypParser,7,&yymsp[-1].minor);
 }
-#line 773 "NCDConfigParser_parse.c"
+#line 810 "NCDConfigParser_parse.c"
         break;
-      case 6: /* statements ::= statement_names statement_args SEMICOLON statements */
+      case 6: /* statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON statements */
 #line 102 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy22 = NCDConfig_make_statements(yymsp[-3].minor.yy24, 1, yymsp[-2].minor.yy24, 1, yymsp[0].minor.yy22);
-    if (!yygotominor.yy22) {
+    yygotominor.yy18 = NCDConfig_make_statements(yymsp[-5].minor.yy10, yymsp[-3].minor.yy34, NULL, yymsp[0].minor.yy18);
+    if (!yygotominor.yy18) {
         parser_out->out_of_memory = 1;
     }
-  yy_destructor(yypParser,5,&yymsp[-1].minor);
+  yy_destructor(yypParser,5,&yymsp[-4].minor);
+  yy_destructor(yypParser,6,&yymsp[-2].minor);
+  yy_destructor(yypParser,7,&yymsp[-1].minor);
 }
-#line 784 "NCDConfigParser_parse.c"
+#line 823 "NCDConfigParser_parse.c"
         break;
-      case 7: /* statement_names ::= NAME */
-      case 9: /* statement_args ::= STRING */ yytestcase(yyruleno==9);
+      case 7: /* statements ::= statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON */
 #line 109 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy24 = NCDConfig_make_strings(yymsp[0].minor.yy0, 0, NULL);
-    if (!yygotominor.yy24) {
+    yygotominor.yy18 = NCDConfig_make_statements(yymsp[-4].minor.yy10, NULL, yymsp[-1].minor.yy0, NULL);
+    if (!yygotominor.yy18) {
         parser_out->out_of_memory = 1;
     }
+  yy_destructor(yypParser,5,&yymsp[-3].minor);
+  yy_destructor(yypParser,6,&yymsp[-2].minor);
+  yy_destructor(yypParser,7,&yymsp[0].minor);
 }
-#line 795 "NCDConfigParser_parse.c"
+#line 836 "NCDConfigParser_parse.c"
         break;
-      case 8: /* statement_names ::= NAME DOT statement_names */
+      case 8: /* statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON */
 #line 116 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy24 = NCDConfig_make_strings(yymsp[-2].minor.yy0, 1, yymsp[0].minor.yy24);
-    if (!yygotominor.yy24) {
+    yygotominor.yy18 = NCDConfig_make_statements(yymsp[-5].minor.yy10, yymsp[-3].minor.yy34, yymsp[-1].minor.yy0, NULL);
+    if (!yygotominor.yy18) {
         parser_out->out_of_memory = 1;
     }
-  yy_destructor(yypParser,6,&yymsp[-1].minor);
+  yy_destructor(yypParser,5,&yymsp[-4].minor);
+  yy_destructor(yypParser,6,&yymsp[-2].minor);
+  yy_destructor(yypParser,7,&yymsp[0].minor);
+}
+#line 849 "NCDConfigParser_parse.c"
+        break;
+      case 9: /* statements ::= statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON statements */
+#line 123 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy18 = NCDConfig_make_statements(yymsp[-5].minor.yy10, NULL, yymsp[-2].minor.yy0, yymsp[0].minor.yy18);
+    if (!yygotominor.yy18) {
+        parser_out->out_of_memory = 1;
+    }
+  yy_destructor(yypParser,5,&yymsp[-4].minor);
+  yy_destructor(yypParser,6,&yymsp[-3].minor);
+  yy_destructor(yypParser,7,&yymsp[-1].minor);
 }
-#line 806 "NCDConfigParser_parse.c"
+#line 862 "NCDConfigParser_parse.c"
         break;
-      case 10: /* statement_args ::= STRING statement_args */
+      case 10: /* statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON statements */
 #line 130 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy24 = NCDConfig_make_strings(yymsp[-1].minor.yy0, 1, yymsp[0].minor.yy24);
-    if (!yygotominor.yy24) {
+    yygotominor.yy18 = NCDConfig_make_statements(yymsp[-6].minor.yy10, yymsp[-4].minor.yy34, yymsp[-2].minor.yy0, yymsp[0].minor.yy18);
+    if (!yygotominor.yy18) {
+        parser_out->out_of_memory = 1;
+    }
+  yy_destructor(yypParser,5,&yymsp[-5].minor);
+  yy_destructor(yypParser,6,&yymsp[-3].minor);
+  yy_destructor(yypParser,7,&yymsp[-1].minor);
+}
+#line 875 "NCDConfigParser_parse.c"
+        break;
+      case 11: /* statement_names ::= NAME */
+#line 137 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy10 = NCDConfig_make_strings(yymsp[0].minor.yy0, 0, NULL);
+    if (!yygotominor.yy10) {
+        parser_out->out_of_memory = 1;
+    }
+}
+#line 885 "NCDConfigParser_parse.c"
+        break;
+      case 12: /* statement_names ::= NAME DOT statement_names */
+#line 144 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy10 = NCDConfig_make_strings(yymsp[-2].minor.yy0, 1, yymsp[0].minor.yy10);
+    if (!yygotominor.yy10) {
+        parser_out->out_of_memory = 1;
+    }
+  yy_destructor(yypParser,8,&yymsp[-1].minor);
+}
+#line 896 "NCDConfigParser_parse.c"
+        break;
+      case 13: /* statement_args ::= STRING */
+#line 151 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy34 = NCDConfig_make_arguments_string(yymsp[0].minor.yy0, 0, NULL);
+    if (!yygotominor.yy34) {
+        parser_out->out_of_memory = 1;
+    }
+}
+#line 906 "NCDConfigParser_parse.c"
+        break;
+      case 14: /* statement_args ::= statement_names */
+#line 158 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy34 = NCDConfig_make_arguments_var(yymsp[0].minor.yy10, 0, NULL);
+    if (!yygotominor.yy34) {
+        parser_out->out_of_memory = 1;
+    }
+}
+#line 916 "NCDConfigParser_parse.c"
+        break;
+      case 15: /* statement_args ::= STRING COMMA statement_args */
+#line 165 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy34 = NCDConfig_make_arguments_string(yymsp[-2].minor.yy0, 1, yymsp[0].minor.yy34);
+    if (!yygotominor.yy34) {
+        parser_out->out_of_memory = 1;
+    }
+  yy_destructor(yypParser,10,&yymsp[-1].minor);
+}
+#line 927 "NCDConfigParser_parse.c"
+        break;
+      case 16: /* statement_args ::= statement_names COMMA statement_args */
+#line 172 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy34 = NCDConfig_make_arguments_var(yymsp[-2].minor.yy10, 1, yymsp[0].minor.yy34);
+    if (!yygotominor.yy34) {
         parser_out->out_of_memory = 1;
     }
+  yy_destructor(yypParser,10,&yymsp[-1].minor);
 }
-#line 816 "NCDConfigParser_parse.c"
+#line 938 "NCDConfigParser_parse.c"
         break;
       default:
         break;
@@ -877,7 +999,7 @@ static void yy_syntax_error(
 #line 55 "NCDConfigParser_parse.y"
 
     parser_out->syntax_error = 1;
-#line 881 "NCDConfigParser_parse.c"
+#line 1003 "NCDConfigParser_parse.c"
   ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
 }
 

+ 8 - 5
generated/NCDConfigParser_parse.h

@@ -1,7 +1,10 @@
-#define INTERFACE                       1
+#define PROCESS                         1
 #define NAME                            2
-#define CURRLY_OPEN                     3
+#define CURLY_OPEN                      3
 #define CURLY_CLOSE                     4
-#define SEMICOLON                       5
-#define DOT                             6
-#define STRING                          7
+#define ROUND_OPEN                      5
+#define ROUND_CLOSE                     6
+#define SEMICOLON                       7
+#define DOT                             8
+#define STRING                          9
+#define COMMA                          10

+ 251 - 97
generated/NCDConfigParser_parse.out

@@ -1,170 +1,324 @@
 State 0:
           input ::= * interfaces
-          interfaces ::= * INTERFACE NAME CURRLY_OPEN statements CURLY_CLOSE
-          interfaces ::= * INTERFACE NAME CURRLY_OPEN statements CURLY_CLOSE interfaces
+          interfaces ::= * PROCESS NAME CURLY_OPEN statements CURLY_CLOSE
+          interfaces ::= * PROCESS NAME CURLY_OPEN statements CURLY_CLOSE interfaces
 
-                     INTERFACE shift  9
-                    interfaces shift  8
+                       PROCESS shift  14
+                    interfaces shift  13
                          input accept
 
 State 1:
-          interfaces ::= INTERFACE NAME CURRLY_OPEN * statements CURLY_CLOSE
-          interfaces ::= INTERFACE NAME CURRLY_OPEN * statements CURLY_CLOSE interfaces
-          statements ::= * statement_names SEMICOLON
-          statements ::= * statement_names statement_args SEMICOLON
-          statements ::= * statement_names SEMICOLON statements
-          statements ::= * statement_names statement_args SEMICOLON statements
+          statements ::= statement_names ROUND_OPEN * ROUND_CLOSE SEMICOLON
+          statements ::= statement_names ROUND_OPEN * statement_args ROUND_CLOSE SEMICOLON
+          statements ::= statement_names ROUND_OPEN * ROUND_CLOSE SEMICOLON statements
+          statements ::= statement_names ROUND_OPEN * statement_args ROUND_CLOSE SEMICOLON statements
+          statements ::= statement_names ROUND_OPEN * ROUND_CLOSE NAME SEMICOLON
+          statements ::= statement_names ROUND_OPEN * statement_args ROUND_CLOSE NAME SEMICOLON
+          statements ::= statement_names ROUND_OPEN * ROUND_CLOSE NAME SEMICOLON statements
+          statements ::= statement_names ROUND_OPEN * statement_args ROUND_CLOSE NAME SEMICOLON statements
           statement_names ::= * NAME
           statement_names ::= * NAME DOT statement_names
+          statement_args ::= * STRING
+          statement_args ::= * statement_names
+          statement_args ::= * STRING COMMA statement_args
+          statement_args ::= * statement_names COMMA statement_args
 
-                          NAME shift  12
-                    statements shift  11
-               statement_names shift  4
+                          NAME shift  18
+                   ROUND_CLOSE shift  11
+                        STRING shift  22
+               statement_names shift  23
+                statement_args shift  20
 
 State 2:
-          statements ::= * statement_names SEMICOLON
-      (3) statements ::= statement_names SEMICOLON *
-          statements ::= * statement_names statement_args SEMICOLON
-          statements ::= * statement_names SEMICOLON statements
-          statements ::= statement_names SEMICOLON * statements
-          statements ::= * statement_names statement_args SEMICOLON statements
           statement_names ::= * NAME
           statement_names ::= * NAME DOT statement_names
+          statement_args ::= * STRING
+          statement_args ::= * statement_names
+          statement_args ::= * STRING COMMA statement_args
+          statement_args ::= STRING COMMA * statement_args
+          statement_args ::= * statement_names COMMA statement_args
 
-                          NAME shift  12
-                    statements shift  15
-               statement_names shift  4
-                     {default} reduce 3
+                          NAME shift  18
+                        STRING shift  22
+               statement_names shift  23
+                statement_args shift  31
 
 State 3:
-          statements ::= * statement_names SEMICOLON
-          statements ::= * statement_names statement_args SEMICOLON
-      (4) statements ::= statement_names statement_args SEMICOLON *
-          statements ::= * statement_names SEMICOLON statements
-          statements ::= * statement_names statement_args SEMICOLON statements
-          statements ::= statement_names statement_args SEMICOLON * statements
           statement_names ::= * NAME
           statement_names ::= * NAME DOT statement_names
+          statement_args ::= * STRING
+          statement_args ::= * statement_names
+          statement_args ::= * STRING COMMA statement_args
+          statement_args ::= * statement_names COMMA statement_args
+          statement_args ::= statement_names COMMA * statement_args
 
-                          NAME shift  12
-                    statements shift  17
-               statement_names shift  4
-                     {default} reduce 4
+                          NAME shift  18
+                        STRING shift  22
+               statement_names shift  23
+                statement_args shift  30
 
 State 4:
-          statements ::= statement_names * SEMICOLON
-          statements ::= statement_names * statement_args SEMICOLON
-          statements ::= statement_names * SEMICOLON statements
-          statements ::= statement_names * statement_args SEMICOLON statements
-          statement_args ::= * STRING
-          statement_args ::= * STRING statement_args
+          interfaces ::= PROCESS NAME CURLY_OPEN * statements CURLY_CLOSE
+          interfaces ::= PROCESS NAME CURLY_OPEN * statements CURLY_CLOSE interfaces
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON statements
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON statements
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON statements
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON statements
+          statement_names ::= * NAME
+          statement_names ::= * NAME DOT statement_names
 
-                     SEMICOLON shift  2
-                        STRING shift  7
-                statement_args shift  13
+                          NAME shift  18
+                    statements shift  16
+               statement_names shift  17
 
 State 5:
-          interfaces ::= * INTERFACE NAME CURRLY_OPEN statements CURLY_CLOSE
-      (1) interfaces ::= INTERFACE NAME CURRLY_OPEN statements CURLY_CLOSE *
-          interfaces ::= * INTERFACE NAME CURRLY_OPEN statements CURLY_CLOSE interfaces
-          interfaces ::= INTERFACE NAME CURRLY_OPEN statements CURLY_CLOSE * interfaces
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON
+      (3) statements ::= statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON *
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON statements
+          statements ::= statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON * statements
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON statements
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON statements
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON statements
+          statement_names ::= * NAME
+          statement_names ::= * NAME DOT statement_names
 
-                     INTERFACE shift  9
-                    interfaces shift  14
-                     {default} reduce 1
+                          NAME shift  18
+                    statements shift  25
+               statement_names shift  17
+                     {default} reduce 3
 
 State 6:
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON statements
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON statements
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON
+      (7) statements ::= statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON *
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON statements
+          statements ::= statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON * statements
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON statements
           statement_names ::= * NAME
           statement_names ::= * NAME DOT statement_names
-          statement_names ::= NAME DOT * statement_names
 
-                          NAME shift  12
-               statement_names shift  16
+                          NAME shift  18
+                    statements shift  27
+               statement_names shift  17
+                     {default} reduce 7
 
 State 7:
-          statement_args ::= * STRING
-      (9) statement_args ::= STRING *
-          statement_args ::= * STRING statement_args
-          statement_args ::= STRING * statement_args
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON
+      (4) statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON *
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON statements
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON statements
+          statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON * statements
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON statements
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON statements
+          statement_names ::= * NAME
+          statement_names ::= * NAME DOT statement_names
 
-                        STRING shift  7
-                statement_args shift  18
-                     {default} reduce 9
+                          NAME shift  18
+                    statements shift  28
+               statement_names shift  17
+                     {default} reduce 4
 
 State 8:
-      (0) input ::= interfaces *
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON statements
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON statements
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON
+      (8) statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON *
+          statements ::= * statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON statements
+          statements ::= * statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON statements
+          statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON * statements
+          statement_names ::= * NAME
+          statement_names ::= * NAME DOT statement_names
 
-                             $ reduce 0
+                          NAME shift  18
+                    statements shift  29
+               statement_names shift  17
+                     {default} reduce 8
 
 State 9:
-          interfaces ::= INTERFACE * NAME CURRLY_OPEN statements CURLY_CLOSE
-          interfaces ::= INTERFACE * NAME CURRLY_OPEN statements CURLY_CLOSE interfaces
+          interfaces ::= * PROCESS NAME CURLY_OPEN statements CURLY_CLOSE
+      (1) interfaces ::= PROCESS NAME CURLY_OPEN statements CURLY_CLOSE *
+          interfaces ::= * PROCESS NAME CURLY_OPEN statements CURLY_CLOSE interfaces
+          interfaces ::= PROCESS NAME CURLY_OPEN statements CURLY_CLOSE * interfaces
 
-                          NAME shift  10
+                       PROCESS shift  14
+                    interfaces shift  24
+                     {default} reduce 1
 
 State 10:
-          interfaces ::= INTERFACE NAME * CURRLY_OPEN statements CURLY_CLOSE
-          interfaces ::= INTERFACE NAME * CURRLY_OPEN statements CURLY_CLOSE interfaces
+          statement_names ::= * NAME
+          statement_names ::= * NAME DOT statement_names
+          statement_names ::= NAME DOT * statement_names
 
-                   CURRLY_OPEN shift  1
+                          NAME shift  18
+               statement_names shift  26
 
 State 11:
-          interfaces ::= INTERFACE NAME CURRLY_OPEN statements * CURLY_CLOSE
-          interfaces ::= INTERFACE NAME CURRLY_OPEN statements * CURLY_CLOSE interfaces
+          statements ::= statement_names ROUND_OPEN ROUND_CLOSE * SEMICOLON
+          statements ::= statement_names ROUND_OPEN ROUND_CLOSE * SEMICOLON statements
+          statements ::= statement_names ROUND_OPEN ROUND_CLOSE * NAME SEMICOLON
+          statements ::= statement_names ROUND_OPEN ROUND_CLOSE * NAME SEMICOLON statements
 
-                   CURLY_CLOSE shift  5
+                          NAME shift  19
+                     SEMICOLON shift  5
 
 State 12:
-      (7) statement_names ::= NAME *
-          statement_names ::= NAME * DOT statement_names
+          statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE * SEMICOLON
+          statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE * SEMICOLON statements
+          statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE * NAME SEMICOLON
+          statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE * NAME SEMICOLON statements
 
-                           DOT shift  6
-                     {default} reduce 7
+                          NAME shift  21
+                     SEMICOLON shift  7
 
 State 13:
-          statements ::= statement_names statement_args * SEMICOLON
-          statements ::= statement_names statement_args * SEMICOLON statements
+      (0) input ::= interfaces *
 
-                     SEMICOLON shift  3
+                             $ reduce 0
 
 State 14:
-      (2) interfaces ::= INTERFACE NAME CURRLY_OPEN statements CURLY_CLOSE interfaces *
+          interfaces ::= PROCESS * NAME CURLY_OPEN statements CURLY_CLOSE
+          interfaces ::= PROCESS * NAME CURLY_OPEN statements CURLY_CLOSE interfaces
 
-                     {default} reduce 2
+                          NAME shift  15
 
 State 15:
-      (5) statements ::= statement_names SEMICOLON statements *
+          interfaces ::= PROCESS NAME * CURLY_OPEN statements CURLY_CLOSE
+          interfaces ::= PROCESS NAME * CURLY_OPEN statements CURLY_CLOSE interfaces
 
-                     {default} reduce 5
+                    CURLY_OPEN shift  4
 
 State 16:
-      (8) statement_names ::= NAME DOT statement_names *
+          interfaces ::= PROCESS NAME CURLY_OPEN statements * CURLY_CLOSE
+          interfaces ::= PROCESS NAME CURLY_OPEN statements * CURLY_CLOSE interfaces
 
-                     {default} reduce 8
+                   CURLY_CLOSE shift  9
 
 State 17:
-      (6) statements ::= statement_names statement_args SEMICOLON statements *
+          statements ::= statement_names * ROUND_OPEN ROUND_CLOSE SEMICOLON
+          statements ::= statement_names * ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON
+          statements ::= statement_names * ROUND_OPEN ROUND_CLOSE SEMICOLON statements
+          statements ::= statement_names * ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON statements
+          statements ::= statement_names * ROUND_OPEN ROUND_CLOSE NAME SEMICOLON
+          statements ::= statement_names * ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON
+          statements ::= statement_names * ROUND_OPEN ROUND_CLOSE NAME SEMICOLON statements
+          statements ::= statement_names * ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON statements
 
-                     {default} reduce 6
+                    ROUND_OPEN shift  1
 
 State 18:
-     (10) statement_args ::= STRING statement_args *
+     (11) statement_names ::= NAME *
+          statement_names ::= NAME * DOT statement_names
+
+                           DOT shift  10
+                     {default} reduce 11
+
+State 19:
+          statements ::= statement_names ROUND_OPEN ROUND_CLOSE NAME * SEMICOLON
+          statements ::= statement_names ROUND_OPEN ROUND_CLOSE NAME * SEMICOLON statements
+
+                     SEMICOLON shift  6
+
+State 20:
+          statements ::= statement_names ROUND_OPEN statement_args * ROUND_CLOSE SEMICOLON
+          statements ::= statement_names ROUND_OPEN statement_args * ROUND_CLOSE SEMICOLON statements
+          statements ::= statement_names ROUND_OPEN statement_args * ROUND_CLOSE NAME SEMICOLON
+          statements ::= statement_names ROUND_OPEN statement_args * ROUND_CLOSE NAME SEMICOLON statements
+
+                   ROUND_CLOSE shift  12
+
+State 21:
+          statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME * SEMICOLON
+          statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME * SEMICOLON statements
+
+                     SEMICOLON shift  8
+
+State 22:
+     (13) statement_args ::= STRING *
+          statement_args ::= STRING * COMMA statement_args
+
+                         COMMA shift  2
+                     {default} reduce 13
+
+State 23:
+     (14) statement_args ::= statement_names *
+          statement_args ::= statement_names * COMMA statement_args
+
+                         COMMA shift  3
+                     {default} reduce 14
+
+State 24:
+      (2) interfaces ::= PROCESS NAME CURLY_OPEN statements CURLY_CLOSE interfaces *
+
+                     {default} reduce 2
+
+State 25:
+      (5) statements ::= statement_names ROUND_OPEN ROUND_CLOSE SEMICOLON statements *
+
+                     {default} reduce 5
+
+State 26:
+     (12) statement_names ::= NAME DOT statement_names *
+
+                     {default} reduce 12
+
+State 27:
+      (9) statements ::= statement_names ROUND_OPEN ROUND_CLOSE NAME SEMICOLON statements *
+
+                     {default} reduce 9
+
+State 28:
+      (6) statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE SEMICOLON statements *
+
+                     {default} reduce 6
+
+State 29:
+     (10) statements ::= statement_names ROUND_OPEN statement_args ROUND_CLOSE NAME SEMICOLON statements *
 
                      {default} reduce 10
 
+State 30:
+     (16) statement_args ::= statement_names COMMA statement_args *
+
+                     {default} reduce 16
+
+State 31:
+     (15) statement_args ::= STRING COMMA statement_args *
+
+                     {default} reduce 15
+
 ----------------------------------------------------
 Symbols:
     0: $:
-    1: INTERFACE
+    1: PROCESS
     2: NAME
-    3: CURRLY_OPEN
+    3: CURLY_OPEN
     4: CURLY_CLOSE
-    5: SEMICOLON
-    6: DOT
-    7: STRING
-    8: error:
-    9: interfaces: INTERFACE
-   10: statements: NAME
-   11: statement_names: NAME
-   12: statement_args: STRING
-   13: input: INTERFACE
+    5: ROUND_OPEN
+    6: ROUND_CLOSE
+    7: SEMICOLON
+    8: DOT
+    9: STRING
+   10: COMMA
+   11: error:
+   12: interfaces: PROCESS
+   13: statements: NAME
+   14: statement_names: NAME
+   15: statement_args: NAME STRING
+   16: input: PROCESS

+ 57 - 15
generated/NCDConfigParser_parse.y

@@ -45,12 +45,12 @@ struct parser_out {
 %type interfaces {struct NCDConfig_interfaces *}
 %type statements {struct NCDConfig_statements *}
 %type statement_names {struct NCDConfig_strings *}
-%type statement_args {struct NCDConfig_strings *}
+%type statement_args {struct NCDConfig_arguments *}
 
 %destructor interfaces { NCDConfig_free_interfaces($$); }
 %destructor statements { NCDConfig_free_statements($$); }
 %destructor statement_names { NCDConfig_free_strings($$); }
-%destructor statement_args { NCDConfig_free_strings($$); }
+%destructor statement_args { NCDConfig_free_arguments($$); }
 
 %syntax_error {
     parser_out->syntax_error = 1;
@@ -64,43 +64,71 @@ input ::= interfaces(A). {
     }
 }
 
-interfaces(R) ::= INTERFACE NAME(A) CURRLY_OPEN statements(B) CURLY_CLOSE. {
+interfaces(R) ::= PROCESS NAME(A) CURLY_OPEN statements(B) CURLY_CLOSE. {
     R = NCDConfig_make_interfaces(A, B, 0, NULL);
     if (!R) {
         parser_out->out_of_memory = 1;
     }
 }
 
-interfaces(R) ::= INTERFACE NAME(A) CURRLY_OPEN statements(B) CURLY_CLOSE interfaces(N). {
+interfaces(R) ::= PROCESS NAME(A) CURLY_OPEN statements(B) CURLY_CLOSE interfaces(N). {
     R = NCDConfig_make_interfaces(A, B, 1, N);
     if (!R) {
         parser_out->out_of_memory = 1;
     }
 }
 
-statements(R) ::= statement_names(A) SEMICOLON. {
-    R = NCDConfig_make_statements(A, 0, NULL, 0, NULL);
+statements(R) ::= statement_names(A) ROUND_OPEN ROUND_CLOSE SEMICOLON. {
+    R = NCDConfig_make_statements(A, NULL, NULL, NULL);
     if (!R) {
         parser_out->out_of_memory = 1;
     }
 }
 
-statements(R) ::= statement_names(A) statement_args(B) SEMICOLON. {
-    R = NCDConfig_make_statements(A, 1, B, 0, NULL);
+statements(R) ::= statement_names(A) ROUND_OPEN statement_args(B) ROUND_CLOSE SEMICOLON. {
+    R = NCDConfig_make_statements(A, B, NULL, NULL);
     if (!R) {
         parser_out->out_of_memory = 1;
     }
 }
 
-statements(R) ::= statement_names(A) SEMICOLON statements(N). {
-    R = NCDConfig_make_statements(A, 0, NULL, 1, N);
+statements(R) ::= statement_names(A) ROUND_OPEN ROUND_CLOSE SEMICOLON statements(N). {
+    R = NCDConfig_make_statements(A, NULL, NULL, N);
     if (!R) {
         parser_out->out_of_memory = 1;
     }
 }
 
-statements(R) ::= statement_names(A) statement_args(B) SEMICOLON statements(N). {
-    R = NCDConfig_make_statements(A, 1, B, 1, N);
+statements(R) ::= statement_names(A) ROUND_OPEN statement_args(B) ROUND_CLOSE SEMICOLON statements(N). {
+    R = NCDConfig_make_statements(A, B, NULL, N);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}
+
+statements(R) ::= statement_names(A) ROUND_OPEN ROUND_CLOSE NAME(C) SEMICOLON. {
+    R = NCDConfig_make_statements(A, NULL, C, NULL);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}
+
+statements(R) ::= statement_names(A) ROUND_OPEN statement_args(B) ROUND_CLOSE NAME(C) SEMICOLON. {
+    R = NCDConfig_make_statements(A, B, C, NULL);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}
+
+statements(R) ::= statement_names(A) ROUND_OPEN ROUND_CLOSE NAME(C) SEMICOLON statements(N). {
+    R = NCDConfig_make_statements(A, NULL, C, N);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}
+
+statements(R) ::= statement_names(A) ROUND_OPEN statement_args(B) ROUND_CLOSE NAME(C) SEMICOLON statements(N). {
+    R = NCDConfig_make_statements(A, B, C, N);
     if (!R) {
         parser_out->out_of_memory = 1;
     }
@@ -121,14 +149,28 @@ statement_names(R) ::= NAME(A) DOT statement_names(N). {
 }
 
 statement_args(R) ::= STRING(A). {
-    R = NCDConfig_make_strings(A, 0, NULL);
+    R = NCDConfig_make_arguments_string(A, 0, NULL);
     if (!R) {
         parser_out->out_of_memory = 1;
     }
 }
 
-statement_args(R) ::= STRING(A) statement_args(N). {
-    R = NCDConfig_make_strings(A, 1, N);
+statement_args(R) ::= statement_names(A). {
+    R = NCDConfig_make_arguments_var(A, 0, NULL);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}
+
+statement_args(R) ::= STRING(A) COMMA statement_args(N). {
+    R = NCDConfig_make_arguments_string(A, 1, N);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}
+
+statement_args(R) ::= statement_names(A) COMMA statement_args(N). {
+    R = NCDConfig_make_arguments_var(A, 1, N);
     if (!R) {
         parser_out->out_of_memory = 1;
     }

+ 4 - 0
generated/blog_channel_ncd_list.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_list

+ 4 - 0
generated/blog_channel_ncd_net_backend_badvpn.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_backend_badvpn

+ 4 - 0
generated/blog_channel_ncd_net_backend_physical.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_backend_physical

+ 4 - 0
generated/blog_channel_ncd_net_dns.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_dns

+ 4 - 0
generated/blog_channel_ncd_net_ipv4_addr.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_ipv4_addr

+ 4 - 0
generated/blog_channel_ncd_net_ipv4_dhcp.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_ipv4_dhcp

+ 4 - 0
generated/blog_channel_ncd_net_ipv4_route.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_ipv4_route

+ 4 - 0
generated/blog_channel_ncd_var.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_var

+ 25 - 17
generated/blog_channels_defines.h

@@ -3,20 +3,28 @@
 #define BLOG_CHANNEL_flooder 2
 #define BLOG_CHANNEL_tun2socks 3
 #define BLOG_CHANNEL_ncd 4
-#define BLOG_CHANNEL_StreamPeerIO 5
-#define BLOG_CHANNEL_DatagramPeerIO 6
-#define BLOG_CHANNEL_BReactor 7
-#define BLOG_CHANNEL_BSignal 8
-#define BLOG_CHANNEL_FragmentProtoAssembler 9
-#define BLOG_CHANNEL_BPredicate 10
-#define BLOG_CHANNEL_ServerConnection 11
-#define BLOG_CHANNEL_Listener 12
-#define BLOG_CHANNEL_DataProto 13
-#define BLOG_CHANNEL_FrameDecider 14
-#define BLOG_CHANNEL_BSocksClient 15
-#define BLOG_CHANNEL_BDHCPClientCore 16
-#define BLOG_CHANNEL_BDHCPClient 17
-#define BLOG_CHANNEL_NCDIfConfig 18
-#define BLOG_CHANNEL_BUnixSignal 19
-#define BLOG_CHANNEL_BProcess 20
-#define BLOG_NUM_CHANNELS 21
+#define BLOG_CHANNEL_ncd_var 5
+#define BLOG_CHANNEL_ncd_list 6
+#define BLOG_CHANNEL_ncd_net_backend_physical 7
+#define BLOG_CHANNEL_ncd_net_backend_badvpn 8
+#define BLOG_CHANNEL_ncd_net_dns 9
+#define BLOG_CHANNEL_ncd_net_ipv4_addr 10
+#define BLOG_CHANNEL_ncd_net_ipv4_dhcp 11
+#define BLOG_CHANNEL_ncd_net_ipv4_route 12
+#define BLOG_CHANNEL_StreamPeerIO 13
+#define BLOG_CHANNEL_DatagramPeerIO 14
+#define BLOG_CHANNEL_BReactor 15
+#define BLOG_CHANNEL_BSignal 16
+#define BLOG_CHANNEL_FragmentProtoAssembler 17
+#define BLOG_CHANNEL_BPredicate 18
+#define BLOG_CHANNEL_ServerConnection 19
+#define BLOG_CHANNEL_Listener 20
+#define BLOG_CHANNEL_DataProto 21
+#define BLOG_CHANNEL_FrameDecider 22
+#define BLOG_CHANNEL_BSocksClient 23
+#define BLOG_CHANNEL_BDHCPClientCore 24
+#define BLOG_CHANNEL_BDHCPClient 25
+#define BLOG_CHANNEL_NCDIfConfig 26
+#define BLOG_CHANNEL_BUnixSignal 27
+#define BLOG_CHANNEL_BProcess 28
+#define BLOG_NUM_CHANNELS 29

+ 8 - 0
generated/blog_channels_list.h

@@ -3,6 +3,14 @@
 {.name = "flooder", .loglevel = 4},
 {.name = "tun2socks", .loglevel = 4},
 {.name = "ncd", .loglevel = 4},
+{.name = "ncd_var", .loglevel = 4},
+{.name = "ncd_list", .loglevel = 4},
+{.name = "ncd_net_backend_physical", .loglevel = 4},
+{.name = "ncd_net_backend_badvpn", .loglevel = 4},
+{.name = "ncd_net_dns", .loglevel = 4},
+{.name = "ncd_net_ipv4_addr", .loglevel = 4},
+{.name = "ncd_net_ipv4_dhcp", .loglevel = 4},
+{.name = "ncd_net_ipv4_route", .loglevel = 4},
 {.name = "StreamPeerIO", .loglevel = 4},
 {.name = "DatagramPeerIO", .loglevel = 4},
 {.name = "BReactor", .loglevel = 3},

+ 21 - 3
misc/ipaddr.h

@@ -38,7 +38,9 @@ struct ipv4_ifaddr {
     int prefix;
 };
 
-static int ipaddr_parse_ipv4_addr (char *name, size_t name_len, uint32_t *out_addr);
+static int ipaddr_parse_ipv4_addr_bin (char *name, size_t name_len, uint32_t *out_addr);
+static int ipaddr_parse_ipv4_addr (char *name, uint32_t *out_addr);
+static int ipaddr_parse_ipv4_prefix (char *str, int *num);
 static int ipaddr_parse_ipv4_ifaddr (char *str, struct ipv4_ifaddr *out);
 static int ipaddr_ipv4_ifaddr_from_addr_mask (uint32_t addr, uint32_t mask, struct ipv4_ifaddr *out);
 
@@ -68,7 +70,7 @@ static int ipaddr_parse_number (char *data, size_t data_len, int *out)
     return 1;
 }
 
-int ipaddr_parse_ipv4_addr (char *name, size_t name_len, uint32_t *out_addr)
+int ipaddr_parse_ipv4_addr_bin (char *name, size_t name_len, uint32_t *out_addr)
 {
     for (size_t i = 0; ; i++) {
         size_t j;
@@ -106,6 +108,22 @@ int ipaddr_parse_ipv4_addr (char *name, size_t name_len, uint32_t *out_addr)
     }
 }
 
+int ipaddr_parse_ipv4_addr (char *name, uint32_t *out_addr)
+{
+    return ipaddr_parse_ipv4_addr_bin(name, strlen(name), out_addr);
+}
+
+int ipaddr_parse_ipv4_prefix (char *str, int *num)
+{
+    if (strlen(str) > 2 || !ipaddr_parse_number(str, strlen(str), num)) {
+        return 0;
+    }
+    if (*num > 32) {
+        return 0;
+    }
+    return 1;
+}
+
 int ipaddr_parse_ipv4_ifaddr (char *str, struct ipv4_ifaddr *out)
 {
     char *slash = strstr(str, "/");
@@ -113,7 +131,7 @@ int ipaddr_parse_ipv4_ifaddr (char *str, struct ipv4_ifaddr *out)
         return 0;
     }
     
-    if (!ipaddr_parse_ipv4_addr(str, (slash - str), &out->addr)) {
+    if (!ipaddr_parse_ipv4_addr_bin(str, (slash - str), &out->addr)) {
         return 0;
     }
     

+ 10 - 3
ncd/CMakeLists.txt

@@ -1,10 +1,17 @@
 add_executable(badvpn-ncd
     ncd.c
+    NCDValue.c
+    NCDModule.c
     NCDIfConfig.c
     NCDInterfaceMonitor.c
-    NCDInterfaceModule.c
-    interface_modules/interface_physical.c
-    interface_modules/interface_badvpn.c
+    modules/var.c
+    modules/list.c
+    modules/net_backend_physical.c
+    modules/net_backend_badvpn.c
+    modules/net_dns.c
+    modules/net_ipv4_addr.c
+    modules/net_ipv4_route.c
+    modules/net_ipv4_dhcp.c
 )
 target_link_libraries(badvpn-ncd system dhcpclient ncdconfig)
 

+ 0 - 152
ncd/NCDInterfaceModule.c

@@ -1,152 +0,0 @@
-/**
- * @file NCDInterfaceModule.c
- * @author Ambroz Bizjak <ambrop7@gmail.com>
- * 
- * @section LICENSE
- * 
- * This file is part of BadVPN.
- * 
- * BadVPN is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2
- * as published by the Free Software Foundation.
- * 
- * BadVPN 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 this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <ncd/NCDInterfaceModule.h>
-
-static void event_job_handler (NCDInterfaceModuleInst *n)
-{
-    DebugObject_Access(&n->d_obj);
-    
-    n->handler_event(n->user, (n->up ? NCDINTERFACEMODULE_EVENT_UP : NCDINTERFACEMODULE_EVENT_DOWN));
-    return;
-}
-
-static void finish_job_handler (NCDInterfaceModuleInst *n)
-{
-    ASSERT(n->finishing)
-    DebugObject_Access(&n->d_obj);
-    
-    n->m->func_finish(n->inst_user);
-    return;
-}
-
-int NCDInterfaceModuleInst_Init (
-    NCDInterfaceModuleInst *n, const struct NCDInterfaceModule *m, BReactor *reactor, BProcessManager *manager,
-    struct NCDConfig_interfaces *conf, NCDInterfaceModule_handler_event handler_event,
-    NCDInterfaceModule_handler_error handler_error,
-    void *user
-)
-{
-    // init arguments
-    n->m = m;
-    n->reactor = reactor;
-    n->manager = manager;
-    n->conf = conf;
-    n->handler_event = handler_event;
-    n->handler_error = handler_error;
-    n->user = user;
-    
-    // init event job
-    BPending_Init(&n->event_job, BReactor_PendingGroup(n->reactor), (BPending_handler)event_job_handler, n);
-    
-    // init finish job
-    BPending_Init(&n->finish_job, BReactor_PendingGroup(n->reactor), (BPending_handler)finish_job_handler, n);
-    
-    // set not up
-    n->up = 0;
-    
-    // set not finishing
-    n->finishing = 0;
-    
-    // init backend
-    if (!(n->inst_user = n->m->func_new(n))) {
-        goto fail1;
-    }
-    
-    DebugObject_Init(&n->d_obj);
-    #ifndef NDEBUG
-    DEAD_INIT(n->d_dead);
-    #endif
-    
-    return 1;
-    
-fail1:
-    BPending_Free(&n->finish_job);
-    BPending_Free(&n->event_job);
-    return 0;
-}
-
-void NCDInterfaceModuleInst_Free (NCDInterfaceModuleInst *n)
-{
-    DebugObject_Free(&n->d_obj);
-    #ifndef NDEBUG
-    DEAD_KILL(n->d_dead);
-    #endif
-    
-    // free backend
-    n->m->func_free(n->inst_user);
-    
-    // free finish job
-    BPending_Free(&n->finish_job);
-    
-    // free event job
-    BPending_Free(&n->event_job);
-}
-
-void NCDInterfaceModuleInst_Finish (NCDInterfaceModuleInst *n)
-{
-    ASSERT(!n->finishing)
-    DebugObject_Access(&n->d_obj);
-    
-    // set finishing
-    n->finishing = 1;
-    
-    // set job
-    BPending_Set(&n->finish_job);
-}
-
-void NCDInterfaceModuleInst_Backend_Event (NCDInterfaceModuleInst *n, int event)
-{
-    ASSERT(event == NCDINTERFACEMODULE_EVENT_UP || event == NCDINTERFACEMODULE_EVENT_DOWN)
-    ASSERT((event == NCDINTERFACEMODULE_EVENT_UP) == !n->up)
-    ASSERT(!BPending_IsSet(&n->event_job))
-    ASSERT(!n->finishing)
-    
-    // change up state
-    n->up = !n->up;
-    
-    // set job
-    BPending_Set(&n->event_job);
-}
-
-void NCDInterfaceModuleInst_Backend_Error (NCDInterfaceModuleInst *n)
-{
-    #ifndef NDEBUG
-    DEAD_ENTER(n->d_dead)
-    #endif
-    
-    n->handler_error(n->user);
-    
-    #ifndef NDEBUG
-    ASSERT(DEAD_KILLED)
-    DEAD_LEAVE(n->d_dead);
-    #endif
-}
-
-void NCDInterfaceModuleInst_Backend_Log (NCDInterfaceModuleInst *n, int level, const char *fmt, ...)
-{
-    va_list vl;
-    va_start(vl, fmt);
-    BLog_Append("interface %s: module: ", n->conf->name);
-    BLog_LogToChannelVarArg(BLOG_CURRENT_CHANNEL, level, fmt, vl);
-    va_end(vl);
-}

+ 0 - 86
ncd/NCDInterfaceModule.h

@@ -1,86 +0,0 @@
-/**
- * @file NCDInterfaceModule.h
- * @author Ambroz Bizjak <ambrop7@gmail.com>
- * 
- * @section LICENSE
- * 
- * This file is part of BadVPN.
- * 
- * BadVPN is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2
- * as published by the Free Software Foundation.
- * 
- * BadVPN 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 this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef BADVPN_NCD_NCDINTERFACEMODULE_H
-#define BADVPN_NCD_NCDINTERFACEMODULE_H
-
-#include <stdarg.h>
-
-#include <system/BReactor.h>
-#include <system/BLog.h>
-#include <system/BProcess.h>
-#include <system/BPending.h>
-#include <ncdconfig/NCDConfig.h>
-
-#include <generated/blog_channel_ncd.h>
-
-#define NCDINTERFACEMODULE_EVENT_UP 1
-#define NCDINTERFACEMODULE_EVENT_DOWN 2
-
-typedef void (*NCDInterfaceModule_handler_event) (void *user, int event);
-typedef void (*NCDInterfaceModule_handler_error) (void *user);
-
-struct NCDInterfaceModule;
-
-typedef struct {
-    const struct NCDInterfaceModule *m;
-    BReactor *reactor;
-    BProcessManager *manager;
-    struct NCDConfig_interfaces *conf;
-    NCDInterfaceModule_handler_event handler_event;
-    NCDInterfaceModule_handler_error handler_error;
-    void *user;
-    BPending event_job;
-    BPending finish_job;
-    int up;
-    int finishing;
-    void *inst_user;
-    DebugObject d_obj;
-    #ifndef NDEBUG
-    dead_t d_dead;
-    #endif
-} NCDInterfaceModuleInst;
-
-int NCDInterfaceModuleInst_Init (
-    NCDInterfaceModuleInst *n, const struct NCDInterfaceModule *m, BReactor *reactor, BProcessManager *manager,
-    struct NCDConfig_interfaces *conf, NCDInterfaceModule_handler_event handler_event,
-    NCDInterfaceModule_handler_error handler_error,
-    void *user
-);
-void NCDInterfaceModuleInst_Free (NCDInterfaceModuleInst *n);
-void NCDInterfaceModuleInst_Finish (NCDInterfaceModuleInst *n);
-void NCDInterfaceModuleInst_Backend_Event (NCDInterfaceModuleInst *n, int event);
-void NCDInterfaceModuleInst_Backend_Error (NCDInterfaceModuleInst *n);
-void NCDInterfaceModuleInst_Backend_Log (NCDInterfaceModuleInst *n, int level, const char *fmt, ...);
-
-typedef void * (*NCDInterfaceModule_func_new) (NCDInterfaceModuleInst *params);
-typedef void (*NCDInterfaceModule_func_free) (void *o);
-typedef void (*NCDInterfaceModule_func_finish) (void *o);
-
-struct NCDInterfaceModule {
-    const char *type;
-    NCDInterfaceModule_func_new func_new;
-    NCDInterfaceModule_func_free func_free;
-    NCDInterfaceModule_func_finish func_finish;
-};
-
-#endif

+ 184 - 0
ncd/NCDModule.c

@@ -0,0 +1,184 @@
+/**
+ * @file NCDModule.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <ncd/NCDModule.h>
+
+#define STATE_DOWN 1
+#define STATE_UP 2
+#define STATE_DYING 3
+
+static void event_job_handler (NCDModuleInst *n)
+{
+    DebugObject_Access(&n->d_obj);
+    
+    n->handler_event(n->user, n->event_job_event);
+    return;
+}
+
+static void die_job_handler (NCDModuleInst *n)
+{
+    DebugObject_Access(&n->d_obj);
+    
+    if (!n->m->func_die) {
+        NCDModuleInst_Backend_Died(n, 0);
+        return;
+    }
+    
+    n->m->func_die(n->inst_user);
+    return;
+}
+
+int NCDModuleInst_Init (NCDModuleInst *n, const char *name, const struct NCDModule *m, NCDValue *args, const char *logprefix, BReactor *reactor, BProcessManager *manager,
+                        NCDModule_handler_event handler_event, NCDModule_handler_died handler_died, void *user)
+{
+    // init arguments
+    n->name = name;
+    n->m = m;
+    n->args = args;
+    n->logprefix = logprefix;
+    n->reactor = reactor;
+    n->manager = manager;
+    n->handler_event = handler_event;
+    n->handler_died = handler_died;
+    n->user = user;
+    
+    // init event job
+    BPending_Init(&n->event_job, BReactor_PendingGroup(n->reactor), (BPending_handler)event_job_handler, n);
+    
+    // init die job
+    BPending_Init(&n->die_job, BReactor_PendingGroup(n->reactor), (BPending_handler)die_job_handler, n);
+    
+    // set state
+    n->state = STATE_DOWN;
+    
+    // init backend
+    if (!(n->inst_user = n->m->func_new(n))) {
+        goto fail1;
+    }
+    
+    DebugObject_Init(&n->d_obj);
+    #ifndef NDEBUG
+    DEAD_INIT(n->d_dead);
+    #endif
+    
+    return 1;
+    
+fail1:
+    BPending_Free(&n->die_job);
+    BPending_Free(&n->event_job);
+    return 0;
+}
+
+void NCDModuleInst_Free (NCDModuleInst *n)
+{
+    #ifndef NDEBUG
+    DEAD_KILL(n->d_dead);
+    #endif
+    DebugObject_Free(&n->d_obj);
+    
+    // free backend
+    n->m->func_free(n->inst_user);
+    
+    // free die job
+    BPending_Free(&n->die_job);
+    
+    // free event job
+    BPending_Free(&n->event_job);
+}
+
+void NCDModuleInst_Die (NCDModuleInst *n)
+{
+    ASSERT(n->state == STATE_UP || n->state == STATE_DOWN)
+    DebugObject_Access(&n->d_obj);
+    
+    // set state
+    n->state = STATE_DYING;
+    
+    // set job
+    BPending_Set(&n->die_job);
+}
+
+int NCDModuleInst_GetVar (NCDModuleInst *n, const char *name, NCDValue *out)
+{
+    ASSERT(n->state == STATE_UP)
+    DebugObject_Access(&n->d_obj);
+    
+    if (!n->m->func_getvar) {
+        return 0;
+    }
+    
+    return n->m->func_getvar(n->inst_user, name, out);
+}
+
+void NCDModuleInst_Backend_Event (NCDModuleInst *n, int event)
+{
+    ASSERT(event == NCDMODULE_EVENT_UP || event == NCDMODULE_EVENT_DOWN || event == NCDMODULE_EVENT_DYING)
+    ASSERT(!(event == NCDMODULE_EVENT_UP) || n->state == STATE_DOWN)
+    ASSERT(!(event == NCDMODULE_EVENT_DOWN) || n->state == STATE_UP)
+    ASSERT(!(event == NCDMODULE_EVENT_DYING) || (n->state == STATE_DOWN || n->state == STATE_UP))
+    ASSERT(!BPending_IsSet(&n->event_job))
+    
+    switch (event) {
+        case NCDMODULE_EVENT_UP:
+            n->state = STATE_UP;
+            break;
+        case NCDMODULE_EVENT_DOWN:
+            n->state = STATE_DOWN;
+            break;
+        case NCDMODULE_EVENT_DYING:
+            n->state = STATE_DYING;
+            break;
+        default:
+            ASSERT(0);
+    }
+    
+    // remember event
+    n->event_job_event = event;
+    
+    // set job
+    BPending_Set(&n->event_job);
+}
+
+void NCDModuleInst_Backend_Died (NCDModuleInst *n, int is_error)
+{
+    ASSERT(is_error == 0 || is_error == 1)
+    
+    #ifndef NDEBUG
+    DEAD_ENTER(n->d_dead)
+    #endif
+    
+    n->handler_died(n->user, is_error);
+    
+    #ifndef NDEBUG
+    ASSERT(DEAD_KILLED)
+    DEAD_LEAVE(n->d_dead);
+    #endif
+}
+
+void NCDModuleInst_Backend_Log (NCDModuleInst *n, int channel, int level, const char *fmt, ...)
+{
+    va_list vl;
+    va_start(vl, fmt);
+    BLog_Append("%s", n->logprefix);
+    BLog_LogToChannelVarArg(channel, level, fmt, vl);
+    va_end(vl);
+}

+ 89 - 0
ncd/NCDModule.h

@@ -0,0 +1,89 @@
+/**
+ * @file NCDModule.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef BADVPN_NCD_NCDMODULE_H
+#define BADVPN_NCD_NCDMODULE_H
+
+#include <stdarg.h>
+
+#include <system/BReactor.h>
+#include <system/BProcess.h>
+#include <system/BPending.h>
+#include <system/BLog.h>
+#include <ncdconfig/NCDConfig.h>
+#include <ncd/NCDValue.h>
+
+#define NCDMODULE_EVENT_UP 1
+#define NCDMODULE_EVENT_DOWN 2
+#define NCDMODULE_EVENT_DYING 3
+
+typedef void (*NCDModule_handler_event) (void *user, int event);
+typedef void (*NCDModule_handler_died) (void *user, int is_error);
+
+struct NCDModule;
+
+typedef struct {
+    const char *name;
+    const struct NCDModule *m;
+    NCDValue *args;
+    const char *logprefix;
+    BReactor *reactor;
+    BProcessManager *manager;
+    NCDModule_handler_event handler_event;
+    NCDModule_handler_died handler_died;
+    void *user;
+    BPending event_job;
+    int event_job_event;
+    BPending die_job;
+    int state;
+    void *inst_user;
+    DebugObject d_obj;
+    #ifndef NDEBUG
+    dead_t d_dead;
+    #endif
+} NCDModuleInst;
+
+int NCDModuleInst_Init (NCDModuleInst *n, const char *name, const struct NCDModule *m, NCDValue *args, const char *logprefix, BReactor *reactor, BProcessManager *manager,
+                        NCDModule_handler_event handler_event, NCDModule_handler_died handler_died, void *user);
+void NCDModuleInst_Free (NCDModuleInst *n);
+void NCDModuleInst_Die (NCDModuleInst *n);
+int NCDModuleInst_GetVar (NCDModuleInst *n, const char *name, NCDValue *out);
+void NCDModuleInst_Backend_Event (NCDModuleInst *n, int event);
+void NCDModuleInst_Backend_Died (NCDModuleInst *n, int is_error);
+void NCDModuleInst_Backend_Log (NCDModuleInst *n, int channel, int level, const char *fmt, ...);
+
+typedef int (*NCDModule_func_globalinit) (void);
+typedef void * (*NCDModule_func_new) (NCDModuleInst *params);
+typedef void (*NCDModule_func_free) (void *o);
+typedef void (*NCDModule_func_die) (void *o);
+typedef int (*NCDModule_func_getvar) (void *o, const char *name, NCDValue *out);
+
+struct NCDModule {
+    const char *type;
+    NCDModule_func_globalinit func_globalinit;
+    NCDModule_func_new func_new;
+    NCDModule_func_free func_free;
+    NCDModule_func_die func_die;
+    NCDModule_func_getvar func_getvar;
+};
+
+#endif

+ 243 - 0
ncd/NCDValue.c

@@ -0,0 +1,243 @@
+/**
+ * @file NCDValue.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+
+#include <ncd/NCDValue.h>
+
+static void value_assert (NCDValue *o)
+{
+    switch (o->type) {
+        case NCDVALUE_STRING:
+        case NCDVALUE_LIST:
+            return;
+        default:
+            ASSERT(0);
+    }
+}
+
+int NCDValue_InitCopy (NCDValue *o, NCDValue *v)
+{
+    value_assert(v);
+    
+    switch (v->type) {
+        case NCDVALUE_STRING: {
+            return NCDValue_InitString(o, v->string);
+        } break;
+        
+        case NCDVALUE_LIST: {
+            NCDValue_InitList(o);
+            
+            LinkedList2Iterator it;
+            LinkedList2Iterator_InitForward(&it, &v->list);
+            LinkedList2Node *n;
+            while (n = LinkedList2Iterator_Next(&it)) {
+                NCDListElement *e = UPPER_OBJECT(n, NCDListElement, list_node);
+                
+                NCDValue tmp;
+                if (!NCDValue_InitCopy(&tmp, &e->v)) {
+                    goto fail;
+                }
+                
+                if (!NCDValue_ListAppend(o, tmp)) {
+                    NCDValue_Free(&tmp);
+                    goto fail;
+                }
+            }
+            
+            return 1;
+            
+        fail:
+            LinkedList2Iterator_Free(&it);
+            NCDValue_Free(o);
+            return 0;
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+    
+    return 0;
+}
+
+void NCDValue_Free (NCDValue *o)
+{
+    switch (o->type) {
+        case NCDVALUE_STRING: {
+            free(o->string);
+        } break;
+        
+        case NCDVALUE_LIST: {
+            LinkedList2Node *n;
+            while (n = LinkedList2_GetFirst(&o->list)) {
+                NCDListElement *e = UPPER_OBJECT(n, NCDListElement, list_node);
+                
+                NCDValue_Free(&e->v);
+                LinkedList2_Remove(&o->list, &e->list_node);
+                free(e);
+            }
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+}
+
+int NCDValue_Type (NCDValue *o)
+{
+    value_assert(o);
+    
+    return o->type;
+}
+
+int NCDValue_InitString (NCDValue *o, char *str)
+{
+    size_t len = strlen(str);
+    
+    if (!(o->string = malloc(len + 1))) {
+        return 0;
+    }
+    
+    memcpy(o->string, str, len);
+    o->string[len] = '\0';
+    
+    o->type = NCDVALUE_STRING;
+    
+    return 1;
+}
+
+char * NCDValue_StringValue (NCDValue *o)
+{
+    ASSERT(o->type == NCDVALUE_STRING)
+    
+    return o->string;
+}
+
+void NCDValue_InitList (NCDValue *o)
+{
+    LinkedList2_Init(&o->list);
+    
+    o->type = NCDVALUE_LIST;
+}
+
+int NCDValue_ListAppend (NCDValue *o, NCDValue v)
+{
+    value_assert(o);
+    value_assert(&v);
+    ASSERT(o->type == NCDVALUE_LIST)
+    
+    NCDListElement *e = malloc(sizeof(*e));
+    if (!e) {
+        return 0;
+    }
+    
+    LinkedList2_Append(&o->list, &e->list_node);
+    e->v = v;
+    
+    return 1;
+}
+
+size_t NCDValue_ListCount (NCDValue *o)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_LIST)
+    
+    size_t c = 0;
+    
+    LinkedList2Iterator it;
+    LinkedList2Iterator_InitForward(&it, &o->list);
+    LinkedList2Node *n;
+    while (n = LinkedList2Iterator_Next(&it)) {
+        c++;
+    }
+    
+    return c;
+}
+
+NCDValue * NCDValue_ListFirst (NCDValue *o)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_LIST)
+    
+    if (LinkedList2_IsEmpty(&o->list)) {
+        return NULL;
+    }
+    
+    NCDListElement *e = UPPER_OBJECT(LinkedList2_GetFirst(&o->list), NCDListElement, list_node);
+    
+    return &e->v;
+}
+
+NCDValue * NCDValue_ListNext (NCDValue *o, NCDValue *ev)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_LIST)
+    
+    NCDListElement *e = UPPER_OBJECT(ev, NCDListElement, v);
+    
+    LinkedList2Iterator it;
+    LinkedList2Iterator_Init(&it, &o->list, 1, &e->list_node);
+    LinkedList2Iterator_Next(&it);
+    LinkedList2Node *nen = LinkedList2Iterator_Next(&it);
+    LinkedList2Iterator_Free(&it);
+    
+    if (!nen) {
+        return NULL;
+    }
+    
+    NCDListElement *ne = UPPER_OBJECT(nen, NCDListElement, list_node);
+    
+    return &ne->v;
+}
+
+int NCDValue_ListRead (NCDValue *o, int num, ...)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_LIST)
+    ASSERT(num >= 0)
+    
+    if (num != NCDValue_ListCount(o)) {
+        return 0;
+    }
+    
+    va_list ap;
+    va_start(ap, num);
+    
+    LinkedList2Iterator it;
+    LinkedList2Iterator_InitForward(&it, &o->list);
+    LinkedList2Node *n;
+    while (n = LinkedList2Iterator_Next(&it)) {
+        NCDListElement *e = UPPER_OBJECT(n, NCDListElement, list_node);
+        
+        NCDValue **dest = va_arg(ap, NCDValue **);
+        *dest = &e->v;
+    }
+    
+    va_end(ap);
+    
+    return 1;
+}

+ 60 - 0
ncd/NCDValue.h

@@ -0,0 +1,60 @@
+/**
+ * @file NCDValue.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef BADVPN_NCD_NCDVALUE_H
+#define BADVPN_NCD_NCDVALUE_H
+
+#include <stddef.h>
+
+#include <structure/LinkedList2.h>
+
+#define NCDVALUE_STRING 1
+#define NCDVALUE_LIST 2
+
+typedef struct {
+    int type;
+    union {
+        char *string;
+        LinkedList2 list;
+    };
+} NCDValue;
+
+typedef struct {
+    LinkedList2Node list_node;
+    NCDValue v;
+} NCDListElement;
+
+int NCDValue_InitCopy (NCDValue *o, NCDValue *v);
+void NCDValue_Free (NCDValue *o);
+int NCDValue_Type (NCDValue *o);
+
+int NCDValue_InitString (NCDValue *o, char *str);
+char * NCDValue_StringValue (NCDValue *o);
+
+void NCDValue_InitList (NCDValue *o);
+int NCDValue_ListAppend (NCDValue *o, NCDValue v);
+size_t NCDValue_ListCount (NCDValue *o);
+NCDValue * NCDValue_ListFirst (NCDValue *o);
+NCDValue * NCDValue_ListNext (NCDValue *o, NCDValue *ev);
+int NCDValue_ListRead (NCDValue *o, int num, ...);
+
+#endif

+ 0 - 231
ncd/interface_modules/interface_badvpn.c

@@ -1,231 +0,0 @@
-/**
- * @file interface_badvpn.c
- * @author Ambroz Bizjak <ambrop7@gmail.com>
- * 
- * @section LICENSE
- * 
- * This file is part of BadVPN.
- * 
- * BadVPN is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2
- * as published by the Free Software Foundation.
- * 
- * BadVPN 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 this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * 
- * @section DESCRIPTION
- * 
- * BadVPN interface backend.
- */
-
-#include <stdlib.h>
-#include <string.h>
-
-#include <misc/cmdline.h>
-#include <system/BProcess.h>
-#include <ncd/NCDInterfaceModule.h>
-#include <ncd/NCDIfConfig.h>
-
-struct instance {
-    NCDInterfaceModuleInst *i;
-    BProcess process;
-    int need_terminate;
-};
-
-static int build_cmdline (struct instance *o, CmdLine *c)
-{
-    if (!CmdLine_Init(c)) {
-        goto fail0;
-    }
-    
-    // find exec statement
-    struct NCDConfig_statements *exec_st = NCDConfig_find_statement(o->i->conf->statements, "badvpn.exec");
-    if (!exec_st) {
-        NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_ERROR, "badvpn.exec missing");
-        goto fail1;
-    }
-    
-    // check arity
-    char *exec_arg;
-    if (!NCDConfig_statement_has_one_arg(exec_st, &exec_arg)) {
-        NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_ERROR, "badvpn.exec: wrong arity");
-        goto fail1;
-    }
-    
-    // append to cmdline
-    if (!CmdLine_Append(c, exec_arg)) {
-        goto fail1;
-    }
-    
-    // append tapdev
-    if (!CmdLine_Append(c, "--tapdev") || !CmdLine_Append(c, o->i->conf->name)) {
-        goto fail1;
-    }
-    
-    // iterate arg statements
-    struct NCDConfig_statements *st = o->i->conf->statements;
-    while (st = NCDConfig_find_statement(st, "badvpn.arg")) {
-        // iterate arguments
-        struct NCDConfig_strings *arg = st->args;
-        while (arg) {
-            if (!CmdLine_Append(c, arg->value)) {
-                goto fail1;
-            }
-            
-            arg = arg->next;
-        }
-        
-        st = st->next;
-    }
-    
-    // terminate
-    if (!CmdLine_Finish(c)) {
-        goto fail1;
-    }
-    
-    return 1;
-    
-fail1:
-    CmdLine_Free(c);
-fail0:
-    return 0;
-}
-
-static const char * read_user (struct instance *o)
-{
-    // find statement
-    struct NCDConfig_statements *user_st = NCDConfig_find_statement(o->i->conf->statements, "badvpn.user");
-    if (!user_st) {
-        NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_ERROR, "badvpn.user missing");
-        return NULL;
-    }
-    
-    // check arity
-    char *user_arg;
-    if (!NCDConfig_statement_has_one_arg(user_st, &user_arg)) {
-        NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_ERROR, "badvpn.user: wrong arity");
-        return NULL;
-    }
-    
-    return user_arg;
-}
-
-static void process_handler (struct instance *o, int normally, uint8_t normally_exit_status)
-{
-    NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_INFO, "process terminated");
-    
-    // set not need terminate
-    o->need_terminate = 0;
-    
-    // report error
-    NCDInterfaceModuleInst_Backend_Error(o->i);
-    return;
-}
-
-static void * func_new (NCDInterfaceModuleInst *i)
-{
-    // allocate instance
-    struct instance *o = malloc(sizeof(*o));
-    if (!o) {
-        NCDInterfaceModuleInst_Backend_Log(i, BLOG_ERROR, "failed to allocate instance");
-        goto fail0;
-    }
-    
-    // init arguments
-    o->i = i;
-    
-    // create TAP device
-    if (!NCDIfConfig_make_tuntap(o->i->conf->name, NULL, 0)) {
-        NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_ERROR, "failed to create TAP device");
-        goto fail1;
-    }
-    
-    // set device up
-    if (!NCDIfConfig_set_up(o->i->conf->name)) {
-        NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_ERROR, "failed to set device up");
-        goto fail2;
-    }
-    
-    // read username
-    const char *username = read_user(o);
-    if (!username) {
-        goto fail2;
-    }
-    
-    // build cmdline
-    CmdLine cl;
-    if (!build_cmdline(o, &cl)) {
-        NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_ERROR, "failed to build cmdline");
-        goto fail2;
-    }
-    
-    // start process
-    if (!BProcess_Init(&o->process, o->i->manager, (BProcess_handler)process_handler, o, ((char **)cl.arr.v)[0], (char **)cl.arr.v, username)) {
-        NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_ERROR, "BProcess_Init failed");
-        CmdLine_Free(&cl);
-        goto fail2;
-    }
-    
-    CmdLine_Free(&cl);
-    
-    // set need terminate
-    o->need_terminate = 1;
-    
-    // report up
-    NCDInterfaceModuleInst_Backend_Event(o->i, NCDINTERFACEMODULE_EVENT_UP);
-    
-    return o;
-    
-fail2:
-    NCDIfConfig_remove_tuntap(o->i->conf->name, 0);
-fail1:
-    free(o);
-fail0:
-    return NULL;
-}
-
-static void func_free (void *vo)
-{
-    struct instance *o = vo;
-    
-    // order process to terminate
-    if (o->need_terminate) {
-        BProcess_Terminate(&o->process);
-    }
-    
-    // free process
-    BProcess_Free(&o->process);
-    
-    // remove TAP device
-    if (!NCDIfConfig_remove_tuntap(o->i->conf->name, 0)) {
-        NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_ERROR, "failed to remove TAP device");
-    }
-    
-    // free instance
-    free(o);
-}
-
-static void func_finish (void *vo)
-{
-    struct instance *o = vo;
-    ASSERT(o->need_terminate)
-    
-    // order process to terminate
-    BProcess_Terminate(&o->process);
-    
-    // set not need terminate
-    o->need_terminate = 0;
-}
-
-const struct NCDInterfaceModule ncd_interface_badvpn = {
-    .type = "badvpn",
-    .func_new = func_new,
-    .func_free = func_free,
-    .func_finish = func_finish
-};

+ 92 - 0
ncd/modules/list.c

@@ -0,0 +1,92 @@
+/**
+ * @file list.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * @section DESCRIPTION
+ * 
+ * List construction module.
+ * 
+ * Synopsis: list(elem1, ..., elemN)
+ * Variables:
+ *   (empty) - list containing elem1, ..., elemN
+ */
+
+#include <stdlib.h>
+
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_list.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+};
+
+static void * func_new (NCDModuleInst *i)
+{
+    // allocate instance
+    struct instance *o = malloc(sizeof(*o));
+    if (!o) {
+        ModuleLog(i, BLOG_ERROR, "failed to allocate instance");
+        goto fail0;
+    }
+    
+    // init arguments
+    o->i = i;
+    
+    NCDModuleInst_Backend_Event(o->i, NCDMODULE_EVENT_UP);
+    
+    return o;
+    
+fail0:
+    return NULL;
+}
+
+static void func_free (void *vo)
+{
+    struct instance *o = vo;
+    
+    // free instance
+    free(o);
+}
+
+static int func_getvar (void *vo, const char *name, NCDValue *out)
+{
+    struct instance *o = vo;
+    
+    if (!strcmp(name, "")) {
+        if (!NCDValue_InitCopy(out, o->i->args)) {
+            ModuleLog(o->i, BLOG_ERROR, "NCDValue_InitCopy failed");
+            return 0;
+        }
+        
+        return 1;
+    }
+    
+    return 0;
+}
+
+const struct NCDModule ncdmodule_list = {
+    .type = "list",
+    .func_new = func_new,
+    .func_free = func_free,
+    .func_getvar = func_getvar
+};

+ 49 - 0
ncd/modules/modules.h

@@ -0,0 +1,49 @@
+/**
+ * @file modules.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef BADVPN_NCD_MODULES_MODULES_H
+#define BADVPN_NCD_MODULES_MODULES_H
+
+#include <ncd/NCDModule.h>
+
+extern const struct NCDModule ncdmodule_var;
+extern const struct NCDModule ncdmodule_list;
+extern const struct NCDModule ncdmodule_net_backend_physical;
+extern const struct NCDModule ncdmodule_net_backend_badvpn;
+extern const struct NCDModule ncdmodule_net_dns;
+extern const struct NCDModule ncdmodule_net_ipv4_addr;
+extern const struct NCDModule ncdmodule_net_ipv4_route;
+extern const struct NCDModule ncdmodule_net_ipv4_dhcp;
+
+static const struct NCDModule *ncd_modules[] = {
+    &ncdmodule_var,
+    &ncdmodule_list,
+    &ncdmodule_net_backend_physical,
+    &ncdmodule_net_backend_badvpn,
+    &ncdmodule_net_dns,
+    &ncdmodule_net_ipv4_addr,
+    &ncdmodule_net_ipv4_route,
+    &ncdmodule_net_ipv4_dhcp,
+    NULL
+};
+
+#endif

+ 261 - 0
ncd/modules/net_backend_badvpn.c

@@ -0,0 +1,261 @@
+/**
+ * @file net_backend_badvpn.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * @section DESCRIPTION
+ * 
+ * BadVPN interface module.
+ * 
+ * Synopsis: net.backend.badvpn(string ifname, string user, string exec, list(string) args)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/cmdline.h>
+#include <system/BProcess.h>
+#include <ncd/NCDModule.h>
+#include <ncd/NCDIfConfig.h>
+
+#include <generated/blog_channel_ncd_net_backend_badvpn.h>
+
+#define RETRY_TIME 5000
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    char *ifname;
+    char *user;
+    char *exec;
+    NCDValue *args;
+    int dying;
+    int started;
+    BTimer timer;
+    BProcess process;
+};
+
+static void try_process (struct instance *o);
+static void process_handler (struct instance *o, int normally, uint8_t normally_exit_status);
+static void timer_handler (struct instance *o);
+
+void try_process (struct instance *o)
+{
+    CmdLine c;
+    if (!CmdLine_Init(&c)) {
+        goto fail0;
+    }
+    
+    // append exec
+    if (!CmdLine_Append(&c, o->exec)) {
+        goto fail1;
+    }
+    
+    // append tapdev
+    if (!CmdLine_Append(&c, "--tapdev") || !CmdLine_Append(&c, o->ifname)) {
+        goto fail1;
+    }
+    
+    // iterate arguments
+    NCDValue *arg = NCDValue_ListFirst(o->args);
+    while (arg) {
+        if (NCDValue_Type(arg) != NCDVALUE_STRING) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong type");
+            goto fail1;
+        }
+        
+        // append argument
+        if (!CmdLine_Append(&c, NCDValue_StringValue(arg))) {
+            goto fail1;
+        }
+        
+        arg = NCDValue_ListNext(o->args, arg);
+    }
+    
+    // terminate cmdline
+    if (!CmdLine_Finish(&c)) {
+        goto fail1;
+    }
+    
+    // start process
+    if (!BProcess_Init(&o->process, o->i->manager, (BProcess_handler)process_handler, o, ((char **)c.arr.v)[0], (char **)c.arr.v, o->user)) {
+        ModuleLog(o->i, BLOG_ERROR, "BProcess_Init failed");
+        goto fail1;
+    }
+    
+    CmdLine_Free(&c);
+    
+    // set started
+    o->started = 1;
+    
+    return;
+    
+fail1:
+    CmdLine_Free(&c);
+fail0:
+    // retry
+    o->started = 0;
+    BReactor_SetTimer(o->i->reactor, &o->timer);
+}
+
+void process_handler (struct instance *o, int normally, uint8_t normally_exit_status)
+{
+    ASSERT(o->started)
+    
+    ModuleLog(o->i, BLOG_INFO, "process terminated");
+    
+    // free process
+    BProcess_Free(&o->process);
+    
+    // set not started
+    o->started = 0;
+    
+    if (o->dying) {
+        NCDModuleInst_Backend_Died(o->i, 0);
+        return;
+    }
+    
+    // set timer
+    BReactor_SetTimer(o->i->reactor, &o->timer);
+}
+
+void timer_handler (struct instance *o)
+{
+    ASSERT(!o->started)
+    
+    ModuleLog(o->i, BLOG_INFO, "retrying");
+    
+    try_process(o);
+}
+
+static void * func_new (NCDModuleInst *i)
+{
+    // allocate instance
+    struct instance *o = malloc(sizeof(*o));
+    if (!o) {
+        ModuleLog(i, BLOG_ERROR, "failed to allocate instance");
+        goto fail0;
+    }
+    
+    // init arguments
+    o->i = i;
+    
+    // read arguments
+    NCDValue *ifname_arg;
+    NCDValue *user_arg;
+    NCDValue *exec_arg;
+    NCDValue *args_arg;
+    if (!NCDValue_ListRead(o->i->args, 4, &ifname_arg, &user_arg, &exec_arg, &args_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail1;
+    }
+    if (NCDValue_Type(ifname_arg) != NCDVALUE_STRING || NCDValue_Type(user_arg) != NCDVALUE_STRING ||
+        NCDValue_Type(exec_arg) != NCDVALUE_STRING || NCDValue_Type(args_arg) != NCDVALUE_LIST) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail1;
+    }
+    o->ifname = NCDValue_StringValue(ifname_arg);
+    o->user = NCDValue_StringValue(user_arg);
+    o->exec = NCDValue_StringValue(exec_arg);
+    o->args = args_arg;
+    
+    // create TAP device
+    if (!NCDIfConfig_make_tuntap(o->ifname, o->user, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to create TAP device");
+        goto fail1;
+    }
+    
+    // set device up
+    if (!NCDIfConfig_set_up(o->ifname)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to set device up");
+        goto fail2;
+    }
+    
+    // set not dying
+    o->dying = 0;
+    
+    // init timer
+    BTimer_Init(&o->timer, RETRY_TIME, (BTimer_handler)timer_handler, o);
+    
+    try_process(o);
+    
+    NCDModuleInst_Backend_Event(o->i, NCDMODULE_EVENT_UP);
+    
+    return o;
+    
+fail2:
+    if (!NCDIfConfig_remove_tuntap(o->ifname, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to remove TAP device");
+    }
+fail1:
+    free(o);
+fail0:
+    return NULL;
+}
+
+static void func_free (void *vo)
+{
+    struct instance *o = vo;
+    
+    if (o->started) {
+        // kill process
+        BProcess_Kill(&o->process);
+        
+        // free process
+        BProcess_Free(&o->process);
+    }
+    
+    // free timer
+    BReactor_RemoveTimer(o->i->reactor, &o->timer);
+    
+    // free TAP device
+    if (!NCDIfConfig_remove_tuntap(o->ifname, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to remove TAP device");
+    }
+    
+    // free instance
+    free(o);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    ASSERT(!o->dying)
+    
+    if (o->started) {
+        // request termination
+        BProcess_Terminate(&o->process);
+        
+        // remember dying
+        o->dying = 1;
+        
+        return;
+    }
+    
+    NCDModuleInst_Backend_Died(o->i, 0);
+    return;
+}
+
+const struct NCDModule ncdmodule_net_backend_badvpn = {
+    .type = "net.backend.badvpn",
+    .func_new = func_new,
+    .func_free = func_free,
+    .func_die = func_die
+};

+ 45 - 35
ncd/interface_modules/interface_physical.c → ncd/modules/net_backend_physical.c

@@ -1,5 +1,5 @@
 /**
- * @file interface_physical.c
+ * @file net_backend_physical.c
  * @author Ambroz Bizjak <ambrop7@gmail.com>
  * 
  * @section LICENSE
@@ -21,22 +21,29 @@
  * 
  * @section DESCRIPTION
  * 
- * Physical interface backend.
+ * Physical network interface module.
+ * 
+ * Synopsis: net.backend.physical(string ifname)
  */
 
 #include <stdlib.h>
 #include <string.h>
 
-#include <ncd/NCDInterfaceModule.h>
+#include <ncd/NCDModule.h>
 #include <ncd/NCDIfConfig.h>
 #include <ncd/NCDInterfaceMonitor.h>
 
+#include <generated/blog_channel_ncd_net_backend_physical.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
 #define STATE_WAITDEVICE 1
 #define STATE_WAITLINK 2
 #define STATE_FINISHED 3
 
 struct instance {
-    NCDInterfaceModuleInst *i;
+    NCDModuleInst *i;
+    const char *ifname;
     NCDInterfaceMonitor monitor;
     int state;
 };
@@ -44,26 +51,26 @@ struct instance {
 static int try_start (struct instance *o)
 {
     // query interface state
-    int flags = NCDIfConfig_query(o->i->conf->name);
+    int flags = NCDIfConfig_query(o->ifname);
     
     if (!(flags&NCDIFCONFIG_FLAG_EXISTS)) {
-        NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_INFO, "device doesn't exist");
+        ModuleLog(o->i, BLOG_INFO, "device doesn't exist");
         
         // waiting for device
         o->state = STATE_WAITDEVICE;
     } else {
         if ((flags&NCDIFCONFIG_FLAG_UP)) {
-            NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_ERROR, "device already up - NOT configuring");
+            ModuleLog(o->i, BLOG_ERROR, "device already up - NOT configuring");
             return 0;
         }
         
         // set interface up
-        if (!NCDIfConfig_set_up(o->i->conf->name)) {
-            NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_ERROR, "failed to set device up");
+        if (!NCDIfConfig_set_up(o->ifname)) {
+            ModuleLog(o->i, BLOG_ERROR, "failed to set device up");
             return 0;
         }
         
-        NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_INFO, "waiting for link");
+        ModuleLog(o->i, BLOG_INFO, "waiting for link");
         
         // waiting for link
         o->state = STATE_WAITLINK;
@@ -74,7 +81,7 @@ static int try_start (struct instance *o)
 
 static void monitor_handler (struct instance *o, const char *ifname, int if_flags)
 {
-    if (strcmp(ifname, o->i->conf->name)) {
+    if (strcmp(ifname, o->ifname)) {
         return;
     }
     
@@ -82,22 +89,22 @@ static void monitor_handler (struct instance *o, const char *ifname, int if_flag
         if (o->state > STATE_WAITDEVICE) {
             int prev_state = o->state;
             
-            NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_INFO, "device down");
+            ModuleLog(o->i, BLOG_INFO, "device down");
             
             // set state
             o->state = STATE_WAITDEVICE;
             
             // report
             if (prev_state == STATE_FINISHED) {
-                NCDInterfaceModuleInst_Backend_Event(o->i, NCDINTERFACEMODULE_EVENT_DOWN);
+                NCDModuleInst_Backend_Event(o->i, NCDMODULE_EVENT_DOWN);
             }
         }
     } else {
         if (o->state == STATE_WAITDEVICE) {
-            NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_INFO, "device up");
+            ModuleLog(o->i, BLOG_INFO, "device up");
             
             if (!try_start(o)) {
-                NCDInterfaceModuleInst_Backend_Error(o->i);
+                NCDModuleInst_Backend_Died(o->i, 1);
                 return;
             }
             
@@ -106,43 +113,55 @@ static void monitor_handler (struct instance *o, const char *ifname, int if_flag
         
         if ((if_flags&NCDIFCONFIG_FLAG_RUNNING)) {
             if (o->state == STATE_WAITLINK) {
-                NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_INFO, "link up");
+                ModuleLog(o->i, BLOG_INFO, "link up");
                 
                 // set state
                 o->state = STATE_FINISHED;
                 
                 // report
-                NCDInterfaceModuleInst_Backend_Event(o->i, NCDINTERFACEMODULE_EVENT_UP);
+                NCDModuleInst_Backend_Event(o->i, NCDMODULE_EVENT_UP);
             }
         } else {
             if (o->state == STATE_FINISHED) {
-                NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_INFO, "link down");
+                ModuleLog(o->i, BLOG_INFO, "link down");
                 
                 // set state
                 o->state = STATE_WAITLINK;
                 
                 // report
-                NCDInterfaceModuleInst_Backend_Event(o->i, NCDINTERFACEMODULE_EVENT_DOWN);
+                NCDModuleInst_Backend_Event(o->i, NCDMODULE_EVENT_DOWN);
             }
         }
     }
 }
 
-static void * func_new (NCDInterfaceModuleInst *i)
+static void * func_new (NCDModuleInst *i)
 {
     // allocate instance
     struct instance *o = malloc(sizeof(*o));
     if (!o) {
-        NCDInterfaceModuleInst_Backend_Log(i, BLOG_ERROR, "failed to allocate instance");
+        ModuleLog(i, BLOG_ERROR, "failed to allocate instance");
         goto fail0;
     }
     
     // init arguments
     o->i = i;
     
+    // check arguments
+    NCDValue *arg;
+    if (!NCDValue_ListRead(i->args, 1, &arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail1;
+    }
+    if (NCDValue_Type(arg) != NCDVALUE_STRING) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail1;
+    }
+    o->ifname = NCDValue_StringValue(arg);
+    
     // init monitor
     if (!NCDInterfaceMonitor_Init(&o->monitor, o->i->reactor, (NCDInterfaceMonitor_handler)monitor_handler, o)) {
-        NCDInterfaceModuleInst_Backend_Log(o->i, BLOG_ERROR, "NCDInterfaceMonitor_Init failed");
+        ModuleLog(o->i, BLOG_ERROR, "NCDInterfaceMonitor_Init failed");
         goto fail1;
     }
     
@@ -166,7 +185,7 @@ static void func_free (void *vo)
     
     // set interface down
     if (o->state > STATE_WAITDEVICE) {
-        NCDIfConfig_set_down(o->i->conf->name);
+        NCDIfConfig_set_down(o->ifname);
     }
     
     // free monitor
@@ -176,17 +195,8 @@ static void func_free (void *vo)
     free(o);
 }
 
-static void func_finish (void *vo)
-{
-    struct instance *o = vo;
-    
-    NCDInterfaceModuleInst_Backend_Error(o->i);
-    return;
-}
-
-const struct NCDInterfaceModule ncd_interface_physical = {
-    .type = "physical",
+const struct NCDModule ncdmodule_net_backend_physical = {
+    .type = "net.backend.physical",
     .func_new = func_new,
-    .func_free = func_free,
-    .func_finish = func_finish
+    .func_free = func_free
 };

+ 278 - 0
ncd/modules/net_dns.c

@@ -0,0 +1,278 @@
+/**
+ * @file net_dns.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * @section DESCRIPTION
+ * 
+ * DNS servers module.
+ * 
+ * Synopsis: net.dns(list(string) servers, string priority)
+ */
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <misc/bsort.h>
+#include <structure/LinkedList2.h>
+#include <ncd/NCDModule.h>
+#include <ncd/NCDIfConfig.h>
+
+#include <generated/blog_channel_ncd_net_dns.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    LinkedList2 ipv4_dns_servers;
+    LinkedList2Node instances_node; // node in instances
+};
+
+struct ipv4_dns_entry {
+    LinkedList2Node list_node; // node in instance.ipv4_dns_servers
+    uint32_t addr;
+    int priority;
+};
+
+static LinkedList2 instances;
+
+static struct ipv4_dns_entry * add_ipv4_dns_entry (struct instance *o, uint32_t addr, int priority)
+{
+    // allocate entry
+    struct ipv4_dns_entry *entry = malloc(sizeof(*entry));
+    if (!entry) {
+        return NULL;
+    }
+    
+    // set info
+    entry->addr = addr;
+    entry->priority = priority;
+    
+    // add to list
+    LinkedList2_Append(&o->ipv4_dns_servers, &entry->list_node);
+    
+    return entry;
+}
+
+static void remove_ipv4_dns_entry (struct instance *o, struct ipv4_dns_entry *entry)
+{
+    // remove from list
+    LinkedList2_Remove(&o->ipv4_dns_servers, &entry->list_node);
+    
+    // free entry
+    free(entry);
+}
+
+static void remove_ipv4_dns_entries (struct instance *o)
+{
+    LinkedList2Node *n;
+    while (n = LinkedList2_GetFirst(&o->ipv4_dns_servers)) {
+        struct ipv4_dns_entry *e = UPPER_OBJECT(n, struct ipv4_dns_entry, list_node);
+        remove_ipv4_dns_entry(o, e);
+    }
+}
+
+static size_t num_servers (void)
+{
+    size_t c = 0;
+    
+    LinkedList2Iterator it;
+    LinkedList2Iterator_InitForward(&it, &instances);
+    LinkedList2Node *n;
+    while (n = LinkedList2Iterator_Next(&it)) {
+        struct instance *o = UPPER_OBJECT(n, struct instance, instances_node);
+        LinkedList2Iterator eit;
+        LinkedList2Iterator_InitForward(&eit, &o->ipv4_dns_servers);
+        while (LinkedList2Iterator_Next(&eit)) {
+            c++;
+        }
+    }
+    
+    return c;
+}
+
+struct dns_sort_entry {
+    uint32_t addr;
+    int priority;
+};
+
+static int dns_sort_comparator (const void *v1, const void *v2)
+{
+    const struct dns_sort_entry *e1 = v1;
+    const struct dns_sort_entry *e2 = v2;
+    
+    if (e1->priority < e2->priority) {
+        return -1;
+    }
+    if (e1->priority > e2->priority) {
+        return 1;
+    }
+    return 0;
+}
+
+static int set_servers (void)
+{
+    // count servers
+    size_t num_ipv4_dns_servers = num_servers();
+    
+    // allocate sort array
+    struct dns_sort_entry servers[num_ipv4_dns_servers];
+    size_t num_servers = 0;
+    
+    // fill sort array
+    LinkedList2Iterator it;
+    LinkedList2Iterator_InitForward(&it, &instances);
+    LinkedList2Node *n;
+    while (n = LinkedList2Iterator_Next(&it)) {
+        struct instance *o = UPPER_OBJECT(n, struct instance, instances_node);
+        LinkedList2Iterator eit;
+        LinkedList2Iterator_InitForward(&eit, &o->ipv4_dns_servers);
+        LinkedList2Node *en;
+        while (en = LinkedList2Iterator_Next(&eit)) {
+            struct ipv4_dns_entry *e = UPPER_OBJECT(en, struct ipv4_dns_entry, list_node);
+            servers[num_servers].addr = e->addr;
+            servers[num_servers].priority= e->priority;
+            num_servers++;
+        }
+    }
+    ASSERT(num_servers == num_ipv4_dns_servers)
+    
+    // sort by priority
+    // use a custom insertion sort instead of qsort() because we want a stable sort
+    BInsertionSort(servers, num_servers, sizeof(servers[0]), dns_sort_comparator);
+    
+    // copy addresses into an array
+    uint32_t addrs[num_servers];
+    for (size_t i = 0; i < num_servers; i++) {
+        addrs[i] = servers[i].addr;
+    }
+    
+    // set servers
+    if (!NCDIfConfig_set_dns_servers(addrs, num_servers)) {
+        return 0;
+    }
+    
+    return 1;
+}
+
+static int func_globalinit (void)
+{
+    LinkedList2_Init(&instances);
+    
+    return 1;
+}
+
+static void * func_new (NCDModuleInst *i)
+{
+    // allocate instance
+    struct instance *o = malloc(sizeof(*o));
+    if (!o) {
+        ModuleLog(i, BLOG_ERROR, "failed to allocate instance");
+        goto fail0;
+    }
+    
+    // init arguments
+    o->i = i;
+    
+    // init servers list
+    LinkedList2_Init(&o->ipv4_dns_servers);
+    
+    // get arguments
+    NCDValue *servers_arg;
+    NCDValue *priority_arg;
+    if (!NCDValue_ListRead(o->i->args, 2, &servers_arg, &priority_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail1;
+    }
+    if (NCDValue_Type(servers_arg) != NCDVALUE_LIST || NCDValue_Type(priority_arg) != NCDVALUE_STRING) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail1;
+    }
+    
+    int priority = atoi(NCDValue_StringValue(priority_arg));
+    
+    // read servers
+    NCDValue *server_arg = NCDValue_ListFirst(servers_arg);
+    while (server_arg) {
+        if (NCDValue_Type(server_arg) != NCDVALUE_STRING) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong type");
+            goto fail1;
+        }
+        
+        uint32_t addr;
+        if (!ipaddr_parse_ipv4_addr(NCDValue_StringValue(server_arg), &addr)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong addr");
+            goto fail1;
+        }
+        
+        if (!add_ipv4_dns_entry(o, addr, priority)) {
+            ModuleLog(o->i, BLOG_ERROR, "failed to add dns entry");
+            goto fail1;
+        }
+        
+        server_arg = NCDValue_ListNext(servers_arg, server_arg);
+    }
+    
+    // add to instances
+    LinkedList2_Append(&instances, &o->instances_node);
+    
+    // set servers
+    if (!set_servers()) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to set DNS servers");
+        goto fail2;
+    }
+    
+    NCDModuleInst_Backend_Event(o->i, NCDMODULE_EVENT_UP);
+    
+    return o;
+    
+fail2:
+    LinkedList2_Remove(&instances, &o->instances_node);
+fail1:
+    remove_ipv4_dns_entries(o);
+    free(o);
+fail0:
+    return NULL;
+}
+
+static void func_free (void *vo)
+{
+    struct instance *o = vo;
+    
+    // remove from instances
+    LinkedList2_Remove(&instances, &o->instances_node);
+    
+    // set servers
+    set_servers();
+    
+    // free servers
+    remove_ipv4_dns_entries(o);
+    
+    // free instance
+    free(o);
+}
+
+const struct NCDModule ncdmodule_net_dns = {
+    .type = "net.dns",
+    .func_globalinit = func_globalinit,
+    .func_new = func_new,
+    .func_free = func_free
+};

+ 112 - 0
ncd/modules/net_ipv4_addr.c

@@ -0,0 +1,112 @@
+/**
+ * @file net_ipv4_addr.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * @section DESCRIPTION
+ * 
+ * IPv4 address module.
+ * 
+ * Synopsis: net.ipv4.addr(string ifname, string addr, string prefix)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/NCDIfConfig.h>
+
+#include <generated/blog_channel_ncd_net_ipv4_addr.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    const char *ifname;
+    struct ipv4_ifaddr ifaddr;
+};
+
+static void * func_new (NCDModuleInst *i)
+{
+    // allocate instance
+    struct instance *o = malloc(sizeof(*o));
+    if (!o) {
+        ModuleLog(i, BLOG_ERROR, "failed to allocate instance");
+        goto fail0;
+    }
+    
+    // init arguments
+    o->i = i;
+    
+    // read arguments
+    NCDValue *ifname_arg;
+    NCDValue *addr_arg;
+    NCDValue *prefix_arg;
+    if (!NCDValue_ListRead(o->i->args, 3, &ifname_arg, &addr_arg, &prefix_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail1;
+    }
+    if (NCDValue_Type(ifname_arg) != NCDVALUE_STRING || NCDValue_Type(addr_arg) != NCDVALUE_STRING || NCDValue_Type(prefix_arg) != NCDVALUE_STRING) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail1;
+    }
+    o->ifname = NCDValue_StringValue(ifname_arg);
+    if (!ipaddr_parse_ipv4_addr(NCDValue_StringValue(addr_arg), &o->ifaddr.addr)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong address");
+        goto fail1;
+    }
+    if (!ipaddr_parse_ipv4_prefix(NCDValue_StringValue(prefix_arg), &o->ifaddr.prefix)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong prefix");
+        goto fail1;
+    }
+    
+    // add address
+    if (!NCDIfConfig_add_ipv4_addr(o->ifname, o->ifaddr)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to add IP address");
+        goto fail1;
+    }
+    
+    NCDModuleInst_Backend_Event(o->i, NCDMODULE_EVENT_UP);
+    
+    return o;
+    
+fail1:
+    free(o);
+fail0:
+    return NULL;
+}
+
+static void func_free (void *vo)
+{
+    struct instance *o = vo;
+    
+    // remove address
+    if (!NCDIfConfig_remove_ipv4_addr(o->ifname, o->ifaddr)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to remove IP address");
+    }
+    
+    // free instance
+    free(o);
+}
+
+const struct NCDModule ncdmodule_net_ipv4_addr = {
+    .type = "net.ipv4.addr",
+    .func_new = func_new,
+    .func_free = func_free
+};

+ 211 - 0
ncd/modules/net_ipv4_dhcp.c

@@ -0,0 +1,211 @@
+/**
+ * @file net_ipv4_dhcp.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Physical network interface module.
+ * 
+ * Synopsis: net.ipv4.dhcp(string ifname)
+ * Variables:
+ *   string addr - assigned IP address ("A.B.C.D")
+ *   string prefix - address prefix length ("N")
+ *   string gateway - router address ("A.B.C.D")
+ *   list(string) dns_servers - DNS server addresses ("A.B.C.D" ...)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <misc/debug.h>
+#include <misc/ipaddr.h>
+#include <dhcpclient/BDHCPClient.h>
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_net_ipv4_dhcp.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    BDHCPClient dhcp;
+};
+
+static void dhcp_handler (struct instance *o, int event)
+{
+    switch (event) {
+        case BDHCPCLIENT_EVENT_UP:
+            NCDModuleInst_Backend_Event(o->i, NCDMODULE_EVENT_UP);
+            break;
+        case BDHCPCLIENT_EVENT_DOWN:
+            NCDModuleInst_Backend_Event(o->i, NCDMODULE_EVENT_DOWN);
+            break;
+        default:
+            ASSERT(0);
+    }
+}
+
+static void * func_new (NCDModuleInst *i)
+{
+    // allocate instance
+    struct instance *o = malloc(sizeof(*o));
+    if (!o) {
+        ModuleLog(i, BLOG_ERROR, "failed to allocate instance");
+        goto fail0;
+    }
+    
+    // init arguments
+    o->i = i;
+    
+    // check arguments
+    NCDValue *arg;
+    if (!NCDValue_ListRead(o->i->args, 1, &arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail1;
+    }
+    if (NCDValue_Type(arg) != NCDVALUE_STRING) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail1;
+    }
+    char *ifname = NCDValue_StringValue(arg);
+    
+    // init DHCP
+    if (!BDHCPClient_Init(&o->dhcp, ifname, o->i->reactor, (BDHCPClient_handler)dhcp_handler, o)) {
+        ModuleLog(o->i, BLOG_ERROR, "BDHCPClient_Init failed");
+        goto fail1;
+    }
+    
+    return o;
+    
+fail1:
+    free(o);
+fail0:
+    return NULL;
+}
+
+static void func_free (void *vo)
+{
+    struct instance *o = vo;
+    
+    // free DHCP
+    BDHCPClient_Free(&o->dhcp);
+    
+    // free instance
+    free(o);
+}
+
+static int func_getvar (void *vo, const char *name, NCDValue *out)
+{
+    struct instance *o = vo;
+    
+    if (!strcmp(name, "addr")) {
+        uint32_t addr;
+        BDHCPClient_GetClientIP(&o->dhcp, &addr);
+        
+        uint8_t *b = (uint8_t *)&addr;
+        char str[50];
+        sprintf(str, "%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8, b[0], b[1], b[2], b[3]);
+        
+        if (!NCDValue_InitString(out, str)) {
+            return 0;
+        }
+        
+        return 1;
+    }
+    
+    if (!strcmp(name, "prefix")) {
+        uint32_t addr;
+        BDHCPClient_GetClientIP(&o->dhcp, &addr);
+        uint32_t mask;
+        BDHCPClient_GetClientMask(&o->dhcp, &mask);
+        
+        struct ipv4_ifaddr ifaddr;
+        if (!ipaddr_ipv4_ifaddr_from_addr_mask(addr, mask, &ifaddr)) {
+            return 0;
+        }
+        
+        char str[10];
+        sprintf(str, "%d", ifaddr.prefix);
+        
+        if (!NCDValue_InitString(out, str)) {
+            return 0;
+        }
+        
+        return 1;
+    }
+    
+    if (!strcmp(name, "gateway")) {
+        uint32_t addr;
+        if (!BDHCPClient_GetRouter(&o->dhcp, &addr)) {
+            return 0;
+        }
+        
+        uint8_t *b = (uint8_t *)&addr;
+        char str[50];
+        sprintf(str, "%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8, b[0], b[1], b[2], b[3]);
+        
+        if (!NCDValue_InitString(out, str)) {
+            return 0;
+        }
+        
+        return 1;
+    }
+    
+    if (!strcmp(name, "dns_servers")) {
+        uint32_t servers[BDHCPCLIENT_MAX_DOMAIN_NAME_SERVERS];
+        int num_servers = BDHCPClient_GetDNS(&o->dhcp, servers, BDHCPCLIENT_MAX_DOMAIN_NAME_SERVERS);
+        
+        NCDValue list;
+        NCDValue_InitList(&list);
+        
+        for (int i = 0; i < num_servers; i++) {
+            uint8_t *b = (uint8_t *)&servers[i];
+            char str[50];
+            sprintf(str, "%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8, b[0], b[1], b[2], b[3]);
+            
+            NCDValue server;
+            if (!NCDValue_InitString(&server, str)) {
+                goto fail1;
+            }
+            
+            if (!NCDValue_ListAppend(&list, server)) {
+                NCDValue_Free(&server);
+                goto fail1;
+            }
+        }
+        
+        *out = list;
+        return 1;
+        
+    fail1:
+        NCDValue_Free(&list);
+        return 0;
+    }
+    
+    return 0;
+}
+
+const struct NCDModule ncdmodule_net_ipv4_dhcp = {
+    .type = "net.ipv4.dhcp",
+    .func_new = func_new,
+    .func_free = func_free,
+    .func_getvar = func_getvar
+};

+ 137 - 0
ncd/modules/net_ipv4_route.c

@@ -0,0 +1,137 @@
+/**
+ * @file net_ipv4_route.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * @section DESCRIPTION
+ * 
+ * IPv4 route module.
+ * 
+ * Synopsis: net.ipv4.route(string dest, string dest_prefix, string gateway, string metric, string ifname)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/NCDIfConfig.h>
+
+#include <generated/blog_channel_ncd_net_ipv4_route.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    struct ipv4_ifaddr dest;
+    int have_gateway;
+    uint32_t gateway;
+    int metric;
+    const char *ifname;
+};
+
+static void * func_new (NCDModuleInst *i)
+{
+    // allocate instance
+    struct instance *o = malloc(sizeof(*o));
+    if (!o) {
+        ModuleLog(i, BLOG_ERROR, "failed to allocate instance");
+        goto fail0;
+    }
+    
+    // init arguments
+    o->i = i;
+    
+    // read arguments
+    NCDValue *dest_arg;
+    NCDValue *dest_prefix_arg;
+    NCDValue *gateway_arg;
+    NCDValue *metric_arg;
+    NCDValue *ifname_arg;
+    if (!NCDValue_ListRead(o->i->args, 5, &dest_arg, &dest_prefix_arg, &gateway_arg, &metric_arg, &ifname_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail1;
+    }
+    if (NCDValue_Type(dest_arg) != NCDVALUE_STRING || NCDValue_Type(dest_prefix_arg) != NCDVALUE_STRING || NCDValue_Type(gateway_arg) != NCDVALUE_STRING ||
+        NCDValue_Type(metric_arg) != NCDVALUE_STRING || NCDValue_Type(ifname_arg) != NCDVALUE_STRING) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail1;
+    }
+    
+    // read dest
+    if (!ipaddr_parse_ipv4_addr(NCDValue_StringValue(dest_arg), &o->dest.addr)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong dest addr");
+        goto fail1;
+    }
+    if (!ipaddr_parse_ipv4_prefix(NCDValue_StringValue(dest_prefix_arg), &o->dest.prefix)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong dest prefix");
+        goto fail1;
+    }
+    
+    // read gateway
+    char *gateway_str = NCDValue_StringValue(gateway_arg);
+    if (!strcmp(gateway_str, "none")) {
+        o->have_gateway = 0;
+    } else {
+        if (!ipaddr_parse_ipv4_addr(gateway_str, &o->gateway)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong gateway");
+            goto fail1;
+        }
+        o->have_gateway = 1;
+    }
+    
+    // read metric
+    o->metric = atoi(NCDValue_StringValue(metric_arg));
+    
+    // read ifname
+    o->ifname = NCDValue_StringValue(ifname_arg);
+    
+    // add route
+    if (!NCDIfConfig_add_ipv4_route(o->dest, (o->have_gateway ? &o->gateway : NULL), o->metric, o->ifname)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to add route");
+        goto fail1;
+    }
+    
+    NCDModuleInst_Backend_Event(o->i, NCDMODULE_EVENT_UP);
+    
+    return o;
+    
+fail1:
+    free(o);
+fail0:
+    return NULL;
+}
+
+static void func_free (void *vo)
+{
+    struct instance *o = vo;
+    
+    // remove route
+    if (!NCDIfConfig_remove_ipv4_route(o->dest, (o->have_gateway ? &o->gateway : NULL), o->metric, o->ifname)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to remove route");
+    }
+    
+    // free instance
+    free(o);
+}
+
+const struct NCDModule ncdmodule_net_ipv4_route = {
+    .type = "net.ipv4.route",
+    .func_new = func_new,
+    .func_free = func_free
+};

+ 101 - 0
ncd/modules/var.c

@@ -0,0 +1,101 @@
+/**
+ * @file var.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Variable module.
+ * 
+ * Synopsis: var(value)
+ * Variables:
+ *   (empty) - value
+ */
+
+#include <stdlib.h>
+
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_var.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    NCDValue *arg;
+};
+
+static void * func_new (NCDModuleInst *i)
+{
+    // allocate instance
+    struct instance *o = malloc(sizeof(*o));
+    if (!o) {
+        ModuleLog(i, BLOG_ERROR, "failed to allocate instance");
+        goto fail0;
+    }
+    
+    // init arguments
+    o->i = i;
+    
+    // read argument
+    if (!NCDValue_ListRead(o->i->args, 1, &o->arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail1;
+    }
+    
+    NCDModuleInst_Backend_Event(o->i, NCDMODULE_EVENT_UP);
+    
+    return o;
+    
+fail1:
+    free(o);
+fail0:
+    return NULL;
+}
+
+static void func_free (void *vo)
+{
+    struct instance *o = vo;
+    
+    // free instance
+    free(o);
+}
+
+static int func_getvar (void *vo, const char *name, NCDValue *out)
+{
+    struct instance *o = vo;
+    
+    if (!strcmp(name, "")) {
+        if (!NCDValue_InitCopy(out, o->arg)) {
+            ModuleLog(o->i, BLOG_ERROR, "NCDValue_InitCopy failed");
+            return 0;
+        }
+        
+        return 1;
+    }
+    
+    return 0;
+}
+
+const struct NCDModule ncdmodule_var = {
+    .type = "var",
+    .func_new = func_new,
+    .func_free = func_free,
+    .func_getvar = func_getvar
+};

+ 493 - 1018
ncd/ncd.c

@@ -24,24 +24,23 @@
 #include <stdio.h>
 #include <stddef.h>
 #include <string.h>
+#include <stdlib.h>
 
 #include <misc/version.h>
 #include <misc/loggers_string.h>
 #include <misc/loglevel.h>
 #include <misc/offset.h>
 #include <misc/read_file.h>
-#include <misc/string_begins_with.h>
-#include <misc/ipaddr.h>
+#include <misc/balloc.h>
 #include <structure/LinkedList2.h>
 #include <system/BLog.h>
 #include <system/BReactor.h>
 #include <system/BProcess.h>
 #include <system/BSignal.h>
 #include <system/BSocket.h>
-#include <dhcpclient/BDHCPClient.h>
 #include <ncdconfig/NCDConfigParser.h>
-#include <ncd/NCDIfConfig.h>
-#include <ncd/NCDInterfaceModule.h>
+#include <ncd/NCDModule.h>
+#include <ncd/modules/modules.h>
 
 #ifndef BADVPN_USE_WINAPI
 #include <system/BLog_syslog.h>
@@ -54,22 +53,49 @@
 #define LOGGER_STDOUT 1
 #define LOGGER_SYSLOG 2
 
-#define INTERFACE_STATE_TERMINATING 0
-#define INTERFACE_STATE_WAITDEPS 1
-#define INTERFACE_STATE_RESETTING 2
-#define INTERFACE_STATE_WAITMODULE 3
-#define INTERFACE_STATE_DHCP 4
-#define INTERFACE_STATE_FINISHED 5
+#define SSTATE_CHILD 1
+#define SSTATE_ADULT 2
+#define SSTATE_DYING 3
+#define SSTATE_FORGOTTEN 4
 
-// interface modules
-extern const struct NCDInterfaceModule ncd_interface_physical;
-extern const struct NCDInterfaceModule ncd_interface_badvpn;
+struct statement {
+    const struct NCDModule *module;
+    struct argument_elem *first_arg;
+    char *name;
+};
+
+struct argument_elem {
+    int is_var;
+    union {
+        struct {
+            char *modname;
+            char *varname;
+        } var;
+        NCDValue val;
+    };
+    struct argument_elem *next_arg;
+};
 
-// interface modules list
-const struct NCDInterfaceModule *interface_modules[] = {
-    &ncd_interface_physical,
-    &ncd_interface_badvpn,
-    NULL
+struct process {
+    char *name;
+    size_t num_statements;
+    struct process_statement *statements;
+    size_t ap;
+    size_t fp;
+    BTimer wait_timer;
+    LinkedList2Node list_node; // node in processes
+};
+
+struct process_statement {
+    struct process *p;
+    size_t i;
+    struct statement s;
+    int state;
+    int have_error;
+    btime_t error_until;
+    NCDModuleInst inst;
+    NCDValue inst_args;
+    char logprefix[50];
 };
 
 // command-line options
@@ -86,52 +112,6 @@ struct {
     char *config_file;
 } options;
 
-struct interface {
-    LinkedList2Node list_node; // node in interfaces
-    struct NCDConfig_interfaces *conf;
-    const struct NCDInterfaceModule *module;
-    int state;
-    BTimer reset_timer;
-    LinkedList2 deps_out;
-    LinkedList2 deps_in;
-    BPending terminating_module_job;
-    int have_module_instance;
-    NCDInterfaceModuleInst module_instance;
-    int module_instance_finishing;
-    int module_instance_error;
-    int have_dhcp;
-    BDHCPClient dhcp;
-    LinkedList2 ipv4_addresses;
-    LinkedList2 ipv4_routes;
-    LinkedList2 ipv4_dns_servers;
-};
-
-struct dependency {
-    struct interface *src;
-    struct interface *dst;
-    LinkedList2Node src_node;
-    LinkedList2Node dst_node;
-};
-
-struct ipv4_addr_entry {
-    LinkedList2Node list_node; // node in interface.ipv4_addresses
-    struct ipv4_ifaddr ifaddr;
-};
-
-struct ipv4_route_entry {
-    LinkedList2Node list_node; // node in interface.ipv4_routes
-    struct ipv4_ifaddr dest;
-    int have_gateway;
-    uint32_t gateway;
-    int metric;
-};
-
-struct ipv4_dns_entry {
-    LinkedList2Node list_node; // node in interface.ipv4_dns_servers
-    uint32_t addr;
-    int priority;
-};
-
 // reactor
 BReactor ss;
 
@@ -144,62 +124,33 @@ BProcessManager manager;
 // configuration
 struct NCDConfig_interfaces *configuration;
 
-// interfaces
-LinkedList2 interfaces;
-
-// number of DNS servers
-size_t num_ipv4_dns_servers;
+// processes
+LinkedList2 processes;
 
 static void terminate (void);
-static void work_terminate (void);
 static void print_help (const char *name);
 static void print_version (void);
 static int parse_arguments (int argc, char *argv[]);
 static void signal_handler (void *unused);
-static void load_interfaces (struct NCDConfig_interfaces *conf);
-static int set_dns_servers (void);
-static int dns_qsort_comparator (const void *v1, const void *v2);
-static struct interface * find_interface (const char *name);
-static int interface_init (struct NCDConfig_interfaces *conf);
-static void interface_terminate (struct interface *iface);
-static void interface_terminating_module_job_handler (struct interface *iface);
-static void interface_down_to (struct interface *iface, int state);
-static void interface_reset (struct interface *iface);
-static void interface_start (struct interface *iface);
-static void interface_module_up (struct interface *iface);
-static void interface_log (struct interface *iface, int level, const char *fmt, ...);
-static void interface_reset_timer_handler (struct interface *iface);
-static int interface_module_init (struct interface *iface);
-static void interface_module_free (struct interface *iface);
-static void interface_module_handler_event (struct interface *iface, int event);
-static void interface_module_handler_error (struct interface *iface);
-static int interface_dhcp_init (struct interface *iface);
-static void interface_dhcp_free (struct interface *iface);
-static void interface_dhcp_handler (struct interface *iface, int event);
-static void interface_remove_dependencies (struct interface *iface);
-static int interface_add_dependency (struct interface *iface, struct interface *dst);
-static void remove_dependency (struct dependency *d);
-static int interface_dependencies_satisfied (struct interface *iface);
-static void interface_satisfy_incoming_depenencies (struct interface *iface);
-static void interface_unsatisfy_incoming_depenencies (struct interface *iface);
-static int interface_configure_ipv4 (struct interface *iface);
-static void interface_deconfigure_ipv4 (struct interface *iface);
-static int interface_add_ipv4_addresses (struct interface *iface);
-static void interface_remove_ipv4_addresses (struct interface *iface);
-static int interface_add_ipv4_routes (struct interface *iface);
-static void interface_remove_ipv4_routes (struct interface *iface);
-static int interface_add_ipv4_dns_servers (struct interface *iface);
-static void interface_remove_ipv4_dns_servers (struct interface *iface);
-static int interface_add_ipv4_addr (struct interface *iface, struct ipv4_ifaddr ifaddr);
-static void interface_remove_ipv4_addr (struct interface *iface, struct ipv4_addr_entry *entry);
-static int interface_add_ipv4_route (struct interface *iface, struct ipv4_ifaddr dest, const uint32_t *gateway, int metric);
-static void interface_remove_ipv4_route (struct interface *iface, struct ipv4_route_entry *entry);
-static struct ipv4_addr_entry * interface_add_ipv4_addr_entry (struct interface *iface, struct ipv4_ifaddr ifaddr);
-static void interface_remove_ipv4_addr_entry (struct interface *iface, struct ipv4_addr_entry *entry);
-static struct ipv4_route_entry * interface_add_ipv4_route_entry (struct interface *iface, struct ipv4_ifaddr dest, const uint32_t *gateway, int metric);
-static void interface_remove_ipv4_route_entry (struct interface *iface, struct ipv4_route_entry *entry);
-static struct ipv4_dns_entry * interface_add_ipv4_dns_entry (struct interface *iface, uint32_t addr, int priority);
-static void interface_remove_ipv4_dns_entry (struct interface *iface, struct ipv4_dns_entry *entry);
+static int statement_init (struct statement *s, struct NCDConfig_statements *conf);
+static void statement_free (struct statement *s);
+static void statement_free_args (struct statement *s);
+static int process_new (struct NCDConfig_interfaces *conf);
+static void process_free (struct process *p);
+static void process_free_statements (struct process *p);
+static void process_assert_pointers (struct process *p);
+static void process_assert (struct process *p);
+static void process_log (struct process *p, int level, const char *fmt, ...);
+static void process_work (struct process *p);
+static void process_fight (struct process *p);
+static void process_advance (struct process *p);
+static void process_wait (struct process *p);
+static void process_wait_timer_handler (struct process *p);
+static void process_retreat (struct process *p);
+static void process_statement_log (struct process_statement *ps, int level, const char *fmt, ...);
+static void process_statement_set_error (struct process_statement *ps);
+static void process_statement_instance_handler_event (struct process_statement *ps, int event);
+static void process_statement_instance_handler_died (struct process_statement *ps, int is_error);
 
 int main (int argc, char **argv)
 {
@@ -302,23 +253,34 @@ int main (int argc, char **argv)
     // fee config file memory
     free(file);
     
-    // init interfaces list
-    LinkedList2_Init(&interfaces);
-    
-    // set no DNS servers
-    num_ipv4_dns_servers = 0;
-    if (!set_dns_servers()) {
-        BLog(BLOG_ERROR, "failed to set no DNS servers");
-        goto fail5;
+    // init modules
+    for (const struct NCDModule **m = ncd_modules; *m; m++) {
+        if ((*m)->func_globalinit && !(*m)->func_globalinit()) {
+            BLog(BLOG_ERROR, "globalinit failed for module %s", (*m)->type);
+            goto fail5;
+        }
     }
     
-    // init interfaces
-    load_interfaces(configuration);
+    // init processes list
+    LinkedList2_Init(&processes);
+    
+    // init processes
+    struct NCDConfig_interfaces *pc = configuration;
+    while (pc) {
+        process_new(pc);
+        pc = pc->next;
+    }
     
     // enter event loop
     BLog(BLOG_NOTICE, "entering event loop");
     BReactor_Exec(&ss);
     
+    // free processes
+    LinkedList2Node *n;
+    while (n = LinkedList2_GetFirst(&processes)) {
+        struct process *p = UPPER_OBJECT(n, struct process, list_node);
+        process_free(p);
+    }
 fail5:
     // free configuration
     NCDConfig_free_interfaces(configuration);
@@ -350,18 +312,17 @@ void terminate (void)
     
     terminating = 1;
     
-    work_terminate();
-}
-
-void work_terminate (void)
-{
-    ASSERT(terminating)
-    
-    if (LinkedList2_IsEmpty(&interfaces)) {
+    if (LinkedList2_IsEmpty(&processes)) {
         BReactor_Quit(&ss, 1);
-    } else {
-        struct interface *iface = UPPER_OBJECT(LinkedList2_GetLast(&interfaces), struct interface, list_node);
-        interface_terminate(iface);
+        return;
+    }
+    
+    LinkedList2Iterator it;
+    LinkedList2Iterator_InitForward(&it, &processes);
+    LinkedList2Node *n;
+    while (n = LinkedList2Iterator_Next(&it)) {
+        struct process *p = UPPER_OBJECT(n, struct process, list_node);
+        process_work(p);
     }
 }
 
@@ -520,1060 +481,574 @@ void signal_handler (void *unused)
     }
 }
 
-void load_interfaces (struct NCDConfig_interfaces *conf)
+int statement_init (struct statement *s, struct NCDConfig_statements *conf)
 {
-    while (conf) {
-        interface_init(conf);
-        conf = conf->next;
+    // find module
+    char *module_name = NCDConfig_concat_strings(conf->names);
+    if (!module_name) {
+        goto fail0;
     }
-}
-
-struct dns_sort_entry {
-    uint32_t addr;
-    int priority;
-};
-
-int set_dns_servers (void)
-{
-    // collect servers
+    const struct NCDModule **m;
+    for (m = ncd_modules; *m; m++) {
+        if (!strcmp(module_name, (*m)->type)) {
+            break;
+        }
+    }
+    if (!*m) {
+        BLog(BLOG_ERROR, "no module for statement %s", module_name);
+        free(module_name);
+        goto fail0;
+    }
+    free(module_name);
     
-    struct dns_sort_entry servers[num_ipv4_dns_servers];
-    size_t num_servers = 0;
+    // set module
+    s->module = *m;
     
-    LinkedList2Iterator if_it;
-    LinkedList2Iterator_InitForward(&if_it, &interfaces);
-    LinkedList2Node *if_node;
-    while (if_node = LinkedList2Iterator_Next(&if_it)) {
-        struct interface *iface = UPPER_OBJECT(if_node, struct interface, list_node);
+    // init arguments
+    s->first_arg = NULL;
+    struct argument_elem **prevptr = &s->first_arg;
+    struct NCDConfig_arguments *arg = conf->args;
+    while (arg) {
+        struct argument_elem *e = malloc(sizeof(*e));
+        if (!e) {
+            goto fail1;
+        }
         
-        LinkedList2Iterator serv_it;
-        LinkedList2Iterator_InitForward(&serv_it, &iface->ipv4_dns_servers);
-        LinkedList2Node *serv_node;
-        while (serv_node = LinkedList2Iterator_Next(&serv_it)) {
-            struct ipv4_dns_entry *dns = UPPER_OBJECT(serv_node, struct ipv4_dns_entry, list_node);
+        switch (arg->type) {
+            case NCDCONFIG_ARG_STRING: {
+                if (!NCDValue_InitString(&e->val, arg->string)) {
+                    free(e);
+                    goto fail1;
+                }
+                
+                e->is_var = 0;
+            } break;
             
-            servers[num_servers].addr = dns->addr;
-            servers[num_servers].priority= dns->priority;
-            num_servers++;
+            case NCDCONFIG_ARG_VAR: {
+                if (!(e->var.modname = strdup(arg->var->value))) {
+                    free(e);
+                    goto fail1;
+                }
+                
+                if (!arg->var->next) {
+                    e->var.varname = NULL;
+                } else {
+                    if (!(e->var.varname = NCDConfig_concat_strings(arg->var->next))) {
+                        free(e->var.modname);
+                        free(e);
+                        goto fail1;
+                    }
+                }
+                
+                e->is_var = 1;
+            } break;
+            
+            default:
+                ASSERT(0);
         }
+        
+        *prevptr = e;
+        e->next_arg = NULL;
+        prevptr = &e->next_arg;
+        
+        arg = arg->next;
     }
     
-    ASSERT(num_servers == num_ipv4_dns_servers)
-    
-    // sort by priority
-    qsort(servers, num_servers, sizeof(servers[0]), dns_qsort_comparator);
-    
-    // copy addresses into an array
-    uint32_t addrs[num_servers];
-    for (size_t i = 0; i < num_servers; i++) {
-        addrs[i] = servers[i].addr;
-    }
-    
-    // set servers
-    if (!NCDIfConfig_set_dns_servers(addrs, num_servers)) {
-        BLog(BLOG_ERROR, "failed to set DNS servers");
-        return 0;
+    // init name
+    if (!conf->name) {
+        s->name = NULL;
+    } else {
+        if (!(s->name = strdup(conf->name))) {
+            goto fail1;
+        }
     }
     
     return 1;
+    
+fail1:
+    statement_free_args(s);
+fail0:
+    return 0;
 }
 
-int dns_qsort_comparator (const void *v1, const void *v2)
+void statement_free (struct statement *s)
 {
-    const struct dns_sort_entry *e1 = v1;
-    const struct dns_sort_entry *e2 = v2;
+    // free name
+    free(s->name);
     
-    if (e1->priority < e2->priority) {
-        return -1;
-    }
-    if (e1->priority > e2->priority) {
-        return 1;
-    }
-    return 0;
+    // free arguments
+    statement_free_args(s);
 }
 
-struct interface * find_interface (const char *name)
+void statement_free_args (struct statement *s)
 {
-    LinkedList2Iterator it;
-    LinkedList2Iterator_InitForward(&it, &interfaces);
-    LinkedList2Node *node;
-    while (node = LinkedList2Iterator_Next(&it)) {
-        struct interface *iface = UPPER_OBJECT(node, struct interface, list_node);
-        if (!strcmp(iface->conf->name, name)) {
-            LinkedList2Iterator_Free(&it);
-            return iface;
+    struct argument_elem *e = s->first_arg;
+    while (e) {
+        if (e->is_var) {
+            free(e->var.modname);
+            free(e->var.varname);
+        } else {
+            NCDValue_Free(&e->val);
         }
+        struct argument_elem *n = e->next_arg;
+        free(e);
+        e = n;
     }
-    
-    return NULL;
 }
 
-int interface_init (struct NCDConfig_interfaces *conf)
+int process_new (struct NCDConfig_interfaces *conf)
 {
-    // check for existing interface
-    if (find_interface(conf->name)) {
-        BLog(BLOG_ERROR, "interface %s already exists", conf->name);
-        goto fail0;
-    }
-    
-    // allocate interface entry
-    struct interface *iface = malloc(sizeof(*iface));
-    if (!iface) {
-        BLog(BLOG_ERROR, "malloc failed for interface %s", conf->name);
+    // allocate strucure
+    struct process *p = malloc(sizeof(*p));
+    if (!p) {
         goto fail0;
     }
     
-    // set conf
-    iface->conf = conf;
-    
-    // find interface module
-    struct NCDConfig_statements *type_st = NCDConfig_find_statement(conf->statements, "type");
-    if (!type_st) {
-        interface_log(iface, BLOG_ERROR, "missing type");
-        goto fail1;
-    }
-    char *type;
-    if (!NCDConfig_statement_has_one_arg(type_st, &type)) {
-        interface_log(iface, BLOG_ERROR, "type: wrong arity");
-        goto fail1;
-    }
-    const struct NCDInterfaceModule **m;
-    for (m = interface_modules; *m; m++) {
-        if (!strcmp((*m)->type, type)) {
-            break;
-        }
-    }
-    if (!*m) {
-        interface_log(iface, BLOG_ERROR, "type: unknown value");
+    // init name
+    if (!(p->name = strdup(conf->name))) {
         goto fail1;
     }
-    iface->module = *m;
     
-    // init reset timer
-    BTimer_Init(&iface->reset_timer, INTERFACE_RETRY_TIME, (BTimer_handler)interface_reset_timer_handler, iface);
+    // count statements
+    size_t num_st = 0;
+    struct NCDConfig_statements *st = conf->statements;
+    while (st) {
+        num_st++;
+        st = st->next;
+    }
     
-    // init outgoing dependencies list
-    LinkedList2_Init(&iface->deps_out);
+    // statements array
+    if (!(p->statements = BAllocArray(num_st, sizeof(p->statements[0])))) {
+        goto fail2;
+    }
+    p->num_statements = 0;
     
-    // init outgoing dependencies
-    struct NCDConfig_statements *need_st = conf->statements;
-    while (need_st = NCDConfig_find_statement(need_st, "need")) {
-        char *need_ifname;
-        if (!NCDConfig_statement_has_one_arg(need_st, &need_ifname)) {
-            interface_log(iface, BLOG_ERROR, "need: wrong arity");
-            goto fail2;
-        }
+    // init statements
+    st = conf->statements;
+    while (st) {
+        struct process_statement *ps = &p->statements[p->num_statements];
         
-        struct interface *need_if = find_interface(need_ifname);
-        if (!need_if) {
-            interface_log(iface, BLOG_ERROR, "need: %s: unknown interface", need_ifname);
-            goto fail2;
-        }
+        ps->p = p;
+        ps->i = p->num_statements;
         
-        if (!interface_add_dependency(iface, need_if)) {
-            interface_log(iface, BLOG_ERROR, "need: %s: failed to add dependency", need_ifname);
-            goto fail2;
+        if (!statement_init(&ps->s, st)) {
+            goto fail3;
         }
         
-        need_st = need_st->next;
+        ps->state = SSTATE_FORGOTTEN;
+        
+        ps->have_error = 0;
+        
+        p->num_statements++;
+        
+        st = st->next;
     }
     
-    // init incoming dependencies list
-    LinkedList2_Init(&iface->deps_in);
+    // set AP=0
+    p->ap = 0;
     
-    // init terminating module job
-    BPending_Init(&iface->terminating_module_job, BReactor_PendingGroup(&ss), (BPending_handler)interface_terminating_module_job_handler, iface);
+    // set FP=0
+    p->fp = 0;
     
-    // set no module instance
-    iface->have_module_instance = 0;
+    // init timer
+    BTimer_Init(&p->wait_timer, RETRY_TIME, (BTimer_handler)process_wait_timer_handler, p);
     
-    // set no DHCP
-    iface->have_dhcp = 0;
+    // insert to processes list
+    LinkedList2_Append(&processes, &p->list_node);
     
-    // init ipv4 addresses list
-    LinkedList2_Init(&iface->ipv4_addresses);
-    
-    // init ipv4 routes list
-    LinkedList2_Init(&iface->ipv4_routes);
-    
-    // init ipv4 dns servers list
-    LinkedList2_Init(&iface->ipv4_dns_servers);
-    
-    // insert to interfaces list
-    LinkedList2_Append(&interfaces, &iface->list_node);
-    
-    interface_start(iface);
+    process_work(p);
     
     return 1;
     
+fail3:
+    process_free_statements(p);
 fail2:
-    interface_remove_dependencies(iface);
+    free(p->name);
 fail1:
-    free(iface);
+    free(p);
 fail0:
     return 0;
 }
 
-void interface_terminate (struct interface *iface)
-{
-    ASSERT(terminating)
-    ASSERT(LinkedList2_IsEmpty(&iface->deps_in))
-    
-    // stop reset timer
-    BReactor_RemoveTimer(&ss, &iface->reset_timer);
-    
-    // deconfigure IPv4
-    interface_deconfigure_ipv4(iface);
-    
-    // free DHCP
-    if (iface->have_dhcp) {
-        interface_dhcp_free(iface);
-    }
-    
-    // finish module instance
-    if (iface->have_module_instance) {
-        if (!iface->module_instance_finishing) {
-            NCDInterfaceModuleInst_Finish(&iface->module_instance);
-            iface->module_instance_finishing = 1;
-        }
-        
-        interface_log(iface, BLOG_INFO, "waiting for module to finish");
-        
-        // wait for module to finish
-        iface->state = INTERFACE_STATE_TERMINATING;
-    } else {
-        // continue now
-        BPending_Set(&iface->terminating_module_job);
-    }
-}
-
-void interface_terminating_module_job_handler (struct interface *iface)
+void process_free (struct process *p)
 {
-    ASSERT(terminating)
-    ASSERT(!BTimer_IsRunning(&iface->reset_timer))
-    ASSERT(LinkedList2_IsEmpty(&iface->ipv4_addresses))
-    ASSERT(LinkedList2_IsEmpty(&iface->ipv4_routes))
-    ASSERT(LinkedList2_IsEmpty(&iface->ipv4_dns_servers))
-    ASSERT(!iface->have_dhcp)
-    ASSERT(!iface->have_module_instance)
+    ASSERT(p->ap == 0)
+    ASSERT(p->fp == 0)
     
-    // free terminating module job
-    BPending_Free(&iface->terminating_module_job);
+    // remove from processes list
+    LinkedList2_Remove(&processes, &p->list_node);
     
-    // remove from interfaces list
-    LinkedList2_Remove(&interfaces, &iface->list_node);
+    // free timer
+    BReactor_RemoveTimer(&ss, &p->wait_timer);
     
-    // remove outgoing dependencies
-    interface_remove_dependencies(iface);
+    // free statements
+    process_free_statements(p);
     
-    // free memory
-    free(iface);
+    // free name
+    free(p->name);
     
-    // continue terminating
-    work_terminate();
+    // free strucure
+    free(p);
 }
 
-void interface_down_to (struct interface *iface, int state)
+void process_free_statements (struct process *p)
 {
-    ASSERT(state >= INTERFACE_STATE_WAITDEPS)
-    
-    if (state < INTERFACE_STATE_FINISHED) {
-        // unsatisfy incoming dependencies
-        interface_unsatisfy_incoming_depenencies(iface);
-        
-        // deconfigure IPv4
-        interface_deconfigure_ipv4(iface);
-    }
-    
-    // deconfigure DHCP
-    if (state < INTERFACE_STATE_DHCP && iface->have_dhcp) {
-        interface_dhcp_free(iface);
-    }
-    
-    // finish module instance
-    if (state < INTERFACE_STATE_WAITMODULE && iface->have_module_instance) {
-        if (iface->module_instance_error) {
-            interface_module_free(iface);
-        }
-        else if (!iface->module_instance_finishing) {
-            NCDInterfaceModuleInst_Finish(&iface->module_instance);
-            iface->module_instance_finishing = 1;
-        }
+    // free statments
+    for (size_t i = 0; i < p->num_statements; i++) {
+        struct process_statement *ps = &p->statements[i];
+        statement_free(&ps->s);
     }
     
-    // start/stop reset timer
-    if (state == INTERFACE_STATE_RESETTING) {
-        BReactor_SetTimer(&ss, &iface->reset_timer);
-    } else {
-        BReactor_RemoveTimer(&ss, &iface->reset_timer);
-    }
-    
-    // set state
-    iface->state = state;
-}
-
-void interface_reset (struct interface *iface)
-{
-    interface_log(iface, BLOG_INFO, "will try again later");
-    
-    interface_down_to(iface, INTERFACE_STATE_RESETTING);
+    // free stataments array
+    free(p->statements);
 }
 
-void interface_start (struct interface *iface)
+void process_assert_pointers (struct process *p)
 {
-    ASSERT(!BTimer_IsRunning(&iface->reset_timer))
-    ASSERT(!iface->have_dhcp)
-    ASSERT(LinkedList2_IsEmpty(&iface->ipv4_addresses))
-    ASSERT(LinkedList2_IsEmpty(&iface->ipv4_routes))
-    ASSERT(LinkedList2_IsEmpty(&iface->ipv4_dns_servers))
-    
-    if (iface->have_module_instance) {
-        ASSERT(iface->module_instance_finishing)
-        
-        interface_log(iface, BLOG_ERROR, "module still finishing");
-        return;
-    }
-    
-    // check dependencies
-    if (!interface_dependencies_satisfied(iface)) {
-        interface_log(iface, BLOG_INFO, "waiting for dependencies");
-        
-        // waiting for dependencies
-        iface->state = INTERFACE_STATE_WAITDEPS;
-        return;
+    ASSERT(p->ap <= p->num_statements)
+    ASSERT(p->fp >= p->ap)
+    ASSERT(p->fp <= p->num_statements)
+    
+    // check AP
+    for (size_t i = 0; i < p->ap; i++) {
+        if (i == p->ap - 1) {
+            ASSERT(p->statements[i].state == SSTATE_ADULT || p->statements[i].state == SSTATE_CHILD)
+        } else {
+            ASSERT(p->statements[i].state == SSTATE_ADULT)
+        }
     }
     
-    // init module
-    if (!interface_module_init(iface)) {
-        goto fail;
+    // check FP
+    size_t fp = p->num_statements;
+    while (fp > 0 && p->statements[fp - 1].state == SSTATE_FORGOTTEN) {
+        fp--;
     }
-    
-    interface_log(iface, BLOG_INFO, "waiting for module");
-    
-    // waiting for module
-    iface->state = INTERFACE_STATE_WAITMODULE;
-    
-    return;
-    
-fail:
-    interface_reset(iface);
+    ASSERT(p->fp == fp)
 }
 
-void interface_module_up (struct interface *iface)
+void process_assert (struct process *p)
 {
-    ASSERT(iface->have_module_instance)
-    ASSERT(!iface->have_dhcp)
-    ASSERT(LinkedList2_IsEmpty(&iface->ipv4_addresses))
-    ASSERT(LinkedList2_IsEmpty(&iface->ipv4_routes))
-    ASSERT(LinkedList2_IsEmpty(&iface->ipv4_dns_servers))
-    
-    // check for DHCP
-    struct NCDConfig_statements *dhcp_st = NCDConfig_find_statement(iface->conf->statements, "dhcp");
-    if (dhcp_st) {
-        if (dhcp_st->args) {
-            interface_log(iface, BLOG_ERROR, "dhcp: wrong arity");
-            goto fail;
-        }
-        
-        // init DHCP client
-        if (!interface_dhcp_init(iface)) {
-            goto fail;
-        }
-        
-        // set state
-        iface->state = INTERFACE_STATE_DHCP;
-        
-        return;
-    }
-    
-    // configure IPv4
-    if (!interface_configure_ipv4(iface)) {
-        goto fail;
-    }
-    
-    // set state finished
-    iface->state = INTERFACE_STATE_FINISHED;
-    
-    // satisfy incoming dependencies
-    interface_satisfy_incoming_depenencies(iface);
-    
-    return;
-    
-fail:
-    interface_reset(iface);
+    process_assert_pointers(p);
 }
 
-void interface_log (struct interface *iface, int level, const char *fmt, ...)
+void process_log (struct process *p, int level, const char *fmt, ...)
 {
     va_list vl;
     va_start(vl, fmt);
-    BLog_Append("interface %s: ", iface->conf->name);
+    BLog_Append("process %s: ", p->name);
     BLog_LogToChannelVarArg(BLOG_CURRENT_CHANNEL, level, fmt, vl);
     va_end(vl);
 }
 
-void interface_reset_timer_handler (struct interface *iface)
+void process_work (struct process *p)
 {
-    ASSERT(iface->state == INTERFACE_STATE_RESETTING)
+    process_assert_pointers(p);
     
-    // start interface
-    interface_start(iface);
-}
-
-int interface_module_init (struct interface *iface)
-{
-    ASSERT(!iface->have_module_instance)
+    // stop timer in case we were WAITING
+    BReactor_RemoveTimer(&ss, &p->wait_timer);
     
-    if (!NCDInterfaceModuleInst_Init(
-        &iface->module_instance, iface->module, &ss, &manager, iface->conf,
-        (NCDInterfaceModule_handler_event)interface_module_handler_event,
-        (NCDInterfaceModule_handler_error)interface_module_handler_error,
-        iface
-    )) {
-        interface_log(iface, BLOG_ERROR, "failed to initialize module instance");
-        return 0;
+    if (terminating) {
+        process_retreat(p);
+        return;
     }
     
-    iface->have_module_instance = 1;
-    iface->module_instance_finishing = 0;
-    iface->module_instance_error = 0;
-    
-    return 1;
+    process_fight(p);
 }
 
-void interface_module_free (struct interface *iface)
+void process_fight (struct process *p)
 {
-    ASSERT(iface->have_module_instance)
-    
-    NCDInterfaceModuleInst_Free(&iface->module_instance);
-    
-    iface->have_module_instance = 0;
-}
-
-void interface_module_handler_event (struct interface *iface, int event)
-{
-    ASSERT(iface->have_module_instance)
-    ASSERT(iface->state >= INTERFACE_STATE_WAITMODULE)
-    
-    switch (event) {
-        case NCDINTERFACEMODULE_EVENT_UP: {
-            ASSERT(iface->state == INTERFACE_STATE_WAITMODULE)
-            
-            interface_log(iface, BLOG_INFO, "module up");
-            
-            interface_module_up(iface);
-        } break;
-        
-        case NCDINTERFACEMODULE_EVENT_DOWN: {
-            ASSERT(iface->state > INTERFACE_STATE_WAITMODULE)
-            
-            interface_log(iface, BLOG_INFO, "module down");
-            
-            interface_down_to(iface, INTERFACE_STATE_WAITMODULE);
-        } break;
+    if (p->ap == p->fp) {
+        if (!(p->ap > 0 && p->statements[p->ap - 1].state == SSTATE_CHILD)) {
+            // advance
+            process_advance(p);
+        }
         
-        default:
-            ASSERT(0);
+        return;
     }
-}
-
-void interface_module_handler_error (struct interface *iface)
-{
-    ASSERT(iface->have_module_instance)
-    ASSERT(!iface->module_instance_error)
     
-    if (iface->state >= INTERFACE_STATE_WAITMODULE) {
-        interface_log(iface, BLOG_INFO, "module error");
-        
-        // set module error so it will be freed
-        iface->module_instance_error = 1;
-        
-        interface_reset(iface);
-    } else {
-        interface_log(iface, BLOG_INFO, "module finished");
+    // order the last living statement to die, if needed
+    struct process_statement *ps = &p->statements[p->fp - 1];
+    if (ps->state != SSTATE_DYING) {
+        process_statement_log(ps, BLOG_INFO, "killing");
         
-        // free module
-        interface_module_free(iface);
+        // order it to die
+        NCDModuleInst_Die(&ps->inst);
         
-        if (iface->state >= INTERFACE_STATE_WAITDEPS) {
-            if (
-                iface->state == INTERFACE_STATE_WAITDEPS ||
-                (iface->state == INTERFACE_STATE_RESETTING && !BTimer_IsRunning(&iface->reset_timer))
-            ) {
-                interface_start(iface);
-            }
-        }
-        else { // INTERFACE_STATE_TERMINATING
-            BPending_Set(&iface->terminating_module_job);
-        }
+        // set statement state DYING
+        ps->state = SSTATE_DYING;
     }
+    
+    process_assert(p);
 }
 
-static int interface_dhcp_init (struct interface *iface)
+void process_advance (struct process *p)
 {
-    ASSERT(!iface->have_dhcp)
+    ASSERT(p->ap == p->fp)
+    ASSERT(!(p->ap > 0) || p->statements[p->ap - 1].state == SSTATE_ADULT)
     
-    if (!BDHCPClient_Init(&iface->dhcp, iface->conf->name, &ss, (BDHCPClient_handler)interface_dhcp_handler, iface)) {
-        interface_log(iface, BLOG_ERROR, "BDHCPClient_Init failed");
-        return 0;
+    if (p->ap == p->num_statements) {
+        process_log(p, BLOG_INFO, "victory");
+        
+        process_assert(p);
+        return;
     }
     
-    iface->have_dhcp = 1;
+    struct process_statement *ps = &p->statements[p->ap];
     
-    return 1;
-}
-
-static void interface_dhcp_free (struct interface *iface)
-{
-    ASSERT(iface->have_dhcp)
+    // check if we need to wait
+    if (ps->have_error && ps->error_until > btime_gettime()) {
+        process_wait(p);
+        return;
+    }
     
-    BDHCPClient_Free(&iface->dhcp);
+    process_statement_log(ps, BLOG_INFO, "initializing");
     
-    iface->have_dhcp = 0;
-}
-
-void interface_dhcp_handler (struct interface *iface, int event)
-{
-    ASSERT(iface->have_dhcp)
+    // init arguments list
+    NCDValue_InitList(&ps->inst_args);
     
-    switch (event) {
-        case BDHCPCLIENT_EVENT_UP: {
-            ASSERT(iface->state == INTERFACE_STATE_DHCP)
-            
-            interface_log(iface, BLOG_INFO, "DHCP up");
-            
-            // configure IPv4
-            if (!interface_configure_ipv4(iface)) {
-                goto fail;
-            }
-            
-            // set state
-            iface->state = INTERFACE_STATE_FINISHED;
-            
-            // satisfy incoming dependencies
-            interface_satisfy_incoming_depenencies(iface);
-            
-            return;
-            
-        fail:
-            interface_reset(iface);
-        } break;
+    // build arguments
+    struct argument_elem *arg = ps->s.first_arg;
+    while (arg) {
+        NCDValue v;
         
-        case BDHCPCLIENT_EVENT_DOWN: {
-            ASSERT(iface->state == INTERFACE_STATE_FINISHED)
-            
-            interface_log(iface, BLOG_INFO, "DHCP down");
+        if (arg->is_var) {
+            // find referred-to statement
+            struct process_statement *rps;
+            size_t i;
+            for (i = p->ap; i > 0; i--) {
+                rps = &p->statements[i - 1];
+                if (rps->s.name && !strcmp(rps->s.name, arg->var.modname)) {
+                    break;
+                }
+            }
+            if (i == 0) {
+                process_statement_log(ps, BLOG_ERROR, "unknown statement name in variable: %s.%s", arg->var.modname, arg->var.varname);
+                goto fail1;
+            }
+            ASSERT(rps->state == SSTATE_ADULT)
             
-            interface_down_to(iface, INTERFACE_STATE_DHCP);
-        } break;
+            // resolve variable
+            const char *real_varname = (arg->var.varname ? arg->var.varname : "");
+            if (!NCDModuleInst_GetVar(&rps->inst, real_varname, &v)) {
+                process_statement_log(ps, BLOG_ERROR, "failed to resolve variable: %s.%s", arg->var.modname, real_varname);
+                goto fail1;
+            }
+        } else {
+            if (!NCDValue_InitCopy(&v, &arg->val)) {
+                process_statement_log(ps, BLOG_ERROR, "NCDValue_InitCopy failed");
+                goto fail1;
+            }
+        }
         
-        default:
-            ASSERT(0);
-    }
-}
-
-void interface_remove_dependencies (struct interface *iface)
-{
-    LinkedList2Node *node;
-    while (node = LinkedList2_GetFirst(&iface->deps_out)) {
-        struct dependency *d = UPPER_OBJECT(node, struct dependency, src_node);
-        ASSERT(d->src == iface)
+        // move to list
+        if (!NCDValue_ListAppend(&ps->inst_args, v)) {
+            process_statement_log(ps, BLOG_ERROR, "NCDValue_ListAppend failed");
+            NCDValue_Free(&v);
+            goto fail1;
+        }
         
-        remove_dependency(d);
-    }
-}
-
-int interface_add_dependency (struct interface *iface, struct interface *dst_iface)
-{
-    // allocate entry
-    struct dependency *d = malloc(sizeof(*d));
-    if (!d) {
-        return 0;
+        arg = arg->next_arg;
     }
     
-    // set src and dst
-    d->src = iface;
-    d->dst = dst_iface;
+    // generate log prefix
+    snprintf(ps->logprefix, sizeof(ps->logprefix), "process %s: statement %zu: module: ", p->name, ps->i);
     
-    // insert to src list
-    LinkedList2_Append(&iface->deps_out, &d->src_node);
+    // initialize module instance
+    if (!NCDModuleInst_Init(
+        &ps->inst, ps->s.name, ps->s.module, &ps->inst_args, ps->logprefix, &ss, &manager,
+        (NCDModule_handler_event)process_statement_instance_handler_event, (NCDModule_handler_died)process_statement_instance_handler_died, ps
+    )) {
+        process_statement_log(ps, BLOG_ERROR, "failed to initialize");
+        goto fail1;
+    }
     
-    // insert to dst list
-    LinkedList2_Append(&dst_iface->deps_in, &d->dst_node);
+    // set statement state CHILD
+    ps->state = SSTATE_CHILD;
     
-    return 1;
-}
-
-void remove_dependency (struct dependency *d)
-{
-    // remove from dst list
-    LinkedList2_Remove(&d->dst->deps_in, &d->dst_node);
+    // increment AP
+    p->ap++;
     
-    // remove from src list
-    LinkedList2_Remove(&d->src->deps_out, &d->src_node);
+    // increment FP
+    p->fp++;
     
-    // free entry
-    free(d);
-}
-
-int interface_dependencies_satisfied (struct interface *iface)
-{
-    LinkedList2Iterator it;
-    LinkedList2Iterator_InitForward(&it, &iface->deps_out);
-    LinkedList2Node *node;
-    while (node = LinkedList2Iterator_Next(&it)) {
-        struct dependency *d = UPPER_OBJECT(node, struct dependency, src_node);
-        ASSERT(d->src == iface)
-        
-        if (d->dst->state != INTERFACE_STATE_FINISHED) {
-            LinkedList2Iterator_Free(&it);
-            return 0;
-        }
-    }
+    process_assert(p);
     
-    return 1;
-}
-
-void interface_satisfy_incoming_depenencies (struct interface *iface)
-{
-    LinkedList2Iterator it;
-    LinkedList2Iterator_InitForward(&it, &iface->deps_in);
-    LinkedList2Node *node;
-    while (node = LinkedList2Iterator_Next(&it)) {
-        struct dependency *d = UPPER_OBJECT(node, struct dependency, dst_node);
-        ASSERT(d->dst == iface)
-        
-        if (d->src->state == INTERFACE_STATE_WAITDEPS) {
-            interface_log(d->src, BLOG_INFO, "dependency %s satisfied", iface->conf->name);
-            
-            interface_start(d->src);
-        }
-    }
-}
-
-void interface_unsatisfy_incoming_depenencies (struct interface *iface)
-{
-    LinkedList2Iterator it;
-    LinkedList2Iterator_InitForward(&it, &iface->deps_in);
-    LinkedList2Node *node;
-    while (node = LinkedList2Iterator_Next(&it)) {
-        struct dependency *d = UPPER_OBJECT(node, struct dependency, dst_node);
-        ASSERT(d->dst == iface)
-        
-        if (d->src->state > INTERFACE_STATE_WAITDEPS) {
-            interface_log(d->src, BLOG_INFO, "dependency %s no longer satisfied", iface->conf->name);
-            
-            interface_down_to(d->src, INTERFACE_STATE_WAITDEPS);
-        }
-    }
+    return;
+    
+fail1:
+    NCDValue_Free(&ps->inst_args);
+    process_statement_set_error(ps);
+    process_wait(p);
 }
 
-int interface_configure_ipv4 (struct interface *iface)
+void process_wait (struct process *p)
 {
-    ASSERT(!(iface->have_dhcp) || BDHCPClient_IsUp(&iface->dhcp))
-    ASSERT(LinkedList2_IsEmpty(&iface->ipv4_addresses))
-    ASSERT(LinkedList2_IsEmpty(&iface->ipv4_routes))
-    ASSERT(LinkedList2_IsEmpty(&iface->ipv4_dns_servers))
+    ASSERT(p->ap == p->fp)
+    ASSERT(!(p->ap > 0) || p->statements[p->ap - 1].state == SSTATE_ADULT)
+    ASSERT(p->ap < p->num_statements)
+    ASSERT(p->statements[p->ap].have_error)
     
-    // configure addresses
-    if (!interface_add_ipv4_addresses(iface)) {
-        return 0;
-    }
+    process_statement_log(&p->statements[p->ap], BLOG_INFO, "waiting after error");
     
-    // configure routes
-    if (!interface_add_ipv4_routes(iface)) {
-        return 0;
-    }
-    
-    // configure DNS servers
-    if (!interface_add_ipv4_dns_servers(iface)) {
-        return 0;
-    }
+    // set timer
+    BReactor_SetTimerAbsolute(&ss, &p->wait_timer, p->statements[p->ap].error_until);
     
-    return 1;
+    process_assert(p);
 }
 
-void interface_deconfigure_ipv4 (struct interface *iface)
+void process_wait_timer_handler (struct process *p)
 {
-    // remove DNS servers
-    interface_remove_ipv4_dns_servers(iface);
+    ASSERT(p->ap == p->fp)
+    ASSERT(!(p->ap > 0) || p->statements[p->ap - 1].state == SSTATE_ADULT)
+    ASSERT(p->ap < p->num_statements)
+    ASSERT(p->statements[p->ap].have_error)
+    
+    process_log(p, BLOG_INFO, "retrying");
     
-    // remove routes
-    interface_remove_ipv4_routes(iface);
+    // clear error
+    p->statements[p->ap].have_error = 0;
     
-    // remove addresses
-    interface_remove_ipv4_addresses(iface);
+    process_advance(p);
 }
 
-int interface_add_ipv4_addresses (struct interface *iface)
+void process_retreat (struct process *p)
 {
-    ASSERT(!(iface->have_dhcp) || BDHCPClient_IsUp(&iface->dhcp))
-    ASSERT(LinkedList2_IsEmpty(&iface->ipv4_addresses))
-    
-    struct NCDConfig_statements *st = iface->conf->statements;
-    
-    while (st = NCDConfig_find_statement(st, "ipv4.addr")) {
-        // get address string
-        char *addrstr;
-        if (!NCDConfig_statement_has_one_arg(st, &addrstr)) {
-            interface_log(iface, BLOG_ERROR, "ipv4.addr: wrong arity");
-            return 0;
-        }
-        
-        struct ipv4_ifaddr ifaddr;
-        
-        // is this a DHCP address?
-        if (!strcmp(addrstr, "dhcp_addr")) {
-            // check if DHCP is enabled
-            if (!iface->have_dhcp) {
-                interface_log(iface, BLOG_ERROR, "ipv4.addr: %s: DHCP not enabled", addrstr);
-                return 0;
-            }
-            
-            // get address and mask
-            uint32_t addr;
-            BDHCPClient_GetClientIP(&iface->dhcp, &addr);
-            uint32_t mask;
-            BDHCPClient_GetClientMask(&iface->dhcp, &mask);
-            
-            // convert to ifaddr
-            if (!ipaddr_ipv4_ifaddr_from_addr_mask(addr, mask, &ifaddr)) {
-                interface_log(iface, BLOG_ERROR, "ipv4.addr: %s: wrong mask", addrstr);
-                return 0;
-            }
-        } else {
-            // parse address string
-            if (!ipaddr_parse_ipv4_ifaddr(addrstr, &ifaddr)) {
-                interface_log(iface, BLOG_ERROR, "ipv4.addr: %s: wrong format", addrstr);
-                return 0;
-            }
-        }
+    if (p->fp == 0) {
+        // finished retreating
+        process_free(p);
         
-        // add this address
-        if (!interface_add_ipv4_addr(iface, ifaddr)) {
-            interface_log(iface, BLOG_ERROR, "ipv4.addr: %s: failed to add", addrstr);
-            return 0;
+        // if there are no more processes, exit program
+        if (LinkedList2_IsEmpty(&processes)) {
+            BReactor_Quit(&ss, 1);
         }
         
-        st = st->next;
+        return;
     }
     
-    return 1;
-}
-
-void interface_remove_ipv4_addresses (struct interface *iface)
-{
-    LinkedList2Node *node;
-    while (node = LinkedList2_GetLast(&iface->ipv4_addresses)) {
-        struct ipv4_addr_entry *entry = UPPER_OBJECT(node, struct ipv4_addr_entry, list_node);
-        interface_remove_ipv4_addr(iface, entry);
-    }
-}
-
-int interface_add_ipv4_routes (struct interface *iface)
-{
-    ASSERT(!(iface->have_dhcp) || BDHCPClient_IsUp(&iface->dhcp))
-    ASSERT(LinkedList2_IsEmpty(&iface->ipv4_routes))
-    
-    struct NCDConfig_statements *st = iface->conf->statements;
-    
-    while (st = NCDConfig_find_statement(st, "ipv4.route")) {
-        // read statement
-        char *dest_str;
-        char *gateway_str;
-        char *metric_str;
-        if (!NCDConfig_statement_has_three_args(st, &dest_str, &gateway_str, &metric_str)) {
-            interface_log(iface, BLOG_ERROR, "ipv4.route: wrong arity");
-            return 0;
-        }
-        
-        // parse dest string
-        struct ipv4_ifaddr dest;
-        if (!ipaddr_parse_ipv4_ifaddr(dest_str, &dest)) {
-            interface_log(iface, BLOG_ERROR, "ipv4.route: (%s,%s,%s): dest: wrong format", dest_str, gateway_str, metric_str);
-            return 0;
-        }
-        
-        int have_gateway;
-        uint32_t gateway;
+    // order the last living statement to die, if needed
+    struct process_statement *ps = &p->statements[p->fp - 1];
+    if (ps->state != SSTATE_DYING) {
+        process_statement_log(ps, BLOG_INFO, "killing");
         
-        // is gateway a DHCP router?
-        if (!strcmp(gateway_str, "dhcp_router")) {
-            // check if DHCP is enabled
-            if (!iface->have_dhcp) {
-                interface_log(iface, BLOG_ERROR, "ipv4.route: (%s,%s,%s): gateway: DHCP not enabled", dest_str, gateway_str, metric_str);
-                return 0;
-            }
-            
-            // obtain gateway from DHCP
-            if (!BDHCPClient_GetRouter(&iface->dhcp, &gateway)) {
-                interface_log(iface, BLOG_ERROR, "ipv4.route: (%s,%s,%s): gateway: DHCP did not provide a router", dest_str, gateway_str, metric_str);
-                return 0;
-            }
-            
-            have_gateway = 1;
-        }
-        else if (!strcmp(gateway_str, "none")) {
-            // no gateway
-            have_gateway = 0;
-        }
-        else {
-            // parse gateway string
-            if (!ipaddr_parse_ipv4_addr(gateway_str, strlen(gateway_str), &gateway)) {
-                interface_log(iface, BLOG_ERROR, "ipv4.route: (%s,%s,%s): gateway: wrong format", dest_str, gateway_str, metric_str);
-                return 0;
-            }
-            
-            have_gateway = 1;
-        }
+        // order it to die
+        NCDModuleInst_Die(&ps->inst);
         
-        // parse metric string
-        int metric = atoi(metric_str);
+        // set statement state DYING
+        ps->state = SSTATE_DYING;
         
-        // add this address
-        if (!interface_add_ipv4_route(iface, dest, (have_gateway ? &gateway : NULL), metric)) {
-            interface_log(iface, BLOG_ERROR, "ipv4.route: (%s,%s,%s): failed to add", dest_str, gateway_str, metric_str);
-            return 0;
+        // update AP
+        if (p->ap > ps->i) {
+            p->ap = ps->i;
         }
-        
-        st = st->next;
     }
     
-    return 1;
+    process_assert(p);
 }
 
-void interface_remove_ipv4_routes (struct interface *iface)
+void process_statement_log (struct process_statement *ps, int level, const char *fmt, ...)
 {
-    LinkedList2Node *node;
-    while (node = LinkedList2_GetLast(&iface->ipv4_routes)) {
-        struct ipv4_route_entry *entry = UPPER_OBJECT(node, struct ipv4_route_entry, list_node);
-        interface_remove_ipv4_route(iface, entry);
-    }
+    va_list vl;
+    va_start(vl, fmt);
+    BLog_Append("process %s: statement %zu: ", ps->p->name, ps->i);
+    BLog_LogToChannelVarArg(BLOG_CURRENT_CHANNEL, level, fmt, vl);
+    va_end(vl);
 }
 
-int interface_add_ipv4_dns_servers (struct interface *iface)
+void process_statement_set_error (struct process_statement *ps)
 {
-    ASSERT(!(iface->have_dhcp) || BDHCPClient_IsUp(&iface->dhcp))
-    ASSERT(LinkedList2_IsEmpty(&iface->ipv4_dns_servers))
+    ASSERT(ps->state == SSTATE_FORGOTTEN)
     
-    // read servers into entries
+    ps->have_error = 1;
+    ps->error_until = btime_gettime() + RETRY_TIME;
+}
+
+void process_statement_instance_handler_event (struct process_statement *ps, int event)
+{
+    ASSERT(ps->state == SSTATE_CHILD || ps->state == SSTATE_ADULT)
     
-    struct NCDConfig_statements *st = iface->conf->statements;
+    struct process *p = ps->p;
     
-    while (st = NCDConfig_find_statement(st, "ipv4.dns")) {
-        // read statement
-        char *addr_str;
-        char *priority_str;
-        if (!NCDConfig_statement_has_two_args(st, &addr_str, &priority_str)) {
-            interface_log(iface, BLOG_ERROR, "ipv4.dns: wrong arity");
-            return 0;
-        }
-        
-        // parse priority string
-        int priority = atoi(priority_str);
+    switch (event) {
+        case NCDMODULE_EVENT_UP: {
+            ASSERT(ps->state == SSTATE_CHILD)
+            
+            process_statement_log(ps, BLOG_INFO, "up");
+            
+            // set state ADULT
+            ps->state = SSTATE_ADULT;
+        } break;
         
-        // are these DHCP DNS servers?
-        if (!strcmp(addr_str, "dhcp_dns_servers")) {
-            // get servers from DHCP
-            uint32_t addrs[BDHCPCLIENT_MAX_DOMAIN_NAME_SERVERS];
-            int num_addrs = BDHCPClient_GetDNS(&iface->dhcp, addrs, BDHCPCLIENT_MAX_DOMAIN_NAME_SERVERS);
+        case NCDMODULE_EVENT_DOWN: {
+            ASSERT(ps->state == SSTATE_ADULT)
             
-            // add entries
-            for (int i = 0; i < num_addrs; i++) {
-                // add entry
-                if (!interface_add_ipv4_dns_entry(iface, addrs[i], priority)) {
-                    interface_log(iface, BLOG_ERROR, "ipv4.dns: (%s,%s): failed to add entry", addr_str, priority_str);
-                    return 0;
-                }
-            }
-        } else {
-            // parse addr string
-            uint32_t addr;
-            if (!ipaddr_parse_ipv4_addr(addr_str, strlen(addr_str), &addr)) {
-                interface_log(iface, BLOG_ERROR, "ipv4.dns: (%s,%s): addr: wrong format", addr_str, priority_str);
-                return 0;
-            }
+            process_statement_log(ps, BLOG_INFO, "down");
             
-            // add entry
-            if (!interface_add_ipv4_dns_entry(iface, addr, priority)) {
-                interface_log(iface, BLOG_ERROR, "ipv4.dns: (%s,%s): failed to add entry", addr_str, priority_str);
-                return 0;
+            // set state CHILD
+            ps->state = SSTATE_CHILD;
+            
+            // update AP
+            if (p->ap > ps->i + 1) {
+                p->ap = ps->i + 1;
             }
-        }
+        } break;
         
-        st = st->next;
-    }
-    
-    // set servers
-    if (!set_dns_servers()) {
-        return 0;
-    }
-    
-    return 1;
-}
-
-void interface_remove_ipv4_dns_servers (struct interface *iface)
-{
-    // remove entries
-    LinkedList2Node *node;
-    while (node = LinkedList2_GetFirst(&iface->ipv4_dns_servers)) {
-        struct ipv4_dns_entry *entry = UPPER_OBJECT(node, struct ipv4_dns_entry, list_node);
-        interface_remove_ipv4_dns_entry(iface, entry);
+        case NCDMODULE_EVENT_DYING: {
+            ASSERT(ps->state == SSTATE_CHILD || ps->state == SSTATE_ADULT)
+            
+            process_statement_log(ps, BLOG_INFO, "dying");
+            
+            // set state DYING
+            ps->state = SSTATE_DYING;
+            
+            // update AP
+            if (p->ap > ps->i) {
+                p->ap = ps->i;
+            }
+        } break;
     }
     
-    // set servers
-    set_dns_servers();
+    process_work(p);
+    return;
 }
 
-int interface_add_ipv4_addr (struct interface *iface, struct ipv4_ifaddr ifaddr)
+void process_statement_instance_handler_died (struct process_statement *ps, int is_error)
 {
-    // add address entry
-    struct ipv4_addr_entry *entry = interface_add_ipv4_addr_entry(iface, ifaddr);
-    if (!entry) {
-        interface_log(iface, BLOG_ERROR, "failed to add ipv4 address entry");
-        return 0;
-    }
+    ASSERT(ps->state == SSTATE_CHILD || ps->state == SSTATE_ADULT || ps->state == SSTATE_DYING)
     
-    // assign the address
-    if (!NCDIfConfig_add_ipv4_addr(iface->conf->name, ifaddr)) {
-        interface_log(iface, BLOG_ERROR, "failed to assign ipv4 address");
-        interface_remove_ipv4_addr_entry(iface, entry);
-        return 0;
-    }
+    struct process *p = ps->p;
     
-    return 1;
-}
-
-void interface_remove_ipv4_addr (struct interface *iface, struct ipv4_addr_entry *entry)
-{
-    // remove the address
-    if (!NCDIfConfig_remove_ipv4_addr(iface->conf->name, entry->ifaddr)) {
-        interface_log(iface, BLOG_ERROR, "failed to remove ipv4 address");
-    }
-    
-    // remove address entry
-    interface_remove_ipv4_addr_entry(iface, entry);
-}
-
-int interface_add_ipv4_route (struct interface *iface, struct ipv4_ifaddr dest, const uint32_t *gateway, int metric)
-{
-    // add address entry
-    struct ipv4_route_entry *entry = interface_add_ipv4_route_entry(iface, dest, gateway, metric);
-    if (!entry) {
-        interface_log(iface, BLOG_ERROR, "failed to add ipv4 route entry");
-        return 0;
-    }
+    // free instance
+    NCDModuleInst_Free(&ps->inst);
     
-    // add the route
-    if (!NCDIfConfig_add_ipv4_route(dest, gateway, metric, iface->conf->name)) {
-        interface_log(iface, BLOG_ERROR, "failed to add ipv4 route");
-        interface_remove_ipv4_route_entry(iface, entry);
-        return 0;
-    }
+    // free instance arguments
+    NCDValue_Free(&ps->inst_args);
     
-    return 1;
-}
-
-void interface_remove_ipv4_route (struct interface *iface, struct ipv4_route_entry *entry)
-{
-    // remove the route
-    if (!NCDIfConfig_remove_ipv4_route(entry->dest, (entry->have_gateway ? &entry->gateway : NULL), entry->metric, iface->conf->name)) {
-        interface_log(iface, BLOG_ERROR, "failed to remove ipv4 route");
-    }
+    // set state FORGOTTEN
+    ps->state = SSTATE_FORGOTTEN;
     
-    // remove address entry
-    interface_remove_ipv4_route_entry(iface, entry);
-}
-
-
-struct ipv4_addr_entry * interface_add_ipv4_addr_entry (struct interface *iface, struct ipv4_ifaddr ifaddr)
-{
-    // allocate entry
-    struct ipv4_addr_entry *entry = malloc(sizeof(*entry));
-    if (!entry) {
-        return NULL;
+    // set error
+    if (is_error) {
+        process_statement_set_error(ps);
+    } else {
+        ps->have_error = 0;
     }
     
-    // set ifaddr
-    entry->ifaddr = ifaddr;
-    
-    // add to list
-    LinkedList2_Append(&iface->ipv4_addresses, &entry->list_node);
-    
-    return entry;
-}
-
-void interface_remove_ipv4_addr_entry (struct interface *iface, struct ipv4_addr_entry *entry)
-{
-    // remove from list
-    LinkedList2_Remove(&iface->ipv4_addresses, &entry->list_node);
-    
-    // free entry
-    free(entry);
-}
-
-struct ipv4_route_entry * interface_add_ipv4_route_entry (struct interface *iface, struct ipv4_ifaddr dest, const uint32_t *gateway, int metric)
-{
-    // allocate entry
-    struct ipv4_route_entry *entry = malloc(sizeof(*entry));
-    if (!entry) {
-        return NULL;
+    // update AP
+    if (p->ap > ps->i) {
+        p->ap = ps->i;
     }
     
-    // set info
-    entry->dest = dest;
-    if (gateway) {
-        entry->have_gateway = 1;
-        entry->gateway = *gateway;
-    } else {
-        entry->have_gateway = 0;
+    // update FP
+    while (p->fp > 0 && p->statements[p->fp - 1].state == SSTATE_FORGOTTEN) {
+        p->fp--;
     }
-    entry->metric = metric;
     
-    // add to list
-    LinkedList2_Append(&iface->ipv4_routes, &entry->list_node);
-    
-    return entry;
-}
-
-void interface_remove_ipv4_route_entry (struct interface *iface, struct ipv4_route_entry *entry)
-{
-    // remove from list
-    LinkedList2_Remove(&iface->ipv4_routes, &entry->list_node);
+    process_statement_log(ps, BLOG_INFO, "died");
     
-    // free entry
-    free(entry);
-}
-
-struct ipv4_dns_entry * interface_add_ipv4_dns_entry (struct interface *iface, uint32_t addr, int priority)
-{
-    // allocate entry
-    struct ipv4_dns_entry *entry = malloc(sizeof(*entry));
-    if (!entry) {
-        return NULL;
+    if (is_error) {
+        process_statement_log(ps, BLOG_ERROR, "with error");
     }
     
-    // set info
-    entry->addr = addr;
-    entry->priority = priority;
-    
-    // add to list
-    LinkedList2_Append(&iface->ipv4_dns_servers, &entry->list_node);
-    
-    // increment number of DNS servers
-    num_ipv4_dns_servers++;
-    
-    return entry;
-}
-
-void interface_remove_ipv4_dns_entry (struct interface *iface, struct ipv4_dns_entry *entry)
-{
-    // decrement number of DNS servers
-    num_ipv4_dns_servers--;
-    
-    // remove from list
-    LinkedList2_Remove(&iface->ipv4_dns_servers, &entry->list_node);
-    
-    // free entry
-    free(entry);
+    process_work(p);
+    return;
 }

+ 18 - 10
ncd/ncd.conf.example

@@ -1,12 +1,20 @@
-interface eth0 {
-    type "physical";
-    dhcp;
-    ipv4.addr "dhcp_addr";
-    ipv4.route "0.0.0.0/0" "dhcp_router" "20";
-    ipv4.dns "dhcp_dns_servers" "20";
-}
+# Network card using DHCP
+process lan {
+    # Make the interface name a variable so we can refer to it.
+    var("eth0") dev;
+
+    # Wait for the network card appear and for the cable to be plugged in.
+    net.backend.physical(dev);
+
+    # Start DHCP.
+    net.ipv4.dhcp(dev) dhcp;
+
+    # Once DHCP obtains an IP address, assign it to the interface.
+    net.ipv4.addr(dev, dhcp.addr, dhcp.prefix);
+
+    # Add a default route.
+    net.ipv4.route("0.0.0.0", "0", dhcp.gateway, "20", dev);
 
-interface eth2 {
-    type "physical";
-    ipv4.addr "192.168.6.1/24";
+    # Add DNS servers.
+    net.dns(dhcp.dns_servers, "20");
 }

+ 2 - 2
ncd/ncd.h

@@ -23,5 +23,5 @@
 // name of the program
 #define PROGRAM_NAME "ncd"
 
-// how long to wait after interface configuratio fails before trying again
-#define INTERFACE_RETRY_TIME 10000
+// how long to wait after an error before retrying
+#define RETRY_TIME 5000

+ 99 - 32
ncdconfig/NCDConfig.c

@@ -24,6 +24,7 @@
 #include <string.h>
 
 #include <misc/string_begins_with.h>
+#include <misc/expstring.h>
 
 #include <ncdconfig/NCDConfig.h>
 
@@ -47,12 +48,35 @@ void NCDConfig_free_statements (struct NCDConfig_statements *v)
     }
     
     NCDConfig_free_strings(v->names);
-    NCDConfig_free_strings(v->args);
+    NCDConfig_free_arguments(v->args);
+    free(v->name);
     NCDConfig_free_statements(v->next);
     
     free(v);
 }
 
+void NCDConfig_free_arguments (struct NCDConfig_arguments *v)
+{
+    if (!v) {
+        return;
+    }
+    
+    switch (v->type) {
+        case NCDCONFIG_ARG_STRING:
+            free(v->string);
+            break;
+        case NCDCONFIG_ARG_VAR:
+            NCDConfig_free_strings(v->var);
+            break;
+        default:
+            ASSERT(0);
+    }
+    
+    NCDConfig_free_arguments(v->next);
+    
+    free(v);
+}
+
 void NCDConfig_free_strings (struct NCDConfig_strings *v)
 {
     if (!v) {
@@ -89,30 +113,74 @@ fail:
     return NULL;
 }
 
-struct NCDConfig_statements * NCDConfig_make_statements (struct NCDConfig_strings *names, int need_args, struct NCDConfig_strings *args, int need_next, struct NCDConfig_statements *next)
+struct NCDConfig_statements * NCDConfig_make_statements (struct NCDConfig_strings *names, struct NCDConfig_arguments *args, char *name, struct NCDConfig_statements *next)
 {
-    if (!names || (need_args && !args) || (need_next && !next)) {
-        goto fail;
-    }
-    
     struct NCDConfig_statements *v = malloc(sizeof(*v));
     if (!v) {
         goto fail;
     }
-
+    
     v->names = names;
     v->args = args;
+    v->name = name;
     v->next = next;
 
     return v;
     
 fail:
     NCDConfig_free_strings(names);
-    NCDConfig_free_strings(args);
+    NCDConfig_free_arguments(args);
+    free(name);
     NCDConfig_free_statements(next);
     return NULL;
 }
 
+struct NCDConfig_arguments * NCDConfig_make_arguments_string (char *str, int need_next, struct NCDConfig_arguments *next)
+{
+    if (!str || (need_next && !next)) {
+        goto fail;
+    }
+    
+    struct NCDConfig_arguments *v = malloc(sizeof(*v));
+    if (!v) {
+        goto fail;
+    }
+    
+    v->type = NCDCONFIG_ARG_STRING;
+    v->string = str;
+    v->next = next;
+    
+    return v;
+    
+fail:
+    free(str);
+    NCDConfig_free_arguments(next);
+    return NULL;
+}
+
+struct NCDConfig_arguments * NCDConfig_make_arguments_var (struct NCDConfig_strings *var, int need_next, struct NCDConfig_arguments *next)
+{
+    if (!var || (need_next && !next)) {
+        goto fail;
+    }
+    
+    struct NCDConfig_arguments *v = malloc(sizeof(*v));
+    if (!v) {
+        goto fail;
+    }
+    
+    v->type = NCDCONFIG_ARG_VAR;
+    v->var = var;
+    v->next = next;
+    
+    return v;
+    
+fail:
+    NCDConfig_free_strings(var);
+    NCDConfig_free_arguments(next);
+    return NULL;
+}
+
 struct NCDConfig_strings * NCDConfig_make_strings (char *value, int need_next, struct NCDConfig_strings *next)
 {
     if (!value || (need_next && !next)) {
@@ -183,35 +251,34 @@ struct NCDConfig_statements * NCDConfig_find_statement (struct NCDConfig_stateme
     return NULL;
 }
 
-int NCDConfig_statement_has_one_arg (struct NCDConfig_statements *st, char **arg1_out)
+char * NCDConfig_concat_strings (struct NCDConfig_strings *s)
 {
-    if (!(st->args && !st->args->next)) {
-        return 0;
+    ExpString str;
+    if (!ExpString_Init(&str)) {
+        goto fail0;
     }
     
-    *arg1_out = st->args->value;
-    return 1;
-}
-
-int NCDConfig_statement_has_two_args (struct NCDConfig_statements *st, char **arg1_out, char **arg2_out)
-{
-    if (!(st->args && st->args->next && !st->args->next->next)) {
-        return 0;
+    if (!ExpString_Append(&str, s->value)) {
+        goto fail1;
     }
     
-    *arg1_out = st->args->value;
-    *arg2_out = st->args->next->value;
-    return 1;
-}
-
-int NCDConfig_statement_has_three_args (struct NCDConfig_statements *st, char **arg1_out, char **arg2_out, char **arg3_out)
-{
-    if (!(st->args && st->args->next && st->args->next->next && !st->args->next->next->next)) {
-        return 0;
+    s = s->next;
+    
+    while (s) {
+        if (!ExpString_Append(&str, ".")) {
+            goto fail1;
+        }
+        if (!ExpString_Append(&str, s->value)) {
+            goto fail1;
+        }
+        
+        s = s->next;
     }
     
-    *arg1_out = st->args->value;
-    *arg2_out = st->args->next->value;
-    *arg3_out = st->args->next->next->value;
-    return 1;
+    return ExpString_Get(&str);
+    
+fail1:
+    ExpString_Free(&str);
+fail0:
+    return NULL;
 }

+ 21 - 5
ncdconfig/NCDConfig.h

@@ -25,6 +25,7 @@
 
 struct NCDConfig_interfaces;
 struct NCDConfig_statements;
+struct NCDConfig_arguments;
 struct NCDConfig_strings;
 
 struct NCDConfig_interfaces {
@@ -35,10 +36,23 @@ struct NCDConfig_interfaces {
 
 struct NCDConfig_statements {
     struct NCDConfig_strings *names;
-    struct NCDConfig_strings *args;
+    struct NCDConfig_arguments *args;
+    char *name;
     struct NCDConfig_statements *next;
 };
 
+#define NCDCONFIG_ARG_STRING 1
+#define NCDCONFIG_ARG_VAR 2
+
+struct NCDConfig_arguments {
+    int type;
+    union {
+        char *string;
+        struct NCDConfig_strings *var;
+    };
+    struct NCDConfig_arguments *next;
+};
+
 struct NCDConfig_strings {
     char *value;
     struct NCDConfig_strings *next;
@@ -46,15 +60,17 @@ struct NCDConfig_strings {
 
 void NCDConfig_free_interfaces (struct NCDConfig_interfaces *v);
 void NCDConfig_free_statements (struct NCDConfig_statements *v);
+void NCDConfig_free_arguments (struct NCDConfig_arguments *v);
 void NCDConfig_free_strings (struct NCDConfig_strings *v);
 struct NCDConfig_interfaces * NCDConfig_make_interfaces (char *name, struct NCDConfig_statements *statements, int have_next, struct NCDConfig_interfaces *next);
-struct NCDConfig_statements * NCDConfig_make_statements (struct NCDConfig_strings *names, int have_args, struct NCDConfig_strings *args, int have_next, struct NCDConfig_statements *next);
+struct NCDConfig_statements * NCDConfig_make_statements (struct NCDConfig_strings *names, struct NCDConfig_arguments *args, char *name, struct NCDConfig_statements *next);
+struct NCDConfig_arguments * NCDConfig_make_arguments_string (char *str, int have_next, struct NCDConfig_arguments *next);
+struct NCDConfig_arguments * NCDConfig_make_arguments_var (struct NCDConfig_strings *var, int have_next, struct NCDConfig_arguments *next);
 struct NCDConfig_strings * NCDConfig_make_strings (char *value, int have_next, struct NCDConfig_strings *next);
 
 int NCDConfig_statement_name_is (struct NCDConfig_statements *st, const char *needle);
 struct NCDConfig_statements * NCDConfig_find_statement (struct NCDConfig_statements *st, const char *needle);
-int NCDConfig_statement_has_one_arg (struct NCDConfig_statements *st, char **arg1_out);
-int NCDConfig_statement_has_two_args (struct NCDConfig_statements *st, char **arg1_out, char **arg2_out);
-int NCDConfig_statement_has_three_args (struct NCDConfig_statements *st, char **arg1_out, char **arg2_out, char **arg3_out);
+
+char * NCDConfig_concat_strings (struct NCDConfig_strings *s);
 
 #endif

+ 15 - 3
ncdconfig/NCDConfigParser.c

@@ -57,13 +57,21 @@ static int tokenizer_output (void *user, int token, char *value, size_t position
         } break;
         
         case NCD_TOKEN_CURLY_OPEN: {
-            Parse(state->parser, CURRLY_OPEN, NULL, &state->out);
+            Parse(state->parser, CURLY_OPEN, NULL, &state->out);
         } break;
         
         case NCD_TOKEN_CURLY_CLOSE: {
             Parse(state->parser, CURLY_CLOSE, NULL, &state->out);
         } break;
         
+        case NCD_TOKEN_ROUND_OPEN: {
+            Parse(state->parser, ROUND_OPEN, NULL, &state->out);
+        } break;
+        
+        case NCD_TOKEN_ROUND_CLOSE: {
+            Parse(state->parser, ROUND_CLOSE, NULL, &state->out);
+        } break;
+        
         case NCD_TOKEN_SEMICOLON: {
             Parse(state->parser, SEMICOLON, NULL, &state->out);
         } break;
@@ -72,8 +80,12 @@ static int tokenizer_output (void *user, int token, char *value, size_t position
             Parse(state->parser, DOT, NULL, &state->out);
         } break;
         
-        case NCD_TOKEN_INTERFACE: {
-            Parse(state->parser, INTERFACE, NULL, &state->out);
+        case NCD_TOKEN_COMMA: {
+            Parse(state->parser, COMMA, NULL, &state->out);
+        } break;
+        
+        case NCD_TOKEN_PROCESS: {
+            Parse(state->parser, PROCESS, NULL, &state->out);
         } break;
         
         case NCD_TOKEN_NAME: {

+ 57 - 15
ncdconfig/NCDConfigParser_parse.y

@@ -45,12 +45,12 @@ struct parser_out {
 %type interfaces {struct NCDConfig_interfaces *}
 %type statements {struct NCDConfig_statements *}
 %type statement_names {struct NCDConfig_strings *}
-%type statement_args {struct NCDConfig_strings *}
+%type statement_args {struct NCDConfig_arguments *}
 
 %destructor interfaces { NCDConfig_free_interfaces($$); }
 %destructor statements { NCDConfig_free_statements($$); }
 %destructor statement_names { NCDConfig_free_strings($$); }
-%destructor statement_args { NCDConfig_free_strings($$); }
+%destructor statement_args { NCDConfig_free_arguments($$); }
 
 %syntax_error {
     parser_out->syntax_error = 1;
@@ -64,43 +64,71 @@ input ::= interfaces(A). {
     }
 }
 
-interfaces(R) ::= INTERFACE NAME(A) CURRLY_OPEN statements(B) CURLY_CLOSE. {
+interfaces(R) ::= PROCESS NAME(A) CURLY_OPEN statements(B) CURLY_CLOSE. {
     R = NCDConfig_make_interfaces(A, B, 0, NULL);
     if (!R) {
         parser_out->out_of_memory = 1;
     }
 }
 
-interfaces(R) ::= INTERFACE NAME(A) CURRLY_OPEN statements(B) CURLY_CLOSE interfaces(N). {
+interfaces(R) ::= PROCESS NAME(A) CURLY_OPEN statements(B) CURLY_CLOSE interfaces(N). {
     R = NCDConfig_make_interfaces(A, B, 1, N);
     if (!R) {
         parser_out->out_of_memory = 1;
     }
 }
 
-statements(R) ::= statement_names(A) SEMICOLON. {
-    R = NCDConfig_make_statements(A, 0, NULL, 0, NULL);
+statements(R) ::= statement_names(A) ROUND_OPEN ROUND_CLOSE SEMICOLON. {
+    R = NCDConfig_make_statements(A, NULL, NULL, NULL);
     if (!R) {
         parser_out->out_of_memory = 1;
     }
 }
 
-statements(R) ::= statement_names(A) statement_args(B) SEMICOLON. {
-    R = NCDConfig_make_statements(A, 1, B, 0, NULL);
+statements(R) ::= statement_names(A) ROUND_OPEN statement_args(B) ROUND_CLOSE SEMICOLON. {
+    R = NCDConfig_make_statements(A, B, NULL, NULL);
     if (!R) {
         parser_out->out_of_memory = 1;
     }
 }
 
-statements(R) ::= statement_names(A) SEMICOLON statements(N). {
-    R = NCDConfig_make_statements(A, 0, NULL, 1, N);
+statements(R) ::= statement_names(A) ROUND_OPEN ROUND_CLOSE SEMICOLON statements(N). {
+    R = NCDConfig_make_statements(A, NULL, NULL, N);
     if (!R) {
         parser_out->out_of_memory = 1;
     }
 }
 
-statements(R) ::= statement_names(A) statement_args(B) SEMICOLON statements(N). {
-    R = NCDConfig_make_statements(A, 1, B, 1, N);
+statements(R) ::= statement_names(A) ROUND_OPEN statement_args(B) ROUND_CLOSE SEMICOLON statements(N). {
+    R = NCDConfig_make_statements(A, B, NULL, N);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}
+
+statements(R) ::= statement_names(A) ROUND_OPEN ROUND_CLOSE NAME(C) SEMICOLON. {
+    R = NCDConfig_make_statements(A, NULL, C, NULL);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}
+
+statements(R) ::= statement_names(A) ROUND_OPEN statement_args(B) ROUND_CLOSE NAME(C) SEMICOLON. {
+    R = NCDConfig_make_statements(A, B, C, NULL);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}
+
+statements(R) ::= statement_names(A) ROUND_OPEN ROUND_CLOSE NAME(C) SEMICOLON statements(N). {
+    R = NCDConfig_make_statements(A, NULL, C, N);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}
+
+statements(R) ::= statement_names(A) ROUND_OPEN statement_args(B) ROUND_CLOSE NAME(C) SEMICOLON statements(N). {
+    R = NCDConfig_make_statements(A, B, C, N);
     if (!R) {
         parser_out->out_of_memory = 1;
     }
@@ -121,14 +149,28 @@ statement_names(R) ::= NAME(A) DOT statement_names(N). {
 }
 
 statement_args(R) ::= STRING(A). {
-    R = NCDConfig_make_strings(A, 0, NULL);
+    R = NCDConfig_make_arguments_string(A, 0, NULL);
     if (!R) {
         parser_out->out_of_memory = 1;
     }
 }
 
-statement_args(R) ::= STRING(A) statement_args(N). {
-    R = NCDConfig_make_strings(A, 1, N);
+statement_args(R) ::= statement_names(A). {
+    R = NCDConfig_make_arguments_var(A, 0, NULL);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}
+
+statement_args(R) ::= STRING(A) COMMA statement_args(N). {
+    R = NCDConfig_make_arguments_string(A, 1, N);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}
+
+statement_args(R) ::= statement_names(A) COMMA statement_args(N). {
+    R = NCDConfig_make_arguments_var(A, 1, N);
     if (!R) {
         parser_out->out_of_memory = 1;
     }

+ 19 - 3
ncdconfig/NCDConfigTokenizer.c

@@ -59,18 +59,34 @@ void NCDConfigTokenizer_Tokenize (char *str, size_t left, NCDConfigTokenizer_out
         void *token_val = NULL;
         char dec[NCD_MAX_SIZE + 1];
         
-        if (l = data_begins_with(str, left, "{")) {
+        if (*str == '#') {
+            l = 1;
+            while (l < left && str[l] != '\n') {
+                l++;
+            }
+            token = 0;
+        }
+        else if (l = data_begins_with(str, left, "{")) {
             token = NCD_TOKEN_CURLY_OPEN;
         }
         else if (l = data_begins_with(str, left, "}")) {
             token = NCD_TOKEN_CURLY_CLOSE;
         }
+        else if (l = data_begins_with(str, left, "(")) {
+            token = NCD_TOKEN_ROUND_OPEN;
+        }
+        else if (l = data_begins_with(str, left, ")")) {
+            token = NCD_TOKEN_ROUND_CLOSE;
+        }
         else if (l = data_begins_with(str, left, ";")) {
             token = NCD_TOKEN_SEMICOLON;
         }
         else if (l = data_begins_with(str, left, ".")) {
             token = NCD_TOKEN_DOT;
         }
+        else if (l = data_begins_with(str, left, ",")) {
+            token = NCD_TOKEN_COMMA;
+        }
         else if (is_name_first_char(*str)) {
             l = 1;
             while (l < left) {
@@ -90,8 +106,8 @@ void NCDConfigTokenizer_Tokenize (char *str, size_t left, NCDConfigTokenizer_out
             memcpy(dec, str, l);
             dec[l] = '\0';
             
-            if (!strcmp(dec, "interface")) {
-                token = NCD_TOKEN_INTERFACE;
+            if (!strcmp(dec, "process")) {
+                token = NCD_TOKEN_PROCESS;
             }
             else {
                 token = NCD_TOKEN_NAME;

+ 8 - 5
ncdconfig/NCDConfigTokenizer.h

@@ -27,11 +27,14 @@
 #define NCD_EOF 0
 #define NCD_TOKEN_CURLY_OPEN 1
 #define NCD_TOKEN_CURLY_CLOSE 2
-#define NCD_TOKEN_SEMICOLON 3
-#define NCD_TOKEN_DOT 4
-#define NCD_TOKEN_INTERFACE 5
-#define NCD_TOKEN_NAME 6
-#define NCD_TOKEN_STRING 7
+#define NCD_TOKEN_ROUND_OPEN 3
+#define NCD_TOKEN_ROUND_CLOSE 4
+#define NCD_TOKEN_SEMICOLON 5
+#define NCD_TOKEN_DOT 6
+#define NCD_TOKEN_COMMA 7
+#define NCD_TOKEN_PROCESS 8
+#define NCD_TOKEN_NAME 9
+#define NCD_TOKEN_STRING 10
 
 #define NCD_MAX_SIZE 128