Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

more options to parse #20

Merged
merged 11 commits into from
Nov 6, 2024
3 changes: 2 additions & 1 deletion dnsvizor.opam
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ depends: [
"fmt" {>= "0.9.0"}
"ipaddr" {>= "5.6.0"}
"cmdliner" {>= "1.2.0"}
"logs" {>= "0.7.0"}

"alcotest" {with-test & >= "1.8.0"}
"logs" {with-test & >= "0.7.0"}
]
available: arch != "arm32" & arch != "x86_32"
1 change: 1 addition & 0 deletions mirage/.ocamlformat
56 changes: 50 additions & 6 deletions mirage/unikernel.ml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@ module K = struct
Mirage_runtime.register_arg
(Mirage_runtime_network.V6.accept_router_advertisements ())

(* TODO support multiple dhcp-range statements *)
let dhcp_range =
let doc = Arg.info ~doc:"Enable DHCP server." [ "dhcp-range" ] in
Mirage_runtime.register_arg
Arg.(value & opt Config_parser.(some dhcp_range_c) None doc)

let dns_cache =
let doc = Arg.info ~doc:"DNS cache size" [ "dns-cache" ] in
Mirage_runtime.register_arg Arg.(value & opt (some int) None doc)
Expand All @@ -47,6 +41,56 @@ module K = struct
[ "dns-upstream" ]
in
Mirage_runtime.register_arg Arg.(value & opt (some string) None doc)

(* DNSmasq configuration options *)
(* TODO support multiple dhcp-range statements *)
let dhcp_range =
let doc = Arg.info ~doc:"Enable DHCP server." [ "dhcp-range" ] in
Mirage_runtime.register_arg
Arg.(value & opt Config_parser.(some dhcp_range_c) None doc)

let ignored = "DNSMASQ COMPATIBILITY OPTIONS (IGNORED)"

let interface =
let doc =
Arg.info ~docs:ignored ~doc:"Interface to listen on." [ "interface" ]
in
Mirage_runtime.register_arg
Arg.(value & opt Config_parser.(some (ignore_c "interface")) None doc)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Annoyingly this collides with the mirage unix argument --interface /o\

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I'm not sure what to do...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can figure out a way to rename the unix interface option


let except_interface =
let doc =
Arg.info ~docs:ignored ~doc:"Interface to not listen on."
[ "except-interface" ]
in
Mirage_runtime.register_arg
Arg.(
value & opt Config_parser.(some (ignore_c "except-interface")) None doc)

let listen_address =
let doc =
Arg.info ~docs:ignored ~doc:"IP address to listen on."
[ "listen-address" ]
in
Mirage_runtime.register_arg
Arg.(
value & opt Config_parser.(some (ignore_c "listen-address")) None doc)

let no_dhcp_interface =
let doc =
Arg.info ~docs:ignored ~doc:"Only provide DNS service on."
[ "no-dhcp-interface" ]
in
Mirage_runtime.register_arg
Arg.(
value & opt Config_parser.(some (ignore_c "no-dhcp-interface")) None doc)

let bind_interfaces =
let doc =
Arg.info ~docs:ignored ~doc:"Bind to interface IP address only."
[ "bind_interfaces" ]
in
Mirage_runtime.register_arg Arg.(value & flag doc)
end

module Main
Expand Down
99 changes: 77 additions & 22 deletions src/config_parser.ml
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
open Angstrom

(* TODO revise the error handling, since rule may output an error *)
let parse_one rule config_str =
parse_string ~consume:Consume.All
(rule
<|> ( available >>| min 100 >>= peek_string >>= fun context ->
pos >>= fun pos ->
fail (Printf.sprintf "Error at byte offset %d: %S" pos context) ))
config_str
module Log =
(val Logs.(
src_log
@@ Src.create ~doc:"DNSvizor configuration module" "dnsvizor.config")
: Logs.LOG)

let lift_err = function Ok _ as o -> o | Error e -> Error (`Msg e)
let parse_one rule config_str =
match parse_string ~consume:Consume.All rule config_str with
| Ok _ as o -> o
| Error msg -> Error (`Msg (Fmt.str "Parse error in %S: %s" config_str msg))

let conv_cmdliner ?docv rule pp =
Cmdliner.Arg.conv ?docv ((fun x -> parse_one rule x |> lift_err), pp)
let conv_cmdliner ?docv rule pp = Cmdliner.Arg.conv ?docv (parse_one rule, pp)

(* some basic rules *)

Expand All @@ -30,22 +29,41 @@ let week = 7 * day
let infinite = 1 lsl 32 (* DHCP has 32 bits for this *)

let lease_time =
(* TODO check that the time fits into 32 bits *)
take_while1 (function '0' .. '9' -> true | _ -> false)
>>= (fun dur ->
match int_of_string_opt dur with
| None -> fail (Fmt.str "Couldn't convert %S to an integer" dur)
| Some dur ->
choice
| Some n ->
choice ~failure_msg:"bad lease time"
[
string "w" *> return (dur * week);
string "d" *> return (dur * day);
string "h" *> return (dur * hour);
string "m" *> return (dur * minute);
end_of_input *> return dur;
])
string "w" *> return ("w", n * week);
string "d" *> return ("d", n * day);
string "h" *> return ("h", n * hour);
string "m" *> return ("m", n * minute);
return ("", n);
]
>>= fun (c, r) ->
if r > 0 && r < infinite then return r
else
fail
(Fmt.str "Value %u (from %S%s) does not fit into 32 bits" r dur
c))
<|> string "infinite" *> return infinite

let line =
take_while (function '\n' -> false | _ -> true)
<* (end_of_line <|> end_of_input)

let ignore_line key =
line >>= fun txt ->
Log.warn (fun m -> m "ignoring %S %S" key txt);
return txt

let pp_ignored_line key ppf data = Fmt.pf ppf "--%s=%s" key data

let ignore_c key =
conv_cmdliner ~docv:"IGNORED" (ignore_line key) (pp_ignored_line key)

(* real grammars *)

type dhcp_range = {
Expand Down Expand Up @@ -84,7 +102,9 @@ let pp_dhcp_range ppf
lease_time

let mode =
choice [ string "static" *> return `Static; string "proxy" *> return `Proxy ]
choice
[ string "static" *> return `Static; string "proxy" *> return `Proxy ]
~failure_msg:"bad mode"

let dhcp_range =
(* TODO prefix: [tag:<tag>[,tag:<tag>],][set:<tag>,]
Expand All @@ -103,7 +123,8 @@ let dhcp_range =
>>| fun broadcast -> (netmask, broadcast) )
>>= fun net_broad ->
option None (string "," *> lease_time >>| fun l -> Some l)
>>| fun lease_time ->
>>= fun lease_time ->
end_of_line <|> end_of_input >>| fun () ->
let end_addr, mode =
match mode_or_end with
| `Mode m -> (None, Some m)
Expand All @@ -116,3 +137,37 @@ let dhcp_range_c =
conv_cmdliner
~docv:"<start>[,<end>|<mode>[,<netmask>[,<broadcast>]]][,<lease-time>]"
dhcp_range pp_dhcp_range

let parse_file data =
let rules =
let ignore_directive key =
string (key ^ "=") *> commit *> ignore_line key >>| fun _ -> `Ignored
in
let ignore_flag key =
string key *> (end_of_line <|> end_of_input) >>| fun _ -> `Ignored
in
let isspace = function
| ' ' | '\x0c' | '\n' | '\r' | '\t' | '\x0b' -> true
| _ -> false
in
skip_while isspace *> commit
*> choice ~failure_msg:"bad configuration directive"
[
( string "dhcp-range=" *> commit *> dhcp_range >>| fun range ->
`Dhcp_range range );
ignore_directive "interface";
ignore_directive "except-interface";
ignore_directive "listen-address";
ignore_directive "no-dhcp-interface";
ignore_flag "bind-interfaces";
(string "#" *> ignore_line "#" >>| fun _ -> `Ignored);
]
in
let top =
fix (fun r ->
rules >>= fun e ->
commit *> end_of_input *> return [ e ] <|> (List.cons e <$> r))
in
match parse_string ~consume:Consume.All top data with
| Ok x -> Ok (List.filter (function `Ignored -> false | _ -> true) x)
| Error msg -> Error (`Msg msg)
2 changes: 1 addition & 1 deletion src/dune
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(library
(name dnsvizor)
(public_name dnsvizor)
(libraries angstrom fmt ipaddr cmdliner))
(libraries angstrom fmt ipaddr cmdliner logs))
34 changes: 30 additions & 4 deletions test/config_tests.ml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
open Dnsvizor.Config_parser

let msg_t =
let pp ppf (`Msg s) = Fmt.string ppf s in
Alcotest.testable pp (fun (`Msg a) (`Msg b) -> String.equal a b)

let opt_eq f a b =
match (a, b) with
| None, None -> true
Expand Down Expand Up @@ -38,7 +42,7 @@ let ok_dhcp_range () =
in
Alcotest.(
check
(result dhcp_range_t string)
(result dhcp_range_t msg_t)
"DHCP range is good" (Ok expected)
(parse_one dhcp_range input))

Expand All @@ -56,7 +60,7 @@ let ok_dhcp_range_with_netmask () =
in
Alcotest.(
check
(result dhcp_range_t string)
(result dhcp_range_t msg_t)
"DHCP range with netmask is good" (Ok expected)
(parse_one dhcp_range input))

Expand All @@ -76,7 +80,7 @@ let ok_dhcp_range_static () =
in
Alcotest.(
check
(result dhcp_range_t string)
(result dhcp_range_t msg_t)
"DHCP range with static is good" (Ok expected)
(parse_one dhcp_range input))

Expand All @@ -87,7 +91,29 @@ let tests =
("DHCP range static", `Quick, ok_dhcp_range_static);
]

let tests = [ ("Config tests", tests) ]
let string_of_file filename =
let config_dir = "sample-configuration-files" in
let file = Filename.concat config_dir filename in
try
let fh = open_in file in
let content = really_input_string fh (in_channel_length fh) in
close_in_noerr fh;
content
with _ -> Alcotest.failf "Error reading file %S" file

let test_configuration config file () =
match parse_file (string_of_file file) with
| Error (`Msg msg) -> Alcotest.failf "Error parsing %S: %s" file msg
| Ok data ->
Alcotest.(check int)
"Number of configuration items matches" (List.length config)
(List.length data)

let config_file_tests =
[ ("First example", `Quick, test_configuration [] "simple.conf") ]

let tests =
[ ("Config tests", tests); ("Configuration file tests", config_file_tests) ]

let () =
Logs.set_reporter @@ Logs_fmt.reporter ~dst:Format.std_formatter ();
Expand Down
4 changes: 3 additions & 1 deletion test/dune
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
(test
(name config_tests)
(modules config_tests)
(libraries alcotest fmt logs.fmt dnsvizor))
(libraries alcotest fmt logs.fmt dnsvizor)
(deps
(source_tree sample-configuration-files)))
6 changes: 6 additions & 0 deletions test/sample-configuration-files/simple.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# This is a comment
listen-address=127.0.0.1,10.8.0.1
# this is also a comment with leading whitespace
# and configuration directives can also be indented with whitespace
bind-interfaces
# Comments are only allowed on a line by themselves (with optional leading whitespace)
Loading