From c11f245d643ee5b5ee2d530009123b589f7ca7b5 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sun, 26 Jun 2016 12:04:47 +0100
Subject: [PATCH 001/281] Cope with writing a frame failing
If a client disconnects suddenly then we may get an error trying to map
its grant to send the frame.
Fixes #8.
---
firewall.ml | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/firewall.ml b/firewall.ml
index 97ce185..4b98302 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -16,7 +16,14 @@ let transmit ~frame iface =
cases. *)
let frame = fixup_checksums frame |> Cstruct.concat in
let packet = Cstruct.shift frame Wire_structs.sizeof_ethernet in
- iface#writev [packet]
+ Lwt.catch
+ (fun () -> iface#writev [packet])
+ (fun ex ->
+ Log.warn (fun f -> f "Failed to write packet to %a: %s"
+ Ipaddr.V4.pp_hum iface#other_ip
+ (Printexc.to_string ex));
+ Lwt.return ()
+ )
let forward_ipv4 t frame =
let packet = Cstruct.shift frame Wire_structs.sizeof_ethernet in
From a7001a70d2f1f929323b7b53101b1638b60a6bc0 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sun, 25 Sep 2016 14:38:17 +0100
Subject: [PATCH 002/281] Allow clients to have any IP address
We previously assumed that Qubes would always give clients IP addresses
on a particular network. However, it is not required to do this and in
fact uses a different network for disposable VMs.
With this change:
- We no longer reject clients with unknown IP addresses
- The `Unknown_client` classification is gone; we have no way to tell
the difference between a client that isn't connected and an external
address.
- We now consider every client to be on a point-to-point link and do not
answer ARP requests on behalf of other clients. Clients should assume
their netmask is 255.255.255.255 (and ignore /qubes-netmask).
This is a partial fix for #9. It allows disposable VMs to connect to the
firewall but for some reason they don't process any frames we send them
(we get their ARP requests but they don't get our replies). Taking eth0
down in the disp VM, then bringing it back up (and re-adding the routes)
allows it to work.
---
_tags | 1 -
client_eth.ml | 19 +++++++++----------
client_eth.mli | 24 ++++++++++++------------
dao.ml | 8 +-------
dao.mli | 1 -
firewall.ml | 5 +----
packet.ml | 2 +-
router.ml | 11 +++--------
rules.ml | 1 -
unikernel.ml | 5 +----
10 files changed, 28 insertions(+), 49 deletions(-)
diff --git a/_tags b/_tags
index 69adb29..7441bd2 100644
--- a/_tags
+++ b/_tags
@@ -1,3 +1,2 @@
not : warn(A-4), strict_sequence
: package(cstruct.syntax)
-true: -syntax(camlp4o)
diff --git a/client_eth.ml b/client_eth.ml
index af0f299..d027134 100644
--- a/client_eth.ml
+++ b/client_eth.ml
@@ -1,32 +1,28 @@
-(* Copyright (C) 2015, Thomas Leonard
+(* Copyright (C) 2016, Thomas Leonard
See the README file for details. *)
open Utils
-let src = Logs.Src.create "client_eth" ~doc:"Ethernet for NetVM clients"
+let src = Logs.Src.create "client_eth" ~doc:"Ethernet networks for NetVM clients"
module Log = (val Logs.src_log src : Logs.LOG)
type t = {
mutable iface_of_ip : client_link IpMap.t;
- prefix : Ipaddr.V4.Prefix.t;
client_gw : Ipaddr.V4.t; (* The IP that clients are given as their default gateway. *)
}
type host =
[ `Client of client_link
- | `Unknown_client of Ipaddr.t
| `Client_gateway
| `External of Ipaddr.t ]
-let create ~prefix ~client_gw =
- { iface_of_ip = IpMap.empty; client_gw; prefix }
+let create ~client_gw =
+ { iface_of_ip = IpMap.empty; client_gw }
-let prefix t = t.prefix
let client_gw t = t.client_gw
let add_client t iface =
let ip = iface#other_ip in
- assert (Ipaddr.V4.Prefix.mem ip t.prefix);
(* TODO: Should probably wait for the previous client to disappear. *)
(* assert (not (IpMap.mem ip t.iface_of_ip)); *)
t.iface_of_ip <- t.iface_of_ip |> IpMap.add ip iface
@@ -45,13 +41,11 @@ let classify t ip =
if ip4 = t.client_gw then `Client_gateway
else match lookup t ip4 with
| Some client_link -> `Client client_link
- | None when Ipaddr.V4.Prefix.mem ip4 t.prefix -> `Unknown_client ip
| None -> `External ip
let resolve t : host -> Ipaddr.t = function
| `Client client_link -> Ipaddr.V4 client_link#other_ip
| `Client_gateway -> Ipaddr.V4 t.client_gw
- | `Unknown_client addr
| `External addr -> addr
module ARP = struct
@@ -62,9 +56,14 @@ module ARP = struct
let lookup t ip =
if ip = t.net.client_gw then Some t.client_link#my_mac
+ else None
+ (* We're now treating client networks as point-to-point links,
+ so we no longer respond on behalf of other clients. *)
+ (*
else match IpMap.find ip t.net.iface_of_ip with
| Some client_iface -> Some client_iface#other_mac
| None -> None
+ *)
let create ~net client_link = {net; client_link}
diff --git a/client_eth.mli b/client_eth.mli
index 45203ae..cd8ccfe 100644
--- a/client_eth.mli
+++ b/client_eth.mli
@@ -1,34 +1,36 @@
-(* Copyright (C) 2015, Thomas Leonard
+(* Copyright (C) 2016, Thomas Leonard
See the README file for details. *)
-(** The ethernet network our client AppVMs are on. *)
+(** The ethernet networks connecting us to our client AppVMs.
+ Note: each AppVM is on a point-to-point link, each link being considered to be a separate Ethernet network. *)
open Utils
type t
-(** A network for client AppVMs to join. *)
+(** A collection of clients. *)
type host =
[ `Client of client_link
- | `Unknown_client of Ipaddr.t
| `Client_gateway
| `External of Ipaddr.t ]
+(* Note: Qubes does not allow us to distinguish between an external address and a
+ disconnected client.
+ See: https://github.com/talex5/qubes-mirage-firewall/issues/9#issuecomment-246956850 *)
-val create : prefix:Ipaddr.V4.Prefix.t -> client_gw:Ipaddr.V4.t -> t
-(** [create ~prefix ~client_gw] is a network of client machines.
- Their IP addresses all start with [prefix] and they are configured to
- use [client_gw] as their default gateway. *)
+val create : client_gw:Ipaddr.V4.t -> t
+(** [create ~client_gw] is a network of client machines.
+ Qubes will have configured the client machines to use [client_gw] as their default gateway. *)
val add_client : t -> client_link -> unit
val remove_client : t -> client_link -> unit
-val prefix : t -> Ipaddr.V4.Prefix.t
val client_gw : t -> Ipaddr.V4.t
val classify : t -> Ipaddr.t -> host
val resolve : t -> host -> Ipaddr.t
val lookup : t -> Ipaddr.V4.t -> client_link option
+(** [lookup t addr] is the client with IP address [addr], if connected. *)
module ARP : sig
(** We already know the correct mapping of IP addresses to MAC addresses, so we never
@@ -40,9 +42,7 @@ module ARP : sig
val create : net:t -> client_link -> arp
(** [create ~net client_link] is an ARP responder for [client_link].
- It answers on behalf of other clients in [net] (but not for the client
- itself, since the client might be trying to check that its own address is
- free). It also answers for the client's gateway address. *)
+ It answers only for the client's gateway address. *)
val input : arp -> Cstruct.t -> Cstruct.t option
(** Process one ethernet frame containing an ARP message.
diff --git a/dao.ml b/dao.ml
index 972d2e9..f0ab65b 100644
--- a/dao.ml
+++ b/dao.ml
@@ -44,7 +44,6 @@ type network_config = {
uplink_netvm_ip : Ipaddr.V4.t; (* The IP address of NetVM (our gateway) *)
uplink_our_ip : Ipaddr.V4.t; (* The IP address of our interface to NetVM *)
- clients_prefix : Ipaddr.V4.Prefix.t; (* The network connecting our client VMs to us *)
clients_our_ip : Ipaddr.V4.t; (* The IP address of our interface to our client VMs (their gateway) *)
}
@@ -56,12 +55,7 @@ let read_network_config qubesDB =
| Some value -> value in
let uplink_our_ip = get "/qubes-ip" |> Ipaddr.V4.of_string_exn in
let uplink_netvm_ip = get "/qubes-gateway" |> Ipaddr.V4.of_string_exn in
- let clients_prefix =
- (* This is oddly named: seems to be the network we provide to our clients *)
- let client_network = get "/qubes-netvm-network" |> Ipaddr.V4.of_string_exn in
- let client_netmask = get "/qubes-netvm-netmask" |> Ipaddr.V4.of_string_exn in
- Ipaddr.V4.Prefix.of_netmask client_netmask client_network in
let clients_our_ip = get "/qubes-netvm-gateway" |> Ipaddr.V4.of_string_exn in
- { uplink_netvm_ip; uplink_our_ip; clients_prefix; clients_our_ip }
+ { uplink_netvm_ip; uplink_our_ip; clients_our_ip }
let set_iptables_error db = Qubes.DB.write db "/qubes-iptables-error"
diff --git a/dao.mli b/dao.mli
index adf036a..c0f2862 100644
--- a/dao.mli
+++ b/dao.mli
@@ -22,7 +22,6 @@ type network_config = {
uplink_netvm_ip : Ipaddr.V4.t; (* The IP address of NetVM (our gateway) *)
uplink_our_ip : Ipaddr.V4.t; (* The IP address of our interface to NetVM *)
- clients_prefix : Ipaddr.V4.Prefix.t; (* The network connecting our client VMs to us *)
clients_our_ip : Ipaddr.V4.t; (* The IP address of our interface to our client VMs (their gateway) *)
}
diff --git a/firewall.ml b/firewall.ml
index 4b98302..cdfd977 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -155,9 +155,6 @@ let apply_rules t rules info =
match rules info, info.dst with
| `Accept, `Client client_link -> transmit ~frame client_link
| `Accept, (`External _ | `NetVM) -> transmit ~frame t.Router.uplink
- | `Accept, `Unknown_client _ ->
- Log.warn (fun f -> f "Dropping packet to unknown client %a" pp_packet info);
- return ()
| `Accept, (`Firewall_uplink | `Client_gateway) ->
Log.warn (fun f -> f "Bad rule: firewall can't accept packets %a" pp_packet info);
return ()
@@ -196,7 +193,7 @@ let ipv4_from_netvm t frame =
| None -> return ()
| Some info ->
match info.src with
- | `Client _ | `Unknown_client _ | `Firewall_uplink | `Client_gateway ->
+ | `Client _ | `Firewall_uplink | `Client_gateway ->
Log.warn (fun f -> f "Frame from NetVM has internal source IP address! %a" pp_packet info);
return ()
| `External _ | `NetVM ->
diff --git a/packet.ml b/packet.ml
index bf9f062..a359e16 100644
--- a/packet.ml
+++ b/packet.ml
@@ -11,7 +11,7 @@ type ports = {
}
type host =
- [ `Client of client_link | `Unknown_client of Ipaddr.t | `Client_gateway | `Firewall_uplink | `NetVM | `External of Ipaddr.t ]
+ [ `Client of client_link | `Client_gateway | `Firewall_uplink | `NetVM | `External of Ipaddr.t ]
type info = {
frame : Cstruct.t;
diff --git a/router.ml b/router.ml
index e86d38b..8e1dc44 100644
--- a/router.ml
+++ b/router.ml
@@ -21,14 +21,9 @@ let create ~client_eth ~uplink =
let target t buf =
let open Wire_structs.Ipv4_wire in
let dst_ip = get_ipv4_dst buf |> Ipaddr.V4.of_int32 in
- if Ipaddr.V4.Prefix.mem dst_ip (Client_eth.prefix t.client_eth) then (
- match Client_eth.lookup t.client_eth dst_ip with
- | Some client_link -> Some (client_link :> interface)
- | None ->
- Log.warn (fun f -> f "Packet to unknown internal client %a - dropping"
- Ipaddr.V4.pp_hum dst_ip);
- None
- ) else Some t.uplink
+ match Client_eth.lookup t.client_eth dst_ip with
+ | Some client_link -> Some (client_link :> interface)
+ | None -> Some t.uplink
let add_client t = Client_eth.add_client t.client_eth
let remove_client t = Client_eth.remove_client t.client_eth
diff --git a/rules.ml b/rules.ml
index a2e86ae..7e62790 100644
--- a/rules.ml
+++ b/rules.ml
@@ -32,7 +32,6 @@ let from_client = function
| { dst = `Client_gateway; proto = `UDP { dport = 53 } } -> `NAT_to (`NetVM, 53)
| { dst = (`Client_gateway | `Firewall_uplink) } -> `Drop "packet addressed to firewall itself"
| { dst = `Client _ } -> `Drop "prevent communication between client VMs"
- | { dst = `Unknown_client _ } -> `Drop "target client not running"
(** Decide what to do with a packet received from the outside world.
Note: If the packet matched an existing NAT rule then this isn't called. *)
diff --git a/unikernel.ml b/unikernel.ml
index d64274f..e03380b 100644
--- a/unikernel.ml
+++ b/unikernel.ml
@@ -14,16 +14,13 @@ module Main (Clock : V1.CLOCK) = struct
let network qubesDB =
(* Read configuration from QubesDB *)
let config = Dao.read_network_config qubesDB in
- Logs.info (fun f -> f "Client (internal) network is %a"
- Ipaddr.V4.Prefix.pp_hum config.Dao.clients_prefix);
(* Initialise connection to NetVM *)
Uplink.connect config >>= fun uplink ->
(* Report success *)
Dao.set_iptables_error qubesDB "" >>= fun () ->
(* Set up client-side networking *)
let client_eth = Client_eth.create
- ~client_gw:config.Dao.clients_our_ip
- ~prefix:config.Dao.clients_prefix in
+ ~client_gw:config.Dao.clients_our_ip in
(* Set up routing between networks and hosts *)
let router = Router.create
~client_eth
From 63cbb4bed066a53cc781631532f541e915307182 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sun, 25 Sep 2016 15:14:16 +0100
Subject: [PATCH 003/281] Ensure that old client has quit before adding new one
Not sure if this can happen, but it removes a TODO from the code.
---
client_eth.ml | 24 +++++++++++++++++++-----
client_eth.mli | 5 ++++-
client_net.ml | 2 +-
router.mli | 5 ++---
4 files changed, 26 insertions(+), 10 deletions(-)
diff --git a/client_eth.ml b/client_eth.ml
index d027134..f30f69c 100644
--- a/client_eth.ml
+++ b/client_eth.ml
@@ -2,12 +2,14 @@
See the README file for details. *)
open Utils
+open Lwt.Infix
let src = Logs.Src.create "client_eth" ~doc:"Ethernet networks for NetVM clients"
module Log = (val Logs.src_log src : Logs.LOG)
type t = {
mutable iface_of_ip : client_link IpMap.t;
+ changed : unit Lwt_condition.t; (* Fires when [iface_of_ip] changes. *)
client_gw : Ipaddr.V4.t; (* The IP that clients are given as their default gateway. *)
}
@@ -17,20 +19,32 @@ type host =
| `External of Ipaddr.t ]
let create ~client_gw =
- { iface_of_ip = IpMap.empty; client_gw }
+ let changed = Lwt_condition.create () in
+ { iface_of_ip = IpMap.empty; client_gw; changed }
let client_gw t = t.client_gw
let add_client t iface =
let ip = iface#other_ip in
- (* TODO: Should probably wait for the previous client to disappear. *)
- (* assert (not (IpMap.mem ip t.iface_of_ip)); *)
- t.iface_of_ip <- t.iface_of_ip |> IpMap.add ip iface
+ let rec aux () =
+ if IpMap.mem ip t.iface_of_ip then (
+ (* Wait for old client to disappear before adding one with the same IP address.
+ Otherwise, its [remove_client] call will remove the new client instead. *)
+ Log.info (fun f -> f "Waiting for old client %a to go away before accepting new one" Ipaddr.V4.pp_hum ip);
+ Lwt_condition.wait t.changed >>= aux
+ ) else (
+ t.iface_of_ip <- t.iface_of_ip |> IpMap.add ip iface;
+ Lwt_condition.broadcast t.changed ();
+ Lwt.return_unit
+ )
+ in
+ aux ()
let remove_client t iface =
let ip = iface#other_ip in
assert (IpMap.mem ip t.iface_of_ip);
- t.iface_of_ip <- t.iface_of_ip |> IpMap.remove ip
+ t.iface_of_ip <- t.iface_of_ip |> IpMap.remove ip;
+ Lwt_condition.broadcast t.changed ()
let lookup t ip = IpMap.find ip t.iface_of_ip
diff --git a/client_eth.mli b/client_eth.mli
index cd8ccfe..41746d3 100644
--- a/client_eth.mli
+++ b/client_eth.mli
@@ -21,7 +21,10 @@ val create : client_gw:Ipaddr.V4.t -> t
(** [create ~client_gw] is a network of client machines.
Qubes will have configured the client machines to use [client_gw] as their default gateway. *)
-val add_client : t -> client_link -> unit
+val add_client : t -> client_link -> unit Lwt.t
+(** [add_client t client] registers a new client. If a client with this IP address is already registered,
+ it waits for [remove_client] to be called on that before adding the new client and returning. *)
+
val remove_client : t -> client_link -> unit
val client_gw : t -> Ipaddr.V4.t
diff --git a/client_net.ml b/client_net.ml
index 0c84921..ebb6851 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -50,7 +50,7 @@ let add_vif { Dao.domid; device_id; client_ip } ~router ~cleanup_tasks =
let client_eth = router.Router.client_eth in
let gateway_ip = Client_eth.client_gw client_eth in
let iface = new client_iface eth ~gateway_ip ~client_ip client_mac in
- Router.add_client router iface;
+ Router.add_client router iface >>= fun () ->
Cleanup.on_cleanup cleanup_tasks (fun () -> Router.remove_client router iface);
let fixed_arp = Client_eth.ARP.create ~net:client_eth iface in
Netback.listen backend (fun frame ->
diff --git a/router.mli b/router.mli
index 8743b57..ac743d3 100644
--- a/router.mli
+++ b/router.mli
@@ -22,9 +22,8 @@ val create :
val target : t -> Cstruct.t -> interface option
(** [target t packet] is the interface to which [packet] (an IP packet) should be routed. *)
-val add_client : t -> client_link -> unit
-(** [add_client t iface] adds a rule for routing packets addressed to [iface].
- The client's IP address must be within the [client_eth] passed to [create]. *)
+val add_client : t -> client_link -> unit Lwt.t
+(** [add_client t iface] adds a rule for routing packets addressed to [iface]. *)
val remove_client : t -> client_link -> unit
From 9c33da3bfd2d9cf142c20f4c1e5726c0b3274abb Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sun, 25 Sep 2016 15:25:51 +0100
Subject: [PATCH 004/281] Handle errors writing to client
mirage-net-xen would report Netback_shutdown if we tried to write to a
client after it had disconnected. Now we just log this and continue.
---
client_net.ml | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/client_net.ml b/client_net.ml
index ebb6851..5de5fa2 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -7,9 +7,19 @@ open Utils
module Netback = Netchannel.Backend.Make(Netchannel.Xenstore.Make(OS.Xs))
module ClientEth = Ethif.Make(Netback)
-let src = Logs.Src.create "net" ~doc:"Client networking"
+let src = Logs.Src.create "client_net" ~doc:"Client networking"
module Log = (val Logs.src_log src : Logs.LOG)
+let writev eth data =
+ Lwt.catch
+ (fun () -> ClientEth.writev eth data)
+ (fun ex ->
+ (* Usually Netback_shutdown, because the client disconnected *)
+ Log.err (fun f -> f "uncaught exception trying to send to client:@\n@[ %a@]@\nException: @[%s@]"
+ Cstruct.hexdump_pp (Cstruct.concat data) (Printexc.to_string ex));
+ Lwt.return ()
+ )
+
class client_iface eth ~gateway_ip ~client_ip client_mac : client_link = object
val queue = FrameQ.create (Ipaddr.V4.to_string client_ip)
method my_mac = ClientEth.mac eth
@@ -19,7 +29,7 @@ class client_iface eth ~gateway_ip ~client_ip client_mac : client_link = object
method writev ip =
FrameQ.send queue (fun () ->
let eth_hdr = eth_header_ipv4 ~src:(ClientEth.mac eth) ~dst:client_mac in
- ClientEth.writev eth (fixup_checksums (Cstruct.concat (eth_hdr :: ip)))
+ writev eth (fixup_checksums (Cstruct.concat (eth_hdr :: ip)))
)
end
@@ -29,7 +39,7 @@ let clients : Cleanup.t IntMap.t ref = ref IntMap.empty
let input_arp ~fixed_arp ~eth request =
match Client_eth.ARP.input fixed_arp request with
| None -> return ()
- | Some response -> ClientEth.write eth response
+ | Some response -> writev eth [response]
(** Handle an IPv4 packet from the client. *)
let input_ipv4 ~client_ip ~router frame packet =
From 79092e1463a807b5e4aac335ed7e92ccdb99674f Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 1 Oct 2016 10:47:19 +0100
Subject: [PATCH 005/281] Avoid using Lwt.join on listening threads
Lwt.join only reports an error if *both* threads fail.
---
unikernel.ml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/unikernel.ml b/unikernel.ml
index e03380b..9e5eba3 100644
--- a/unikernel.ml
+++ b/unikernel.ml
@@ -26,7 +26,7 @@ module Main (Clock : V1.CLOCK) = struct
~client_eth
~uplink:(Uplink.interface uplink) in
(* Handle packets from both networks *)
- Lwt.join [
+ Lwt.choose [
Client_net.listen router;
Uplink.listen uplink router
]
From 312627e078240a6db64793dcae7411cc93253492 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 1 Oct 2016 14:42:27 +0100
Subject: [PATCH 006/281] Monitor set of client interfaces, not client domains
Qubes does not remove the client directory itself when the domain exits.
Combined with 63cbb4bed0, this prevented clients from reconnecting.
This may also make it possible to connect clients to the firewall via
multiple interfaces, although this doesn't seem useful.
---
client_net.ml | 50 ++++++++++++++-------------------
dao.ml | 77 ++++++++++++++++++++++++++++++++++++++-------------
dao.mli | 27 +++++++++---------
3 files changed, 92 insertions(+), 62 deletions(-)
diff --git a/client_net.ml b/client_net.ml
index 5de5fa2..ca39938 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -33,7 +33,7 @@ class client_iface eth ~gateway_ip ~client_ip client_mac : client_link = object
)
end
-let clients : Cleanup.t IntMap.t ref = ref IntMap.empty
+let clients : Cleanup.t Dao.VifMap.t ref = ref Dao.VifMap.empty
(** Handle an ARP message from the client. *)
let input_arp ~fixed_arp ~eth request =
@@ -52,7 +52,7 @@ let input_ipv4 ~client_ip ~router frame packet =
)
(** Connect to a new client's interface and listen for incoming frames. *)
-let add_vif { Dao.domid; device_id; client_ip } ~router ~cleanup_tasks =
+let add_vif { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks =
Netback.make ~domid ~device_id >>= fun backend ->
Log.info (fun f -> f "Client %d (IP: %s) ready" domid (Ipaddr.V4.to_string client_ip));
ClientEth.connect backend >>= or_fail "Can't make Ethernet device" >>= fun eth ->
@@ -75,45 +75,37 @@ let add_vif { Dao.domid; device_id; client_ip } ~router ~cleanup_tasks =
)
(** A new client VM has been found in XenStore. Find its interface and connect to it. *)
-let add_client ~router domid =
+let add_client ~router vif client_ip =
let cleanup_tasks = Cleanup.create () in
- Log.info (fun f -> f "add client domain %d" domid);
+ Log.info (fun f -> f "add client vif %a" Dao.ClientVif.pp vif);
Lwt.async (fun () ->
- Lwt.catch (fun () ->
- Dao.client_vifs domid >>= function
- | [] ->
- Log.warn (fun f -> f "Client has no interfaces");
- return ()
- | vif :: others ->
- if others <> [] then Log.warn (fun f -> f "Client has multiple interfaces; using first");
- add_vif vif ~router ~cleanup_tasks
- )
- (fun ex ->
- Log.warn (fun f -> f "Error connecting client domain %d: %s"
- domid (Printexc.to_string ex));
- return ()
- )
- );
+ Lwt.catch (fun () ->
+ add_vif vif ~client_ip ~router ~cleanup_tasks
+ )
+ (fun ex ->
+ Log.warn (fun f -> f "Error connecting client %a: %s"
+ Dao.ClientVif.pp vif (Printexc.to_string ex));
+ return ()
+ )
+ );
cleanup_tasks
(** Watch XenStore for notifications of new clients. *)
let listen router =
- let backend_vifs = "backend/vif" in
- Log.info (fun f -> f "Watching %s" backend_vifs);
Dao.watch_clients (fun new_set ->
(* Check for removed clients *)
- !clients |> IntMap.iter (fun key cleanup ->
- if not (IntSet.mem key new_set) then (
- clients := !clients |> IntMap.remove key;
- Log.info (fun f -> f "client %d has gone" key);
+ !clients |> Dao.VifMap.iter (fun key cleanup ->
+ if not (Dao.VifMap.mem key new_set) then (
+ clients := !clients |> Dao.VifMap.remove key;
+ Log.info (fun f -> f "client %a has gone" Dao.ClientVif.pp key);
Cleanup.cleanup cleanup
)
);
(* Check for added clients *)
- new_set |> IntSet.iter (fun key ->
- if not (IntMap.mem key !clients) then (
- let cleanup = add_client ~router key in
- clients := !clients |> IntMap.add key cleanup
+ new_set |> Dao.VifMap.iter (fun key ip_addr ->
+ if not (Dao.VifMap.mem key !clients) then (
+ let cleanup = add_client ~router key ip_addr in
+ clients := !clients |> Dao.VifMap.add key cleanup
)
)
)
diff --git a/dao.ml b/dao.ml
index f0ab65b..dd22735 100644
--- a/dao.ml
+++ b/dao.ml
@@ -4,38 +4,75 @@
open Lwt.Infix
open Utils
open Qubes
+open Astring
-type client_vif = {
- domid : int;
- device_id : int;
- client_ip : Ipaddr.V4.t;
-}
+let src = Logs.Src.create "dao" ~doc:"QubesDB data access"
+module Log = (val Logs.src_log src : Logs.LOG)
-let client_vifs domid =
- let path = Printf.sprintf "backend/vif/%d" domid in
- OS.Xs.make () >>= fun xs ->
- OS.Xs.immediate xs (fun h ->
- OS.Xs.directory h path >>=
- Lwt_list.map_p (fun device_id ->
- let device_id = int_of_string device_id in
- OS.Xs.read h (Printf.sprintf "%s/%d/ip" path device_id) >|= fun client_ip ->
- let client_ip = Ipaddr.V4.of_string_exn client_ip in
- { domid; device_id; client_ip }
- )
- )
+module ClientVif = struct
+ type t = {
+ domid : int;
+ device_id : int;
+ }
+
+ let pp f { domid; device_id } = Fmt.pf f "{domid=%d;device_id=%d}" domid device_id
+
+ let compare = compare
+end
+module VifMap = struct
+ include Map.Make(ClientVif)
+ let rec of_list = function
+ | [] -> empty
+ | (k, v) :: rest -> add k v (of_list rest)
+ let find key t =
+ try Some (find key t)
+ with Not_found -> None
+end
+
+let directory ~handle dir =
+ OS.Xs.directory handle dir >|= function
+ | [""] -> [] (* XenStore client bug *)
+ | items -> items
+
+let vifs ~handle domid =
+ match String.to_int domid with
+ | None -> Log.err (fun f -> f "Invalid domid %S" domid); Lwt.return []
+ | Some domid ->
+ let path = Printf.sprintf "backend/vif/%d" domid in
+ directory ~handle path >>=
+ Lwt_list.filter_map_p (fun device_id ->
+ match String.to_int device_id with
+ | None -> Log.err (fun f -> f "Invalid device ID %S for domid %d" device_id domid); Lwt.return_none
+ | Some device_id ->
+ let vif = { ClientVif.domid; device_id } in
+ Lwt.try_bind
+ (fun () -> OS.Xs.read handle (Printf.sprintf "%s/%d/ip" path device_id))
+ (fun client_ip ->
+ let client_ip = Ipaddr.V4.of_string_exn client_ip in
+ Lwt.return (Some (vif, client_ip))
+ )
+ (function
+ | Xs_protocol.Enoent _ -> Lwt.return None
+ | ex ->
+ Log.err (fun f -> f "Error getting IP address of %a: %s"
+ ClientVif.pp vif (Printexc.to_string ex));
+ Lwt.return None
+ )
+ )
let watch_clients fn =
OS.Xs.make () >>= fun xs ->
let backend_vifs = "backend/vif" in
+ Log.info (fun f -> f "Watching %s" backend_vifs);
OS.Xs.wait xs (fun handle ->
begin Lwt.catch
- (fun () -> OS.Xs.directory handle backend_vifs)
+ (fun () -> directory ~handle backend_vifs)
(function
| Xs_protocol.Enoent _ -> return []
| ex -> fail ex)
end >>= fun items ->
- let items = items |> List.fold_left (fun acc key -> IntSet.add (int_of_string key) acc) IntSet.empty in
- fn items;
+ Lwt_list.map_p (vifs ~handle) items >>= fun items ->
+ fn (List.concat items |> VifMap.of_list);
(* Wait for further updates *)
fail Xs_protocol.Eagain
)
diff --git a/dao.mli b/dao.mli
index c0f2862..e1b96c6 100644
--- a/dao.mli
+++ b/dao.mli
@@ -3,20 +3,21 @@
(** Wrapper for XenStore and QubesDB databases. *)
-open Utils
+module ClientVif : sig
+ type t = {
+ domid : int;
+ device_id : int;
+ }
+ val pp : t Fmt.t
+end
+module VifMap : sig
+ include Map.S with type key = ClientVif.t
+ val find : key -> 'a t -> 'a option
+end
-type client_vif = {
- domid : int;
- device_id : int;
- client_ip : Ipaddr.V4.t;
-}
-
-val watch_clients : (IntSet.t -> unit) -> 'a Lwt.t
-(** [watch_clients fn] calls [fn clients] with the current set of backend client domain IDs
- in XenStore, and again each time the set changes. *)
-
-val client_vifs : int -> client_vif list Lwt.t
-(** [client_vif domid] is the list of network interfaces to the client VM [domid]. *)
+val watch_clients : (Ipaddr.V4.t VifMap.t -> unit) -> 'a Lwt.t
+(** [watch_clients fn] calls [fn clients] with the list of backend clients
+ in XenStore, and again each time XenStore updates. *)
type network_config = {
uplink_netvm_ip : Ipaddr.V4.t; (* The IP address of NetVM (our gateway) *)
From d6074f2271eb3acd1cbd1538444c4c7929b2dcb1 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Mon, 9 Jan 2017 16:45:16 +0000
Subject: [PATCH 007/281] Add option to build with Docker
---
.dockerignore | 2 ++
Dockerfile | 10 ++++++++++
README.md | 18 +++++++++++++++++-
build-with-docker.sh | 4 ++++
4 files changed, 33 insertions(+), 1 deletion(-)
create mode 100644 .dockerignore
create mode 100644 Dockerfile
create mode 100755 build-with-docker.sh
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..5fde600
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,2 @@
+.git
+_build
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..097b564
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,10 @@
+FROM ocaml/opam:debian-8_ocaml-4.03.0
+RUN sudo apt-get install -y m4 libxen-dev
+RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage
+RUN opam pin add -n -y mirage-nat 'https://github.com/talex5/mirage-nat.git#simplify-checksum'
+RUN mkdir /home/opam/qubes-mirage-firewall
+ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
+WORKDIR /home/opam/qubes-mirage-firewall
+RUN opam config exec -- mirage configure --xen
+CMD opam config exec -- mirage configure --xen --no-opam && \
+ opam config exec -- make tar
diff --git a/README.md b/README.md
index a62ed36..2fc182a 100644
--- a/README.md
+++ b/README.md
@@ -7,11 +7,25 @@ Note: This firewall *ignores the rules set in the Qubes GUI*. See `rules.ml` for
See [A Unikernel Firewall for QubesOS][] for more details.
+## Build (with Docker)
+
+Clone this Git repository and run the `build-with-docker.sh` script:
+
+ sudo yum install docker
+ sudo systemctl start docker
+ git clone https://github.com/talex5/qubes-mirage-firewall.git
+ cd qubes-mirage-firewall
+ ./build-with-docker.sh
+
+This took about 10 minutes on my laptop (it will be much quicker if you run it again).
+
+## Build (without Docker)
+
To build (tested by creating a fresh Fedora 23 AppVM in Qubes):
1. Install build tools:
- sudo yum install git gcc m4 0install patch ncurses-devel
+ sudo yum install git gcc m4 0install patch ncurses-devel tar bzip2 unzip make which findutils xen-devel
mkdir ~/bin
0install add opam http://tools.ocaml.org/opam.xml
opam init --comp=4.02.3
@@ -29,6 +43,8 @@ To build (tested by creating a fresh Fedora 23 AppVM in Qubes):
mirage configure --xen
make
+## Deploy
+
If you want to deploy manually, use `make tar` to create `mirage-firewall.tar.bz2` and unpack this in dom0, inside `/var/lib/qubes/vm-kernels/`. e.g. (if `dev` is the AppVM where you built it):
[tal@dom0 ~]$ cd /var/lib/qubes/vm-kernels/
diff --git a/build-with-docker.sh b/build-with-docker.sh
new file mode 100755
index 0000000..31f85f4
--- /dev/null
+++ b/build-with-docker.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+set -eux
+docker build -t qubes-mirage-firewall .
+docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
From 07ff3d61477383860216c69869a1ffee59145e45 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 28 Jan 2017 13:44:21 +0000
Subject: [PATCH 008/281] Fix opam-repository commit for reproducible builds
Also, display the actual and expected SHA hashes after building.
---
Dockerfile | 5 +++++
build-with-docker.sh | 6 +++++-
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/Dockerfile b/Dockerfile
index 097b564..9424fc7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,9 @@
FROM ocaml/opam:debian-8_ocaml-4.03.0
+
+# Pin last known-good version for reproducible builds.
+# Remove this line if you want to test with the latest versions.
+RUN cd opam-repository && git reset --hard 0f17b354206c97e729700ce60ddce3789ccb1d52 && opam update
+
RUN sudo apt-get install -y m4 libxen-dev
RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage
RUN opam pin add -n -y mirage-nat 'https://github.com/talex5/mirage-nat.git#simplify-checksum'
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 31f85f4..d61f13c 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -1,4 +1,8 @@
#!/bin/sh
-set -eux
+set -eu
+echo Building Docker image with dependencies..
docker build -t qubes-mirage-firewall .
+echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
+echo "SHA2 of build: $(sha256sum mir-qubes-firewall.xen)"
+echo "SHA2 last known: f0c1a06fc4b02b494c81972dc89419af6cffa73b75839c0e8ee3798d77bf69b3"
From 036d92b0ff9236062550836c3e35e48ea37311b5 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 28 Jan 2017 15:19:05 +0000
Subject: [PATCH 009/281] Update README: you need "sudo docker" by default
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 2fc182a..396f545 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ Clone this Git repository and run the `build-with-docker.sh` script:
sudo systemctl start docker
git clone https://github.com/talex5/qubes-mirage-firewall.git
cd qubes-mirage-firewall
- ./build-with-docker.sh
+ sudo ./build-with-docker.sh
This took about 10 minutes on my laptop (it will be much quicker if you run it again).
From 150208fc722185dbe135294f65e0bf08a5e0737e Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Tue, 31 Jan 2017 09:26:57 +0000
Subject: [PATCH 010/281] Pin Docker base image to a specific hash
Requested by Joanna Rutkowska.
---
Dockerfile | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 9424fc7..2182d1e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,12 @@
-FROM ocaml/opam:debian-8_ocaml-4.03.0
+# Pin the base image to a specific hash for maximum reproducibility.
+# It will probably still work on newer images, though, unless Debian 8
+# changes some compiler optimisations (unlikely).
+#FROM ocaml/opam:debian-8_ocaml-4.03.0
+FROM ocaml/opam@sha256:28efab6a5535a517aa719ba5ac6d2e6fddd4831afaeabf5eee6470717eda9cca
# Pin last known-good version for reproducible builds.
-# Remove this line if you want to test with the latest versions.
+# Remove this line (and the base image pin above) if you want to test with the
+# latest versions.
RUN cd opam-repository && git reset --hard 0f17b354206c97e729700ce60ddce3789ccb1d52 && opam update
RUN sudo apt-get install -y m4 libxen-dev
From bb78a726e463267b96de8f285ff422d50be9691a Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Thu, 2 Mar 2017 14:52:55 +0000
Subject: [PATCH 011/281] Mirage 3 support
---
.travis.yml | 2 +-
Makefile.user | 2 +-
README.md | 6 +-
client_eth.ml | 108 ++++++++++---------------------
client_eth.mli | 4 +-
client_net.ml | 69 ++++++++++++--------
config.ml | 15 +++--
dao.ml | 2 +-
firewall.ml | 171 +++++++++++++++++++------------------------------
firewall.mli | 8 +--
fw_utils.ml | 48 ++++++++++++++
my_nat.ml | 139 ++++++++++++++++++++++++++++++++++++++++
my_nat.mli | 19 ++++++
packet.ml | 4 +-
router.ml | 22 ++-----
router.mli | 12 ++--
unikernel.ml | 29 ++++++---
uplink.ml | 33 +++++-----
uplink.mli | 6 +-
utils.ml | 65 -------------------
20 files changed, 423 insertions(+), 341 deletions(-)
create mode 100644 fw_utils.ml
create mode 100644 my_nat.ml
create mode 100644 my_nat.mli
delete mode 100644 utils.ml
diff --git a/.travis.yml b/.travis.yml
index 9842928..e9d1353 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,4 +20,4 @@ addons:
- time
- libxen-dev
env:
- - FORK_USER=talex5 FORK_BRANCH=unikernel OCAML_VERSION=4.02 MIRAGE_BACKEND=xen PINS="mirage-nat:https://github.com/talex5/mirage-nat.git#simplify-checksum"
+ - OCAML_VERSION=4.04 MIRAGE_BACKEND=xen PINS="mirage-nat:https://github.com/talex5/mirage-nat.git#mirage3"
diff --git a/Makefile.user b/Makefile.user
index 61ad38e..33335e6 100644
--- a/Makefile.user
+++ b/Makefile.user
@@ -1,7 +1,7 @@
tar: build
rm -rf _build/mirage-firewall
mkdir _build/mirage-firewall
- cp mir-qubes-firewall.xen _build/mirage-firewall/vmlinuz
+ cp qubes_firewall.xen _build/mirage-firewall/vmlinuz
touch _build/mirage-firewall/modules.img
cat /dev/null | gzip > _build/mirage-firewall/initramfs
tar cjf mirage-firewall.tar.bz2 -C _build mirage-firewall
diff --git a/README.md b/README.md
index 396f545..18b0b7e 100644
--- a/README.md
+++ b/README.md
@@ -21,19 +21,17 @@ This took about 10 minutes on my laptop (it will be much quicker if you run it a
## Build (without Docker)
-To build (tested by creating a fresh Fedora 23 AppVM in Qubes):
-
1. Install build tools:
sudo yum install git gcc m4 0install patch ncurses-devel tar bzip2 unzip make which findutils xen-devel
mkdir ~/bin
0install add opam http://tools.ocaml.org/opam.xml
- opam init --comp=4.02.3
+ opam init --comp=4.04.0
eval `opam config env`
2. Install mirage, pinning a few unreleased features we need:
- opam pin add -y mirage-nat 'https://github.com/talex5/mirage-nat.git#simplify-checksum'
+ opam pin add -y mirage-nat 'https://github.com/talex5/mirage-nat.git#mirage3'
opam install mirage
3. Build mirage-firewall:
diff --git a/client_eth.ml b/client_eth.ml
index f30f69c..751274b 100644
--- a/client_eth.ml
+++ b/client_eth.ml
@@ -1,7 +1,7 @@
(* Copyright (C) 2016, Thomas Leonard
See the README file for details. *)
-open Utils
+open Fw_utils
open Lwt.Infix
let src = Logs.Src.create "client_eth" ~doc:"Ethernet networks for NetVM clients"
@@ -52,10 +52,10 @@ let classify t ip =
match ip with
| Ipaddr.V6 _ -> `External ip
| Ipaddr.V4 ip4 ->
- if ip4 = t.client_gw then `Client_gateway
- else match lookup t ip4 with
- | Some client_link -> `Client client_link
- | None -> `External ip
+ if ip4 = t.client_gw then `Client_gateway
+ else match lookup t ip4 with
+ | Some client_link -> `Client client_link
+ | None -> `External ip
let resolve t : host -> Ipaddr.t = function
| `Client client_link -> Ipaddr.V4 client_link#other_ip
@@ -71,8 +71,8 @@ module ARP = struct
let lookup t ip =
if ip = t.net.client_gw then Some t.client_link#my_mac
else None
- (* We're now treating client networks as point-to-point links,
- so we no longer respond on behalf of other clients. *)
+ (* We're now treating client networks as point-to-point links,
+ so we no longer respond on behalf of other clients. *)
(*
else match IpMap.find ip t.net.iface_of_ip with
| Some client_iface -> Some client_iface#other_mac
@@ -81,84 +81,46 @@ module ARP = struct
let create ~net client_link = {net; client_link}
- type arp_msg = {
- op: [ `Request |`Reply |`Unknown of int ];
- sha: Macaddr.t;
- spa: Ipaddr.V4.t;
- tha: Macaddr.t;
- tpa: Ipaddr.V4.t;
- }
-
- let to_wire arp =
- let open Arpv4_wire in
- (* Obtain a buffer to write into *)
- let buf = Cstruct.create (Wire_structs.sizeof_ethernet + sizeof_arp) in
- (* Write the ARP packet *)
- let dmac = Macaddr.to_bytes arp.tha in
- let smac = Macaddr.to_bytes arp.sha in
- let spa = Ipaddr.V4.to_int32 arp.spa in
- let tpa = Ipaddr.V4.to_int32 arp.tpa in
- let op =
- match arp.op with
- |`Request -> 1
- |`Reply -> 2
- |`Unknown n -> n
- in
- Wire_structs.set_ethernet_dst dmac 0 buf;
- Wire_structs.set_ethernet_src smac 0 buf;
- Wire_structs.set_ethernet_ethertype buf 0x0806; (* ARP *)
- let arpbuf = Cstruct.shift buf 14 in
- set_arp_htype arpbuf 1;
- set_arp_ptype arpbuf 0x0800; (* IPv4 *)
- set_arp_hlen arpbuf 6; (* ethernet mac size *)
- set_arp_plen arpbuf 4; (* ipv4 size *)
- set_arp_op arpbuf op;
- set_arp_sha smac 0 arpbuf;
- set_arp_spa arpbuf spa;
- set_arp_tha dmac 0 arpbuf;
- set_arp_tpa arpbuf tpa;
- buf
-
- let input_query t frame =
- let open Arpv4_wire in
- let req_ipv4 = Ipaddr.V4.of_int32 (get_arp_tpa frame) in
+ let input_query t arp =
+ let req_ipv4 = arp.Arpv4_packet.tpa in
Log.info (fun f -> f "who-has %s?" (Ipaddr.V4.to_string req_ipv4));
if req_ipv4 = t.client_link#other_ip then (
Log.info (fun f -> f "ignoring request for client's own IP");
None
) else match lookup t req_ipv4 with
- | None ->
+ | None ->
Log.info (fun f -> f "unknown address; not responding");
None
- | Some req_mac ->
+ | Some req_mac ->
Log.info (fun f -> f "responding to: who-has %s?" (Ipaddr.V4.to_string req_ipv4));
- Some (to_wire {
- op = `Reply;
- (* The Target Hardware Address and IP are copied from the request *)
- tha = Macaddr.of_bytes_exn (copy_arp_sha frame);
- tpa = Ipaddr.V4.of_int32 (get_arp_spa frame);
- sha = req_mac;
- spa = req_ipv4;
- })
+ let req_spa = arp.Arpv4_packet.spa in
+ let req_sha = arp.Arpv4_packet.sha in
+ Some { Arpv4_packet.
+ op = Arpv4_wire.Reply;
+ (* The Target Hardware Address and IP are copied from the request *)
+ tha = req_sha;
+ tpa = req_spa;
+ sha = req_mac;
+ spa = req_ipv4;
+ }
- let input_gratuitous t frame =
- let open Arpv4_wire in
- let spa = Ipaddr.V4.of_int32 (get_arp_spa frame) in
- let sha = Macaddr.of_bytes_exn (copy_arp_sha frame) in
+ let input_gratuitous t arp =
+ let spa = arp.Arpv4_packet.spa in
+ let sha = arp.Arpv4_packet.sha in
match lookup t spa with
| Some real_mac when Macaddr.compare sha real_mac = 0 ->
- Log.info (fun f -> f "client suggests updating %s -> %s (as expected)"
- (Ipaddr.V4.to_string spa) (Macaddr.to_string sha));
+ Log.info (fun f -> f "client suggests updating %s -> %s (as expected)"
+ (Ipaddr.V4.to_string spa) (Macaddr.to_string sha));
| Some other_mac ->
- Log.warn (fun f -> f "client suggests incorrect update %s -> %s (should be %s)"
- (Ipaddr.V4.to_string spa) (Macaddr.to_string sha) (Macaddr.to_string other_mac));
+ Log.warn (fun f -> f "client suggests incorrect update %s -> %s (should be %s)"
+ (Ipaddr.V4.to_string spa) (Macaddr.to_string sha) (Macaddr.to_string other_mac));
| None ->
- Log.warn (fun f -> f "client suggests incorrect update %s -> %s (unexpected IP)"
- (Ipaddr.V4.to_string spa) (Macaddr.to_string sha))
+ Log.warn (fun f -> f "client suggests incorrect update %s -> %s (unexpected IP)"
+ (Ipaddr.V4.to_string spa) (Macaddr.to_string sha))
- let input t frame =
- match Arpv4_wire.get_arp_op frame with
- |1 -> input_query t frame
- |2 -> input_gratuitous t frame; None
- |n -> Log.warn (fun f -> f "unknown message %d - ignored" n); None
+ let input t arp =
+ let op = arp.Arpv4_packet.op in
+ match op with
+ | Arpv4_wire.Request -> input_query t arp
+ | Arpv4_wire.Reply -> input_gratuitous t arp; None
end
diff --git a/client_eth.mli b/client_eth.mli
index 41746d3..0851913 100644
--- a/client_eth.mli
+++ b/client_eth.mli
@@ -4,7 +4,7 @@
(** The ethernet networks connecting us to our client AppVMs.
Note: each AppVM is on a point-to-point link, each link being considered to be a separate Ethernet network. *)
-open Utils
+open Fw_utils
type t
(** A collection of clients. *)
@@ -47,7 +47,7 @@ module ARP : sig
(** [create ~net client_link] is an ARP responder for [client_link].
It answers only for the client's gateway address. *)
- val input : arp -> Cstruct.t -> Cstruct.t option
+ val input : arp -> Arpv4_packet.t -> Arpv4_packet.t option
(** Process one ethernet frame containing an ARP message.
Returns a response frame, if one is needed. *)
end
diff --git a/client_net.ml b/client_net.ml
index ca39938..50f22ea 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -2,7 +2,7 @@
See the README file for details. *)
open Lwt.Infix
-open Utils
+open Fw_utils
module Netback = Netchannel.Backend.Make(Netchannel.Xenstore.Make(OS.Xs))
module ClientEth = Ethif.Make(Netback)
@@ -12,7 +12,13 @@ module Log = (val Logs.src_log src : Logs.LOG)
let writev eth data =
Lwt.catch
- (fun () -> ClientEth.writev eth data)
+ (fun () ->
+ ClientEth.writev eth data >|= function
+ | Ok () -> ()
+ | Error e ->
+ Log.err (fun f -> f "error trying to send to client:@\n@[ %a@]@\nError: @[%a@]"
+ Cstruct.hexdump_pp (Cstruct.concat data) ClientEth.pp_error e);
+ )
(fun ex ->
(* Usually Netback_shutdown, because the client disconnected *)
Log.err (fun f -> f "uncaught exception trying to send to client:@\n@[ %a@]@\nException: @[%s@]"
@@ -26,36 +32,47 @@ class client_iface eth ~gateway_ip ~client_ip client_mac : client_link = object
method other_mac = client_mac
method my_ip = gateway_ip
method other_ip = client_ip
- method writev ip =
+ method writev proto ip =
FrameQ.send queue (fun () ->
- let eth_hdr = eth_header_ipv4 ~src:(ClientEth.mac eth) ~dst:client_mac in
- writev eth (fixup_checksums (Cstruct.concat (eth_hdr :: ip)))
+ let eth_hdr = eth_header proto ~src:(ClientEth.mac eth) ~dst:client_mac in
+ writev eth (eth_hdr :: ip)
)
end
let clients : Cleanup.t Dao.VifMap.t ref = ref Dao.VifMap.empty
(** Handle an ARP message from the client. *)
-let input_arp ~fixed_arp ~eth request =
- match Client_eth.ARP.input fixed_arp request with
- | None -> return ()
- | Some response -> writev eth [response]
+let input_arp ~fixed_arp ~iface request =
+ match Arpv4_packet.Unmarshal.of_cstruct request with
+ | Error e ->
+ Log.warn (fun f -> f "ignored unknown ARP message: %a" Arpv4_packet.Unmarshal.pp_error e);
+ Lwt.return ()
+ | Ok arp ->
+ match Client_eth.ARP.input fixed_arp arp with
+ | None -> return ()
+ | Some response ->
+ iface#writev Ethif_wire.ARP [Arpv4_packet.Marshal.make_cstruct response]
(** Handle an IPv4 packet from the client. *)
-let input_ipv4 ~client_ip ~router frame packet =
- let src = Wire_structs.Ipv4_wire.get_ipv4_src packet |> Ipaddr.V4.of_int32 in
- if src = client_ip then Firewall.ipv4_from_client router frame
- else (
- Log.warn (fun f -> f "Incorrect source IP %a in IP packet from %a (dropping)"
- Ipaddr.V4.pp_hum src Ipaddr.V4.pp_hum client_ip);
- return ()
- )
+let input_ipv4 ~client_ip ~router packet =
+ match Ipv4_packet.Unmarshal.of_cstruct packet with
+ | Error e ->
+ Log.warn (fun f -> f "ignored unknown IPv4 message: %s" e);
+ Lwt.return ()
+ | Ok (ip, payload) ->
+ let src = ip.Ipv4_packet.src in
+ if src = client_ip then Firewall.ipv4_from_client router (ip, payload)
+ else (
+ Log.warn (fun f -> f "Incorrect source IP %a in IP packet from %a (dropping)"
+ Ipaddr.V4.pp_hum src Ipaddr.V4.pp_hum client_ip);
+ return ()
+ )
(** Connect to a new client's interface and listen for incoming frames. *)
let add_vif { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks =
Netback.make ~domid ~device_id >>= fun backend ->
Log.info (fun f -> f "Client %d (IP: %s) ready" domid (Ipaddr.V4.to_string client_ip));
- ClientEth.connect backend >>= or_fail "Can't make Ethernet device" >>= fun eth ->
+ ClientEth.connect backend >>= fun eth ->
let client_mac = Netback.mac backend in
let client_eth = router.Router.client_eth in
let gateway_ip = Client_eth.client_gw client_eth in
@@ -64,15 +81,15 @@ let add_vif { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks
Cleanup.on_cleanup cleanup_tasks (fun () -> Router.remove_client router iface);
let fixed_arp = Client_eth.ARP.create ~net:client_eth iface in
Netback.listen backend (fun frame ->
- match Wire_structs.parse_ethernet_frame frame with
- | None -> Log.warn (fun f -> f "Invalid Ethernet frame"); return ()
- | Some (typ, _destination, payload) ->
- match typ with
- | Some Wire_structs.ARP -> input_arp ~fixed_arp ~eth payload
- | Some Wire_structs.IPv4 -> input_ipv4 ~client_ip ~router frame payload
- | Some Wire_structs.IPv6 -> return ()
- | None -> Logs.warn (fun f -> f "Unknown Ethernet type"); Lwt.return_unit
+ match Ethif_packet.Unmarshal.of_cstruct frame with
+ | Error err -> Log.warn (fun f -> f "Invalid Ethernet frame: %s" err); return ()
+ | Ok (eth, payload) ->
+ match eth.Ethif_packet.ethertype with
+ | Ethif_wire.ARP -> input_arp ~fixed_arp ~iface payload
+ | Ethif_wire.IPv4 -> input_ipv4 ~client_ip ~router payload
+ | Ethif_wire.IPv6 -> return ()
)
+ >|= or_raise "Listen on client interface" Netback.pp_error
(** A new client VM has been found in XenStore. Find its interface and connect to it. *)
let add_client ~router vif client_ip =
diff --git a/config.ml b/config.ml
index e2be6f3..6ac02db 100644
--- a/config.ml
+++ b/config.ml
@@ -7,10 +7,17 @@ open Mirage
let main =
foreign
- ~libraries:["mirage-net-xen"; "tcpip.stack-direct"; "tcpip.xen"; "mirage-qubes"; "mirage-nat"; "mirage-logs"]
- ~packages:["vchan"; "cstruct"; "tcpip"; "mirage-net-xen"; "mirage-qubes"; "mirage-nat"; "mirage-logs"]
- "Unikernel.Main" (clock @-> job)
+ ~packages:[
+ package "vchan";
+ package "cstruct";
+ package "tcpip" ~sublibs:["stack-direct"; "xen"];
+ package "mirage-net-xen";
+ package "mirage-qubes";
+ package "mirage-nat" ~sublibs:["hashtable"];
+ package "mirage-logs";
+ ]
+ "Unikernel.Main" (mclock @-> job)
let () =
- register "qubes-firewall" [main $ default_clock]
+ register "qubes-firewall" [main $ default_monotonic_clock]
~argv:no_argv
diff --git a/dao.ml b/dao.ml
index dd22735..9ce0766 100644
--- a/dao.ml
+++ b/dao.ml
@@ -2,8 +2,8 @@
See the README file for details. *)
open Lwt.Infix
-open Utils
open Qubes
+open Fw_utils
open Astring
let src = Logs.Src.create "dao" ~doc:"QubesDB data access"
diff --git a/firewall.ml b/firewall.ml
index cdfd977..226a56c 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -1,23 +1,19 @@
(* Copyright (C) 2015, Thomas Leonard
See the README file for details. *)
-open Utils
+open Fw_utils
open Packet
+open Lwt.Infix
let src = Logs.Src.create "firewall" ~doc:"Packet handler"
module Log = (val Logs.src_log src : Logs.LOG)
(* Transmission *)
-let transmit ~frame iface =
- (* If packet has been NAT'd then we certainly need to recalculate the checksum,
- but even for direct pass-through it might have been received with an invalid
- checksum due to checksum offload. For now, recalculate full checksum in all
- cases. *)
- let frame = fixup_checksums frame |> Cstruct.concat in
- let packet = Cstruct.shift frame Wire_structs.sizeof_ethernet in
+let transmit (ip, payload) iface =
+ let packet = Ipv4_packet.Marshal.make_cstruct ~payload ip in
Lwt.catch
- (fun () -> iface#writev [packet])
+ (fun () -> iface#writev Ethif_wire.IPv4 [packet; payload])
(fun ex ->
Log.warn (fun f -> f "Failed to write packet to %a: %s"
Ipaddr.V4.pp_hum iface#other_ip
@@ -25,35 +21,44 @@ let transmit ~frame iface =
Lwt.return ()
)
-let forward_ipv4 t frame =
- let packet = Cstruct.shift frame Wire_structs.sizeof_ethernet in
- match Router.target t packet with
- | Some iface -> transmit ~frame iface
+let forward_ipv4 t (ip, packet) =
+ match Router.target t ip with
+ | Some iface -> transmit (ip, packet) iface
| None -> return ()
(* Packet classification *)
-let ports transport =
- let sport, dport = Nat_rewrite.ports_of_transport transport in
- { sport; dport }
+let classify_tcp trans =
+ match Tcp.Tcp_packet.Unmarshal.of_cstruct trans with
+ | Error e ->
+ Log.info (fun f -> f "Failed to parse TCP packet: %s" e);
+ `Unknown
+ | Ok (tcp, _payload) ->
+ let sport = tcp.Tcp.Tcp_packet.src_port in
+ let dport = tcp.Tcp.Tcp_packet.dst_port in
+ `TCP {sport; dport}
-let classify t frame =
- match Nat_rewrite.layers frame with
- | None ->
- Log.warn (fun f -> f "Failed to parse frame");
- None
- | Some (_eth, ip, transport) ->
- let src, dst = Nat_rewrite.addresses_of_ip ip in
+let classify_udp trans =
+ match Udp_packet.Unmarshal.of_cstruct trans with
+ | Error e ->
+ Log.info (fun f -> f "Failed to parse UDP packet: %s" e);
+ `Unknown
+ | Ok (udp, _payload) ->
+ let sport = udp.Udp_packet.src_port in
+ let dport = udp.Udp_packet.dst_port in
+ `UDP {sport; dport}
+
+let classify t (ip, transport) =
let proto =
- match Nat_rewrite.proto_of_ip ip with
- | 1 -> `ICMP
- | 6 -> `TCP (ports transport)
- | 17 -> `UDP (ports transport)
- | _ -> `Unknown in
+ match ip.Ipv4_packet.proto |> Ipv4_packet.Unmarshal.int_to_protocol with
+ | Some `ICMP -> `ICMP
+ | Some `TCP -> classify_tcp transport
+ | Some `UDP -> classify_udp transport
+ | None -> `Unknown in
Some {
- frame;
- src = Router.classify t src;
- dst = Router.classify t dst;
+ packet = (ip, transport);
+ src = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.src);
+ dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst);
proto;
}
@@ -74,7 +79,7 @@ let pp_proto fmt = function
| `ICMP -> Format.pp_print_string fmt "ICMP"
| `Unknown -> Format.pp_print_string fmt "UnknownProtocol"
-let pp_packet fmt {src; dst; proto; frame = _} =
+let pp_packet fmt {src; dst; proto; packet = _} =
Format.fprintf fmt "[src=%a dst=%a proto=%a]"
pp_host src
pp_host dst
@@ -82,84 +87,40 @@ let pp_packet fmt {src; dst; proto; frame = _} =
(* NAT *)
-let translate t frame =
- Nat_rewrite.translate t.Router.nat frame
-
-let random_user_port () =
- 1024 + Random.int (0xffff - 1024)
-
-let rec add_nat_rule_and_transmit ?(retries=100) t frame fn logf =
- let xl_port = random_user_port () in
- match fn xl_port with
- | exception Out_of_memory ->
- (* Because hash tables resize in big steps, this can happen even if we have a fair
- chunk of free memory. *)
- Log.warn (fun f -> f "Out_of_memory adding NAT rule. Dropping NAT table...");
- Router.reset t;
- add_nat_rule_and_transmit ~retries:(retries - 1) t frame fn logf
- | Nat_rewrite.Overlap when retries < 0 -> return ()
- | Nat_rewrite.Overlap ->
- if retries = 0 then (
- Log.warn (fun f -> f "Failed to find a free port; resetting NAT table");
- Router.reset t;
- );
- add_nat_rule_and_transmit ~retries:(retries - 1) t frame fn logf (* Try a different port *)
- | Nat_rewrite.Unparseable ->
- Log.warn (fun f -> f "Failed to add NAT rule: Unparseable");
- return ()
- | Nat_rewrite.Ok _ ->
- Log.debug (logf xl_port);
- match translate t frame with
- | Some frame -> forward_ipv4 t frame
- | None ->
- Log.warn (fun f -> f "No NAT entry, even after adding one!");
- return ()
+let translate t packet =
+ My_nat.translate t.Router.nat packet
(* Add a NAT rule for the endpoints in this frame, via a random port on the firewall. *)
-let add_nat_and_forward_ipv4 t ~frame =
+let add_nat_and_forward_ipv4 t packet =
let xl_host = Ipaddr.V4 t.Router.uplink#my_ip in
- add_nat_rule_and_transmit t frame
- (* Note: DO NOT partially apply; [t.nat] may change between calls *)
- (fun xl_port -> Nat_rewrite.make_nat_entry t.Router.nat frame xl_host xl_port)
- (fun xl_port f ->
- match Nat_rewrite.layers frame with
- | None -> assert false
- | Some (_eth, ip, transport) ->
- let src, dst = Nat_rewrite.addresses_of_ip ip in
- let sport, dport = Nat_rewrite.ports_of_transport transport in
- f "added NAT entry: %s:%d -> firewall:%d -> %d:%s" (Ipaddr.to_string src) sport xl_port dport (Ipaddr.to_string dst)
- )
+ My_nat.add_nat_rule_and_translate t.Router.nat ~xl_host `Rewrite packet >>= function
+ | Ok packet -> forward_ipv4 t packet
+ | Error e ->
+ Log.warn (fun f -> f "Failed to add NAT rewrite rule: %s" e);
+ Lwt.return ()
(* Add a NAT rule to redirect this conversation to [host:port] instead of us. *)
-let nat_to t ~frame ~host ~port =
+let nat_to t ~host ~port packet =
let target = Router.resolve t host in
let xl_host = Ipaddr.V4 t.Router.uplink#my_ip in
- add_nat_rule_and_transmit t frame
- (fun xl_port ->
- Nat_rewrite.make_redirect_entry t.Router.nat frame (xl_host, xl_port) (target, port)
- )
- (fun xl_port f ->
- match Nat_rewrite.layers frame with
- | None -> assert false
- | Some (_eth, ip, transport) ->
- let src, _dst = Nat_rewrite.addresses_of_ip ip in
- let sport, dport = Nat_rewrite.ports_of_transport transport in
- f "added NAT redirect %s:%d -> %d:firewall:%d -> %d:%a"
- (Ipaddr.to_string src) sport dport xl_port port pp_host host
- )
+ My_nat.add_nat_rule_and_translate t.Router.nat ~xl_host (`Redirect (target, port)) packet >>= function
+ | Ok packet -> forward_ipv4 t packet
+ | Error e ->
+ Log.warn (fun f -> f "Failed to add NAT redirect rule: %s" e);
+ Lwt.return ()
(* Handle incoming packets *)
let apply_rules t rules info =
- let frame = info.frame in
+ let packet = info.packet in
match rules info, info.dst with
- | `Accept, `Client client_link -> transmit ~frame client_link
- | `Accept, (`External _ | `NetVM) -> transmit ~frame t.Router.uplink
+ | `Accept, `Client client_link -> transmit packet client_link
+ | `Accept, (`External _ | `NetVM) -> transmit packet t.Router.uplink
| `Accept, (`Firewall_uplink | `Client_gateway) ->
Log.warn (fun f -> f "Bad rule: firewall can't accept packets %a" pp_packet info);
return ()
- | `NAT, _ -> add_nat_and_forward_ipv4 t ~frame
- | `NAT_to (host, port), _ -> nat_to t ~frame ~host ~port
+ | `NAT, _ -> add_nat_and_forward_ipv4 t packet
+ | `NAT_to (host, port), _ -> nat_to t packet ~host ~port
| `Drop reason, _ ->
Log.info (fun f -> f "Dropped packet (%s) %a" reason pp_packet info);
return ()
@@ -168,28 +129,28 @@ let handle_low_memory t =
match Memory_pressure.status () with
| `Memory_critical -> (* TODO: should happen before copying and async *)
Log.warn (fun f -> f "Memory low - dropping packet and resetting NAT table");
- Router.reset t;
+ My_nat.reset t.Router.nat >|= fun () ->
`Memory_critical
- | `Ok -> `Ok
+ | `Ok -> Lwt.return `Ok
-let ipv4_from_client t frame =
- match handle_low_memory t with
+let ipv4_from_client t (ip, payload) =
+ handle_low_memory t >>= function
| `Memory_critical -> return ()
| `Ok ->
(* Check for existing NAT entry for this packet *)
- match translate t frame with
+ translate t (ip, payload) >>= function
| Some frame -> forward_ipv4 t frame (* Some existing connection or redirect *)
| None ->
(* No existing NAT entry. Check the firewall rules. *)
- match classify t frame with
+ match classify t (ip, payload) with
| None -> return ()
| Some info -> apply_rules t Rules.from_client info
-let ipv4_from_netvm t frame =
- match handle_low_memory t with
+let ipv4_from_netvm t (ip, payload) =
+ handle_low_memory t >>= function
| `Memory_critical -> return ()
| `Ok ->
- match classify t frame with
+ match classify t (ip, payload) with
| None -> return ()
| Some info ->
match info.src with
@@ -197,7 +158,7 @@ let ipv4_from_netvm t frame =
Log.warn (fun f -> f "Frame from NetVM has internal source IP address! %a" pp_packet info);
return ()
| `External _ | `NetVM ->
- match translate t frame with
+ translate t (ip, payload) >>= function
| Some frame -> forward_ipv4 t frame
| None ->
apply_rules t Rules.from_netvm info
diff --git a/firewall.mli b/firewall.mli
index a8e5624..3a88270 100644
--- a/firewall.mli
+++ b/firewall.mli
@@ -3,9 +3,9 @@
(** Classify IP packets, apply rules and send as appropriate. *)
-val ipv4_from_netvm : Router.t -> Cstruct.t -> unit Lwt.t
-(** Handle a frame from the outside world (this module will validate the source IP). *)
+val ipv4_from_netvm : Router.t -> Ipv4_packet.t * Cstruct.t -> unit Lwt.t
+(** Handle a packet from the outside world (this module will validate the source IP). *)
-val ipv4_from_client : Router.t -> Cstruct.t -> unit Lwt.t
-(** Handle a frame from a client. Caller must check the source IP matches the client's
+val ipv4_from_client : Router.t -> Ipv4_packet.t * Cstruct.t -> unit Lwt.t
+(** Handle a packet from a client. Caller must check the source IP matches the client's
before calling this. *)
diff --git a/fw_utils.ml b/fw_utils.ml
new file mode 100644
index 0000000..f4e63e8
--- /dev/null
+++ b/fw_utils.ml
@@ -0,0 +1,48 @@
+(* Copyright (C) 2015, Thomas Leonard
+ See the README file for details. *)
+
+(** General utility functions. *)
+
+module IpMap = struct
+ include Map.Make(Ipaddr.V4)
+ let find x map =
+ try Some (find x map)
+ with Not_found -> None
+end
+
+module Int = struct
+ type t = int
+ let compare (a:t) (b:t) = compare a b
+end
+
+module IntSet = Set.Make(Int)
+module IntMap = Map.Make(Int)
+
+(** An Ethernet interface. *)
+class type interface = object
+ method my_mac : Macaddr.t
+ method writev : Ethif_wire.ethertype -> Cstruct.t list -> unit Lwt.t
+ method my_ip : Ipaddr.V4.t
+ method other_ip : Ipaddr.V4.t
+end
+
+(** An Ethernet interface connected to a clientVM. *)
+class type client_link = object
+ inherit interface
+ method other_mac : Macaddr.t
+end
+
+(** An Ethernet header from [src]'s MAC address to [dst]'s with an IPv4 payload. *)
+let eth_header ethertype ~src ~dst =
+ Ethif_packet.Marshal.make_cstruct { Ethif_packet.source = src; destination = dst; ethertype }
+
+let error fmt =
+ let err s = Failure s in
+ Printf.ksprintf err fmt
+
+let return = Lwt.return
+let fail = Lwt.fail
+
+let or_raise msg pp = function
+ | Ok x -> x
+ | Error e -> failwith (Fmt.strf "%s: %a" msg pp e)
diff --git a/my_nat.ml b/my_nat.ml
new file mode 100644
index 0000000..ec9d0f2
--- /dev/null
+++ b/my_nat.ml
@@ -0,0 +1,139 @@
+(* Copyright (C) 2015, Thomas Leonard
+ See the README file for details. *)
+
+open Lwt.Infix
+
+let src = Logs.Src.create "my-nat" ~doc:"NAT shim"
+module Log = (val Logs.src_log src : Logs.LOG)
+
+type action = [
+ | `Rewrite
+ | `Redirect of Ipaddr.t * int
+]
+
+type packet = Ipv4_packet.t * Cstruct.t
+
+(* To avoid needing to allocate a new NAT table when we've run out of
+ memory, pre-allocate the new one ahead of time. *)
+type 'a with_standby = {
+ mutable current :'a;
+ mutable next : 'a;
+}
+
+type t = Nat : (module Mirage_nat.S with type t = 't and type config = 'c) * 'c * 't with_standby -> t
+
+let create (type c t) (nat:(module Mirage_nat.S with type config = c and type t = t)) (c:c) =
+ let (module Nat : Mirage_nat.S with type config = c and type t = t) = nat in
+ Nat.empty c >>= fun current ->
+ Nat.empty c >>= fun next ->
+ let table = { current; next } in
+ Lwt.return (Nat (nat, c, table))
+
+(* Ideally, mirage-nat wouldn't ask us for an ethernet header, since it only
+ cares about the IP layer anyway. *)
+let fake_ipv4_eth =
+ let dontcare = Macaddr.broadcast in
+ Fw_utils.eth_header Ethif_wire.IPv4 ~src:dontcare ~dst:dontcare
+
+let translate (Nat ((module Nat), _, table)) (ip, payload) =
+ (* XXX: change Nat.translate API *)
+ let packet = Ipv4_packet.Marshal.make_cstruct ~payload ip in
+ let frame = Cstruct.concat [
+ fake_ipv4_eth;
+ packet;
+ payload;
+ ] in
+ Nat.translate table.current frame >|= function
+ | Mirage_nat.Untranslated -> None
+ | Mirage_nat.Translated _ -> (* XXX: translate mutates frame *)
+ let packet = Cstruct.shift frame Ethif_wire.sizeof_ethernet in
+ match Ipv4_packet.Unmarshal.of_cstruct packet with
+ | Error e -> Log.err (fun f -> f "Translation failed: %s" e); None
+ | Ok packet -> Some packet
+
+let random_user_port () =
+ 1024 + Random.int (0xffff - 1024)
+
+let reset (Nat ((module Nat), c, table)) =
+ table.current <- table.next;
+ (* (at this point, the big old NAT table can be GC'd, so allocating
+ a new one should be OK) *)
+ Nat.empty c >|= fun next ->
+ table.next <- next
+
+let add_nat_rule_and_translate ((Nat ((module Nat), c, table)) as t) ~xl_host action packet =
+ let frame =
+ let (ip, payload) = packet in
+ Cstruct.concat [
+ fake_ipv4_eth;
+ Ipv4_packet.Marshal.make_cstruct ~payload ip;
+ payload;
+ ] in
+ let apply_action xl_port =
+ Lwt.try_bind (fun () ->
+ match action with
+ | `Rewrite ->
+ Nat.add_nat table.current frame (xl_host, xl_port)
+ | `Redirect target ->
+ Nat.add_redirect table.current frame (xl_host, xl_port) target
+ )
+ (function
+ | Nat.Ok -> Lwt.return (Ok ())
+ | Nat.Overlap -> Lwt.return (Error `Overlap)
+ | Nat.Unparseable -> Lwt.return (Error `Unparseable)
+ )
+ (function
+ | Out_of_memory -> Lwt.return (Error `Out_of_memory)
+ | x -> Lwt.fail x
+ )
+ in
+ let reset () =
+ table.current <- table.next;
+ (* (at this point, the big old NAT table can be GC'd, so allocating
+ a new one should be OK) *)
+ Nat.empty c >|= fun next ->
+ table.next <- next
+ in
+ let rec aux ~retries =
+ let xl_port = random_user_port () in
+ apply_action xl_port >>= function
+ | Error `Out_of_memory ->
+ (* Because hash tables resize in big steps, this can happen even if we have a fair
+ chunk of free memory. *)
+ Log.warn (fun f -> f "Out_of_memory adding NAT rule. Dropping NAT table...");
+ reset () >>= fun () ->
+ aux ~retries:(retries - 1)
+ | Error `Overlap when retries < 0 -> Lwt.return (Error "Too many retries")
+ | Error `Overlap ->
+ if retries = 0 then (
+ Log.warn (fun f -> f "Failed to find a free port; resetting NAT table");
+ reset () >>= fun () ->
+ aux ~retries:(retries - 1)
+ ) else (
+ aux ~retries:(retries - 1)
+ )
+ | Error `Unparseable ->
+ Lwt.return (Error "Unparseable by mirage-nat")
+ | Ok () ->
+ translate t packet >|= function
+ | None -> Error "No NAT entry, even after adding one!"
+ | Some packet ->
+(*
+ Log.debug (fun f ->
+ match action with
+ | `Rewrite ->
+ let (ip, trans) = packet in
+ let src, dst = Nat_rewrite.addresses_of_ip ip in
+ let sport, dport = Nat_rewrite.ports_of_transport transport in
+ f "added NAT entry: %s:%d -> firewall:%d -> %d:%s" (Ipaddr.to_string src) sport xl_port dport (Ipaddr.to_string dst)
+ | `Redirect ->
+ let (ip, transport) = packet in
+ let src, _dst = Nat_rewrite.addresses_of_ip ip in
+ let sport, dport = Nat_rewrite.ports_of_transport transport in
+ f "added NAT redirect %s:%d -> %d:firewall:%d -> %d:%a"
+ (Ipaddr.to_string src) sport dport xl_port port pp_host host
+ );
+*)
+ Ok packet
+ in
+ aux ~retries:100
diff --git a/my_nat.mli b/my_nat.mli
new file mode 100644
index 0000000..cf71dec
--- /dev/null
+++ b/my_nat.mli
@@ -0,0 +1,19 @@
+(* Copyright (C) 2015, Thomas Leonard
+ See the README file for details. *)
+
+(* Abstract over NAT interface (todo: remove this) *)
+
+type t
+
+type action = [
+ | `Rewrite
+ | `Redirect of Ipaddr.t * int
+]
+
+type packet = Ipv4_packet.t * Cstruct.t
+
+val create : (module Mirage_nat.S with type t = 'a and type config = 'c) -> 'c -> t Lwt.t
+val reset : t -> unit Lwt.t
+val translate : t -> packet -> packet option Lwt.t
+val add_nat_rule_and_translate : t -> xl_host:Ipaddr.t ->
+ action -> packet -> (packet, string) result Lwt.t
diff --git a/packet.ml b/packet.ml
index a359e16..bea2594 100644
--- a/packet.ml
+++ b/packet.ml
@@ -1,7 +1,7 @@
(* Copyright (C) 2015, Thomas Leonard
See the README file for details. *)
-open Utils
+open Fw_utils
type port = int
@@ -14,7 +14,7 @@ type host =
[ `Client of client_link | `Client_gateway | `Firewall_uplink | `NetVM | `External of Ipaddr.t ]
type info = {
- frame : Cstruct.t;
+ packet : Ipv4_packet.t * Cstruct.t;
src : host;
dst : host;
proto : [ `UDP of ports | `TCP of ports | `ICMP | `Unknown ];
diff --git a/router.ml b/router.ml
index 8e1dc44..ff5fddc 100644
--- a/router.ml
+++ b/router.ml
@@ -1,26 +1,21 @@
(* Copyright (C) 2015, Thomas Leonard
See the README file for details. *)
-open Utils
-
-let src = Logs.Src.create "router" ~doc:"Router"
-module Log = (val Logs.src_log src : Logs.LOG)
+open Fw_utils
(* The routing table *)
type t = {
client_eth : Client_eth.t;
- mutable nat : Nat_lookup.t;
+ nat : My_nat.t;
uplink : interface;
}
-let create ~client_eth ~uplink =
- let nat = Nat_lookup.empty () in
+let create ~client_eth ~uplink ~nat =
{ client_eth; nat; uplink }
let target t buf =
- let open Wire_structs.Ipv4_wire in
- let dst_ip = get_ipv4_dst buf |> Ipaddr.V4.of_int32 in
+ let dst_ip = buf.Ipv4_packet.dst in
match Client_eth.lookup t.client_eth dst_ip with
| Some client_link -> Some (client_link :> interface)
| None -> Some t.uplink
@@ -37,12 +32,3 @@ let resolve t = function
| `Firewall_uplink -> Ipaddr.V4 t.uplink#my_ip
| `NetVM -> Ipaddr.V4 t.uplink#other_ip
| #Client_eth.host as host -> Client_eth.resolve t.client_eth host
-
-(* To avoid needing to allocate a new NAT table when we've run out of
- memory, pre-allocate the new one ahead of time. *)
-let next_nat = ref (Nat_lookup.empty ())
-let reset t =
- t.nat <- !next_nat;
- (* (at this point, the big old NAT table can be GC'd, so allocating
- a new one should be OK) *)
- next_nat := Nat_lookup.empty ()
diff --git a/router.mli b/router.mli
index ac743d3..80678fb 100644
--- a/router.mli
+++ b/router.mli
@@ -3,11 +3,11 @@
(** Routing packets to the right network interface. *)
-open Utils
+open Fw_utils
type t = private {
client_eth : Client_eth.t;
- mutable nat : Nat_lookup.t;
+ nat : My_nat.t;
uplink : interface;
}
(** A routing table. *)
@@ -15,12 +15,13 @@ type t = private {
val create :
client_eth:Client_eth.t ->
uplink:interface ->
+ nat:My_nat.t ->
t
(** [create ~client_eth ~uplink] is a new routing table
that routes packets outside of [client_eth] via [uplink]. *)
-val target : t -> Cstruct.t -> interface option
-(** [target t packet] is the interface to which [packet] (an IP packet) should be routed. *)
+val target : t -> Ipv4_packet.t -> interface option
+(** [target t packet] is the interface to which [packet] should be routed. *)
val add_client : t -> client_link -> unit Lwt.t
(** [add_client t iface] adds a rule for routing packets addressed to [iface]. *)
@@ -29,6 +30,3 @@ val remove_client : t -> client_link -> unit
val classify : t -> Ipaddr.t -> Packet.host
val resolve : t -> Packet.host -> Ipaddr.t
-
-val reset : t -> unit
-(** Clear the NAT table (to free memory). *)
diff --git a/unikernel.ml b/unikernel.ml
index 9e5eba3..3189bb0 100644
--- a/unikernel.ml
+++ b/unikernel.ml
@@ -7,15 +7,16 @@ open Qubes
let src = Logs.Src.create "unikernel" ~doc:"Main unikernel code"
module Log = (val Logs.src_log src : Logs.LOG)
-module Main (Clock : V1.CLOCK) = struct
+module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
module Uplink = Uplink.Make(Clock)
+ module Nat = Mirage_nat_hashtable.Make(Clock)(OS.Time)
(* Set up networking and listen for incoming packets. *)
- let network qubesDB =
+ let network ~clock nat qubesDB =
(* Read configuration from QubesDB *)
let config = Dao.read_network_config qubesDB in
(* Initialise connection to NetVM *)
- Uplink.connect config >>= fun uplink ->
+ Uplink.connect ~clock config >>= fun uplink ->
(* Report success *)
Dao.set_iptables_error qubesDB "" >>= fun () ->
(* Set up client-side networking *)
@@ -24,7 +25,9 @@ module Main (Clock : V1.CLOCK) = struct
(* Set up routing between networks and hosts *)
let router = Router.create
~client_eth
- ~uplink:(Uplink.interface uplink) in
+ ~uplink:(Uplink.interface uplink)
+ ~nat
+ in
(* Handle packets from both networks *)
Lwt.choose [
Client_net.listen router;
@@ -45,8 +48,8 @@ module Main (Clock : V1.CLOCK) = struct
)
(* Main unikernel entry point (called from auto-generated main.ml). *)
- let start () =
- let start_time = Clock.time () in
+ let start clock =
+ let start_time = Clock.elapsed_ns clock in
(* Start qrexec agent, GUI agent and QubesDB agent in parallel *)
let qrexec = RExec.connect ~domid:0 () in
let gui = GUI.connect ~domid:0 () in
@@ -57,18 +60,24 @@ module Main (Clock : V1.CLOCK) = struct
gui >>= fun gui ->
watch_gui gui;
qubesDB >>= fun qubesDB ->
- Log.info (fun f -> f "agents connected in %.3f s (CPU time used since boot: %.3f s)"
- (Clock.time () -. start_time) (Sys.time ()));
+ let startup_time =
+ let (-) = Int64.sub in
+ let time_in_ns = Clock.elapsed_ns clock - start_time in
+ Int64.to_float time_in_ns /. 1e9
+ in
+ Log.info (fun f -> f "Qubes agents connected in %.3f s (CPU time used since boot: %.3f s)"
+ startup_time (Sys.time ()));
(* Watch for shutdown requests from Qubes *)
let shutdown_rq =
OS.Lifecycle.await_shutdown_request () >>= fun (`Poweroff | `Reboot) ->
return () in
(* Set up networking *)
- let net_listener = network qubesDB in
+ My_nat.create (module Nat) clock >>= fun nat ->
+ let net_listener = network ~clock nat qubesDB in
(* Report memory usage to XenStore *)
Memory_pressure.init ();
(* Run until something fails or we get a shutdown request. *)
Lwt.choose [agent_listener; net_listener; shutdown_rq] >>= fun () ->
(* Give the console daemon time to show any final log messages. *)
- OS.Time.sleep 1.0
+ OS.Time.sleep_ns (1.0 *. 1e9 |> Int64.of_float)
end
diff --git a/uplink.ml b/uplink.ml
index 711b5f5..0dfe79c 100644
--- a/uplink.ml
+++ b/uplink.ml
@@ -2,16 +2,15 @@
See the README file for details. *)
open Lwt.Infix
-open Utils
+open Fw_utils
module Eth = Ethif.Make(Netif)
let src = Logs.Src.create "uplink" ~doc:"Network connection to NetVM"
module Log = (val Logs.src_log src : Logs.LOG)
-module Make(Clock : V1.CLOCK) = struct
+module Make(Clock : Mirage_clock_lwt.MCLOCK) = struct
module Arp = Arpv4.Make(Eth)(Clock)(OS.Time)
- module IPv4 = Ipv4.Make(Eth)(Arp)
type t = {
net : Netif.t;
@@ -25,11 +24,11 @@ module Make(Clock : V1.CLOCK) = struct
method my_mac = Eth.mac eth
method my_ip = my_ip
method other_ip = other_ip
- method writev ip =
+ method writev ethertype payload =
FrameQ.send queue (fun () ->
mac >>= fun dst ->
- let eth_hdr = eth_header_ipv4 ~src:(Eth.mac eth) ~dst in
- Eth.writev eth (eth_hdr :: ip)
+ let eth_hdr = eth_header ethertype ~src:(Eth.mac eth) ~dst in
+ Eth.writev eth (eth_hdr :: payload) >|= or_raise "Write to uplink" Eth.pp_error
)
end
@@ -38,22 +37,26 @@ module Make(Clock : V1.CLOCK) = struct
(* Handle one Ethernet frame from NetVM *)
Eth.input t.eth
~arpv4:(Arp.input t.arp)
- ~ipv4:(fun _ip -> Firewall.ipv4_from_netvm router frame)
+ ~ipv4:(fun ip ->
+ match Ipv4_packet.Unmarshal.of_cstruct ip with
+ | Error e -> Log.warn (fun f -> f "Bad IPv4 packet from uplink: %s" e); Lwt.return ()
+ | Ok packet -> Firewall.ipv4_from_netvm router packet
+ )
~ipv6:(fun _ip -> return ())
frame
- )
+ ) >|= or_raise "Uplink listen loop" Netif.pp_error
let interface t = t.interface
- let connect config =
+ let connect ~clock config =
let ip = config.Dao.uplink_our_ip in
- Netif.connect "0" >>= or_fail "Can't connect uplink device" >>= fun net ->
- Eth.connect net >>= or_fail "Can't make Ethernet device for tap" >>= fun eth ->
- Arp.connect eth >>= or_fail "Can't add ARP" >>= fun arp ->
+ Netif.connect "0" >>= fun net ->
+ Eth.connect net >>= fun eth ->
+ Arp.connect eth clock >>= fun arp ->
Arp.add_ip arp ip >>= fun () ->
- let netvm_mac = Arp.query arp config.Dao.uplink_netvm_ip >|= function
- | `Timeout -> failwith "ARP timeout getting MAC of our NetVM"
- | `Ok netvm_mac -> netvm_mac in
+ let netvm_mac =
+ Arp.query arp config.Dao.uplink_netvm_ip
+ >|= or_raise "Getting MAC of our NetVM" Arp.pp_error in
let interface = new netvm_iface eth netvm_mac
~my_ip:ip
~other_ip:config.Dao.uplink_netvm_ip in
diff --git a/uplink.mli b/uplink.mli
index 156e91f..6e2f5f4 100644
--- a/uplink.mli
+++ b/uplink.mli
@@ -3,12 +3,12 @@
(** The link from us to NetVM (and, through that, to the outside world). *)
-open Utils
+open Fw_utils
-module Make(Clock : V1.CLOCK) : sig
+module Make(Clock : Mirage_clock_lwt.MCLOCK) : sig
type t
- val connect : Dao.network_config -> t Lwt.t
+ val connect : clock:Clock.t -> Dao.network_config -> t Lwt.t
(** Connect to our NetVM (gateway). *)
val interface : t -> interface
diff --git a/utils.ml b/utils.ml
deleted file mode 100644
index 13d512a..0000000
--- a/utils.ml
+++ /dev/null
@@ -1,65 +0,0 @@
-(* Copyright (C) 2015, Thomas Leonard
- See the README file for details. *)
-
-(** General utility functions. *)
-
-module IpMap = struct
- include Map.Make(Ipaddr.V4)
- let find x map =
- try Some (find x map)
- with Not_found -> None
-end
-
-module Int = struct
- type t = int
- let compare (a:t) (b:t) = compare a b
-end
-
-module IntSet = Set.Make(Int)
-module IntMap = Map.Make(Int)
-
-(** An Ethernet interface. *)
-class type interface = object
- method my_mac : Macaddr.t
- method writev : Cstruct.t list -> unit Lwt.t
- method my_ip : Ipaddr.V4.t
- method other_ip : Ipaddr.V4.t
-end
-
-(** An Ethernet interface connected to a clientVM. *)
-class type client_link = object
- inherit interface
- method other_mac : Macaddr.t
-end
-
-(** An Ethernet header from [src]'s MAC address to [dst]'s with an IPv4 payload. *)
-let eth_header_ipv4 ~src ~dst =
- let open Wire_structs in
- let frame = Cstruct.create sizeof_ethernet in
- frame |> set_ethernet_src (Macaddr.to_bytes src) 0;
- frame |> set_ethernet_dst (Macaddr.to_bytes dst) 0;
- set_ethernet_ethertype frame (ethertype_to_int IPv4);
- frame
-
-(** Recalculate checksums after modifying packets.
- Note that frames often arrive with invalid checksums due to checksum offload.
- For now, we always calculate valid checksums for out-bound frames. *)
-let fixup_checksums frame =
- match Nat_rewrite.layers frame with
- | None -> raise (Invalid_argument "NAT transformation rendered packet unparseable")
- | Some (ether, ip, tx) ->
- let (just_headers, higherlevel_data) =
- Nat_rewrite.recalculate_transport_checksum (ether, ip, tx)
- in
- [just_headers; higherlevel_data]
-
-let error fmt =
- let err s = Failure s in
- Printf.ksprintf err fmt
-
-let return = Lwt.return
-let fail = Lwt.fail
-
-let or_fail msg = function
- | `Ok x -> return x
- | `Error _ -> fail (Failure msg)
From b4079ac8619c58354cc25132f7e55556de0645b8 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sun, 5 Mar 2017 16:31:04 +0000
Subject: [PATCH 012/281] Update to new mirage-nat API
---
.travis.yml | 2 +-
Dockerfile | 11 ++++----
README.md | 3 ++-
build-with-docker.sh | 4 +--
client_net.ml | 11 ++++----
firewall.ml | 61 +++++++++++++++-----------------------------
firewall.mli | 4 +--
my_nat.ml | 30 ++++------------------
my_nat.mli | 6 ++---
packet.ml | 2 +-
uplink.ml | 25 ++++++++++--------
11 files changed, 62 insertions(+), 97 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index e9d1353..6ef81aa 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,4 +20,4 @@ addons:
- time
- libxen-dev
env:
- - OCAML_VERSION=4.04 MIRAGE_BACKEND=xen PINS="mirage-nat:https://github.com/talex5/mirage-nat.git#mirage3"
+ - OCAML_VERSION=4.04 MIRAGE_BACKEND=xen PINS="mirage-nat:https://github.com/talex5/mirage-nat.git#cleanup"
diff --git a/Dockerfile b/Dockerfile
index 2182d1e..4c8b436 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,19 +2,20 @@
# It will probably still work on newer images, though, unless Debian 8
# changes some compiler optimisations (unlikely).
#FROM ocaml/opam:debian-8_ocaml-4.03.0
-FROM ocaml/opam@sha256:28efab6a5535a517aa719ba5ac6d2e6fddd4831afaeabf5eee6470717eda9cca
+FROM ocaml/opam@sha256:72ebf516fca7a9464db2136f2dcf2a58d09547669b60f3643a8329768febaed6
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN cd opam-repository && git reset --hard 0f17b354206c97e729700ce60ddce3789ccb1d52 && opam update
+RUN cd opam-repository && git reset --hard 8f4d15eae94dfe6f70a66a7572a21a0c60d9f4f4 && opam update
RUN sudo apt-get install -y m4 libxen-dev
RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage
-RUN opam pin add -n -y mirage-nat 'https://github.com/talex5/mirage-nat.git#simplify-checksum'
+RUN opam pin add -n -y tcpip 'https://github.com/talex5/mirage-tcpip.git#fix-length-checks'
+RUN opam pin add -n -y mirage-nat 'https://github.com/talex5/mirage-nat.git#cleanup'
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
WORKDIR /home/opam/qubes-mirage-firewall
-RUN opam config exec -- mirage configure --xen
-CMD opam config exec -- mirage configure --xen --no-opam && \
+RUN opam config exec -- mirage configure -t xen && make depend
+CMD opam config exec -- mirage configure -t xen && \
opam config exec -- make tar
diff --git a/README.md b/README.md
index 18b0b7e..7e24e99 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,8 @@ This took about 10 minutes on my laptop (it will be much quicker if you run it a
2. Install mirage, pinning a few unreleased features we need:
- opam pin add -y mirage-nat 'https://github.com/talex5/mirage-nat.git#mirage3'
+ opam pin add -n -y tcpip 'https://github.com/talex5/mirage-tcpip.git#fix-length-checks'
+ opam pin add -y mirage-nat 'https://github.com/talex5/mirage-nat.git#cleanup'
opam install mirage
3. Build mirage-firewall:
diff --git a/build-with-docker.sh b/build-with-docker.sh
index d61f13c..f004471 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -4,5 +4,5 @@ echo Building Docker image with dependencies..
docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
-echo "SHA2 of build: $(sha256sum mir-qubes-firewall.xen)"
-echo "SHA2 last known: f0c1a06fc4b02b494c81972dc89419af6cffa73b75839c0e8ee3798d77bf69b3"
+echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
+echo "SHA2 last known: 45b82182341237ca9e754636f771ef3f4c93824212d1a76520a8a79bbee18668"
diff --git a/client_net.ml b/client_net.ml
index 50f22ea..7148011 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -45,7 +45,7 @@ let clients : Cleanup.t Dao.VifMap.t ref = ref Dao.VifMap.empty
let input_arp ~fixed_arp ~iface request =
match Arpv4_packet.Unmarshal.of_cstruct request with
| Error e ->
- Log.warn (fun f -> f "ignored unknown ARP message: %a" Arpv4_packet.Unmarshal.pp_error e);
+ Log.warn (fun f -> f "Ignored unknown ARP message: %a" Arpv4_packet.Unmarshal.pp_error e);
Lwt.return ()
| Ok arp ->
match Client_eth.ARP.input fixed_arp arp with
@@ -55,13 +55,14 @@ let input_arp ~fixed_arp ~iface request =
(** Handle an IPv4 packet from the client. *)
let input_ipv4 ~client_ip ~router packet =
- match Ipv4_packet.Unmarshal.of_cstruct packet with
+ match Nat_packet.of_ipv4_packet packet with
| Error e ->
- Log.warn (fun f -> f "ignored unknown IPv4 message: %s" e);
+ Log.warn (fun f -> f "Ignored unknown IPv4 message: %a" Nat_packet.pp_error e);
Lwt.return ()
- | Ok (ip, payload) ->
+ | Ok packet ->
+ let `IPv4 (ip, _) = packet in
let src = ip.Ipv4_packet.src in
- if src = client_ip then Firewall.ipv4_from_client router (ip, payload)
+ if src = client_ip then Firewall.ipv4_from_client router packet
else (
Log.warn (fun f -> f "Incorrect source IP %a in IP packet from %a (dropping)"
Ipaddr.V4.pp_hum src Ipaddr.V4.pp_hum client_ip);
diff --git a/firewall.ml b/firewall.ml
index 226a56c..350eecf 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -10,10 +10,10 @@ module Log = (val Logs.src_log src : Logs.LOG)
(* Transmission *)
-let transmit (ip, payload) iface =
- let packet = Ipv4_packet.Marshal.make_cstruct ~payload ip in
+let transmit_ipv4 packet iface =
+ let headers, payload = Nat_packet.make_headers_cstruct packet in
Lwt.catch
- (fun () -> iface#writev Ethif_wire.IPv4 [packet; payload])
+ (fun () -> iface#writev Ethif_wire.IPv4 [headers; payload])
(fun ex ->
Log.warn (fun f -> f "Failed to write packet to %a: %s"
Ipaddr.V4.pp_hum iface#other_ip
@@ -21,42 +21,23 @@ let transmit (ip, payload) iface =
Lwt.return ()
)
-let forward_ipv4 t (ip, packet) =
+let forward_ipv4 t packet =
+ let `IPv4 (ip, _) = packet in
match Router.target t ip with
- | Some iface -> transmit (ip, packet) iface
+ | Some iface -> transmit_ipv4 packet iface
| None -> return ()
(* Packet classification *)
-let classify_tcp trans =
- match Tcp.Tcp_packet.Unmarshal.of_cstruct trans with
- | Error e ->
- Log.info (fun f -> f "Failed to parse TCP packet: %s" e);
- `Unknown
- | Ok (tcp, _payload) ->
- let sport = tcp.Tcp.Tcp_packet.src_port in
- let dport = tcp.Tcp.Tcp_packet.dst_port in
- `TCP {sport; dport}
-
-let classify_udp trans =
- match Udp_packet.Unmarshal.of_cstruct trans with
- | Error e ->
- Log.info (fun f -> f "Failed to parse UDP packet: %s" e);
- `Unknown
- | Ok (udp, _payload) ->
- let sport = udp.Udp_packet.src_port in
- let dport = udp.Udp_packet.dst_port in
- `UDP {sport; dport}
-
-let classify t (ip, transport) =
+let classify t packet =
+ let `IPv4 (ip, transport) = packet in
let proto =
- match ip.Ipv4_packet.proto |> Ipv4_packet.Unmarshal.int_to_protocol with
- | Some `ICMP -> `ICMP
- | Some `TCP -> classify_tcp transport
- | Some `UDP -> classify_udp transport
- | None -> `Unknown in
+ match transport with
+ | `TCP ({Tcp.Tcp_packet.src_port; dst_port; _}, _) -> `TCP {sport = src_port; dport = dst_port}
+ | `UDP ({Udp_packet.src_port; dst_port; _}, _) -> `UDP {sport = src_port; dport = dst_port}
+ in
Some {
- packet = (ip, transport);
+ packet;
src = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.src);
dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst);
proto;
@@ -114,8 +95,8 @@ let nat_to t ~host ~port packet =
let apply_rules t rules info =
let packet = info.packet in
match rules info, info.dst with
- | `Accept, `Client client_link -> transmit packet client_link
- | `Accept, (`External _ | `NetVM) -> transmit packet t.Router.uplink
+ | `Accept, `Client client_link -> transmit_ipv4 packet client_link
+ | `Accept, (`External _ | `NetVM) -> transmit_ipv4 packet t.Router.uplink
| `Accept, (`Firewall_uplink | `Client_gateway) ->
Log.warn (fun f -> f "Bad rule: firewall can't accept packets %a" pp_packet info);
return ()
@@ -133,24 +114,24 @@ let handle_low_memory t =
`Memory_critical
| `Ok -> Lwt.return `Ok
-let ipv4_from_client t (ip, payload) =
+let ipv4_from_client t packet =
handle_low_memory t >>= function
| `Memory_critical -> return ()
| `Ok ->
(* Check for existing NAT entry for this packet *)
- translate t (ip, payload) >>= function
+ translate t packet >>= function
| Some frame -> forward_ipv4 t frame (* Some existing connection or redirect *)
| None ->
(* No existing NAT entry. Check the firewall rules. *)
- match classify t (ip, payload) with
+ match classify t packet with
| None -> return ()
| Some info -> apply_rules t Rules.from_client info
-let ipv4_from_netvm t (ip, payload) =
+let ipv4_from_netvm t packet =
handle_low_memory t >>= function
| `Memory_critical -> return ()
| `Ok ->
- match classify t (ip, payload) with
+ match classify t packet with
| None -> return ()
| Some info ->
match info.src with
@@ -158,7 +139,7 @@ let ipv4_from_netvm t (ip, payload) =
Log.warn (fun f -> f "Frame from NetVM has internal source IP address! %a" pp_packet info);
return ()
| `External _ | `NetVM ->
- translate t (ip, payload) >>= function
+ translate t packet >>= function
| Some frame -> forward_ipv4 t frame
| None ->
apply_rules t Rules.from_netvm info
diff --git a/firewall.mli b/firewall.mli
index 3a88270..3909ee0 100644
--- a/firewall.mli
+++ b/firewall.mli
@@ -3,9 +3,9 @@
(** Classify IP packets, apply rules and send as appropriate. *)
-val ipv4_from_netvm : Router.t -> Ipv4_packet.t * Cstruct.t -> unit Lwt.t
+val ipv4_from_netvm : Router.t -> Nat_packet.t -> unit Lwt.t
(** Handle a packet from the outside world (this module will validate the source IP). *)
-val ipv4_from_client : Router.t -> Ipv4_packet.t * Cstruct.t -> unit Lwt.t
+val ipv4_from_client : Router.t -> Nat_packet.t -> unit Lwt.t
(** Handle a packet from a client. Caller must check the source IP matches the client's
before calling this. *)
diff --git a/my_nat.ml b/my_nat.ml
index ec9d0f2..8d81258 100644
--- a/my_nat.ml
+++ b/my_nat.ml
@@ -11,8 +11,6 @@ type action = [
| `Redirect of Ipaddr.t * int
]
-type packet = Ipv4_packet.t * Cstruct.t
-
(* To avoid needing to allocate a new NAT table when we've run out of
memory, pre-allocate the new one ahead of time. *)
type 'a with_standby = {
@@ -35,21 +33,10 @@ let fake_ipv4_eth =
let dontcare = Macaddr.broadcast in
Fw_utils.eth_header Ethif_wire.IPv4 ~src:dontcare ~dst:dontcare
-let translate (Nat ((module Nat), _, table)) (ip, payload) =
- (* XXX: change Nat.translate API *)
- let packet = Ipv4_packet.Marshal.make_cstruct ~payload ip in
- let frame = Cstruct.concat [
- fake_ipv4_eth;
- packet;
- payload;
- ] in
- Nat.translate table.current frame >|= function
+let translate (Nat ((module Nat), _, table)) packet =
+ Nat.translate table.current packet >|= function
| Mirage_nat.Untranslated -> None
- | Mirage_nat.Translated _ -> (* XXX: translate mutates frame *)
- let packet = Cstruct.shift frame Ethif_wire.sizeof_ethernet in
- match Ipv4_packet.Unmarshal.of_cstruct packet with
- | Error e -> Log.err (fun f -> f "Translation failed: %s" e); None
- | Ok packet -> Some packet
+ | Mirage_nat.Translated packet -> Some packet
let random_user_port () =
1024 + Random.int (0xffff - 1024)
@@ -62,20 +49,13 @@ let reset (Nat ((module Nat), c, table)) =
table.next <- next
let add_nat_rule_and_translate ((Nat ((module Nat), c, table)) as t) ~xl_host action packet =
- let frame =
- let (ip, payload) = packet in
- Cstruct.concat [
- fake_ipv4_eth;
- Ipv4_packet.Marshal.make_cstruct ~payload ip;
- payload;
- ] in
let apply_action xl_port =
Lwt.try_bind (fun () ->
match action with
| `Rewrite ->
- Nat.add_nat table.current frame (xl_host, xl_port)
+ Nat.add_nat table.current packet (xl_host, xl_port)
| `Redirect target ->
- Nat.add_redirect table.current frame (xl_host, xl_port) target
+ Nat.add_redirect table.current packet (xl_host, xl_port) target
)
(function
| Nat.Ok -> Lwt.return (Ok ())
diff --git a/my_nat.mli b/my_nat.mli
index cf71dec..ac6e0f9 100644
--- a/my_nat.mli
+++ b/my_nat.mli
@@ -10,10 +10,8 @@ type action = [
| `Redirect of Ipaddr.t * int
]
-type packet = Ipv4_packet.t * Cstruct.t
-
val create : (module Mirage_nat.S with type t = 'a and type config = 'c) -> 'c -> t Lwt.t
val reset : t -> unit Lwt.t
-val translate : t -> packet -> packet option Lwt.t
+val translate : t -> Nat_packet.t -> Nat_packet.t option Lwt.t
val add_nat_rule_and_translate : t -> xl_host:Ipaddr.t ->
- action -> packet -> (packet, string) result Lwt.t
+ action -> Nat_packet.t -> (Nat_packet.t, string) result Lwt.t
diff --git a/packet.ml b/packet.ml
index bea2594..a9fa4e7 100644
--- a/packet.ml
+++ b/packet.ml
@@ -14,7 +14,7 @@ type host =
[ `Client of client_link | `Client_gateway | `Firewall_uplink | `NetVM | `External of Ipaddr.t ]
type info = {
- packet : Ipv4_packet.t * Cstruct.t;
+ packet : Nat_packet.t;
src : host;
dst : host;
proto : [ `UDP of ports | `TCP of ports | `ICMP | `Unknown ];
diff --git a/uplink.ml b/uplink.ml
index 0dfe79c..ff7e718 100644
--- a/uplink.ml
+++ b/uplink.ml
@@ -34,17 +34,20 @@ module Make(Clock : Mirage_clock_lwt.MCLOCK) = struct
let listen t router =
Netif.listen t.net (fun frame ->
- (* Handle one Ethernet frame from NetVM *)
- Eth.input t.eth
- ~arpv4:(Arp.input t.arp)
- ~ipv4:(fun ip ->
- match Ipv4_packet.Unmarshal.of_cstruct ip with
- | Error e -> Log.warn (fun f -> f "Bad IPv4 packet from uplink: %s" e); Lwt.return ()
- | Ok packet -> Firewall.ipv4_from_netvm router packet
- )
- ~ipv6:(fun _ip -> return ())
- frame
- ) >|= or_raise "Uplink listen loop" Netif.pp_error
+ (* Handle one Ethernet frame from NetVM *)
+ Eth.input t.eth
+ ~arpv4:(Arp.input t.arp)
+ ~ipv4:(fun ip ->
+ match Nat_packet.of_ipv4_packet ip with
+ | Error e ->
+ Log.warn (fun f -> f "Ignored unknown IPv4 message from uplink: %a" Nat_packet.pp_error e);
+ Lwt.return ()
+ | Ok packet ->
+ Firewall.ipv4_from_netvm router packet
+ )
+ ~ipv6:(fun _ip -> return ())
+ frame
+ ) >|= or_raise "Uplink listen loop" Netif.pp_error
let interface t = t.interface
From e070044fefcd43d6024099bf27794bfe474bec31 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Mon, 6 Mar 2017 14:30:41 +0000
Subject: [PATCH 013/281] Add extra logging
---
client_net.ml | 5 +++++
firewall.ml | 20 +++++++++++++++-----
my_nat.ml | 6 ------
uplink.ml | 5 +++++
4 files changed, 25 insertions(+), 11 deletions(-)
diff --git a/client_net.ml b/client_net.ml
index 7148011..e7bc744 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -83,6 +83,11 @@ let add_vif { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks
let fixed_arp = Client_eth.ARP.create ~net:client_eth iface in
Netback.listen backend (fun frame ->
match Ethif_packet.Unmarshal.of_cstruct frame with
+ | exception ex ->
+ Log.err (fun f -> f "Error unmarshalling ethernet frame from client: %s@.%a" (Printexc.to_string ex)
+ Cstruct.hexdump_pp frame
+ );
+ Lwt.return_unit
| Error err -> Log.warn (fun f -> f "Invalid Ethernet frame: %s" err); return ()
| Ok (eth, payload) ->
match eth.Ethif_packet.ethertype with
diff --git a/firewall.ml b/firewall.ml
index 350eecf..623c071 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -11,13 +11,23 @@ module Log = (val Logs.src_log src : Logs.LOG)
(* Transmission *)
let transmit_ipv4 packet iface =
- let headers, payload = Nat_packet.make_headers_cstruct packet in
Lwt.catch
- (fun () -> iface#writev Ethif_wire.IPv4 [headers; payload])
+ (fun () ->
+ let transport = Nat_packet.to_cstruct packet in
+ Lwt.catch
+ (fun () -> iface#writev Ethif_wire.IPv4 transport)
+ (fun ex ->
+ Log.warn (fun f -> f "Failed to write packet to %a: %s"
+ Ipaddr.V4.pp_hum iface#other_ip
+ (Printexc.to_string ex));
+ Lwt.return ()
+ )
+ )
(fun ex ->
- Log.warn (fun f -> f "Failed to write packet to %a: %s"
- Ipaddr.V4.pp_hum iface#other_ip
- (Printexc.to_string ex));
+ Log.err (fun f -> f "Exception in transmit_ipv4: %s for:@.%a"
+ (Printexc.to_string ex)
+ Nat_packet.pp packet
+ );
Lwt.return ()
)
diff --git a/my_nat.ml b/my_nat.ml
index 8d81258..665e703 100644
--- a/my_nat.ml
+++ b/my_nat.ml
@@ -27,12 +27,6 @@ let create (type c t) (nat:(module Mirage_nat.S with type config = c and type t
let table = { current; next } in
Lwt.return (Nat (nat, c, table))
-(* Ideally, mirage-nat wouldn't ask us for an ethernet header, since it only
- cares about the IP layer anyway. *)
-let fake_ipv4_eth =
- let dontcare = Macaddr.broadcast in
- Fw_utils.eth_header Ethif_wire.IPv4 ~src:dontcare ~dst:dontcare
-
let translate (Nat ((module Nat), _, table)) packet =
Nat.translate table.current packet >|= function
| Mirage_nat.Untranslated -> None
diff --git a/uplink.ml b/uplink.ml
index ff7e718..5735418 100644
--- a/uplink.ml
+++ b/uplink.ml
@@ -39,6 +39,11 @@ module Make(Clock : Mirage_clock_lwt.MCLOCK) = struct
~arpv4:(Arp.input t.arp)
~ipv4:(fun ip ->
match Nat_packet.of_ipv4_packet ip with
+ | exception ex ->
+ Log.err (fun f -> f "Error unmarshalling ethernet frame from uplink: %s@.%a" (Printexc.to_string ex)
+ Cstruct.hexdump_pp frame
+ );
+ Lwt.return_unit
| Error e ->
Log.warn (fun f -> f "Ignored unknown IPv4 message from uplink: %a" Nat_packet.pp_error e);
Lwt.return ()
From 15fb063137ce2e9b70d7f6136589adbc2599d418 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Mon, 6 Mar 2017 14:31:26 +0000
Subject: [PATCH 014/281] Pin tcpip
---
.travis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 6ef81aa..e46684f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,4 +20,4 @@ addons:
- time
- libxen-dev
env:
- - OCAML_VERSION=4.04 MIRAGE_BACKEND=xen PINS="mirage-nat:https://github.com/talex5/mirage-nat.git#cleanup"
+ - OCAML_VERSION=4.04 MIRAGE_BACKEND=xen PINS="mirage-nat:https://github.com/talex5/mirage-nat.git#cleanup tcpip:https://github.com/talex5/mirage-tcpip.git#fix-length-checks"
From ac711f4eee40b7c817baf7136295a4d0106e0e50 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Tue, 7 Mar 2017 10:02:54 +0000
Subject: [PATCH 015/281] Add ICMP ping support
---
firewall.ml | 1 +
my_nat.ml | 25 ++++++++++---------------
2 files changed, 11 insertions(+), 15 deletions(-)
diff --git a/firewall.ml b/firewall.ml
index 623c071..341f103 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -45,6 +45,7 @@ let classify t packet =
match transport with
| `TCP ({Tcp.Tcp_packet.src_port; dst_port; _}, _) -> `TCP {sport = src_port; dport = dst_port}
| `UDP ({Udp_packet.src_port; dst_port; _}, _) -> `UDP {sport = src_port; dport = dst_port}
+ | `ICMP _ -> `ICMP
in
Some {
packet;
diff --git a/my_nat.ml b/my_nat.ml
index 665e703..4d15111 100644
--- a/my_nat.ml
+++ b/my_nat.ml
@@ -29,8 +29,8 @@ let create (type c t) (nat:(module Mirage_nat.S with type config = c and type t
let translate (Nat ((module Nat), _, table)) packet =
Nat.translate table.current packet >|= function
- | Mirage_nat.Untranslated -> None
- | Mirage_nat.Translated packet -> Some packet
+ | Error `Untranslated -> None
+ | Ok packet -> Some packet
let random_user_port () =
1024 + Random.int (0xffff - 1024)
@@ -44,17 +44,12 @@ let reset (Nat ((module Nat), c, table)) =
let add_nat_rule_and_translate ((Nat ((module Nat), c, table)) as t) ~xl_host action packet =
let apply_action xl_port =
- Lwt.try_bind (fun () ->
- match action with
- | `Rewrite ->
- Nat.add_nat table.current packet (xl_host, xl_port)
- | `Redirect target ->
- Nat.add_redirect table.current packet (xl_host, xl_port) target
- )
- (function
- | Nat.Ok -> Lwt.return (Ok ())
- | Nat.Overlap -> Lwt.return (Error `Overlap)
- | Nat.Unparseable -> Lwt.return (Error `Unparseable)
+ Lwt.catch (fun () ->
+ match action with
+ | `Rewrite ->
+ Nat.add_nat table.current packet (xl_host, xl_port)
+ | `Redirect target ->
+ Nat.add_redirect table.current packet (xl_host, xl_port) target
)
(function
| Out_of_memory -> Lwt.return (Error `Out_of_memory)
@@ -86,8 +81,8 @@ let add_nat_rule_and_translate ((Nat ((module Nat), c, table)) as t) ~xl_host ac
) else (
aux ~retries:(retries - 1)
)
- | Error `Unparseable ->
- Lwt.return (Error "Unparseable by mirage-nat")
+ | Error `Cannot_NAT ->
+ Lwt.return (Error "Cannot NAT this packet")
| Ok () ->
translate t packet >|= function
| None -> Error "No NAT entry, even after adding one!"
From 6f8d83f82875eb07561a47f45de178d7b5abc924 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Tue, 7 Mar 2017 16:06:18 +0000
Subject: [PATCH 016/281] Use new Nat.reset function to clear the table
---
my_nat.ml | 63 +++++++++++-----------------------------------------
my_nat.mli | 2 +-
unikernel.ml | 2 +-
3 files changed, 15 insertions(+), 52 deletions(-)
diff --git a/my_nat.ml b/my_nat.ml
index 4d15111..6cdcae1 100644
--- a/my_nat.ml
+++ b/my_nat.ml
@@ -11,58 +11,37 @@ type action = [
| `Redirect of Ipaddr.t * int
]
-(* To avoid needing to allocate a new NAT table when we've run out of
- memory, pre-allocate the new one ahead of time. *)
-type 'a with_standby = {
- mutable current :'a;
- mutable next : 'a;
-}
+type t = Nat : (module Mirage_nat.S with type t = 't) * 't -> t
-type t = Nat : (module Mirage_nat.S with type t = 't and type config = 'c) * 'c * 't with_standby -> t
+let create (type t) (nat:(module Mirage_nat.S with type t = t)) (table:t) =
+ let (module Nat : Mirage_nat.S with type t = t) = nat in
+ Nat (nat, table)
-let create (type c t) (nat:(module Mirage_nat.S with type config = c and type t = t)) (c:c) =
- let (module Nat : Mirage_nat.S with type config = c and type t = t) = nat in
- Nat.empty c >>= fun current ->
- Nat.empty c >>= fun next ->
- let table = { current; next } in
- Lwt.return (Nat (nat, c, table))
-
-let translate (Nat ((module Nat), _, table)) packet =
- Nat.translate table.current packet >|= function
+let translate (Nat ((module Nat), table)) packet =
+ Nat.translate table packet >|= function
| Error `Untranslated -> None
| Ok packet -> Some packet
let random_user_port () =
1024 + Random.int (0xffff - 1024)
-let reset (Nat ((module Nat), c, table)) =
- table.current <- table.next;
- (* (at this point, the big old NAT table can be GC'd, so allocating
- a new one should be OK) *)
- Nat.empty c >|= fun next ->
- table.next <- next
+let reset (Nat ((module Nat), table)) =
+ Nat.reset table
-let add_nat_rule_and_translate ((Nat ((module Nat), c, table)) as t) ~xl_host action packet =
+let add_nat_rule_and_translate ((Nat ((module Nat), table)) as t) ~xl_host action packet =
let apply_action xl_port =
Lwt.catch (fun () ->
match action with
| `Rewrite ->
- Nat.add_nat table.current packet (xl_host, xl_port)
+ Nat.add_nat table packet (xl_host, xl_port)
| `Redirect target ->
- Nat.add_redirect table.current packet (xl_host, xl_port) target
+ Nat.add_redirect table packet (xl_host, xl_port) target
)
(function
| Out_of_memory -> Lwt.return (Error `Out_of_memory)
| x -> Lwt.fail x
)
in
- let reset () =
- table.current <- table.next;
- (* (at this point, the big old NAT table can be GC'd, so allocating
- a new one should be OK) *)
- Nat.empty c >|= fun next ->
- table.next <- next
- in
let rec aux ~retries =
let xl_port = random_user_port () in
apply_action xl_port >>= function
@@ -70,13 +49,13 @@ let add_nat_rule_and_translate ((Nat ((module Nat), c, table)) as t) ~xl_host ac
(* Because hash tables resize in big steps, this can happen even if we have a fair
chunk of free memory. *)
Log.warn (fun f -> f "Out_of_memory adding NAT rule. Dropping NAT table...");
- reset () >>= fun () ->
+ Nat.reset table >>= fun () ->
aux ~retries:(retries - 1)
| Error `Overlap when retries < 0 -> Lwt.return (Error "Too many retries")
| Error `Overlap ->
if retries = 0 then (
Log.warn (fun f -> f "Failed to find a free port; resetting NAT table");
- reset () >>= fun () ->
+ Nat.reset table >>= fun () ->
aux ~retries:(retries - 1)
) else (
aux ~retries:(retries - 1)
@@ -87,22 +66,6 @@ let add_nat_rule_and_translate ((Nat ((module Nat), c, table)) as t) ~xl_host ac
translate t packet >|= function
| None -> Error "No NAT entry, even after adding one!"
| Some packet ->
-(*
- Log.debug (fun f ->
- match action with
- | `Rewrite ->
- let (ip, trans) = packet in
- let src, dst = Nat_rewrite.addresses_of_ip ip in
- let sport, dport = Nat_rewrite.ports_of_transport transport in
- f "added NAT entry: %s:%d -> firewall:%d -> %d:%s" (Ipaddr.to_string src) sport xl_port dport (Ipaddr.to_string dst)
- | `Redirect ->
- let (ip, transport) = packet in
- let src, _dst = Nat_rewrite.addresses_of_ip ip in
- let sport, dport = Nat_rewrite.ports_of_transport transport in
- f "added NAT redirect %s:%d -> %d:firewall:%d -> %d:%a"
- (Ipaddr.to_string src) sport dport xl_port port pp_host host
- );
-*)
Ok packet
in
aux ~retries:100
diff --git a/my_nat.mli b/my_nat.mli
index ac6e0f9..7ff5b88 100644
--- a/my_nat.mli
+++ b/my_nat.mli
@@ -10,7 +10,7 @@ type action = [
| `Redirect of Ipaddr.t * int
]
-val create : (module Mirage_nat.S with type t = 'a and type config = 'c) -> 'c -> t Lwt.t
+val create : (module Mirage_nat.S with type t = 'a) -> 'a -> t
val reset : t -> unit Lwt.t
val translate : t -> Nat_packet.t -> Nat_packet.t option Lwt.t
val add_nat_rule_and_translate : t -> xl_host:Ipaddr.t ->
diff --git a/unikernel.ml b/unikernel.ml
index 3189bb0..f0368a7 100644
--- a/unikernel.ml
+++ b/unikernel.ml
@@ -72,7 +72,7 @@ module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
OS.Lifecycle.await_shutdown_request () >>= fun (`Poweroff | `Reboot) ->
return () in
(* Set up networking *)
- My_nat.create (module Nat) clock >>= fun nat ->
+ Nat.empty clock >|= My_nat.create (module Nat) >>= fun nat ->
let net_listener = network ~clock nat qubesDB in
(* Report memory usage to XenStore *)
Memory_pressure.init ();
From 0ef60ae76789ea3b8144b744d0e14a35512a381d Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Fri, 10 Mar 2017 16:09:36 +0000
Subject: [PATCH 017/281] Update to new mirage-nat API
---
firewall.ml | 20 +++++++++++---------
my_nat.ml | 45 ++++++++++++++++++++++++++-------------------
my_nat.mli | 8 ++++----
unikernel.ml | 4 ++--
4 files changed, 43 insertions(+), 34 deletions(-)
diff --git a/firewall.ml b/firewall.ml
index 341f103..f0d29ef 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -84,8 +84,8 @@ let translate t packet =
(* Add a NAT rule for the endpoints in this frame, via a random port on the firewall. *)
let add_nat_and_forward_ipv4 t packet =
- let xl_host = Ipaddr.V4 t.Router.uplink#my_ip in
- My_nat.add_nat_rule_and_translate t.Router.nat ~xl_host `Rewrite packet >>= function
+ let xl_host = t.Router.uplink#my_ip in
+ My_nat.add_nat_rule_and_translate t.Router.nat ~xl_host `NAT packet >>= function
| Ok packet -> forward_ipv4 t packet
| Error e ->
Log.warn (fun f -> f "Failed to add NAT rewrite rule: %s" e);
@@ -93,13 +93,15 @@ let add_nat_and_forward_ipv4 t packet =
(* Add a NAT rule to redirect this conversation to [host:port] instead of us. *)
let nat_to t ~host ~port packet =
- let target = Router.resolve t host in
- let xl_host = Ipaddr.V4 t.Router.uplink#my_ip in
- My_nat.add_nat_rule_and_translate t.Router.nat ~xl_host (`Redirect (target, port)) packet >>= function
- | Ok packet -> forward_ipv4 t packet
- | Error e ->
- Log.warn (fun f -> f "Failed to add NAT redirect rule: %s" e);
- Lwt.return ()
+ match Router.resolve t host with
+ | Ipaddr.V6 _ -> Log.warn (fun f -> f "Cannot NAT with IPv6"); Lwt.return ()
+ | Ipaddr.V4 target ->
+ let xl_host = t.Router.uplink#my_ip in
+ My_nat.add_nat_rule_and_translate t.Router.nat ~xl_host (`Redirect (target, port)) packet >>= function
+ | Ok packet -> forward_ipv4 t packet
+ | Error e ->
+ Log.warn (fun f -> f "Failed to add NAT redirect rule: %s" e);
+ Lwt.return ()
(* Handle incoming packets *)
diff --git a/my_nat.ml b/my_nat.ml
index 6cdcae1..be9b57b 100644
--- a/my_nat.ml
+++ b/my_nat.ml
@@ -7,35 +7,42 @@ let src = Logs.Src.create "my-nat" ~doc:"NAT shim"
module Log = (val Logs.src_log src : Logs.LOG)
type action = [
- | `Rewrite
- | `Redirect of Ipaddr.t * int
+ | `NAT
+ | `Redirect of Mirage_nat.endpoint
]
-type t = Nat : (module Mirage_nat.S with type t = 't) * 't -> t
+module Nat = Mirage_nat_hashtable
-let create (type t) (nat:(module Mirage_nat.S with type t = t)) (table:t) =
- let (module Nat : Mirage_nat.S with type t = t) = nat in
- Nat (nat, table)
+type t = {
+ table : Nat.t;
+ get_time : unit -> Mirage_nat.time;
+}
-let translate (Nat ((module Nat), table)) packet =
- Nat.translate table packet >|= function
- | Error `Untranslated -> None
+let create ~get_time =
+ Nat.empty () >|= fun table ->
+ { get_time; table }
+
+let translate t packet =
+ Nat.translate t.table packet >|= function
+ | Error (`Untranslated | `TTL_exceeded as e) ->
+ Log.debug (fun f -> f "Failed to NAT %a: %a"
+ Nat_packet.pp packet
+ Mirage_nat.pp_error e
+ );
+ None
| Ok packet -> Some packet
let random_user_port () =
1024 + Random.int (0xffff - 1024)
-let reset (Nat ((module Nat), table)) =
- Nat.reset table
+let reset t =
+ Nat.reset t.table
-let add_nat_rule_and_translate ((Nat ((module Nat), table)) as t) ~xl_host action packet =
+let add_nat_rule_and_translate t ~xl_host action packet =
+ let now = t.get_time () in
let apply_action xl_port =
Lwt.catch (fun () ->
- match action with
- | `Rewrite ->
- Nat.add_nat table packet (xl_host, xl_port)
- | `Redirect target ->
- Nat.add_redirect table packet (xl_host, xl_port) target
+ Nat.add t.table ~now packet (xl_host, xl_port) action
)
(function
| Out_of_memory -> Lwt.return (Error `Out_of_memory)
@@ -49,13 +56,13 @@ let add_nat_rule_and_translate ((Nat ((module Nat), table)) as t) ~xl_host actio
(* Because hash tables resize in big steps, this can happen even if we have a fair
chunk of free memory. *)
Log.warn (fun f -> f "Out_of_memory adding NAT rule. Dropping NAT table...");
- Nat.reset table >>= fun () ->
+ Nat.reset t.table >>= fun () ->
aux ~retries:(retries - 1)
| Error `Overlap when retries < 0 -> Lwt.return (Error "Too many retries")
| Error `Overlap ->
if retries = 0 then (
Log.warn (fun f -> f "Failed to find a free port; resetting NAT table");
- Nat.reset table >>= fun () ->
+ Nat.reset t.table >>= fun () ->
aux ~retries:(retries - 1)
) else (
aux ~retries:(retries - 1)
diff --git a/my_nat.mli b/my_nat.mli
index 7ff5b88..6761b73 100644
--- a/my_nat.mli
+++ b/my_nat.mli
@@ -6,12 +6,12 @@
type t
type action = [
- | `Rewrite
- | `Redirect of Ipaddr.t * int
+ | `NAT
+ | `Redirect of Mirage_nat.endpoint
]
-val create : (module Mirage_nat.S with type t = 'a) -> 'a -> t
+val create : get_time:(unit -> Mirage_nat.time) -> t Lwt.t
val reset : t -> unit Lwt.t
val translate : t -> Nat_packet.t -> Nat_packet.t option Lwt.t
-val add_nat_rule_and_translate : t -> xl_host:Ipaddr.t ->
+val add_nat_rule_and_translate : t -> xl_host:Ipaddr.V4.t ->
action -> Nat_packet.t -> (Nat_packet.t, string) result Lwt.t
diff --git a/unikernel.ml b/unikernel.ml
index f0368a7..5cf69f9 100644
--- a/unikernel.ml
+++ b/unikernel.ml
@@ -9,7 +9,6 @@ module Log = (val Logs.src_log src : Logs.LOG)
module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
module Uplink = Uplink.Make(Clock)
- module Nat = Mirage_nat_hashtable.Make(Clock)(OS.Time)
(* Set up networking and listen for incoming packets. *)
let network ~clock nat qubesDB =
@@ -72,7 +71,8 @@ module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
OS.Lifecycle.await_shutdown_request () >>= fun (`Poweroff | `Reboot) ->
return () in
(* Set up networking *)
- Nat.empty clock >|= My_nat.create (module Nat) >>= fun nat ->
+ let get_time () = Clock.elapsed_ns clock in
+ My_nat.create ~get_time >>= fun nat ->
let net_listener = network ~clock nat qubesDB in
(* Report memory usage to XenStore *)
Memory_pressure.init ();
From 75dd8503c5ddb3bb6824e12be4fb15489673adf9 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Wed, 15 Mar 2017 08:56:24 +0000
Subject: [PATCH 018/281] Use LRU cache to prevent out-of-memory errors
---
README.md | 4 ++--
config.ml | 10 ++++++++++
my_nat.ml | 41 ++++++++++++++++++++++-------------------
my_nat.mli | 2 +-
unikernel.ml | 3 ++-
5 files changed, 37 insertions(+), 23 deletions(-)
diff --git a/README.md b/README.md
index 7e24e99..a819a00 100644
--- a/README.md
+++ b/README.md
@@ -31,8 +31,8 @@ This took about 10 minutes on my laptop (it will be much quicker if you run it a
2. Install mirage, pinning a few unreleased features we need:
- opam pin add -n -y tcpip 'https://github.com/talex5/mirage-tcpip.git#fix-length-checks'
- opam pin add -y mirage-nat 'https://github.com/talex5/mirage-nat.git#cleanup'
+ opam pin add -n -y tcpip.3.0.0 'https://github.com/talex5/mirage-tcpip.git#fix-length-checks'
+ opam pin add -y mirage-nat 'https://github.com/talex5/mirage-nat.git#lru'
opam install mirage
3. Build mirage-firewall:
diff --git a/config.ml b/config.ml
index 6ac02db..37207aa 100644
--- a/config.ml
+++ b/config.ml
@@ -5,8 +5,18 @@
open Mirage
+let table_size =
+ let open Functoria_key in
+ let info = Arg.info
+ ~doc:"The number of NAT entries to allocate."
+ ~docv:"ENTRIES" ["nat-table-size"]
+ in
+ let key = Arg.opt ~stage:`Both Arg.int 5_000 info in
+ create "nat_table_size" key
+
let main =
foreign
+ ~keys:[Functoria_key.abstract table_size]
~packages:[
package "vchan";
package "cstruct";
diff --git a/my_nat.ml b/my_nat.ml
index be9b57b..fa995b1 100644
--- a/my_nat.ml
+++ b/my_nat.ml
@@ -18,8 +18,10 @@ type t = {
get_time : unit -> Mirage_nat.time;
}
-let create ~get_time =
- Nat.empty () >|= fun table ->
+let create ~get_time ~max_entries =
+ let tcp_size = 7 * max_entries / 8 in
+ let udp_size = max_entries - tcp_size in
+ Nat.empty ~tcp_size ~udp_size ~icmp_size:100 >|= fun table ->
{ get_time; table }
let translate t packet =
@@ -53,26 +55,27 @@ let add_nat_rule_and_translate t ~xl_host action packet =
let xl_port = random_user_port () in
apply_action xl_port >>= function
| Error `Out_of_memory ->
- (* Because hash tables resize in big steps, this can happen even if we have a fair
- chunk of free memory. *)
- Log.warn (fun f -> f "Out_of_memory adding NAT rule. Dropping NAT table...");
- Nat.reset t.table >>= fun () ->
- aux ~retries:(retries - 1)
+ (* Because hash tables resize in big steps, this can happen even if we have a fair
+ chunk of free memory. *)
+ Log.warn (fun f -> f "Out_of_memory adding NAT rule. Dropping NAT table...");
+ Nat.reset t.table >>= fun () ->
+ aux ~retries:(retries - 1)
| Error `Overlap when retries < 0 -> Lwt.return (Error "Too many retries")
| Error `Overlap ->
- if retries = 0 then (
- Log.warn (fun f -> f "Failed to find a free port; resetting NAT table");
- Nat.reset t.table >>= fun () ->
- aux ~retries:(retries - 1)
- ) else (
- aux ~retries:(retries - 1)
- )
+ if retries = 0 then (
+ Log.warn (fun f -> f "Failed to find a free port; resetting NAT table");
+ Nat.reset t.table >>= fun () ->
+ aux ~retries:(retries - 1)
+ ) else (
+ aux ~retries:(retries - 1)
+ )
| Error `Cannot_NAT ->
- Lwt.return (Error "Cannot NAT this packet")
+ Lwt.return (Error "Cannot NAT this packet")
| Ok () ->
- translate t packet >|= function
- | None -> Error "No NAT entry, even after adding one!"
- | Some packet ->
- Ok packet
+ Log.debug (fun f -> f "Updated NAT table: %a" Nat.pp_summary t.table);
+ translate t packet >|= function
+ | None -> Error "No NAT entry, even after adding one!"
+ | Some packet ->
+ Ok packet
in
aux ~retries:100
diff --git a/my_nat.mli b/my_nat.mli
index 6761b73..770eaa0 100644
--- a/my_nat.mli
+++ b/my_nat.mli
@@ -10,7 +10,7 @@ type action = [
| `Redirect of Mirage_nat.endpoint
]
-val create : get_time:(unit -> Mirage_nat.time) -> t Lwt.t
+val create : get_time:(unit -> Mirage_nat.time) -> max_entries:int -> t Lwt.t
val reset : t -> unit Lwt.t
val translate : t -> Nat_packet.t -> Nat_packet.t option Lwt.t
val add_nat_rule_and_translate : t -> xl_host:Ipaddr.V4.t ->
diff --git a/unikernel.ml b/unikernel.ml
index 5cf69f9..e35d1d1 100644
--- a/unikernel.ml
+++ b/unikernel.ml
@@ -72,7 +72,8 @@ module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
return () in
(* Set up networking *)
let get_time () = Clock.elapsed_ns clock in
- My_nat.create ~get_time >>= fun nat ->
+ let max_entries = Key_gen.nat_table_size () in
+ My_nat.create ~get_time ~max_entries >>= fun nat ->
let net_listener = network ~clock nat qubesDB in
(* Report memory usage to XenStore *)
Memory_pressure.init ();
From 630304500fef32eab4d71ba9613371d917d536d4 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 18 Mar 2017 10:20:26 +0000
Subject: [PATCH 019/281] Update build for Mirage 3
---
.travis.yml | 2 +-
Dockerfile | 7 +++----
build-with-docker.sh | 2 +-
config.ml | 4 ++--
4 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index e46684f..ba4e918 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,4 +20,4 @@ addons:
- time
- libxen-dev
env:
- - OCAML_VERSION=4.04 MIRAGE_BACKEND=xen PINS="mirage-nat:https://github.com/talex5/mirage-nat.git#cleanup tcpip:https://github.com/talex5/mirage-tcpip.git#fix-length-checks"
+ - FORK_USER=talex5 FORK_BRANCH=unikernel OCAML_VERSION=4.04 MIRAGE_BACKEND=xen PINS="mirage-nat:https://github.com/talex5/mirage-nat.git#lru"
diff --git a/Dockerfile b/Dockerfile
index 4c8b436..479eac0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,17 +2,16 @@
# It will probably still work on newer images, though, unless Debian 8
# changes some compiler optimisations (unlikely).
#FROM ocaml/opam:debian-8_ocaml-4.03.0
-FROM ocaml/opam@sha256:72ebf516fca7a9464db2136f2dcf2a58d09547669b60f3643a8329768febaed6
+FROM ocaml/opam@sha256:48c025a4ec2e6ff6dcb4c14f8cae0f332a090fa1ed677170912c4a48627778ab
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN cd opam-repository && git reset --hard 8f4d15eae94dfe6f70a66a7572a21a0c60d9f4f4 && opam update
+RUN cd opam-repository && git reset --hard a51e30ffcec63836014a5bd2408203ec02e4c7af && opam update
RUN sudo apt-get install -y m4 libxen-dev
RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage
-RUN opam pin add -n -y tcpip 'https://github.com/talex5/mirage-tcpip.git#fix-length-checks'
-RUN opam pin add -n -y mirage-nat 'https://github.com/talex5/mirage-nat.git#cleanup'
+RUN opam pin add -n -y mirage-nat 'https://github.com/talex5/mirage-nat.git#lru'
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
WORKDIR /home/opam/qubes-mirage-firewall
diff --git a/build-with-docker.sh b/build-with-docker.sh
index f004471..4823c77 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,4 +5,4 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 45b82182341237ca9e754636f771ef3f4c93824212d1a76520a8a79bbee18668"
+echo "SHA2 last known: 4b24bab81f9c1b14bafabd9725428456c4d6eaff0ef5cefd032a59b9f4021693"
diff --git a/config.ml b/config.ml
index 37207aa..0b4cf79 100644
--- a/config.ml
+++ b/config.ml
@@ -1,4 +1,4 @@
-(* Copyright (C) 2015, Thomas Leonard
+(* Copyright (C) 2017, Thomas Leonard
See the README file for details. *)
(** Configuration for the "mirage" tool. *)
@@ -20,7 +20,7 @@ let main =
~packages:[
package "vchan";
package "cstruct";
- package "tcpip" ~sublibs:["stack-direct"; "xen"];
+ package "tcpip" ~sublibs:["stack-direct"; "xen"] ~min:"3.1.0";
package "mirage-net-xen";
package "mirage-qubes";
package "mirage-nat" ~sublibs:["hashtable"];
From 5158853c30982448aada620fdea250a2e1f1e4c9 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 18 Mar 2017 11:29:55 +0000
Subject: [PATCH 020/281] Update README
---
README.md | 54 +++++++++++++++++++++++-------------------------------
1 file changed, 23 insertions(+), 31 deletions(-)
diff --git a/README.md b/README.md
index a819a00..3982cd0 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,6 @@ This took about 10 minutes on my laptop (it will be much quicker if you run it a
2. Install mirage, pinning a few unreleased features we need:
- opam pin add -n -y tcpip.3.0.0 'https://github.com/talex5/mirage-tcpip.git#fix-length-checks'
opam pin add -y mirage-nat 'https://github.com/talex5/mirage-nat.git#lru'
opam install mirage
@@ -39,7 +38,7 @@ This took about 10 minutes on my laptop (it will be much quicker if you run it a
git clone https://github.com/talex5/qubes-mirage-firewall.git
cd qubes-mirage-firewall
- mirage configure --xen
+ mirage configure -t xen
make
## Deploy
@@ -53,9 +52,9 @@ The tarball contains `vmlinuz`, which is the unikernel itself, plus a couple of
For development, use the [test-mirage][] scripts to deploy the unikernel (`mir-qubes-firewall.xen`) from your development AppVM. e.g.
- $ test-mirage mir-firewall.xen mirage-firewall
+ $ test-mirage qubes_firewall.xen mirage-firewall
Waiting for 'Ready'... OK
- Uploading 'mir-qubes-firewall.xen' (4843304 bytes) to "mirage-firewall"
+ Uploading 'qubes_firewall.xen' (5901080 bytes) to "mirage-firewall"
Waiting for 'Booting'... OK
--> Loading the VM (type = ProxyVM)...
--> Starting Qubes DB...
@@ -72,38 +71,31 @@ For development, use the [test-mirage][] scripts to deploy the unikernel (`mir-q
MirageOS booting...
Initialising timer interface
Initialising console ... done.
- Netif: add resume hook
gnttab_stubs.c: initialised mini-os gntmap
- 2015-12-30 10:04.42: INF [qubes.rexec] waiting for client...
- 2015-12-30 10:04.42: INF [qubes.gui] waiting for client...
- 2015-12-30 10:04.42: INF [qubes.db] connecting to server...
- 2015-12-30 10:04.42: INF [qubes.db] connected
- 2015-12-30 10:04.42: INF [qubes.rexec] client connected, using protocol version 2
- 2015-12-30 10:04.42: INF [qubes.db] got update: "/qubes-keyboard" = "xkb_keymap {\n\txkb_keycodes { include \"evdev+aliases(qwerty)\"\t};\n\txkb_types { include \"complete\"\t};\n\txkb_compat { include \"complete\"\t};\n\txkb_symbols { include \"pc+gb+inet(evdev)\"\t};\n\txkb_geometry { include \"pc(pc104)\"\t};\n};"
- 2015-12-30 10:04.42: INF [qubes.gui] client connected (screen size: 6720x2160)
- 2015-12-30 10:04.42: INF [unikernel] agents connected in 0.052 s (CPU time used since boot: 0.007 s)
- Netif.connect 0
- Netfront.create: id=0 domid=1
- sg:true gso_tcpv4:true rx_copy:true rx_flip:false smart_poll:false
- MAC: 00:16:3e:5e:6c:0b
- ARP: sending gratuitous from 10.137.1.13
- 2015-12-30 10:04.42: INF [application] Client (internal) network is 10.137.3.0/24
- ARP: transmitting probe -> 10.137.1.1
- 2015-12-30 10:04.42: INF [net] Watching backend/vif
- 2015-12-30 10:04.42: INF [qubes.rexec] Execute "user:QUBESRPC qubes.SetMonitorLayout dom0\000"
- 2015-12-30 10:04.42: WRN [command] << Unknown command "QUBESRPC qubes.SetMonitorLayout dom0"
- 2015-12-30 10:04.42: INF [qubes.rexec] Execute "root:QUBESRPC qubes.WaitForSession none\000"
- 2015-12-30 10:04.42: WRN [command] << Unknown command "QUBESRPC qubes.WaitForSession none"
- 2015-12-30 10:04.42: INF [qubes.db] got update: "/qubes-netvm-domid" = "1"
- ARP: retrying 10.137.1.1 (n=1)
- ARP: transmitting probe -> 10.137.1.1
- ARP: updating 10.137.1.1 -> fe:ff:ff:ff:ff:ff
-
+ 2017-03-18 11:32:37 -00:00: INF [qubes.rexec] waiting for client...
+ 2017-03-18 11:32:37 -00:00: INF [qubes.gui] waiting for client...
+ 2017-03-18 11:32:37 -00:00: INF [qubes.db] connecting to server...
+ 2017-03-18 11:32:37 -00:00: INF [qubes.db] connected
+ 2017-03-18 11:32:37 -00:00: INF [qubes.rexec] client connected, using protocol version 2
+ 2017-03-18 11:32:37 -00:00: INF [qubes.db] got update: "/qubes-keyboard" = "xkb_keymap {\n\txkb_keycodes { include \"evdev+aliases(qwerty)\"\t};\n\txkb_types { include \"complete\"\t};\n\txkb_compat { include \"complete\"\t};\n\txkb_symbols { include \"pc+gb+inet(evdev)\"\t};\n\txkb_geometry { include \"pc(pc105)\"\t};\n};"
+ 2017-03-18 11:32:37 -00:00: INF [qubes.gui] client connected (screen size: 6720x2160)
+ 2017-03-18 11:32:37 -00:00: INF [unikernel] Qubes agents connected in 0.095 s (CPU time used since boot: 0.008 s)
+ 2017-03-18 11:32:37 -00:00: INF [net-xen:frontend] connect 0
+ 2017-03-18 11:32:37 -00:00: INF [memory_pressure] Writing meminfo: free 6584 / 17504 kB (37.61 %)
+ Note: cannot write Xen 'control' directory
+ 2017-03-18 11:32:37 -00:00: INF [net-xen:frontend] create: id=0 domid=1
+ 2017-03-18 11:32:37 -00:00: INF [net-xen:frontend] sg:true gso_tcpv4:true rx_copy:true rx_flip:false smart_poll:false
+ 2017-03-18 11:32:37 -00:00: INF [net-xen:frontend] MAC: 00:16:3e:5e:6c:11
+ 2017-03-18 11:32:37 -00:00: WRN [command] << Unknown command "QUBESRPC qubes.SetMonitorLayout dom0"
+ 2017-03-18 11:32:38 -00:00: INF [ethif] Connected Ethernet interface 00:16:3e:5e:6c:11
+ 2017-03-18 11:32:38 -00:00: INF [arpv4] Connected arpv4 device on 00:16:3e:5e:6c:11
+ 2017-03-18 11:32:38 -00:00: INF [dao] Watching backend/vif
+ 2017-03-18 11:32:38 -00:00: INF [qubes.db] got update: "/qubes-netvm-domid" = "1"
# LICENSE
-Copyright (c) 2015, Thomas Leonard
+Copyright (c) 2017, Thomas Leonard
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
From 583366b22bf78be060bf3ab7be353bc7a3479f79 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 18 Mar 2017 17:59:06 +0000
Subject: [PATCH 021/281] Remove non-Docker build instructions
Fedora 24 doesn't work with opam (because the current binary release of
aspcud's clasp binary segfaults, which opam reports as `External solver
failed with inconsistent return value.`).
---
README.md | 25 +++----------------------
1 file changed, 3 insertions(+), 22 deletions(-)
diff --git a/README.md b/README.md
index 3982cd0..4da31f4 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ Note: This firewall *ignores the rules set in the Qubes GUI*. See `rules.ml` for
See [A Unikernel Firewall for QubesOS][] for more details.
-## Build (with Docker)
+## Build
Clone this Git repository and run the `build-with-docker.sh` script:
@@ -19,27 +19,8 @@ Clone this Git repository and run the `build-with-docker.sh` script:
This took about 10 minutes on my laptop (it will be much quicker if you run it again).
-## Build (without Docker)
-
-1. Install build tools:
-
- sudo yum install git gcc m4 0install patch ncurses-devel tar bzip2 unzip make which findutils xen-devel
- mkdir ~/bin
- 0install add opam http://tools.ocaml.org/opam.xml
- opam init --comp=4.04.0
- eval `opam config env`
-
-2. Install mirage, pinning a few unreleased features we need:
-
- opam pin add -y mirage-nat 'https://github.com/talex5/mirage-nat.git#lru'
- opam install mirage
-
-3. Build mirage-firewall:
-
- git clone https://github.com/talex5/qubes-mirage-firewall.git
- cd qubes-mirage-firewall
- mirage configure -t xen
- make
+You can also build without Docker, as for any normal Mirage unikernel;
+see [the Mirage installation instructions](https://mirage.io/wiki/install) for details.
## Deploy
From 78f25ea2c5b79b6ebdb83810adc8676c804e0a44 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Mon, 27 Mar 2017 13:45:06 +0100
Subject: [PATCH 022/281] Fix build instructions
No need to run `make tar` manually now.
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 4da31f4..9bf0e00 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ see [the Mirage installation instructions](https://mirage.io/wiki/install) for d
## Deploy
-If you want to deploy manually, use `make tar` to create `mirage-firewall.tar.bz2` and unpack this in dom0, inside `/var/lib/qubes/vm-kernels/`. e.g. (if `dev` is the AppVM where you built it):
+If you want to deploy manually, unpack `mirage-firewall.tar.bz2` in dom0, inside `/var/lib/qubes/vm-kernels/`. e.g. (if `dev` is the AppVM where you built it):
[tal@dom0 ~]$ cd /var/lib/qubes/vm-kernels/
[tal@dom0 vm-kernels]$ qvm-run -p dev 'cat qubes-mirage-firewall/mirage-firewall.tar.bz2' | tar xjf -
From f4df389713bf8bad65834bbbe693d5d07a729106 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Fri, 7 Apr 2017 13:07:07 +0100
Subject: [PATCH 023/281] Add more detailed installation instructions
---
README.md | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 9bf0e00..1448f20 100644
--- a/README.md
+++ b/README.md
@@ -26,10 +26,24 @@ see [the Mirage installation instructions](https://mirage.io/wiki/install) for d
If you want to deploy manually, unpack `mirage-firewall.tar.bz2` in dom0, inside `/var/lib/qubes/vm-kernels/`. e.g. (if `dev` is the AppVM where you built it):
- [tal@dom0 ~]$ cd /var/lib/qubes/vm-kernels/
- [tal@dom0 vm-kernels]$ qvm-run -p dev 'cat qubes-mirage-firewall/mirage-firewall.tar.bz2' | tar xjf -
+ [tal@dom0 ~]$ cd /var/lib/qubes/vm-kernels/
+ [tal@dom0 vm-kernels]$ qvm-run -p dev 'cat qubes-mirage-firewall/mirage-firewall.tar.bz2' | tar xjf -
The tarball contains `vmlinuz`, which is the unikernel itself, plus a couple of dummy files that Qubes requires.
+To configure your new firewall using the Qubes Manager GUI:
+
+- Create a new ProxyVM named `mirage-firewall` to run the unikernel.
+- You can use any template, and make it standalone or not. It doesn’t matter, since we don’t use the hard disk.
+- Set the type to `ProxyVM`.
+- Select `sys-net` for networking (not `sys-firewall`).
+- Click `OK` to create the VM.
+- Go to the VM settings, and look in the `Advanced` tab:
+ - Set the kernel to `mirage-firewall`.
+ - Turn off memory balancing and set the memory to 20 MB or so (you might have to fight a bit with the Qubes GUI to get it this low).
+ - Set VCPUs (number of virtual CPUs) to 1.
+
+You can run `mirage-firewall` alongside your existing `sys-firewall` and you can choose which AppVMs use which firewall using the GUI.
+To configure an AppVM to use it, go to the app VM's settings in the GUI and change its `NetVM` from `default (sys-firewall)` to `mirage-firewall`. Alternatively, you can configure `mirage-firewall` to be your default firewall VM.
For development, use the [test-mirage][] scripts to deploy the unikernel (`mir-qubes-firewall.xen`) from your development AppVM. e.g.
From 445b1711cbc3e27e0b81ad826b37435478d443b0 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 8 Apr 2017 13:12:38 +0100
Subject: [PATCH 024/281] Show the packet when failing to add a NAT rule
The previous message was just:
WRN [firewall] Failed to add NAT rewrite rule: Cannot NAT this packet
---
firewall.ml | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/firewall.ml b/firewall.ml
index f0d29ef..337c5c8 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -77,6 +77,17 @@ let pp_packet fmt {src; dst; proto; packet = _} =
pp_host dst
pp_proto proto
+let pp_transport_headers f = function
+ | `ICMP (h, _) -> Icmpv4_packet.pp f h
+ | `TCP (h, _) -> Tcp.Tcp_packet.pp f h
+ | `UDP (h, _) -> Udp_packet.pp f h
+
+let pp_header f = function
+ | `IPv4 (ip, transport) ->
+ Fmt.pf f "%a %a"
+ Ipv4_packet.pp ip
+ pp_transport_headers transport
+
(* NAT *)
let translate t packet =
@@ -88,7 +99,7 @@ let add_nat_and_forward_ipv4 t packet =
My_nat.add_nat_rule_and_translate t.Router.nat ~xl_host `NAT packet >>= function
| Ok packet -> forward_ipv4 t packet
| Error e ->
- Log.warn (fun f -> f "Failed to add NAT rewrite rule: %s" e);
+ Log.warn (fun f -> f "Failed to add NAT rewrite rule: %s (%a)" e pp_header packet);
Lwt.return ()
(* Add a NAT rule to redirect this conversation to [host:port] instead of us. *)
@@ -100,7 +111,7 @@ let nat_to t ~host ~port packet =
My_nat.add_nat_rule_and_translate t.Router.nat ~xl_host (`Redirect (target, port)) packet >>= function
| Ok packet -> forward_ipv4 t packet
| Error e ->
- Log.warn (fun f -> f "Failed to add NAT redirect rule: %s" e);
+ Log.warn (fun f -> f "Failed to add NAT redirect rule: %s (%a)" e pp_header packet);
Lwt.return ()
(* Handle incoming packets *)
From e55c304160e61296ea32bfa36733600c15e85d2c Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 29 Apr 2017 11:42:31 +0100
Subject: [PATCH 025/281] Drop frames if the xmit queue gets too long
With lots of VMs updating, the firewall quit with:
2017-04-23 20:47:52 -00:00: INF [frameQ] Queue length for 10.137.3.11: incr to 474
2017-04-23 20:47:52 -00:00: INF [memory_pressure] Writing meminfo: free 2648 / 17504 kB (15.13 %)
[...]
Fatal error: out of memory.
The firewall will now drop frames when more than 10 are queued (note
that queuing only starts once the network driver's transmit buffer is
already full).
---
frameQ.ml | 31 +++++++++++++++++++------------
frameQ.mli | 2 +-
2 files changed, 20 insertions(+), 13 deletions(-)
diff --git a/frameQ.ml b/frameQ.ml
index bea4cf2..b6b7ed1 100644
--- a/frameQ.ml
+++ b/frameQ.ml
@@ -10,16 +10,23 @@ type t = {
}
let create name = { name; items = 0 }
-
+
+(* Note: the queue is only used if we already filled the transmit buffer. *)
+let max_qlen = 10
+
let send q fn =
- (* TODO: drop if queue too long *)
- let sent = fn () in
- if Lwt.state sent = Lwt.Sleep then (
- q.items <- q.items + 1;
- Log.info (fun f -> f "Queue length for %s: incr to %d" q.name q.items);
- Lwt.on_termination sent (fun () ->
- q.items <- q.items - 1;
- Log.info (fun f -> f "Queue length for %s: decr to %d" q.name q.items);
- )
- );
- sent
+ if q.items = max_qlen then (
+ Log.warn (fun f -> f "Maximim queue length exceeded for %s: dropping frame" q.name);
+ Lwt.return_unit
+ ) else (
+ let sent = fn () in
+ if Lwt.state sent = Lwt.Sleep then (
+ q.items <- q.items + 1;
+ Log.info (fun f -> f "Queue length for %s: incr to %d" q.name q.items);
+ Lwt.on_termination sent (fun () ->
+ q.items <- q.items - 1;
+ Log.info (fun f -> f "Queue length for %s: decr to %d" q.name q.items);
+ )
+ );
+ sent
+ )
diff --git a/frameQ.mli b/frameQ.mli
index de72211..f11e1ae 100644
--- a/frameQ.mli
+++ b/frameQ.mli
@@ -8,7 +8,7 @@ type t
val create : string -> t
(** [create name] is a new empty queue. [name] is used in log messages. *)
-val send : t -> (unit -> 'a Lwt.t) -> 'a Lwt.t
+val send : t -> (unit -> unit Lwt.t) -> unit Lwt.t
(** [send t fn] checks that the queue isn't overloaded and calls [fn ()] if it's OK.
The item is considered to be queued until the result of [fn] has resolved.
In the case of mirage-net-xen's [writev], this happens when the frame has been
From 794ca35d234b454ef438c9f8d14856f897180703 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Tue, 12 Sep 2017 16:57:01 +0100
Subject: [PATCH 026/281] Update Dockerfile to use newer Debian base image
Was failing with
```
E: Failed to fetch http://security.debian.org/pool/updates/main/x/xen/libxenstore3.0_4.4.1-9+deb8u8_amd64.deb 404 Not Found [IP: 212.211.132.32 80]
```
---
Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Dockerfile b/Dockerfile
index 479eac0..e04f6d1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,7 +2,7 @@
# It will probably still work on newer images, though, unless Debian 8
# changes some compiler optimisations (unlikely).
#FROM ocaml/opam:debian-8_ocaml-4.03.0
-FROM ocaml/opam@sha256:48c025a4ec2e6ff6dcb4c14f8cae0f332a090fa1ed677170912c4a48627778ab
+FROM ocaml/opam@sha256:66f9d402ab6dc00c47d2ee3195ab247f9c1c8e7e774197f4fa6ea2a290a3ebbc
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
From d61c2312c12a8f8e82e13ddca866cb788893aecd Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Tue, 12 Sep 2017 18:05:55 +0100
Subject: [PATCH 027/281] Fix Travis
---
.travis.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index ba4e918..4a58a64 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,5 @@
language: c
-install: wget https://raw.githubusercontent.com/ocaml/ocaml-travisci-skeleton/master/.travis-mirage.sh
+install: wget https://raw.githubusercontent.com/ocaml/ocaml-ci-scripts/master/.travis-mirage.sh
script: bash -ex .travis-mirage.sh
sudo: required
dist: trusty
@@ -20,4 +20,4 @@ addons:
- time
- libxen-dev
env:
- - FORK_USER=talex5 FORK_BRANCH=unikernel OCAML_VERSION=4.04 MIRAGE_BACKEND=xen PINS="mirage-nat:https://github.com/talex5/mirage-nat.git#lru"
+ - OCAML_VERSION=4.04 MIRAGE_BACKEND=xen PINS="mirage-nat:https://github.com/talex5/mirage-nat.git#lru"
From 997d538a93f3c3effe0bd5cabfb7b1f877eb6e0b Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sun, 15 Oct 2017 14:35:03 +0100
Subject: [PATCH 028/281] Use released mirage-nat 1.0
---
.travis.yml | 2 +-
Dockerfile | 9 ++++-----
config.ml | 4 ++--
my_nat.ml | 2 +-
4 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 4a58a64..1325706 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,4 +20,4 @@ addons:
- time
- libxen-dev
env:
- - OCAML_VERSION=4.04 MIRAGE_BACKEND=xen PINS="mirage-nat:https://github.com/talex5/mirage-nat.git#lru"
+ - OCAML_VERSION=4.04 MIRAGE_BACKEND=xen
diff --git a/Dockerfile b/Dockerfile
index e04f6d1..e971234 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,17 +1,16 @@
# Pin the base image to a specific hash for maximum reproducibility.
# It will probably still work on newer images, though, unless Debian 8
# changes some compiler optimisations (unlikely).
-#FROM ocaml/opam:debian-8_ocaml-4.03.0
-FROM ocaml/opam@sha256:66f9d402ab6dc00c47d2ee3195ab247f9c1c8e7e774197f4fa6ea2a290a3ebbc
+#FROM ocaml/opam:debian-8_ocaml-4.04.2
+FROM ocaml/opam@sha256:17a527319b850bdaf6759386a566dd088a053758b6d0603712dbcb10ad62f86c
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN cd opam-repository && git reset --hard a51e30ffcec63836014a5bd2408203ec02e4c7af && opam update
+RUN cd opam-repository && git fetch origin && git reset --hard ad6348231fa14e1d9df724db908a1b7fe07d3ab9 && opam update
RUN sudo apt-get install -y m4 libxen-dev
-RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage
-RUN opam pin add -n -y mirage-nat 'https://github.com/talex5/mirage-nat.git#lru'
+RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage mirage-nat
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
WORKDIR /home/opam/qubes-mirage-firewall
diff --git a/config.ml b/config.ml
index 0b4cf79..0a73b48 100644
--- a/config.ml
+++ b/config.ml
@@ -20,10 +20,10 @@ let main =
~packages:[
package "vchan";
package "cstruct";
- package "tcpip" ~sublibs:["stack-direct"; "xen"] ~min:"3.1.0";
+ package "tcpip" ~sublibs:["stack-direct"; "xen"; "arpv4"] ~min:"3.1.0";
package "mirage-net-xen";
package "mirage-qubes";
- package "mirage-nat" ~sublibs:["hashtable"];
+ package "mirage-nat";
package "mirage-logs";
]
"Unikernel.Main" (mclock @-> job)
diff --git a/my_nat.ml b/my_nat.ml
index fa995b1..bfaf702 100644
--- a/my_nat.ml
+++ b/my_nat.ml
@@ -11,7 +11,7 @@ type action = [
| `Redirect of Mirage_nat.endpoint
]
-module Nat = Mirage_nat_hashtable
+module Nat = Mirage_nat_lru
type t = {
table : Nat.t;
From b114e569f23db06bb9624d6f74ae9b4fe2542c2c Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Thu, 9 Nov 2017 15:20:55 +0000
Subject: [PATCH 029/281] Use Git master for shared-memory-ring and netchannel
This adds support for HVM and disposable domains.
Also, update the suggested RAM allocation slightly as 20 MB can be too
small with lots of VMs.
---
Dockerfile | 9 ++++++---
README.md | 2 +-
build-with-docker.sh | 2 +-
client_net.ml | 2 +-
4 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index e971234..e3cf30c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,15 +2,18 @@
# It will probably still work on newer images, though, unless Debian 8
# changes some compiler optimisations (unlikely).
#FROM ocaml/opam:debian-8_ocaml-4.04.2
-FROM ocaml/opam@sha256:17a527319b850bdaf6759386a566dd088a053758b6d0603712dbcb10ad62f86c
+FROM ocaml/opam@sha256:17143ad95a2e944758fd9de6ee831e9af98367455cd273b17139c38dcb032f09
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN cd opam-repository && git fetch origin && git reset --hard ad6348231fa14e1d9df724db908a1b7fe07d3ab9 && opam update
+RUN cd opam-repository && git reset --hard 26fc7c2d5eb5041b7348e28e8300d376a1c31a62 && opam update
RUN sudo apt-get install -y m4 libxen-dev
-RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage mirage-nat
+# TODO: remove this once the new versions are released (smr>2.0.1 and mnx>1.7.1)
+RUN opam pin add -yn --dev netchannel
+RUN opam pin add -yn --dev shared-memory-ring
+RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage mirage-nat mirage-qubes
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
WORKDIR /home/opam/qubes-mirage-firewall
diff --git a/README.md b/README.md
index 1448f20..6b90ac3 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ To configure your new firewall using the Qubes Manager GUI:
- Click `OK` to create the VM.
- Go to the VM settings, and look in the `Advanced` tab:
- Set the kernel to `mirage-firewall`.
- - Turn off memory balancing and set the memory to 20 MB or so (you might have to fight a bit with the Qubes GUI to get it this low).
+ - Turn off memory balancing and set the memory to 32 MB or so (you might have to fight a bit with the Qubes GUI to get it this low).
- Set VCPUs (number of virtual CPUs) to 1.
You can run `mirage-firewall` alongside your existing `sys-firewall` and you can choose which AppVMs use which firewall using the GUI.
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 4823c77..11be5c0 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,4 +5,4 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 4b24bab81f9c1b14bafabd9725428456c4d6eaff0ef5cefd032a59b9f4021693"
+echo "SHA2 last known: 2cad66c4b83817cdd1650f174586fd4daab7b7c271abd62844de6e6a17200750"
diff --git a/client_net.ml b/client_net.ml
index e7bc744..995b5f5 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -106,7 +106,7 @@ let add_client ~router vif client_ip =
add_vif vif ~client_ip ~router ~cleanup_tasks
)
(fun ex ->
- Log.warn (fun f -> f "Error connecting client %a: %s"
+ Log.warn (fun f -> f "Error with client %a: %s"
Dao.ClientVif.pp vif (Printexc.to_string ex));
return ()
)
From f4a978b13c44335151bb7c0aa4b5be0e136669b0 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Thu, 9 Nov 2017 17:31:02 +0000
Subject: [PATCH 030/281] Update Travis to test with Docker
---
.dockerignore | 2 ++
.travis.yml | 25 +++++--------------------
2 files changed, 7 insertions(+), 20 deletions(-)
diff --git a/.dockerignore b/.dockerignore
index 5fde600..85fe546 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,2 +1,4 @@
.git
_build
+*.xen
+*.bz2
diff --git a/.travis.yml b/.travis.yml
index 1325706..fb11f9a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,23 +1,8 @@
language: c
-install: wget https://raw.githubusercontent.com/ocaml/ocaml-ci-scripts/master/.travis-mirage.sh
-script: bash -ex .travis-mirage.sh
+script:
+ - echo 'ADD . /home/opam/qubes-mirage-firewall' >> Dockerfile
+ - echo 'RUN sudo chown -R opam /home/opam/qubes-mirage-firewall' >> Dockerfile
+ - docker build -t qubes-mirage-firewall .
+ - docker run --rm -i qubes-mirage-firewall
sudo: required
dist: trusty
-addons:
- apt:
- sources:
- - avsm
- packages:
- - ocaml
- - ocaml-base
- - ocaml-native-compilers
- - ocaml-compiler-libs
- - ocaml-interp
- - ocaml-base-nox
- - ocaml-nox
- - camlp4
- - camlp4-extra
- - time
- - libxen-dev
-env:
- - OCAML_VERSION=4.04 MIRAGE_BACKEND=xen
From aca156f21b8e255165b99c5fd8fd53ee6137a1ba Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Tue, 14 Nov 2017 12:35:33 +0000
Subject: [PATCH 031/281] Update to released shared-memory-ring
---
Dockerfile | 5 ++---
config.ml | 1 +
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index e3cf30c..211f42e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,12 +7,11 @@ FROM ocaml/opam@sha256:17143ad95a2e944758fd9de6ee831e9af98367455cd273b17139c38dc
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN cd opam-repository && git reset --hard 26fc7c2d5eb5041b7348e28e8300d376a1c31a62 && opam update
+RUN cd opam-repository && git fetch origin && git reset --hard 67ab04a9a142da70935c9fdf919bf09b517499c9 && opam update
RUN sudo apt-get install -y m4 libxen-dev
-# TODO: remove this once the new versions are released (smr>2.0.1 and mnx>1.7.1)
+# TODO: remove this once the new versions are released (mnx>1.7.1)
RUN opam pin add -yn --dev netchannel
-RUN opam pin add -yn --dev shared-memory-ring
RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage mirage-nat mirage-qubes
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
diff --git a/config.ml b/config.ml
index 0a73b48..8fb088e 100644
--- a/config.ml
+++ b/config.ml
@@ -21,6 +21,7 @@ let main =
package "vchan";
package "cstruct";
package "tcpip" ~sublibs:["stack-direct"; "xen"; "arpv4"] ~min:"3.1.0";
+ package "shared-memory-ring" ~min:"3.0.0";
package "mirage-net-xen";
package "mirage-qubes";
package "mirage-nat";
From 6e6ff755ebf7337c1f39bafebbc50c63a8de30af Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 16 Dec 2017 12:37:42 +0000
Subject: [PATCH 032/281] Update to newly released version of netchannel
---
Dockerfile | 4 +---
build-with-docker.sh | 2 +-
config.ml | 3 ++-
3 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 211f42e..a680fc2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,11 +7,9 @@ FROM ocaml/opam@sha256:17143ad95a2e944758fd9de6ee831e9af98367455cd273b17139c38dc
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN cd opam-repository && git fetch origin && git reset --hard 67ab04a9a142da70935c9fdf919bf09b517499c9 && opam update
+RUN cd opam-repository && git fetch origin && git reset --hard eb49e10ee78f36c660a1f57aea45f7a6ed932460 && opam update
RUN sudo apt-get install -y m4 libxen-dev
-# TODO: remove this once the new versions are released (mnx>1.7.1)
-RUN opam pin add -yn --dev netchannel
RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage mirage-nat mirage-qubes
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 11be5c0..c7858c0 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,4 +5,4 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 2cad66c4b83817cdd1650f174586fd4daab7b7c271abd62844de6e6a17200750"
+echo "SHA2 last known: dc0e1e614e113b4e0d4fbd71e90d0489b3fc26a64cd1fbd0df8a56499dfa9a45"
diff --git a/config.ml b/config.ml
index 8fb088e..3f112fb 100644
--- a/config.ml
+++ b/config.ml
@@ -22,7 +22,8 @@ let main =
package "cstruct";
package "tcpip" ~sublibs:["stack-direct"; "xen"; "arpv4"] ~min:"3.1.0";
package "shared-memory-ring" ~min:"3.0.0";
- package "mirage-net-xen";
+ package "netchannel" ~min:"1.8.0";
+ package "mirage-net-xen" ~min:"1.7.1";
package "mirage-qubes";
package "mirage-nat";
package "mirage-logs";
From b77d91cb20e07566b4397dcbc654f6431d7392fd Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 6 Jan 2018 12:09:26 +0000
Subject: [PATCH 033/281] Add installation instructions for Qubes 4
---
README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 47 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index 6b90ac3..961393f 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,12 @@ Note: This firewall *ignores the rules set in the Qubes GUI*. See `rules.ml` for
See [A Unikernel Firewall for QubesOS][] for more details.
-## Build
+
+## Binary releases
+
+Pre-built binaries are available from the [releases page][].
+
+## Build from source
Clone this Git repository and run the `build-with-docker.sh` script:
@@ -30,7 +35,10 @@ If you want to deploy manually, unpack `mirage-firewall.tar.bz2` in dom0, inside
[tal@dom0 vm-kernels]$ qvm-run -p dev 'cat qubes-mirage-firewall/mirage-firewall.tar.bz2' | tar xjf -
The tarball contains `vmlinuz`, which is the unikernel itself, plus a couple of dummy files that Qubes requires.
-To configure your new firewall using the Qubes Manager GUI:
+
+### Qubes 3
+
+To configure your new firewall using the Qubes 3 Manager GUI:
- Create a new ProxyVM named `mirage-firewall` to run the unikernel.
- You can use any template, and make it standalone or not. It doesn’t matter, since we don’t use the hard disk.
@@ -42,10 +50,42 @@ To configure your new firewall using the Qubes Manager GUI:
- Turn off memory balancing and set the memory to 32 MB or so (you might have to fight a bit with the Qubes GUI to get it this low).
- Set VCPUs (number of virtual CPUs) to 1.
-You can run `mirage-firewall` alongside your existing `sys-firewall` and you can choose which AppVMs use which firewall using the GUI.
-To configure an AppVM to use it, go to the app VM's settings in the GUI and change its `NetVM` from `default (sys-firewall)` to `mirage-firewall`. Alternatively, you can configure `mirage-firewall` to be your default firewall VM.
+### Qubes 4
-For development, use the [test-mirage][] scripts to deploy the unikernel (`mir-qubes-firewall.xen`) from your development AppVM. e.g.
+Run this command in dom0 to create a `mirage-firewall` VM using the `mirage-firewall` kernel you added above:
+
+```
+qvm-create \
+ --property kernel=mirage-firewall \
+ --property kernelopts=None \
+ --property memory=32 \
+ --property maxmem=32 \
+ --property netvm=sys-net \
+ --property provides_network=True \
+ --property vcpus=1 \
+ --property virt_mode=pv \
+ --label=green \
+ --class StandaloneVM \
+ mirage-firewall
+```
+
+### Configure AppVMs to use it
+
+You can run `mirage-firewall` alongside your existing `sys-firewall` and you can choose which AppVMs use which firewall using the GUI.
+To configure an AppVM to use it, go to the app VM's settings in the GUI and change its `NetVM` from `default (sys-firewall)` to `mirage-firewall`.
+
+You can also configure it by running this command in dom0 (replace `my-app-vm` with the AppVM's name):
+
+```
+qvm-prefs --set my-app-vm netvm mirage-firewall
+```
+
+Alternatively, you can configure `mirage-firewall` to be your default firewall VM.
+
+### Easy deployment for developers
+
+For development, use the [test-mirage][] scripts to deploy the unikernel (`qubes_firewall.xen`) from your development AppVM.
+This takes a little more setting up the first time, but will be much quicker after that. e.g.
$ test-mirage qubes_firewall.xen mirage-firewall
Waiting for 'Ready'... OK
@@ -90,7 +130,7 @@ For development, use the [test-mirage][] scripts to deploy the unikernel (`mir-q
# LICENSE
-Copyright (c) 2017, Thomas Leonard
+Copyright (c) 2018, Thomas Leonard
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
@@ -105,3 +145,4 @@ gg
[test-mirage]: https://github.com/talex5/qubes-test-mirage
[mirage-qubes]: https://github.com/talex5/mirage-qubes
[A Unikernel Firewall for QubesOS]: http://roscidus.com/blog/blog/2016/01/01/a-unikernel-firewall-for-qubesos/
+[releases page]: https://github.com/talex5/qubes-mirage-firewall/releases
From 78e219da8cf5413b5f12b354b0cd46c7635dc324 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 3 Nov 2018 17:25:40 +0000
Subject: [PATCH 034/281] Update Debian base image in Docker build
Had stopped working:
Err http://security.debian.org/ jessie/updates/main libxenstore3.0 amd64 4.4.1-9+deb8u10
404 Not Found [IP: 128.61.240.73 80]
Updated from Debian 8 to Debian 9, and from opam to opam2.
---
Dockerfile | 10 +++++-----
build-with-docker.sh | 2 +-
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index a680fc2..a6b1c52 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,15 +1,15 @@
# Pin the base image to a specific hash for maximum reproducibility.
-# It will probably still work on newer images, though, unless Debian 8
+# It will probably still work on newer images, though, unless Debian
# changes some compiler optimisations (unlikely).
-#FROM ocaml/opam:debian-8_ocaml-4.04.2
-FROM ocaml/opam@sha256:17143ad95a2e944758fd9de6ee831e9af98367455cd273b17139c38dcb032f09
+#FROM ocaml/opam2:debian-9-ocaml-4.04
+FROM ocaml/opam2@sha256:feebac4b6f9df9ed52ca1fe7266335cb9fdfffbdc0f6ba4f5e8603ece7e8b096
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN cd opam-repository && git fetch origin && git reset --hard eb49e10ee78f36c660a1f57aea45f7a6ed932460 && opam update
+RUN git fetch origin && git reset --hard 1fa4c078f5b145bd4a455eb0a5559f761d0a94c0 && opam update
-RUN sudo apt-get install -y m4 libxen-dev
+RUN sudo apt-get install -y m4 libxen-dev pkg-config
RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage mirage-nat mirage-qubes
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
diff --git a/build-with-docker.sh b/build-with-docker.sh
index c7858c0..76b6a97 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,4 +5,4 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: dc0e1e614e113b4e0d4fbd71e90d0489b3fc26a64cd1fbd0df8a56499dfa9a45"
+echo "SHA2 last known: dbc245bc425537082e64cf4b4822ce300ddeab10a272a009881e0bd22e06455a"
From 0d0159b56fe9ae9fc745805b38b89e1c8994ef3b Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sun, 4 Nov 2018 14:33:47 +0000
Subject: [PATCH 035/281] Update build instructions for latest Fedora
`yum` no longer exists. Also, show how to create a symlink for
/var/lib/docker on build VMs that aren't standalone.
Reported by xaki23.
---
README.md | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 961393f..02dc576 100644
--- a/README.md
+++ b/README.md
@@ -16,13 +16,16 @@ Pre-built binaries are available from the [releases page][].
Clone this Git repository and run the `build-with-docker.sh` script:
- sudo yum install docker
+ sudo ln -s /var/lib/docker /home/user/docker
+ sudo dnf install docker
sudo systemctl start docker
git clone https://github.com/talex5/qubes-mirage-firewall.git
cd qubes-mirage-firewall
sudo ./build-with-docker.sh
This took about 10 minutes on my laptop (it will be much quicker if you run it again).
+The symlink step at the start isn't needed if your build VM is standalone.
+It gives Docker more disk space and avoids losing the Docker image cache when you reboot the Qube.
You can also build without Docker, as for any normal Mirage unikernel;
see [the Mirage installation instructions](https://mirage.io/wiki/install) for details.
From 184d320a8fe8e6e4f63730c3a9d2020ce492ba7c Mon Sep 17 00:00:00 2001
From: xaki23
Date: Fri, 30 Nov 2018 00:08:26 +0100
Subject: [PATCH 036/281] add stub makefile for qubes-builder
---
Makefile.builder | 8 ++++++++
1 file changed, 8 insertions(+)
create mode 100644 Makefile.builder
diff --git a/Makefile.builder b/Makefile.builder
new file mode 100644
index 0000000..7ad9df1
--- /dev/null
+++ b/Makefile.builder
@@ -0,0 +1,8 @@
+MIRAGE_KERNEL_NAME = qubes_firewall.xen
+#SOURCE_BUILD_DEP := ssh-agent-build-dep
+OCAML_VERSION ?= 4.05.0
+
+#ssh-agent-build-dep:
+# opam pin -y add angstrom https://github.com/reynir/angstrom.git#no-c-blit
+# opam pin -y add ssh-agent https://github.com/reynir/ocaml-ssh-agent.git
+
From d849a09a2505188c0c97c8f83696b4e9c7232db4 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Thu, 10 Jan 2019 12:39:39 +0000
Subject: [PATCH 037/281] Don't wait for GUI before attaching client VMs
If the firewall is restarted while AppVMs are connected, qubesd tries to
reconnect them before starting the GUI agent. However, the firewall was
waiting for the GUI agent to connect before handling the connections.
This led to a 10s delay on restart for each client VM.
Reported by xaki23.
---
unikernel.ml | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/unikernel.ml b/unikernel.ml
index e35d1d1..4a63403 100644
--- a/unikernel.ml
+++ b/unikernel.ml
@@ -34,11 +34,15 @@ module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
]
(* We don't use the GUI, but it's interesting to keep an eye on it.
- If the other end dies, don't let it take us with it (can happen on log out). *)
+ If the other end dies, don't let it take us with it (can happen on logout). *)
let watch_gui gui =
Lwt.async (fun () ->
Lwt.try_bind
- (fun () -> GUI.listen gui)
+ (fun () ->
+ gui >>= fun gui ->
+ Log.info (fun f -> f "GUI agent connected");
+ GUI.listen gui
+ )
(fun `Cant_happen -> assert false)
(fun ex ->
Log.warn (fun f -> f "GUI thread failed: %s" (Printexc.to_string ex));
@@ -51,21 +55,18 @@ module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
let start_time = Clock.elapsed_ns clock in
(* Start qrexec agent, GUI agent and QubesDB agent in parallel *)
let qrexec = RExec.connect ~domid:0 () in
- let gui = GUI.connect ~domid:0 () in
+ GUI.connect ~domid:0 () |> watch_gui;
let qubesDB = DB.connect ~domid:0 () in
(* Wait for clients to connect *)
qrexec >>= fun qrexec ->
let agent_listener = RExec.listen qrexec Command.handler in
- gui >>= fun gui ->
- watch_gui gui;
qubesDB >>= fun qubesDB ->
let startup_time =
let (-) = Int64.sub in
let time_in_ns = Clock.elapsed_ns clock - start_time in
Int64.to_float time_in_ns /. 1e9
in
- Log.info (fun f -> f "Qubes agents connected in %.3f s (CPU time used since boot: %.3f s)"
- startup_time (Sys.time ()));
+ Log.info (fun f -> f "QubesDB and qrexec agents connected in %.3f s" startup_time);
(* Watch for shutdown requests from Qubes *)
let shutdown_rq =
OS.Lifecycle.await_shutdown_request () >>= fun (`Poweroff | `Reboot) ->
From ef09eb50ac883b6df000ff1a90138ecebc09a5a4 Mon Sep 17 00:00:00 2001
From: Ahmed Al-Sudani
Date: Wed, 16 Jan 2019 14:17:09 -0500
Subject: [PATCH 038/281] Update last known build hash
---
build-with-docker.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 76b6a97..bdada12 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,4 +5,4 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: dbc245bc425537082e64cf4b4822ce300ddeab10a272a009881e0bd22e06455a"
+echo "SHA2 last known: 3605a97fbdb9e699a9ceb9e43def8a3cdd04e5cefb48b5824df8f55e7f949203"
From 4526375a1915e34d763da5306f0793bd021fb312 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 19 Jan 2019 10:32:27 +0000
Subject: [PATCH 039/281] Note that Git versions might have different hashes
---
build-with-docker.sh | 1 +
1 file changed, 1 insertion(+)
diff --git a/build-with-docker.sh b/build-with-docker.sh
index bdada12..7ba6fa6 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -6,3 +6,4 @@ echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
echo "SHA2 last known: 3605a97fbdb9e699a9ceb9e43def8a3cdd04e5cefb48b5824df8f55e7f949203"
+echo "(hashes should match for released versions)"
From 2edb0886507beef9b7f0c6935bafccc4e9a67136 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Fri, 1 Feb 2019 09:25:29 +0000
Subject: [PATCH 040/281] Update to latest Debian and opam
Reported by Honzoo.
---
Dockerfile | 6 +++---
README.md | 3 +++
build-with-docker.sh | 2 +-
client_eth.ml | 2 +-
client_net.ml | 2 +-
config.ml | 2 ++
firewall.ml | 8 ++++----
7 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index a6b1c52..6b277c2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,13 +1,13 @@
# Pin the base image to a specific hash for maximum reproducibility.
# It will probably still work on newer images, though, unless Debian
# changes some compiler optimisations (unlikely).
-#FROM ocaml/opam2:debian-9-ocaml-4.04
-FROM ocaml/opam2@sha256:feebac4b6f9df9ed52ca1fe7266335cb9fdfffbdc0f6ba4f5e8603ece7e8b096
+#FROM ocaml/opam2:debian-9-ocaml-4.07
+FROM ocaml/opam2@sha256:5ff7e5a1d4ab951dcc26cca7834fa57dce8bb08d1d27ba67a0e51071c2197599
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN git fetch origin && git reset --hard 1fa4c078f5b145bd4a455eb0a5559f761d0a94c0 && opam update
+RUN git fetch origin && git reset --hard 95448cbb9fad7515e104222f92b3d1e0bee70ede && opam update
RUN sudo apt-get install -y m4 libxen-dev pkg-config
RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage mirage-nat mirage-qubes
diff --git a/README.md b/README.md
index 02dc576..b63222a 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,9 @@ This took about 10 minutes on my laptop (it will be much quicker if you run it a
The symlink step at the start isn't needed if your build VM is standalone.
It gives Docker more disk space and avoids losing the Docker image cache when you reboot the Qube.
+Note: the object files are stored in the `_build` directory to speed up incremental builds.
+If you change the dependencies, you will need to delete this directory before rebuilding.
+
You can also build without Docker, as for any normal Mirage unikernel;
see [the Mirage installation instructions](https://mirage.io/wiki/install) for details.
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 7ba6fa6..8836e95 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 3605a97fbdb9e699a9ceb9e43def8a3cdd04e5cefb48b5824df8f55e7f949203"
+echo "SHA2 last known: 21bd3e48dbca42ea5327a4fc6e27f9fe1f35f97e65864fff64e7a7675191148c"
echo "(hashes should match for released versions)"
diff --git a/client_eth.ml b/client_eth.ml
index 751274b..e8e20c1 100644
--- a/client_eth.ml
+++ b/client_eth.ml
@@ -30,7 +30,7 @@ let add_client t iface =
if IpMap.mem ip t.iface_of_ip then (
(* Wait for old client to disappear before adding one with the same IP address.
Otherwise, its [remove_client] call will remove the new client instead. *)
- Log.info (fun f -> f "Waiting for old client %a to go away before accepting new one" Ipaddr.V4.pp_hum ip);
+ Log.info (fun f -> f "Waiting for old client %a to go away before accepting new one" Ipaddr.V4.pp ip);
Lwt_condition.wait t.changed >>= aux
) else (
t.iface_of_ip <- t.iface_of_ip |> IpMap.add ip iface;
diff --git a/client_net.ml b/client_net.ml
index 995b5f5..4b906e7 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -65,7 +65,7 @@ let input_ipv4 ~client_ip ~router packet =
if src = client_ip then Firewall.ipv4_from_client router packet
else (
Log.warn (fun f -> f "Incorrect source IP %a in IP packet from %a (dropping)"
- Ipaddr.V4.pp_hum src Ipaddr.V4.pp_hum client_ip);
+ Ipaddr.V4.pp src Ipaddr.V4.pp client_ip);
return ()
)
diff --git a/config.ml b/config.ml
index 3f112fb..c115c1b 100644
--- a/config.ml
+++ b/config.ml
@@ -20,10 +20,12 @@ let main =
~packages:[
package "vchan";
package "cstruct";
+ package "astring";
package "tcpip" ~sublibs:["stack-direct"; "xen"; "arpv4"] ~min:"3.1.0";
package "shared-memory-ring" ~min:"3.0.0";
package "netchannel" ~min:"1.8.0";
package "mirage-net-xen" ~min:"1.7.1";
+ package "ipaddr" ~min:"3.0.0";
package "mirage-qubes";
package "mirage-nat";
package "mirage-logs";
diff --git a/firewall.ml b/firewall.ml
index 337c5c8..98f5b21 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -18,7 +18,7 @@ let transmit_ipv4 packet iface =
(fun () -> iface#writev Ethif_wire.IPv4 transport)
(fun ex ->
Log.warn (fun f -> f "Failed to write packet to %a: %s"
- Ipaddr.V4.pp_hum iface#other_ip
+ Ipaddr.V4.pp iface#other_ip
(Printexc.to_string ex));
Lwt.return ()
)
@@ -58,10 +58,10 @@ let pp_ports fmt {sport; dport} =
Format.fprintf fmt "sport=%d dport=%d" sport dport
let pp_host fmt = function
- | `Client c -> Ipaddr.V4.pp_hum fmt (c#other_ip)
- | `Unknown_client ip -> Format.fprintf fmt "unknown-client(%a)" Ipaddr.pp_hum ip
+ | `Client c -> Ipaddr.V4.pp fmt (c#other_ip)
+ | `Unknown_client ip -> Format.fprintf fmt "unknown-client(%a)" Ipaddr.pp ip
| `NetVM -> Format.pp_print_string fmt "net-vm"
- | `External ip -> Format.fprintf fmt "external(%a)" Ipaddr.pp_hum ip
+ | `External ip -> Format.fprintf fmt "external(%a)" Ipaddr.pp ip
| `Firewall_uplink -> Format.pp_print_string fmt "firewall(uplink)"
| `Client_gateway -> Format.pp_print_string fmt "firewall(client-gw)"
From ab88d413c483ac05e72db9e18421c6244a1ea653 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Tue, 26 Feb 2019 16:57:40 +0000
Subject: [PATCH 041/281] Update links from talex5 to mirage
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index b63222a..cb084ad 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ Clone this Git repository and run the `build-with-docker.sh` script:
sudo ln -s /var/lib/docker /home/user/docker
sudo dnf install docker
sudo systemctl start docker
- git clone https://github.com/talex5/qubes-mirage-firewall.git
+ git clone https://github.com/mirage/qubes-mirage-firewall.git
cd qubes-mirage-firewall
sudo ./build-with-docker.sh
@@ -149,6 +149,6 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
gg
[test-mirage]: https://github.com/talex5/qubes-test-mirage
-[mirage-qubes]: https://github.com/talex5/mirage-qubes
+[mirage-qubes]: https://github.com/mirage/mirage-qubes
[A Unikernel Firewall for QubesOS]: http://roscidus.com/blog/blog/2016/01/01/a-unikernel-firewall-for-qubesos/
-[releases page]: https://github.com/talex5/qubes-mirage-firewall/releases
+[releases page]: https://github.com/mirage/qubes-mirage-firewall/releases
From 04bea6e9baf4f449252dd7d9730ab54301c70e14 Mon Sep 17 00:00:00 2001
From: xaki23
Date: Wed, 6 Mar 2019 23:43:49 +0100
Subject: [PATCH 042/281] update ocaml version (from 4.05 to 4.07), pin-down
mirage version (to 3.4, 3.5 is current)
---
Makefile.builder | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Makefile.builder b/Makefile.builder
index 7ad9df1..b41efd6 100644
--- a/Makefile.builder
+++ b/Makefile.builder
@@ -1,8 +1,8 @@
MIRAGE_KERNEL_NAME = qubes_firewall.xen
-#SOURCE_BUILD_DEP := ssh-agent-build-dep
-OCAML_VERSION ?= 4.05.0
+SOURCE_BUILD_DEP := mfw-build-dep
+OCAML_VERSION ?= 4.07.1
-#ssh-agent-build-dep:
-# opam pin -y add angstrom https://github.com/reynir/angstrom.git#no-c-blit
+mfw-build-dep:
+ opam pin -y add mirage 3.4.0
# opam pin -y add ssh-agent https://github.com/reynir/ocaml-ssh-agent.git
From d7cd4e29619432a0dff2cf83cc32b9a5e987c736 Mon Sep 17 00:00:00 2001
From: Mindy
Date: Sun, 17 Mar 2019 17:42:05 -0500
Subject: [PATCH 043/281] typo fix
---
frameQ.ml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frameQ.ml b/frameQ.ml
index b6b7ed1..390ac7a 100644
--- a/frameQ.ml
+++ b/frameQ.ml
@@ -16,7 +16,7 @@ let max_qlen = 10
let send q fn =
if q.items = max_qlen then (
- Log.warn (fun f -> f "Maximim queue length exceeded for %s: dropping frame" q.name);
+ Log.warn (fun f -> f "Maximum queue length exceeded for %s: dropping frame" q.name);
Lwt.return_unit
) else (
let sent = fn () in
From 0852aa0f437848128f7f9b7b4f2589b5f579317c Mon Sep 17 00:00:00 2001
From: Mindy
Date: Sun, 17 Mar 2019 16:32:17 -0500
Subject: [PATCH 044/281] use tcpip 3.7, ethernet, arp, mirage-nat 1.1.0
---
client_eth.ml | 36 +++++++++++++++++-------------------
client_eth.mli | 2 +-
client_net.ml | 37 ++++++++++++++++++-------------------
config.ml | 8 ++++++--
firewall.ml | 15 ++++++++++++---
fw_utils.ml | 4 ++--
uplink.ml | 15 +++++++--------
7 files changed, 63 insertions(+), 54 deletions(-)
diff --git a/client_eth.ml b/client_eth.ml
index e8e20c1..019e459 100644
--- a/client_eth.ml
+++ b/client_eth.ml
@@ -82,7 +82,7 @@ module ARP = struct
let create ~net client_link = {net; client_link}
let input_query t arp =
- let req_ipv4 = arp.Arpv4_packet.tpa in
+ let req_ipv4 = arp.Arp_packet.target_ip in
Log.info (fun f -> f "who-has %s?" (Ipaddr.V4.to_string req_ipv4));
if req_ipv4 = t.client_link#other_ip then (
Log.info (fun f -> f "ignoring request for client's own IP");
@@ -93,34 +93,32 @@ module ARP = struct
None
| Some req_mac ->
Log.info (fun f -> f "responding to: who-has %s?" (Ipaddr.V4.to_string req_ipv4));
- let req_spa = arp.Arpv4_packet.spa in
- let req_sha = arp.Arpv4_packet.sha in
- Some { Arpv4_packet.
- op = Arpv4_wire.Reply;
+ Some { Arp_packet.
+ operation = Arp_packet.Reply;
(* The Target Hardware Address and IP are copied from the request *)
- tha = req_sha;
- tpa = req_spa;
- sha = req_mac;
- spa = req_ipv4;
+ target_ip = arp.Arp_packet.source_ip;
+ target_mac = arp.Arp_packet.source_mac;
+ source_ip = req_ipv4;
+ source_mac = req_mac;
}
let input_gratuitous t arp =
- let spa = arp.Arpv4_packet.spa in
- let sha = arp.Arpv4_packet.sha in
- match lookup t spa with
- | Some real_mac when Macaddr.compare sha real_mac = 0 ->
+ let source_ip = arp.Arp_packet.source_ip in
+ let source_mac = arp.Arp_packet.source_mac in
+ match lookup t source_ip with
+ | Some real_mac when Macaddr.compare source_mac real_mac = 0 ->
Log.info (fun f -> f "client suggests updating %s -> %s (as expected)"
- (Ipaddr.V4.to_string spa) (Macaddr.to_string sha));
+ (Ipaddr.V4.to_string source_ip) (Macaddr.to_string source_mac));
| Some other_mac ->
Log.warn (fun f -> f "client suggests incorrect update %s -> %s (should be %s)"
- (Ipaddr.V4.to_string spa) (Macaddr.to_string sha) (Macaddr.to_string other_mac));
+ (Ipaddr.V4.to_string source_ip) (Macaddr.to_string source_mac) (Macaddr.to_string other_mac));
| None ->
Log.warn (fun f -> f "client suggests incorrect update %s -> %s (unexpected IP)"
- (Ipaddr.V4.to_string spa) (Macaddr.to_string sha))
+ (Ipaddr.V4.to_string source_ip) (Macaddr.to_string source_mac))
let input t arp =
- let op = arp.Arpv4_packet.op in
+ let op = arp.Arp_packet.operation in
match op with
- | Arpv4_wire.Request -> input_query t arp
- | Arpv4_wire.Reply -> input_gratuitous t arp; None
+ | Arp_packet.Request -> input_query t arp
+ | Arp_packet.Reply -> input_gratuitous t arp; None
end
diff --git a/client_eth.mli b/client_eth.mli
index 0851913..952e970 100644
--- a/client_eth.mli
+++ b/client_eth.mli
@@ -47,7 +47,7 @@ module ARP : sig
(** [create ~net client_link] is an ARP responder for [client_link].
It answers only for the client's gateway address. *)
- val input : arp -> Arpv4_packet.t -> Arpv4_packet.t option
+ val input : arp -> Arp_packet.t -> Arp_packet.t option
(** Process one ethernet frame containing an ARP message.
Returns a response frame, if one is needed. *)
end
diff --git a/client_net.ml b/client_net.ml
index 4b906e7..01a27f6 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -5,24 +5,24 @@ open Lwt.Infix
open Fw_utils
module Netback = Netchannel.Backend.Make(Netchannel.Xenstore.Make(OS.Xs))
-module ClientEth = Ethif.Make(Netback)
+module ClientEth = Ethernet.Make(Netback)
let src = Logs.Src.create "client_net" ~doc:"Client networking"
module Log = (val Logs.src_log src : Logs.LOG)
-let writev eth data =
+let writev eth dst proto fillfn =
Lwt.catch
(fun () ->
- ClientEth.writev eth data >|= function
+ ClientEth.write eth dst proto fillfn >|= function
| Ok () -> ()
| Error e ->
- Log.err (fun f -> f "error trying to send to client:@\n@[ %a@]@\nError: @[%a@]"
- Cstruct.hexdump_pp (Cstruct.concat data) ClientEth.pp_error e);
+ Log.err (fun f -> f "error trying to send to client: @[%a@]"
+ ClientEth.pp_error e);
)
(fun ex ->
(* Usually Netback_shutdown, because the client disconnected *)
- Log.err (fun f -> f "uncaught exception trying to send to client:@\n@[ %a@]@\nException: @[%s@]"
- Cstruct.hexdump_pp (Cstruct.concat data) (Printexc.to_string ex));
+ Log.err (fun f -> f "uncaught exception trying to send to client: @[%s@]"
+ (Printexc.to_string ex));
Lwt.return ()
)
@@ -32,10 +32,9 @@ class client_iface eth ~gateway_ip ~client_ip client_mac : client_link = object
method other_mac = client_mac
method my_ip = gateway_ip
method other_ip = client_ip
- method writev proto ip =
+ method writev proto fillfn =
FrameQ.send queue (fun () ->
- let eth_hdr = eth_header proto ~src:(ClientEth.mac eth) ~dst:client_mac in
- writev eth (eth_hdr :: ip)
+ writev eth client_mac proto fillfn
)
end
@@ -43,15 +42,15 @@ let clients : Cleanup.t Dao.VifMap.t ref = ref Dao.VifMap.empty
(** Handle an ARP message from the client. *)
let input_arp ~fixed_arp ~iface request =
- match Arpv4_packet.Unmarshal.of_cstruct request with
+ match Arp_packet.decode request with
| Error e ->
- Log.warn (fun f -> f "Ignored unknown ARP message: %a" Arpv4_packet.Unmarshal.pp_error e);
+ Log.warn (fun f -> f "Ignored unknown ARP message: %a" Arp_packet.pp_error e);
Lwt.return ()
| Ok arp ->
match Client_eth.ARP.input fixed_arp arp with
| None -> return ()
| Some response ->
- iface#writev Ethif_wire.ARP [Arpv4_packet.Marshal.make_cstruct response]
+ iface#writev `ARP (fun b -> Arp_packet.encode_into response b; Arp_packet.size)
(** Handle an IPv4 packet from the client. *)
let input_ipv4 ~client_ip ~router packet =
@@ -81,8 +80,8 @@ let add_vif { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks
Router.add_client router iface >>= fun () ->
Cleanup.on_cleanup cleanup_tasks (fun () -> Router.remove_client router iface);
let fixed_arp = Client_eth.ARP.create ~net:client_eth iface in
- Netback.listen backend (fun frame ->
- match Ethif_packet.Unmarshal.of_cstruct frame with
+ Netback.listen backend ~header_size:14 (fun frame ->
+ match Ethernet_packet.Unmarshal.of_cstruct frame with
| exception ex ->
Log.err (fun f -> f "Error unmarshalling ethernet frame from client: %s@.%a" (Printexc.to_string ex)
Cstruct.hexdump_pp frame
@@ -90,10 +89,10 @@ let add_vif { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks
Lwt.return_unit
| Error err -> Log.warn (fun f -> f "Invalid Ethernet frame: %s" err); return ()
| Ok (eth, payload) ->
- match eth.Ethif_packet.ethertype with
- | Ethif_wire.ARP -> input_arp ~fixed_arp ~iface payload
- | Ethif_wire.IPv4 -> input_ipv4 ~client_ip ~router payload
- | Ethif_wire.IPv6 -> return ()
+ match eth.Ethernet_packet.ethertype with
+ | `ARP -> input_arp ~fixed_arp ~iface payload
+ | `IPv4 -> input_ipv4 ~client_ip ~router payload
+ | `IPv6 -> return () (* TODO: oh no! *)
)
>|= or_raise "Listen on client interface" Netback.pp_error
diff --git a/config.ml b/config.ml
index c115c1b..d0f702a 100644
--- a/config.ml
+++ b/config.ml
@@ -21,13 +21,17 @@ let main =
package "vchan";
package "cstruct";
package "astring";
- package "tcpip" ~sublibs:["stack-direct"; "xen"; "arpv4"] ~min:"3.1.0";
+ package "tcpip" ~min:"3.7.0";
+ package "arp";
+ package "arp-mirage";
+ package "ethernet";
+ package "mirage-protocols";
package "shared-memory-ring" ~min:"3.0.0";
package "netchannel" ~min:"1.8.0";
package "mirage-net-xen" ~min:"1.7.1";
package "ipaddr" ~min:"3.0.0";
package "mirage-qubes";
- package "mirage-nat";
+ package "mirage-nat" ~min:"1.1.0";
package "mirage-logs";
]
"Unikernel.Main" (mclock @-> job)
diff --git a/firewall.ml b/firewall.ml
index 98f5b21..39254d3 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -13,9 +13,18 @@ module Log = (val Logs.src_log src : Logs.LOG)
let transmit_ipv4 packet iface =
Lwt.catch
(fun () ->
- let transport = Nat_packet.to_cstruct packet in
Lwt.catch
- (fun () -> iface#writev Ethif_wire.IPv4 transport)
+ (fun () ->
+ iface#writev `IPv4 (fun b ->
+ match Nat_packet.into_cstruct packet b with
+ | Error e ->
+ Log.warn (fun f -> f "Failed to write packet to %a: %a"
+ Ipaddr.V4.pp iface#other_ip
+ Nat_packet.pp_error e);
+ 0
+ | Ok n -> n
+ )
+ )
(fun ex ->
Log.warn (fun f -> f "Failed to write packet to %a: %s"
Ipaddr.V4.pp iface#other_ip
@@ -35,7 +44,7 @@ let forward_ipv4 t packet =
let `IPv4 (ip, _) = packet in
match Router.target t ip with
| Some iface -> transmit_ipv4 packet iface
- | None -> return ()
+ | None -> Lwt.return_unit
(* Packet classification *)
diff --git a/fw_utils.ml b/fw_utils.ml
index f4e63e8..65a769f 100644
--- a/fw_utils.ml
+++ b/fw_utils.ml
@@ -21,7 +21,7 @@ module IntMap = Map.Make(Int)
(** An Ethernet interface. *)
class type interface = object
method my_mac : Macaddr.t
- method writev : Ethif_wire.ethertype -> Cstruct.t list -> unit Lwt.t
+ method writev : Mirage_protocols.Ethernet.proto -> (Cstruct.t -> int) -> unit Lwt.t
method my_ip : Ipaddr.V4.t
method other_ip : Ipaddr.V4.t
end
@@ -34,7 +34,7 @@ end
(** An Ethernet header from [src]'s MAC address to [dst]'s with an IPv4 payload. *)
let eth_header ethertype ~src ~dst =
- Ethif_packet.Marshal.make_cstruct { Ethif_packet.source = src; destination = dst; ethertype }
+ Ethernet_packet.Marshal.make_cstruct { Ethernet_packet.source = src; destination = dst; ethertype }
let error fmt =
let err s = Failure s in
diff --git a/uplink.ml b/uplink.ml
index 5735418..7579292 100644
--- a/uplink.ml
+++ b/uplink.ml
@@ -4,13 +4,13 @@
open Lwt.Infix
open Fw_utils
-module Eth = Ethif.Make(Netif)
+module Eth = Ethernet.Make(Netif)
let src = Logs.Src.create "uplink" ~doc:"Network connection to NetVM"
module Log = (val Logs.src_log src : Logs.LOG)
module Make(Clock : Mirage_clock_lwt.MCLOCK) = struct
- module Arp = Arpv4.Make(Eth)(Clock)(OS.Time)
+ module Arp = Arp.Make(Eth)(OS.Time)
type t = {
net : Netif.t;
@@ -24,16 +24,15 @@ module Make(Clock : Mirage_clock_lwt.MCLOCK) = struct
method my_mac = Eth.mac eth
method my_ip = my_ip
method other_ip = other_ip
- method writev ethertype payload =
+ method writev ethertype fillfn =
FrameQ.send queue (fun () ->
mac >>= fun dst ->
- let eth_hdr = eth_header ethertype ~src:(Eth.mac eth) ~dst in
- Eth.writev eth (eth_hdr :: payload) >|= or_raise "Write to uplink" Eth.pp_error
+ Eth.write eth dst ethertype fillfn >|= or_raise "Write to uplink" Eth.pp_error
)
end
let listen t router =
- Netif.listen t.net (fun frame ->
+ Netif.listen t.net ~header_size:14 (fun frame ->
(* Handle one Ethernet frame from NetVM *)
Eth.input t.eth
~arpv4:(Arp.input t.arp)
@@ -56,11 +55,11 @@ module Make(Clock : Mirage_clock_lwt.MCLOCK) = struct
let interface t = t.interface
- let connect ~clock config =
+ let connect ~clock:_ config =
let ip = config.Dao.uplink_our_ip in
Netif.connect "0" >>= fun net ->
Eth.connect net >>= fun eth ->
- Arp.connect eth clock >>= fun arp ->
+ Arp.connect eth >>= fun arp ->
Arp.add_ip arp ip >>= fun () ->
let netvm_mac =
Arp.query arp config.Dao.uplink_netvm_ip
From 7f99973a02b1799efae05cb2385208ee68544683 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sun, 24 Mar 2019 13:13:11 +0000
Subject: [PATCH 045/281] Update Docker build for Mirage 3.5
---
Dockerfile | 4 ++--
build-with-docker.sh | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 6b277c2..e8c8c74 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,12 +2,12 @@
# It will probably still work on newer images, though, unless Debian
# changes some compiler optimisations (unlikely).
#FROM ocaml/opam2:debian-9-ocaml-4.07
-FROM ocaml/opam2@sha256:5ff7e5a1d4ab951dcc26cca7834fa57dce8bb08d1d27ba67a0e51071c2197599
+FROM ocaml/opam2@sha256:f7125924dd6632099ff98b2505536fe5f5c36bf0beb24779431bb62be5748562
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN git fetch origin && git reset --hard 95448cbb9fad7515e104222f92b3d1e0bee70ede && opam update
+RUN git fetch origin && git reset --hard 55e835f197d5a6961ff9b22eb5bbcb5a17f13e65 && opam update
RUN sudo apt-get install -y m4 libxen-dev pkg-config
RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage mirage-nat mirage-qubes
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 8836e95..2f895e6 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 21bd3e48dbca42ea5327a4fc6e27f9fe1f35f97e65864fff64e7a7675191148c"
+echo "SHA2 last known: addeb78681d73ee44df328ca059f6f15b8b7bbdff38a3de5363229cdf3da2eda"
echo "(hashes should match for released versions)"
From 3553a7aa93e8341d19d9d46206ff53e286a5439f Mon Sep 17 00:00:00 2001
From: Hannes Mehnert
Date: Sun, 24 Mar 2019 14:29:21 +0100
Subject: [PATCH 046/281] use Ethernet_wire.sizeof_ethernet instead of a magic
'14'
---
client_net.ml | 2 +-
uplink.ml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/client_net.ml b/client_net.ml
index 01a27f6..95b51c4 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -80,7 +80,7 @@ let add_vif { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks
Router.add_client router iface >>= fun () ->
Cleanup.on_cleanup cleanup_tasks (fun () -> Router.remove_client router iface);
let fixed_arp = Client_eth.ARP.create ~net:client_eth iface in
- Netback.listen backend ~header_size:14 (fun frame ->
+ Netback.listen backend ~header_size:Ethernet_wire.sizeof_ethernet (fun frame ->
match Ethernet_packet.Unmarshal.of_cstruct frame with
| exception ex ->
Log.err (fun f -> f "Error unmarshalling ethernet frame from client: %s@.%a" (Printexc.to_string ex)
diff --git a/uplink.ml b/uplink.ml
index 7579292..06d4df3 100644
--- a/uplink.ml
+++ b/uplink.ml
@@ -32,7 +32,7 @@ module Make(Clock : Mirage_clock_lwt.MCLOCK) = struct
end
let listen t router =
- Netif.listen t.net ~header_size:14 (fun frame ->
+ Netif.listen t.net ~header_size:Ethernet_wire.sizeof_ethernet (fun frame ->
(* Handle one Ethernet frame from NetVM *)
Eth.input t.eth
~arpv4:(Arp.input t.arp)
From cb7078633e98113d2e09e7e063ca091860e9cc00 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Wed, 3 Apr 2019 12:32:13 +0100
Subject: [PATCH 047/281] Update dependencies
Remove pin on mirage 3.4 - it should now be working with the latest
release.
---
Dockerfile | 2 +-
Makefile.builder | 6 ------
build-with-docker.sh | 2 +-
config.ml | 6 +++---
4 files changed, 5 insertions(+), 11 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index e8c8c74..72e2516 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,7 +7,7 @@ FROM ocaml/opam2@sha256:f7125924dd6632099ff98b2505536fe5f5c36bf0beb24779431bb62b
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN git fetch origin && git reset --hard 55e835f197d5a6961ff9b22eb5bbcb5a17f13e65 && opam update
+RUN git fetch origin && git reset --hard c261c4ee9c1ef032af93483913b60f674d4acdb2 && opam update
RUN sudo apt-get install -y m4 libxen-dev pkg-config
RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage mirage-nat mirage-qubes
diff --git a/Makefile.builder b/Makefile.builder
index b41efd6..098463d 100644
--- a/Makefile.builder
+++ b/Makefile.builder
@@ -1,8 +1,2 @@
MIRAGE_KERNEL_NAME = qubes_firewall.xen
-SOURCE_BUILD_DEP := mfw-build-dep
OCAML_VERSION ?= 4.07.1
-
-mfw-build-dep:
- opam pin -y add mirage 3.4.0
-# opam pin -y add ssh-agent https://github.com/reynir/ocaml-ssh-agent.git
-
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 2f895e6..2570b28 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: addeb78681d73ee44df328ca059f6f15b8b7bbdff38a3de5363229cdf3da2eda"
+echo "SHA2 last known: 1f72adad30cbd4f8315983240bd150811084cb93d360c14740fadb36394c7aa8"
echo "(hashes should match for released versions)"
diff --git a/config.ml b/config.ml
index d0f702a..f7d5169 100644
--- a/config.ml
+++ b/config.ml
@@ -18,7 +18,7 @@ let main =
foreign
~keys:[Functoria_key.abstract table_size]
~packages:[
- package "vchan";
+ package "vchan" ~min:"4.0.2";
package "cstruct";
package "astring";
package "tcpip" ~min:"3.7.0";
@@ -27,8 +27,8 @@ let main =
package "ethernet";
package "mirage-protocols";
package "shared-memory-ring" ~min:"3.0.0";
- package "netchannel" ~min:"1.8.0";
- package "mirage-net-xen" ~min:"1.7.1";
+ package "netchannel" ~min:"1.10.2";
+ package "mirage-net-xen";
package "ipaddr" ~min:"3.0.0";
package "mirage-qubes";
package "mirage-nat" ~min:"1.1.0";
From bd7babeda0d5ff507f4e3226b0a0cba05b5a1847 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Thu, 4 Apr 2019 11:04:09 +0100
Subject: [PATCH 048/281] Remove Qubes 3 instructions from README
See https://www.qubes-os.org/news/2019/03/28/qubes-3-2-has-reached-eol/
---
README.md | 18 ++----------------
1 file changed, 2 insertions(+), 16 deletions(-)
diff --git a/README.md b/README.md
index cb084ad..3c3195b 100644
--- a/README.md
+++ b/README.md
@@ -42,22 +42,6 @@ If you want to deploy manually, unpack `mirage-firewall.tar.bz2` in dom0, inside
The tarball contains `vmlinuz`, which is the unikernel itself, plus a couple of dummy files that Qubes requires.
-### Qubes 3
-
-To configure your new firewall using the Qubes 3 Manager GUI:
-
-- Create a new ProxyVM named `mirage-firewall` to run the unikernel.
-- You can use any template, and make it standalone or not. It doesn’t matter, since we don’t use the hard disk.
-- Set the type to `ProxyVM`.
-- Select `sys-net` for networking (not `sys-firewall`).
-- Click `OK` to create the VM.
-- Go to the VM settings, and look in the `Advanced` tab:
- - Set the kernel to `mirage-firewall`.
- - Turn off memory balancing and set the memory to 32 MB or so (you might have to fight a bit with the Qubes GUI to get it this low).
- - Set VCPUs (number of virtual CPUs) to 1.
-
-### Qubes 4
-
Run this command in dom0 to create a `mirage-firewall` VM using the `mirage-firewall` kernel you added above:
```
@@ -75,6 +59,8 @@ qvm-create \
mirage-firewall
```
+To upgrade from an earlier release, just overwrite `/var/lib/qubes/vm-kernels/mirage-firewall/vmlinuz` with the new version and restart the firewall VM.
+
### Configure AppVMs to use it
You can run `mirage-firewall` alongside your existing `sys-firewall` and you can choose which AppVMs use which firewall using the GUI.
From 74479c792ee29fa6a4dc459825f513625f096616 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Fri, 5 Apr 2019 09:37:02 +0100
Subject: [PATCH 049/281] Use source date in .tar.bz2 archive
All files are now added using the date the build-with-docker script was
last changed. Since this includes the hash of the result, it should be
up-to-date. This ensures that rebuilding the archive doesn't change it
in any way.
Reported-by: Holger Levsen
---
Makefile.user | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Makefile.user b/Makefile.user
index 33335e6..da810cd 100644
--- a/Makefile.user
+++ b/Makefile.user
@@ -3,5 +3,5 @@ tar: build
mkdir _build/mirage-firewall
cp qubes_firewall.xen _build/mirage-firewall/vmlinuz
touch _build/mirage-firewall/modules.img
- cat /dev/null | gzip > _build/mirage-firewall/initramfs
- tar cjf mirage-firewall.tar.bz2 -C _build mirage-firewall
+ cat /dev/null | gzip -n > _build/mirage-firewall/initramfs
+ tar cjf mirage-firewall.tar.bz2 -C _build --mtime=./build-with-docker.sh mirage-firewall
From 06511e076f8114fec17f36cc4aa849715d121792 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Mon, 8 Apr 2019 10:34:30 +0100
Subject: [PATCH 050/281] Add patch to cmdliner for reproducible build
See https://github.com/dbuenzli/cmdliner/pull/106
---
Dockerfile | 1 +
build-with-docker.sh | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/Dockerfile b/Dockerfile
index 72e2516..4558a7e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,6 +10,7 @@ FROM ocaml/opam2@sha256:f7125924dd6632099ff98b2505536fe5f5c36bf0beb24779431bb62b
RUN git fetch origin && git reset --hard c261c4ee9c1ef032af93483913b60f674d4acdb2 && opam update
RUN sudo apt-get install -y m4 libxen-dev pkg-config
+RUN opam pin add -yn cmdliner 'https://github.com/talex5/cmdliner.git#repro-builds'
RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage mirage-nat mirage-qubes
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 2570b28..3f6c59c 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 1f72adad30cbd4f8315983240bd150811084cb93d360c14740fadb36394c7aa8"
+echo "SHA2 last known: ce9a16b6f5ce0123f289b3586492f9f4b921f6e788f8e333784545807bb1b0f2"
echo "(hashes should match for released versions)"
From 5958cfed97dc33669db640279fb41c22580a1662 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Mon, 8 Apr 2019 10:23:34 +0100
Subject: [PATCH 051/281] Clarify how to build from source
---
README.md | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 3c3195b..3dc4d72 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@ Pre-built binaries are available from the [releases page][].
## Build from source
+Create a new Fedora-29 AppVM (or reuse an existing one). Open a terminal.
Clone this Git repository and run the `build-with-docker.sh` script:
sudo ln -s /var/lib/docker /home/user/docker
@@ -30,9 +31,19 @@ It gives Docker more disk space and avoids losing the Docker image cache when yo
Note: the object files are stored in the `_build` directory to speed up incremental builds.
If you change the dependencies, you will need to delete this directory before rebuilding.
+If you want to build on Debian, follow the instructions at [docker.com][debian-docker] to get Docker and then run `sudo ./build-with-docker.sh` as above.
+
+It's OK to install the Docker package in a template VM if you want it to remain
+after a reboot, but the build of the firewall itself should be done in a regular AppVM.
+
You can also build without Docker, as for any normal Mirage unikernel;
see [the Mirage installation instructions](https://mirage.io/wiki/install) for details.
+The Docker build fixes the versions of the libraries it uses, ensuring that you will get
+exactly the same binary that is in the release. If you build without Docker, it will build
+against the latest versions instead (and the hash will therefore probably not match).
+However, it should still work fine.
+
## Deploy
If you want to deploy manually, unpack `mirage-firewall.tar.bz2` in dom0, inside `/var/lib/qubes/vm-kernels/`. e.g. (if `dev` is the AppVM where you built it):
@@ -122,7 +133,7 @@ This takes a little more setting up the first time, but will be much quicker aft
# LICENSE
-Copyright (c) 2018, Thomas Leonard
+Copyright (c) 2019, Thomas Leonard
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
@@ -138,3 +149,4 @@ gg
[mirage-qubes]: https://github.com/mirage/mirage-qubes
[A Unikernel Firewall for QubesOS]: http://roscidus.com/blog/blog/2016/01/01/a-unikernel-firewall-for-qubesos/
[releases page]: https://github.com/mirage/qubes-mirage-firewall/releases
+[debian-docker]: https://docs.docker.com/install/linux/docker-ce/debian/#install-using-the-repository
From 45eef49c95048d5112257b8056c780f97ca58eb5 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Tue, 16 Apr 2019 18:05:08 +0100
Subject: [PATCH 052/281] Upgrade to latest mirage-nat to fix ICMP
Now ping and traceroute should work.
---
Dockerfile | 4 ++--
build-with-docker.sh | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 4558a7e..b2abb28 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,11 +7,11 @@ FROM ocaml/opam2@sha256:f7125924dd6632099ff98b2505536fe5f5c36bf0beb24779431bb62b
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN git fetch origin && git reset --hard c261c4ee9c1ef032af93483913b60f674d4acdb2 && opam update
+RUN git fetch origin && git reset --hard e77756e92274790668ed1f6f998d66fa2e744fb6 && opam update
RUN sudo apt-get install -y m4 libxen-dev pkg-config
RUN opam pin add -yn cmdliner 'https://github.com/talex5/cmdliner.git#repro-builds'
-RUN opam install -y vchan xen-gnt mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage mirage-nat mirage-qubes
+RUN opam install -y vchan mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage mirage-nat mirage-qubes
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
WORKDIR /home/opam/qubes-mirage-firewall
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 3f6c59c..1389a8d 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: ce9a16b6f5ce0123f289b3586492f9f4b921f6e788f8e333784545807bb1b0f2"
+echo "SHA2 last known: 765cf16c2e85feb7e5dfd3e409a3013c91c2b07f5680ed9f4e487e27213f1355"
echo "(hashes should match for released versions)"
From eb14f7e777ca56fa7d5f42c502a7e2c9987fd579 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Fri, 26 Apr 2019 12:38:36 +0100
Subject: [PATCH 053/281] Link to security advisories from README
Also, link from binary installation to deployment section.
---
README.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/README.md b/README.md
index 3dc4d72..bfbef5f 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ See [A Unikernel Firewall for QubesOS][] for more details.
## Binary releases
Pre-built binaries are available from the [releases page][].
+See the [Deploy](#deploy) section below for installation instructions.
## Build from source
@@ -130,6 +131,9 @@ This takes a little more setting up the first time, but will be much quicker aft
2017-03-18 11:32:38 -00:00: INF [dao] Watching backend/vif
2017-03-18 11:32:38 -00:00: INF [qubes.db] got update: "/qubes-netvm-domid" = "1"
+# Security advisories
+
+See [issues tagged "security"](https://github.com/mirage/qubes-mirage-firewall/issues?utf8=%E2%9C%93&q=label%3Asecurity+) for security advisories affecting the firewall.
# LICENSE
From c7fc54af02621284489069ad91fd648f12cefdec Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sun, 28 Apr 2019 16:06:03 +0100
Subject: [PATCH 054/281] Wait if dom0 is slow to set the network configuration
Sometimes we boot before dom0 has put the network settings in QubesDB.
If that happens, log a message, wait until the database changes, and
retry.
---
dao.ml | 24 +++++++++++++++++++++---
dao.mli | 4 +++-
unikernel.ml | 2 +-
3 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/dao.ml b/dao.ml
index 9ce0766..a68cc64 100644
--- a/dao.ml
+++ b/dao.ml
@@ -84,15 +84,33 @@ type network_config = {
clients_our_ip : Ipaddr.V4.t; (* The IP address of our interface to our client VMs (their gateway) *)
}
+exception Missing_key of string
+
(* TODO: /qubes-secondary-dns *)
-let read_network_config qubesDB =
+let try_read_network_config db =
let get name =
- match DB.read qubesDB name with
- | None -> raise (error "QubesDB key %S not present" name)
+ match DB.KeyMap.find_opt name db with
+ | None -> raise (Missing_key name)
| Some value -> value in
let uplink_our_ip = get "/qubes-ip" |> Ipaddr.V4.of_string_exn in
let uplink_netvm_ip = get "/qubes-gateway" |> Ipaddr.V4.of_string_exn in
let clients_our_ip = get "/qubes-netvm-gateway" |> Ipaddr.V4.of_string_exn in
+ Log.info (fun f -> f "@[Got network configuration from QubesDB:@,\
+ NetVM IP on uplink network: %a@,\
+ Our IP on uplink network: %a@,\
+ Our IP on client networks: %a@]"
+ Ipaddr.V4.pp uplink_netvm_ip
+ Ipaddr.V4.pp uplink_our_ip
+ Ipaddr.V4.pp clients_our_ip);
{ uplink_netvm_ip; uplink_our_ip; clients_our_ip }
+let read_network_config qubesDB =
+ let rec aux bindings =
+ try Lwt.return (try_read_network_config bindings)
+ with Missing_key key ->
+ Log.warn (fun f -> f "QubesDB key %S not (yet) present; waiting for QubesDB to change..." key);
+ DB.after qubesDB bindings >>= aux
+ in
+ aux (DB.bindings qubesDB)
+
let set_iptables_error db = Qubes.DB.write db "/qubes-iptables-error"
diff --git a/dao.mli b/dao.mli
index e1b96c6..b1f56b6 100644
--- a/dao.mli
+++ b/dao.mli
@@ -26,6 +26,8 @@ type network_config = {
clients_our_ip : Ipaddr.V4.t; (* The IP address of our interface to our client VMs (their gateway) *)
}
-val read_network_config : Qubes.DB.t -> network_config
+val read_network_config : Qubes.DB.t -> network_config Lwt.t
+(** [read_network_config db] fetches the configuration from QubesDB.
+ If it isn't there yet, it waits until it is. *)
val set_iptables_error : Qubes.DB.t -> string -> unit Lwt.t
diff --git a/unikernel.ml b/unikernel.ml
index 4a63403..84cac6d 100644
--- a/unikernel.ml
+++ b/unikernel.ml
@@ -13,7 +13,7 @@ module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
(* Set up networking and listen for incoming packets. *)
let network ~clock nat qubesDB =
(* Read configuration from QubesDB *)
- let config = Dao.read_network_config qubesDB in
+ Dao.read_network_config qubesDB >>= fun config ->
(* Initialise connection to NetVM *)
Uplink.connect ~clock config >>= fun uplink ->
(* Report success *)
From 9d2723a08ad0cfef3dd081232491ea7cc49cf11d Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sun, 28 Apr 2019 16:10:02 +0100
Subject: [PATCH 055/281] Require mirage-nat >= 1.2.0 for ICMP support
---
config.ml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config.ml b/config.ml
index f7d5169..50de8ab 100644
--- a/config.ml
+++ b/config.ml
@@ -31,7 +31,7 @@ let main =
package "mirage-net-xen";
package "ipaddr" ~min:"3.0.0";
package "mirage-qubes";
- package "mirage-nat" ~min:"1.1.0";
+ package "mirage-nat" ~min:"1.2.0";
package "mirage-logs";
]
"Unikernel.Main" (mclock @-> job)
From 0a4dd7413ca52bcf942ca2806734530fcb366a3d Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Wed, 1 May 2019 10:05:14 +0100
Subject: [PATCH 056/281] Force backend MAC to fe:ff:ff:ff:ff:ff to fix HVM
clients
Xen appears to configure the same MAC address for both the frontend
and backend in XenStore. e.g.
[tal@dom0 ~]$ xenstore-ls /local/domain/3/backend/vif/19/0
frontend = "/local/domain/19/device/vif/0"
mac = "00:16:3e:5e:6c:00"
[...]
[tal@dom0 ~]$ xenstore-ls /local/domain/19/device/vif/0
mac = "00:16:3e:5e:6c:00"
This works if the client uses just a simple ethernet device, but fails
if it connects via a bridge. HVM domains have an associated stub domain
running qemu, which provides an emulated network device. The stub domain
uses a bridge to connect qemu's interface with eth0, and this didn't
work.
Force the use of the fixed version of mirage-net-xen, which no longer
uses XenStore to get the backend MAC, and provides a new function to get
the frontend one.
---
Dockerfile | 2 +-
client_net.ml | 2 +-
config.ml | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index b2abb28..1cbe558 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,7 +7,7 @@ FROM ocaml/opam2@sha256:f7125924dd6632099ff98b2505536fe5f5c36bf0beb24779431bb62b
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN git fetch origin && git reset --hard e77756e92274790668ed1f6f998d66fa2e744fb6 && opam update
+RUN git fetch origin && git reset --hard d1b2a1cbc28d43926b37e61f46fc403b48ab9c23 && opam update
RUN sudo apt-get install -y m4 libxen-dev pkg-config
RUN opam pin add -yn cmdliner 'https://github.com/talex5/cmdliner.git#repro-builds'
diff --git a/client_net.ml b/client_net.ml
index 95b51c4..636198a 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -73,7 +73,7 @@ let add_vif { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks
Netback.make ~domid ~device_id >>= fun backend ->
Log.info (fun f -> f "Client %d (IP: %s) ready" domid (Ipaddr.V4.to_string client_ip));
ClientEth.connect backend >>= fun eth ->
- let client_mac = Netback.mac backend in
+ let client_mac = Netback.frontend_mac backend in
let client_eth = router.Router.client_eth in
let gateway_ip = Client_eth.client_gw client_eth in
let iface = new client_iface eth ~gateway_ip ~client_ip client_mac in
diff --git a/config.ml b/config.ml
index 50de8ab..4171927 100644
--- a/config.ml
+++ b/config.ml
@@ -27,7 +27,7 @@ let main =
package "ethernet";
package "mirage-protocols";
package "shared-memory-ring" ~min:"3.0.0";
- package "netchannel" ~min:"1.10.2";
+ package "netchannel" ~min:"1.11.0" ~pin:"git+https://github.com/mirage/mirage-net-xen.git";
package "mirage-net-xen";
package "ipaddr" ~min:"3.0.0";
package "mirage-qubes";
From 8b4cc6f5a9e896491c35a2eebe5f6677d4e39875 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Mon, 6 May 2019 09:54:35 +0100
Subject: [PATCH 057/281] Improve logging
---
client_eth.ml | 25 ++++++++++++++-----------
client_net.ml | 29 ++++++++++++++++-------------
fw_utils.ml | 1 +
3 files changed, 31 insertions(+), 24 deletions(-)
diff --git a/client_eth.ml b/client_eth.ml
index 019e459..345552a 100644
--- a/client_eth.ml
+++ b/client_eth.ml
@@ -27,16 +27,16 @@ let client_gw t = t.client_gw
let add_client t iface =
let ip = iface#other_ip in
let rec aux () =
- if IpMap.mem ip t.iface_of_ip then (
+ match IpMap.find ip t.iface_of_ip with
+ | Some old ->
(* Wait for old client to disappear before adding one with the same IP address.
Otherwise, its [remove_client] call will remove the new client instead. *)
- Log.info (fun f -> f "Waiting for old client %a to go away before accepting new one" Ipaddr.V4.pp ip);
+ Log.info (fun f -> f ~header:iface#log_header "Waiting for old client %s to go away before accepting new one" old#log_header);
Lwt_condition.wait t.changed >>= aux
- ) else (
+ | None ->
t.iface_of_ip <- t.iface_of_ip |> IpMap.add ip iface;
Lwt_condition.broadcast t.changed ();
Lwt.return_unit
- )
in
aux ()
@@ -83,16 +83,18 @@ module ARP = struct
let input_query t arp =
let req_ipv4 = arp.Arp_packet.target_ip in
- Log.info (fun f -> f "who-has %s?" (Ipaddr.V4.to_string req_ipv4));
+ let pf (f : ?header:string -> ?tags:_ -> _) fmt =
+ f ~header:t.client_link#log_header ("who-has %a? " ^^ fmt) Ipaddr.V4.pp req_ipv4
+ in
if req_ipv4 = t.client_link#other_ip then (
- Log.info (fun f -> f "ignoring request for client's own IP");
+ Log.info (fun f -> pf f "ignoring request for client's own IP");
None
) else match lookup t req_ipv4 with
| None ->
- Log.info (fun f -> f "unknown address; not responding");
+ Log.info (fun f -> pf f "unknown address; not responding");
None
| Some req_mac ->
- Log.info (fun f -> f "responding to: who-has %s?" (Ipaddr.V4.to_string req_ipv4));
+ Log.info (fun f -> pf f "responding with %a" Macaddr.pp req_mac);
Some { Arp_packet.
operation = Arp_packet.Reply;
(* The Target Hardware Address and IP are copied from the request *)
@@ -105,15 +107,16 @@ module ARP = struct
let input_gratuitous t arp =
let source_ip = arp.Arp_packet.source_ip in
let source_mac = arp.Arp_packet.source_mac in
+ let header = t.client_link#log_header in
match lookup t source_ip with
| Some real_mac when Macaddr.compare source_mac real_mac = 0 ->
- Log.info (fun f -> f "client suggests updating %s -> %s (as expected)"
+ Log.info (fun f -> f ~header "client suggests updating %s -> %s (as expected)"
(Ipaddr.V4.to_string source_ip) (Macaddr.to_string source_mac));
| Some other_mac ->
- Log.warn (fun f -> f "client suggests incorrect update %s -> %s (should be %s)"
+ Log.warn (fun f -> f ~header "client suggests incorrect update %s -> %s (should be %s)"
(Ipaddr.V4.to_string source_ip) (Macaddr.to_string source_mac) (Macaddr.to_string other_mac));
| None ->
- Log.warn (fun f -> f "client suggests incorrect update %s -> %s (unexpected IP)"
+ Log.warn (fun f -> f ~header "client suggests incorrect update %s -> %s (unexpected IP)"
(Ipaddr.V4.to_string source_ip) (Macaddr.to_string source_mac))
let input t arp =
diff --git a/client_net.ml b/client_net.ml
index 636198a..0649567 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -26,17 +26,20 @@ let writev eth dst proto fillfn =
Lwt.return ()
)
-class client_iface eth ~gateway_ip ~client_ip client_mac : client_link = object
- val queue = FrameQ.create (Ipaddr.V4.to_string client_ip)
- method my_mac = ClientEth.mac eth
- method other_mac = client_mac
- method my_ip = gateway_ip
- method other_ip = client_ip
- method writev proto fillfn =
- FrameQ.send queue (fun () ->
- writev eth client_mac proto fillfn
- )
-end
+class client_iface eth ~domid ~gateway_ip ~client_ip client_mac : client_link =
+ let log_header = Fmt.strf "dom%d:%a" domid Ipaddr.V4.pp client_ip in
+ object
+ val queue = FrameQ.create (Ipaddr.V4.to_string client_ip)
+ method my_mac = ClientEth.mac eth
+ method other_mac = client_mac
+ method my_ip = gateway_ip
+ method other_ip = client_ip
+ method writev proto fillfn =
+ FrameQ.send queue (fun () ->
+ writev eth client_mac proto fillfn
+ )
+ method log_header = log_header
+ end
let clients : Cleanup.t Dao.VifMap.t ref = ref Dao.VifMap.empty
@@ -76,7 +79,7 @@ let add_vif { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks
let client_mac = Netback.frontend_mac backend in
let client_eth = router.Router.client_eth in
let gateway_ip = Client_eth.client_gw client_eth in
- let iface = new client_iface eth ~gateway_ip ~client_ip client_mac in
+ let iface = new client_iface eth ~domid ~gateway_ip ~client_ip client_mac in
Router.add_client router iface >>= fun () ->
Cleanup.on_cleanup cleanup_tasks (fun () -> Router.remove_client router iface);
let fixed_arp = Client_eth.ARP.create ~net:client_eth iface in
@@ -99,7 +102,7 @@ let add_vif { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks
(** A new client VM has been found in XenStore. Find its interface and connect to it. *)
let add_client ~router vif client_ip =
let cleanup_tasks = Cleanup.create () in
- Log.info (fun f -> f "add client vif %a" Dao.ClientVif.pp vif);
+ Log.info (fun f -> f "add client vif %a with IP %a" Dao.ClientVif.pp vif Ipaddr.V4.pp client_ip);
Lwt.async (fun () ->
Lwt.catch (fun () ->
add_vif vif ~client_ip ~router ~cleanup_tasks
diff --git a/fw_utils.ml b/fw_utils.ml
index 65a769f..c034e72 100644
--- a/fw_utils.ml
+++ b/fw_utils.ml
@@ -30,6 +30,7 @@ end
class type client_link = object
inherit interface
method other_mac : Macaddr.t
+ method log_header : string (* For log messages *)
end
(** An Ethernet header from [src]'s MAC address to [dst]'s with an IPv4 payload. *)
From d7b376d3730bc9dae82a359906ab4f8fb44a5d59 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sun, 5 May 2019 17:26:56 +0100
Subject: [PATCH 058/281] Respond to ARP requests for *.*.*.1
This is a work-around to get DHCP working with HVM domains.
See: https://github.com/QubesOS/qubes-issues/issues/5022
---
build-with-docker.sh | 2 +-
client_eth.ml | 6 +++++-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 1389a8d..d14c057 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 765cf16c2e85feb7e5dfd3e409a3013c91c2b07f5680ed9f4e487e27213f1355"
+echo "SHA2 last known: dbf7460fa628bea5d132a96fe7ba2cd832e3d9da7005ae74f6a124957f4848ea"
echo "(hashes should match for released versions)"
diff --git a/client_eth.ml b/client_eth.ml
index 345552a..a65325c 100644
--- a/client_eth.ml
+++ b/client_eth.ml
@@ -70,7 +70,11 @@ module ARP = struct
let lookup t ip =
if ip = t.net.client_gw then Some t.client_link#my_mac
- else None
+ else if (Ipaddr.V4.to_bytes ip).[3] = '\x01' then (
+ Log.info (fun f -> f ~header:t.client_link#log_header
+ "Request for %a is invalid, but pretending it's me (see Qubes issue #5022)" Ipaddr.V4.pp ip);
+ Some t.client_link#my_mac
+ ) else None
(* We're now treating client networks as point-to-point links,
so we no longer respond on behalf of other clients. *)
(*
From acf46b423185a5faad5c95700bf17a24ca127358 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Thu, 11 Apr 2019 12:25:19 +0100
Subject: [PATCH 059/281] Allow naming hosts and add examples to rules.ml
Previously we passed in the interface, from which it was possible (but
a little difficult) to extract the IP address and compare with some
predefined ones. Now, we allow the user to list IP addresses and named
tags for them, which can be matched on easily.
Added example rules showing how to block access to an external service
or allow SSH between AppVMs.
Requested at
https://groups.google.com/d/msg/qubes-users/BnL0nZGpJOE/61HOBg1rCgAJ.
---
firewall.ml | 14 +++++++++++++-
packet.ml | 12 +++++++++---
rules.ml | 26 +++++++++++++++++++++++++-
3 files changed, 47 insertions(+), 5 deletions(-)
diff --git a/firewall.ml b/firewall.ml
index 39254d3..0e38d45 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -125,9 +125,21 @@ let nat_to t ~host ~port packet =
(* Handle incoming packets *)
+let parse_ips ips = List.map (fun (ip_str, id) -> (Ipaddr.of_string_exn ip_str, id)) ips
+
+let clients = parse_ips Rules.clients
+let externals = parse_ips Rules.externals
+
+let resolve_host = function
+ | `Client c -> `Client (try List.assoc (Ipaddr.V4 c#other_ip) clients with Not_found -> `Unknown)
+ | `External ip -> `External (try List.assoc ip externals with Not_found -> `Unknown)
+ | (`Client_gateway | `Firewall_uplink | `NetVM) as x -> x
+
let apply_rules t rules info =
let packet = info.packet in
- match rules info, info.dst with
+ let resolved_info = { info with src = resolve_host info.src;
+ dst = resolve_host info.dst } in
+ match rules resolved_info, info.dst with
| `Accept, `Client client_link -> transmit_ipv4 packet client_link
| `Accept, (`External _ | `NetVM) -> transmit_ipv4 packet t.Router.uplink
| `Accept, (`Firewall_uplink | `Client_gateway) ->
diff --git a/packet.ml b/packet.ml
index a9fa4e7..607fd37 100644
--- a/packet.ml
+++ b/packet.ml
@@ -13,9 +13,15 @@ type ports = {
type host =
[ `Client of client_link | `Client_gateway | `Firewall_uplink | `NetVM | `External of Ipaddr.t ]
-type info = {
+(* Note: 'a is either [host], or the result of applying [Rules.clients] and [Rules.externals] to a host. *)
+type 'a info = {
packet : Nat_packet.t;
- src : host;
- dst : host;
+ src : 'a;
+ dst : 'a;
proto : [ `UDP of ports | `TCP of ports | `ICMP | `Unknown ];
}
+
+(* The first message in a TCP connection has SYN set and ACK clear. *)
+let is_tcp_start = function
+ | `IPv4 (_ip, `TCP (hdr, _body)) -> Tcp.Tcp_packet.(hdr.syn && not hdr.ack)
+ | _ -> false
diff --git a/rules.ml b/rules.ml
index 7e62790..7980469 100644
--- a/rules.ml
+++ b/rules.ml
@@ -25,13 +25,37 @@ open Packet
- [`Drop reason] drop the packet and log the reason.
*)
+(* List your AppVM IP addresses here if you want to match on them in the rules below.
+ Any client not listed here will appear as [`Client `Unknown]. *)
+let clients = [
+ (*
+ "10.137.0.12", `Dev;
+ "10.137.0.14", `Untrusted;
+ *)
+]
+
+(* List your external (non-AppVM) IP addresses here if you want to match on them in the rules below.
+ Any external machine not listed here will appear as [`External `Unknown]. *)
+let externals = [
+ (*
+ "8.8.8.8", `GoogleDNS;
+ *)
+]
+
(** Decide what to do with a packet from a client VM.
Note: If the packet matched an existing NAT rule then this isn't called. *)
let from_client = function
+ (* Examples (add your own rules here): *)
+ (*
+ | { src = `Client `Dev; dst = `Client `Untrusted; proto = `TCP { dport = 22 } } -> `Accept
+ | { src = `Client _; dst = `Client _; proto = `TCP _; packet }
+ when not (is_tcp_start packet) -> `Accept
+ | { dst = `External `GoogleDNS } -> `Drop "block Google DNS"
+ *)
| { dst = (`External _ | `NetVM) } -> `NAT
| { dst = `Client_gateway; proto = `UDP { dport = 53 } } -> `NAT_to (`NetVM, 53)
| { dst = (`Client_gateway | `Firewall_uplink) } -> `Drop "packet addressed to firewall itself"
- | { dst = `Client _ } -> `Drop "prevent communication between client VMs"
+ | { dst = `Client _ } -> `Drop "prevent communication between client VMs by default"
(** Decide what to do with a packet received from the outside world.
Note: If the packet matched an existing NAT rule then this isn't called. *)
From 189a7363680c2f0075a4c730d493f5321f04c122 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Wed, 17 Apr 2019 10:26:32 +0100
Subject: [PATCH 060/281] Add some types to the rules
Before, we inferred the types from rules.ml and then the compiler
checked that it was consistent with what firewall.ml expected. If it
wasn't it reported the problem as being with firewall.ml, which could be
confusing to users.
---
packet.ml | 11 +++++++++++
rules.ml | 23 ++++-------------------
2 files changed, 15 insertions(+), 19 deletions(-)
diff --git a/packet.ml b/packet.ml
index 607fd37..97f1feb 100644
--- a/packet.ml
+++ b/packet.ml
@@ -25,3 +25,14 @@ type 'a info = {
let is_tcp_start = function
| `IPv4 (_ip, `TCP (hdr, _body)) -> Tcp.Tcp_packet.(hdr.syn && not hdr.ack)
| _ -> false
+
+(* The possible actions we can take for a packet: *)
+type action = [
+ | `Accept (* Send the packet to its destination. *)
+ | `NAT (* Rewrite the packet's source field so packet appears to
+ have come from the firewall, via an unused port.
+ Also, add NAT rules so related packets will be translated accordingly. *)
+ | `NAT_to of host * port (* As for [`NAT], but also rewrite the packet's
+ destination fields so it will be sent to [host:port]. *)
+ | `Drop of string (* Drop the packet and log the given reason. *)
+]
diff --git a/rules.ml b/rules.ml
index 7980469..352c98b 100644
--- a/rules.ml
+++ b/rules.ml
@@ -8,23 +8,6 @@ open Packet
(* OCaml normally warns if you don't match all fields, but that's OK here. *)
[@@@ocaml.warning "-9"]
-(** {2 Actions}
-
- The possible actions are:
-
- - [`Accept] : Send the packet to its destination.
-
- - [`NAT] : Rewrite the packet's source field so packet appears to
- have come from the firewall, via an unused port.
- Also, add NAT rules so related packets will be translated accordingly.
-
- - [`NAT_to (host, port)] :
- As for [`NAT], but also rewrite the packet's destination fields so it
- will be sent to [host:port].
-
- - [`Drop reason] drop the packet and log the reason.
-*)
-
(* List your AppVM IP addresses here if you want to match on them in the rules below.
Any client not listed here will appear as [`Client `Unknown]. *)
let clients = [
@@ -44,7 +27,8 @@ let externals = [
(** Decide what to do with a packet from a client VM.
Note: If the packet matched an existing NAT rule then this isn't called. *)
-let from_client = function
+let from_client (info : _ info) : action =
+ match info with
(* Examples (add your own rules here): *)
(*
| { src = `Client `Dev; dst = `Client `Untrusted; proto = `TCP { dport = 22 } } -> `Accept
@@ -59,5 +43,6 @@ let from_client = function
(** Decide what to do with a packet received from the outside world.
Note: If the packet matched an existing NAT rule then this isn't called. *)
-let from_netvm = function
+let from_netvm (info : _ info) : action =
+ match info with
| _ -> `Drop "drop by default"
From b60d098e96b2b713589d51748cc06e387f92519c Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Wed, 17 Apr 2019 11:03:17 +0100
Subject: [PATCH 061/281] Give exact types for Packet.src
Before, the packet passed to rules.ml could have any host as its src.
Now, `from_client` knows that `src` must be a `Client`, and `from_netvm`
knows that `src` is `External` or `NetVM`.
---
client_net.ml | 8 +++---
firewall.ml | 67 +++++++++++++++++++++++++++++----------------------
firewall.mli | 2 +-
packet.ml | 7 +++---
rules.ml | 33 ++++++++++++++++++-------
5 files changed, 70 insertions(+), 47 deletions(-)
diff --git a/client_net.ml b/client_net.ml
index 0649567..68fe6d3 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -56,7 +56,7 @@ let input_arp ~fixed_arp ~iface request =
iface#writev `ARP (fun b -> Arp_packet.encode_into response b; Arp_packet.size)
(** Handle an IPv4 packet from the client. *)
-let input_ipv4 ~client_ip ~router packet =
+let input_ipv4 ~iface ~router packet =
match Nat_packet.of_ipv4_packet packet with
| Error e ->
Log.warn (fun f -> f "Ignored unknown IPv4 message: %a" Nat_packet.pp_error e);
@@ -64,10 +64,10 @@ let input_ipv4 ~client_ip ~router packet =
| Ok packet ->
let `IPv4 (ip, _) = packet in
let src = ip.Ipv4_packet.src in
- if src = client_ip then Firewall.ipv4_from_client router packet
+ if src = iface#other_ip then Firewall.ipv4_from_client router ~src:iface packet
else (
Log.warn (fun f -> f "Incorrect source IP %a in IP packet from %a (dropping)"
- Ipaddr.V4.pp src Ipaddr.V4.pp client_ip);
+ Ipaddr.V4.pp src Ipaddr.V4.pp iface#other_ip);
return ()
)
@@ -94,7 +94,7 @@ let add_vif { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks
| Ok (eth, payload) ->
match eth.Ethernet_packet.ethertype with
| `ARP -> input_arp ~fixed_arp ~iface payload
- | `IPv4 -> input_ipv4 ~client_ip ~router payload
+ | `IPv4 -> input_ipv4 ~iface ~router payload
| `IPv6 -> return () (* TODO: oh no! *)
)
>|= or_raise "Listen on client interface" Netback.pp_error
diff --git a/firewall.ml b/firewall.ml
index 0e38d45..cbb47b7 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -48,8 +48,21 @@ let forward_ipv4 t packet =
(* Packet classification *)
-let classify t packet =
- let `IPv4 (ip, transport) = packet in
+let parse_ips ips = List.map (fun (ip_str, id) -> (Ipaddr.of_string_exn ip_str, id)) ips
+
+let clients = parse_ips Rules.clients
+let externals = parse_ips Rules.externals
+
+let resolve_client client =
+ `Client (try List.assoc (Ipaddr.V4 client#other_ip) clients with Not_found -> `Unknown)
+
+let resolve_host = function
+ | `Client c -> resolve_client c
+ | `External ip -> `External (try List.assoc ip externals with Not_found -> `Unknown)
+ | (`Client_gateway | `Firewall_uplink | `NetVM) as x -> x
+
+let classify ~src ~dst packet =
+ let `IPv4 (_ip, transport) = packet in
let proto =
match transport with
| `TCP ({Tcp.Tcp_packet.src_port; dst_port; _}, _) -> `TCP {sport = src_port; dport = dst_port}
@@ -58,8 +71,8 @@ let classify t packet =
in
Some {
packet;
- src = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.src);
- dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst);
+ src;
+ dst;
proto;
}
@@ -80,7 +93,10 @@ let pp_proto fmt = function
| `ICMP -> Format.pp_print_string fmt "ICMP"
| `Unknown -> Format.pp_print_string fmt "UnknownProtocol"
-let pp_packet fmt {src; dst; proto; packet = _} =
+let pp_packet t fmt {src = _; dst = _; proto; packet} =
+ let `IPv4 (ip, _transport) = packet in
+ let src = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.src) in
+ let dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst) in
Format.fprintf fmt "[src=%a dst=%a proto=%a]"
pp_host src
pp_host dst
@@ -125,30 +141,18 @@ let nat_to t ~host ~port packet =
(* Handle incoming packets *)
-let parse_ips ips = List.map (fun (ip_str, id) -> (Ipaddr.of_string_exn ip_str, id)) ips
-
-let clients = parse_ips Rules.clients
-let externals = parse_ips Rules.externals
-
-let resolve_host = function
- | `Client c -> `Client (try List.assoc (Ipaddr.V4 c#other_ip) clients with Not_found -> `Unknown)
- | `External ip -> `External (try List.assoc ip externals with Not_found -> `Unknown)
- | (`Client_gateway | `Firewall_uplink | `NetVM) as x -> x
-
-let apply_rules t rules info =
+let apply_rules t rules ~dst info =
let packet = info.packet in
- let resolved_info = { info with src = resolve_host info.src;
- dst = resolve_host info.dst } in
- match rules resolved_info, info.dst with
+ match rules info, dst with
| `Accept, `Client client_link -> transmit_ipv4 packet client_link
| `Accept, (`External _ | `NetVM) -> transmit_ipv4 packet t.Router.uplink
| `Accept, (`Firewall_uplink | `Client_gateway) ->
- Log.warn (fun f -> f "Bad rule: firewall can't accept packets %a" pp_packet info);
+ Log.warn (fun f -> f "Bad rule: firewall can't accept packets %a" (pp_packet t) info);
return ()
| `NAT, _ -> add_nat_and_forward_ipv4 t packet
| `NAT_to (host, port), _ -> nat_to t packet ~host ~port
| `Drop reason, _ ->
- Log.info (fun f -> f "Dropped packet (%s) %a" reason pp_packet info);
+ Log.info (fun f -> f "Dropped packet (%s) %a" reason (pp_packet t) info);
return ()
let handle_low_memory t =
@@ -159,7 +163,7 @@ let handle_low_memory t =
`Memory_critical
| `Ok -> Lwt.return `Ok
-let ipv4_from_client t packet =
+let ipv4_from_client t ~src packet =
handle_low_memory t >>= function
| `Memory_critical -> return ()
| `Ok ->
@@ -168,23 +172,28 @@ let ipv4_from_client t packet =
| Some frame -> forward_ipv4 t frame (* Some existing connection or redirect *)
| None ->
(* No existing NAT entry. Check the firewall rules. *)
- match classify t packet with
+ let `IPv4 (ip, _transport) = packet in
+ let dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst) in
+ match classify ~src:(resolve_client src) ~dst:(resolve_host dst) packet with
| None -> return ()
- | Some info -> apply_rules t Rules.from_client info
+ | Some info -> apply_rules t Rules.from_client ~dst info
let ipv4_from_netvm t packet =
handle_low_memory t >>= function
| `Memory_critical -> return ()
| `Ok ->
- match classify t packet with
+ let `IPv4 (ip, _transport) = packet in
+ let src = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.src) in
+ let dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst) in
+ match classify ~src ~dst:(resolve_host dst) packet with
| None -> return ()
| Some info ->
- match info.src with
+ match src with
| `Client _ | `Firewall_uplink | `Client_gateway ->
- Log.warn (fun f -> f "Frame from NetVM has internal source IP address! %a" pp_packet info);
+ Log.warn (fun f -> f "Frame from NetVM has internal source IP address! %a" (pp_packet t) info);
return ()
- | `External _ | `NetVM ->
+ | `External _ | `NetVM as src ->
translate t packet >>= function
| Some frame -> forward_ipv4 t frame
| None ->
- apply_rules t Rules.from_netvm info
+ apply_rules t Rules.from_netvm ~dst { info with src }
diff --git a/firewall.mli b/firewall.mli
index 3909ee0..9900f56 100644
--- a/firewall.mli
+++ b/firewall.mli
@@ -6,6 +6,6 @@
val ipv4_from_netvm : Router.t -> Nat_packet.t -> unit Lwt.t
(** Handle a packet from the outside world (this module will validate the source IP). *)
-val ipv4_from_client : Router.t -> Nat_packet.t -> unit Lwt.t
+val ipv4_from_client : Router.t -> src:Fw_utils.client_link -> Nat_packet.t -> unit Lwt.t
(** Handle a packet from a client. Caller must check the source IP matches the client's
before calling this. *)
diff --git a/packet.ml b/packet.ml
index 97f1feb..d9b49bb 100644
--- a/packet.ml
+++ b/packet.ml
@@ -13,11 +13,10 @@ type ports = {
type host =
[ `Client of client_link | `Client_gateway | `Firewall_uplink | `NetVM | `External of Ipaddr.t ]
-(* Note: 'a is either [host], or the result of applying [Rules.clients] and [Rules.externals] to a host. *)
-type 'a info = {
+type ('src, 'dst) info = {
packet : Nat_packet.t;
- src : 'a;
- dst : 'a;
+ src : 'src;
+ dst : 'dst;
proto : [ `UDP of ports | `TCP of ports | `ICMP | `Unknown ];
}
diff --git a/rules.ml b/rules.ml
index 352c98b..f8f253d 100644
--- a/rules.ml
+++ b/rules.ml
@@ -1,12 +1,9 @@
(* Copyright (C) 2015, Thomas Leonard
See the README file for details. *)
-(** Put your firewall rules here. *)
+(** Put your firewall rules in this file. *)
-open Packet
-
-(* OCaml normally warns if you don't match all fields, but that's OK here. *)
-[@@@ocaml.warning "-9"]
+open Packet (* Allow us to use definitions in packet.ml *)
(* List your AppVM IP addresses here if you want to match on them in the rules below.
Any client not listed here will appear as [`Client `Unknown]. *)
@@ -25,11 +22,29 @@ let externals = [
*)
]
-(** Decide what to do with a packet from a client VM.
+(* OCaml normally warns if you don't match all fields, but that's OK here. *)
+[@@@ocaml.warning "-9"]
+
+(** This function decides what to do with a packet from a client VM.
+
+ It takes as input an argument [info] (of type [Packet.info]) describing the
+ packet, and returns an action (of type [Packet.action]) to perform.
+
+ See packet.ml for the definitions of [info] and [action].
+
Note: If the packet matched an existing NAT rule then this isn't called. *)
-let from_client (info : _ info) : action =
+let from_client (info : ([`Client of _], _) Packet.info) : Packet.action =
match info with
- (* Examples (add your own rules here): *)
+ (* Examples (add your own rules here):
+
+ 1. Allows Dev to send SSH packets to Untrusted.
+ Note: responses are not covered by this!
+ 2. Allows clients to continue existing TCP connections with other clients.
+ This allows responses to SSH packets from the previous rule.
+ 3. Blocks an external site.
+
+ In all cases, make sure you've added the VM name to [clients] or [externals] above, or it won't
+ match anything! *)
(*
| { src = `Client `Dev; dst = `Client `Untrusted; proto = `TCP { dport = 22 } } -> `Accept
| { src = `Client _; dst = `Client _; proto = `TCP _; packet }
@@ -43,6 +58,6 @@ let from_client (info : _ info) : action =
(** Decide what to do with a packet received from the outside world.
Note: If the packet matched an existing NAT rule then this isn't called. *)
-let from_netvm (info : _ info) : action =
+let from_netvm (info : ([`NetVM | `External of _], _) Packet.info) : Packet.action =
match info with
| _ -> `Drop "drop by default"
From eec1e985e5ed1209979d799ad9ffe4b125f602ed Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Fri, 3 May 2019 10:45:15 +0100
Subject: [PATCH 062/281] Add overview of the main components of the firewall
---
.gitignore | 2 +-
README.md | 23 +++++++
diagrams/Makefile | 6 ++
diagrams/components.svg | 149 ++++++++++++++++++++++++++++++++++++++++
diagrams/components.txt | 20 ++++++
5 files changed, 199 insertions(+), 1 deletion(-)
create mode 100644 diagrams/Makefile
create mode 100644 diagrams/components.svg
create mode 100644 diagrams/components.txt
diff --git a/.gitignore b/.gitignore
index f5cd959..bd2f111 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-Makefile
+/Makefile
_build/
log
key_gen.ml
diff --git a/README.md b/README.md
index bfbef5f..960e568 100644
--- a/README.md
+++ b/README.md
@@ -86,6 +86,29 @@ qvm-prefs --set my-app-vm netvm mirage-firewall
Alternatively, you can configure `mirage-firewall` to be your default firewall VM.
+### Components
+
+This diagram show the main components (each box corresponds to a source `.ml` file with the same name):
+
+
+
+
+
+Ethernet frames arrives from client qubes (such as `work` or `personal`) or from `sys-net`.
+Internet (IP) packets are sent to `firewall`, which consults `rules` to decide what to do with the packet.
+If it should be sent on, it uses `router` to send it to the chosen destination.
+`client_net` watches the XenStore database provided by dom0
+to find out when clients need to be added or removed.
+
+The boot process:
+
+- `config.ml` describes the libraries used and static configuration settings (NAT table size).
+ The `mirage` tool uses this to generate `main.ml`.
+- `main.ml` initialises the drivers selected by `config.ml`
+ and calls the `start` function in `unikernel.ml`.
+- `unikernel.ml` connects the Qubes agents, sets up the networking components,
+ and then waits for a shutdown request.
+
### Easy deployment for developers
For development, use the [test-mirage][] scripts to deploy the unikernel (`qubes_firewall.xen`) from your development AppVM.
diff --git a/diagrams/Makefile b/diagrams/Makefile
new file mode 100644
index 0000000..a6fbc5f
--- /dev/null
+++ b/diagrams/Makefile
@@ -0,0 +1,6 @@
+# Requires https://github.com/blampe/goat
+
+all: components.svg
+
+%.svg: %.txt
+ goat $^ > $@
diff --git a/diagrams/components.svg b/diagrams/components.svg
new file mode 100644
index 0000000..1e996b1
--- /dev/null
+++ b/diagrams/components.svg
@@ -0,0 +1,149 @@
+
diff --git a/diagrams/components.txt b/diagrams/components.txt
new file mode 100644
index 0000000..62e4f9e
--- /dev/null
+++ b/diagrams/components.txt
@@ -0,0 +1,20 @@
+ +----------+
+ | rules |
+ +----------+
+ ^
+ |checks
+ |
+ +------------+ +-----+----+
+ work <---->| +---->| firewall |<--------.
+ | | +-----+----+ |
+ | | | +----+---+
+ [...] <---->| client_net | | | uplink |<----> sys-net
+ | | v +--------+
+ | | +----------+ ^
+personal <---->| |<----+ router +---------'
+ +------+-----+ +----------+
+ |
+ |monitors
+ v
+ XenStore
+ (dom0)
From e15fc8c219d2b38aa4b16e9eb2e6224455355903 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Fri, 3 May 2019 11:12:58 +0100
Subject: [PATCH 063/281] Make example rule more restrictive
In the (commented-out) example rules, instead of allowing any client to
continue a TCP flow with any other client, just allow Untrusted to reply
to Dev. This is all that is needed to make the SSH example work.
---
rules.ml | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/rules.ml b/rules.ml
index f8f253d..3959d14 100644
--- a/rules.ml
+++ b/rules.ml
@@ -39,15 +39,14 @@ let from_client (info : ([`Client of _], _) Packet.info) : Packet.action =
1. Allows Dev to send SSH packets to Untrusted.
Note: responses are not covered by this!
- 2. Allows clients to continue existing TCP connections with other clients.
- This allows responses to SSH packets from the previous rule.
+ 2. Allows Untrusted to reply to Dev.
3. Blocks an external site.
In all cases, make sure you've added the VM name to [clients] or [externals] above, or it won't
match anything! *)
(*
| { src = `Client `Dev; dst = `Client `Untrusted; proto = `TCP { dport = 22 } } -> `Accept
- | { src = `Client _; dst = `Client _; proto = `TCP _; packet }
+ | { src = `Client `Untrusted; dst = `Client `Dev; proto = `TCP _; packet }
when not (is_tcp_start packet) -> `Accept
| { dst = `External `GoogleDNS } -> `Drop "block Google DNS"
*)
From 691c4ae745c80d24132c0c2d67c39db66fafb26f Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Mon, 6 May 2019 10:37:24 +0100
Subject: [PATCH 064/281] Update build hash
---
build-with-docker.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/build-with-docker.sh b/build-with-docker.sh
index d14c057..7345ca5 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: dbf7460fa628bea5d132a96fe7ba2cd832e3d9da7005ae74f6a124957f4848ea"
+echo "SHA2 last known: 888cfd66e54c14da75be2bc4272efdb74c2ec8f9f144979f508a09410121482e"
echo "(hashes should match for released versions)"
From 672c82c43c44a24d31a0bf43988104fdce618a00 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Thu, 16 May 2019 19:18:31 +0100
Subject: [PATCH 065/281] Combine Client_gateway and Firewall_uplink
Before, we used Client_gateway for the IP address of the firewall on the
client network and Firewall_uplink for its address on the uplink
network. However, Qubes 4 uses the same IP address for both, so we can't
separate these any longer, and there doesn't seem to be any advantage to
keeping them separate anyway.
---
build-with-docker.sh | 2 +-
client_eth.ml | 6 +++---
client_eth.mli | 2 +-
firewall.ml | 9 ++++-----
packet.ml | 2 +-
router.ml | 4 ++--
rules.ml | 4 ++--
7 files changed, 14 insertions(+), 15 deletions(-)
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 7345ca5..701c686 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 888cfd66e54c14da75be2bc4272efdb74c2ec8f9f144979f508a09410121482e"
+echo "SHA2 last known: 5ee982b12fb3964e7d9e32ca74ce377ec068b3bbef2b6c86c131f8bb422a3134"
echo "(hashes should match for released versions)"
diff --git a/client_eth.ml b/client_eth.ml
index a65325c..3aa3a8a 100644
--- a/client_eth.ml
+++ b/client_eth.ml
@@ -15,7 +15,7 @@ type t = {
type host =
[ `Client of client_link
- | `Client_gateway
+ | `Firewall
| `External of Ipaddr.t ]
let create ~client_gw =
@@ -52,14 +52,14 @@ let classify t ip =
match ip with
| Ipaddr.V6 _ -> `External ip
| Ipaddr.V4 ip4 ->
- if ip4 = t.client_gw then `Client_gateway
+ if ip4 = t.client_gw then `Firewall
else match lookup t ip4 with
| Some client_link -> `Client client_link
| None -> `External ip
let resolve t : host -> Ipaddr.t = function
| `Client client_link -> Ipaddr.V4 client_link#other_ip
- | `Client_gateway -> Ipaddr.V4 t.client_gw
+ | `Firewall -> Ipaddr.V4 t.client_gw
| `External addr -> addr
module ARP = struct
diff --git a/client_eth.mli b/client_eth.mli
index 952e970..2bbb672 100644
--- a/client_eth.mli
+++ b/client_eth.mli
@@ -11,7 +11,7 @@ type t
type host =
[ `Client of client_link
- | `Client_gateway
+ | `Firewall
| `External of Ipaddr.t ]
(* Note: Qubes does not allow us to distinguish between an external address and a
disconnected client.
diff --git a/firewall.ml b/firewall.ml
index cbb47b7..77656d2 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -59,7 +59,7 @@ let resolve_client client =
let resolve_host = function
| `Client c -> resolve_client c
| `External ip -> `External (try List.assoc ip externals with Not_found -> `Unknown)
- | (`Client_gateway | `Firewall_uplink | `NetVM) as x -> x
+ | (`Firewall | `NetVM) as x -> x
let classify ~src ~dst packet =
let `IPv4 (_ip, transport) = packet in
@@ -84,8 +84,7 @@ let pp_host fmt = function
| `Unknown_client ip -> Format.fprintf fmt "unknown-client(%a)" Ipaddr.pp ip
| `NetVM -> Format.pp_print_string fmt "net-vm"
| `External ip -> Format.fprintf fmt "external(%a)" Ipaddr.pp ip
- | `Firewall_uplink -> Format.pp_print_string fmt "firewall(uplink)"
- | `Client_gateway -> Format.pp_print_string fmt "firewall(client-gw)"
+ | `Firewall -> Format.pp_print_string fmt "firewall"
let pp_proto fmt = function
| `UDP ports -> Format.fprintf fmt "UDP(%a)" pp_ports ports
@@ -146,7 +145,7 @@ let apply_rules t rules ~dst info =
match rules info, dst with
| `Accept, `Client client_link -> transmit_ipv4 packet client_link
| `Accept, (`External _ | `NetVM) -> transmit_ipv4 packet t.Router.uplink
- | `Accept, (`Firewall_uplink | `Client_gateway) ->
+ | `Accept, `Firewall ->
Log.warn (fun f -> f "Bad rule: firewall can't accept packets %a" (pp_packet t) info);
return ()
| `NAT, _ -> add_nat_and_forward_ipv4 t packet
@@ -189,7 +188,7 @@ let ipv4_from_netvm t packet =
| None -> return ()
| Some info ->
match src with
- | `Client _ | `Firewall_uplink | `Client_gateway ->
+ | `Client _ | `Firewall ->
Log.warn (fun f -> f "Frame from NetVM has internal source IP address! %a" (pp_packet t) info);
return ()
| `External _ | `NetVM as src ->
diff --git a/packet.ml b/packet.ml
index d9b49bb..7838a6b 100644
--- a/packet.ml
+++ b/packet.ml
@@ -11,7 +11,7 @@ type ports = {
}
type host =
- [ `Client of client_link | `Client_gateway | `Firewall_uplink | `NetVM | `External of Ipaddr.t ]
+ [ `Client of client_link | `Firewall | `NetVM | `External of Ipaddr.t ]
type ('src, 'dst) info = {
packet : Nat_packet.t;
diff --git a/router.ml b/router.ml
index ff5fddc..4d7ed90 100644
--- a/router.ml
+++ b/router.ml
@@ -24,11 +24,11 @@ let add_client t = Client_eth.add_client t.client_eth
let remove_client t = Client_eth.remove_client t.client_eth
let classify t ip =
- if ip = Ipaddr.V4 t.uplink#my_ip then `Firewall_uplink
+ if ip = Ipaddr.V4 t.uplink#my_ip then `Firewall
else if ip = Ipaddr.V4 t.uplink#other_ip then `NetVM
else (Client_eth.classify t.client_eth ip :> Packet.host)
let resolve t = function
- | `Firewall_uplink -> Ipaddr.V4 t.uplink#my_ip
+ | `Firewall -> Ipaddr.V4 t.uplink#my_ip
| `NetVM -> Ipaddr.V4 t.uplink#other_ip
| #Client_eth.host as host -> Client_eth.resolve t.client_eth host
diff --git a/rules.ml b/rules.ml
index 3959d14..ec0c1c3 100644
--- a/rules.ml
+++ b/rules.ml
@@ -51,8 +51,8 @@ let from_client (info : ([`Client of _], _) Packet.info) : Packet.action =
| { dst = `External `GoogleDNS } -> `Drop "block Google DNS"
*)
| { dst = (`External _ | `NetVM) } -> `NAT
- | { dst = `Client_gateway; proto = `UDP { dport = 53 } } -> `NAT_to (`NetVM, 53)
- | { dst = (`Client_gateway | `Firewall_uplink) } -> `Drop "packet addressed to firewall itself"
+ | { dst = `Firewall; proto = `UDP { dport = 53 } } -> `NAT_to (`NetVM, 53)
+ | { dst = `Firewall } -> `Drop "packet addressed to firewall itself"
| { dst = `Client _ } -> `Drop "prevent communication between client VMs by default"
(** Decide what to do with a packet received from the outside world.
From ee97d67c844e9b6483b4bc360ce65283031c3f43 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Tue, 28 May 2019 21:04:31 +0100
Subject: [PATCH 066/281] Add CHANGELOG
Older entries are imported from the release notes. The 0.6 ones are from
the Git commits.
---
CHANGES.md | 212 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 212 insertions(+)
create mode 100644 CHANGES.md
diff --git a/CHANGES.md b/CHANGES.md
new file mode 100644
index 0000000..7fde759
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,212 @@
+### 0.6
+
+Changes to rules language:
+
+- Allow naming hosts (@talex5, #54).
+ Previously, we passed in the interface, from which it was possible (but a
+ little difficult) to extract the IP address and compare with some predefined
+ ones. Now, we allow the user to list IP addresses and named tags for them,
+ which can be matched on easily.
+
+- Add some types to the rules (@talex5, #54).
+ Before, we inferred the types from `rules.ml` and then the compiler checked that
+ it was consistent with what `firewall.ml` expected. If it wasn't then it
+ reported the problem as being with `firewall.ml`, which could be confusing to
+ users.
+
+- Give exact types for `Packet.src` (@talex5, #54).
+ Before, the packet passed to `rules.ml` could have any host as its `src`.
+ Now, `from_client` knows that `src` must be a `Client`,
+ and `from_netvm` knows that `src` is `External` or `NetVM`.
+
+- Combine `Client_gateway` and `Firewall_uplink` (@talex5, #65).
+ Before, we used `Client_gateway` for the IP address of the firewall on the client network
+ and `Firewall_uplink` for its address on the uplink network.
+ However, Qubes 4 uses the same IP address for both, so we can't separate these any longer,
+ and there doesn't seem to be any advantage to keeping them separate anyway.
+
+Bug fixes:
+
+- Upgrade to latest mirage-nat to fix ICMP (@yomimono, @linse, #55).
+ Now ping and traceroute should work. Reported by @xaki23.
+
+- Respond to ARP requests for `*.*.*.1` (@talex5, #61).
+ This is a work-around to get DHCP working with HVM domains.
+ Reported by @cgchinicz.
+ See: https://github.com/QubesOS/qubes-issues/issues/5022
+
+- Force backend MAC to `fe:ff:ff:ff:ff:ff` to fix HVM clients (@talex5, #61).
+ Xen appears to configure the same MAC address for both the frontend and
+ backend in XenStore. This works if the client uses just a simple ethernet
+ device, but fails if it connects via a bridge. HVM domains have an associated
+ stub domain running qemu, which provides an emulated network device. The stub
+ domain uses a bridge to connect qemu's interface with eth0, and this didn't
+ work. Force the use of the fixed version of mirage-net-xen, which no longer
+ uses XenStore to get the backend MAC, and provides a new function to get the
+ frontend one.
+
+- Wait if dom0 is slow to set the network configuration (@talex5, #60).
+ Sometimes we boot before dom0 has put the network settings in QubesDB.
+ If that happens, log a message, wait until the database changes, and retry.
+
+Reproducible builds:
+
+- Add patch to cmdliner for reproducible build (@talex5, #52).
+ See https://github.com/dbuenzli/cmdliner/pull/106
+
+- Use source date in .tar.bz2 archive (@talex5, #49).
+ All files are now added using the date the `build-with-docker` script was last changed.
+ Since this includes the hash of the result, it should be up-to-date.
+ This ensures that rebuilding the archive doesn't change it in any way.
+ Reported by Holger Levsen.
+
+Documentation changes:
+
+- Added example rules showing how to block access to an external service or
+ allow SSH between AppVMs (@talex5, #54). Requested at
+ https://groups.google.com/d/msg/qubes-users/BnL0nZGpJOE/61HOBg1rCgAJ.
+
+- Add overview of the main components of the firewall in the README (@talex5, #54).
+
+- Link to security advisories from README (@talex5, #58).
+
+- Clarify how to build from source (@talex5, #51).
+
+- Remove Qubes 3 instructions (@talex5, #48).
+ See https://www.qubes-os.org/news/2019/03/28/qubes-3-2-has-reached-eol/
+
+### 0.5
+
+- Update to the latest mirage-net-xen, mirage-nat and tcpip libraries (@yomimono, @talex5, #45, #47).
+ In iperf benchmarks between a client VM and sys-net, this more than doubled the reported bandwidth!
+
+- Don't wait for the Qubes GUI daemon to connect before attaching client VMs (@talex5, #38).
+ If the firewall is restarted while AppVMs are connected, qubesd tries to
+ reconnect them before starting the GUI agent. However, the firewall was
+ waiting for the GUI agent to connect before handling the connections. This
+ led to a 10s delay on restart for each client VM. Reported by @xaki23.
+
+- Add stub makefile for qubes-builder (@xaki23, #37).
+
+- Update build instructions for latest Fedora (@talex5, #36). `yum` no longer exists.
+ Also, show how to create a symlink for `/var/lib/docker` on build VMs that aren't standalone.
+ Reported by @xaki23.
+
+- Add installation instructions for Qubes 4 (@yomimono, @reynir, @talex5, #27).
+
+- Use `Ethernet_wire.sizeof_ethernet` instead of a magic `14` (@hannesm, #46).
+
+### 0.4
+
+- Add support for HVM guests (needed for Qubes 4).
+
+- Add support for disposable VMs.
+
+- Drop frames if an interface's queue gets too long.
+
+- Show the packet when failing to add a NAT rule. The previous message was
+ just: `WRN [firewall] Failed to add NAT rewrite rule: Cannot NAT this packet`
+
+### 0.3
+
+- Add support for NAT of ICMP queries (e.g. pings) and errors (e.g. "Host unreachable").
+ Before, these packets would be dropped.
+
+- Use an LRU cache to avoid running out of memory and needing to reset the table.
+ Should avoid any more out-of-memory bugs.
+
+- Pass around parsed packets rather than raw ethernet frames.
+
+- Pin Docker base image to a specific hash. Requested by Joanna Rutkowska.
+
+- Update for Mirage 3.
+
+- Remove non-Docker build instructions. Fedora 24 doesn't work with opam
+ (because the current binary release of aspcud's clasp binary segfaults, which
+ opam reports as `External solver failed with inconsistent return value.`).
+
+### 0.2
+
+Build:
+
+- Add option to build with Docker. This fixes opam-repository to a known commit
+ for reproducible builds. It also displays the actual and expected SHA hashes
+ after building.
+
+Bug fixes:
+
+- Updated README: the build also requires "patch". Reported by William Waites.
+- Monitor set of client interfaces, not client domains. Qubes does not remove
+ the client directory itself when the domain exits. This prevented clients
+ from reconnecting. This may also make it possible to connect clients to the
+ firewall via multiple interfaces, although this doesn't seem useful.
+- Handle errors writing to client. mirage-net-xen would report `Netback_shutdown`
+ if we tried to write to a client after it had disconnected. Now we just log
+ this and continue.
+- Ensure that old client has quit before adding new one. Not sure if this can
+ happen, but it removes a TODO from the code.
+- Allow clients to have any IP address. We previously assumed that Qubes would
+ always give clients IP addresses on a particular network. However, it is not
+ required to do this and in fact uses a different network for disposable VMs.
+ With this change:
+ - We no longer reject clients with unknown IP addresses.
+ - The `Unknown_client` classification is gone; we have no way to tell the
+ difference between a client that isn't connected and an external address.
+ - We now consider every client to be on a point-to-point link and do not
+ answer ARP requests on behalf of other clients. Clients should assume their
+ netmask is `255.255.255.255` (and ignore `/qubes-netmask`). This allows
+ disposable VMs to connect to the firewall but for some reason they don't
+ process any frames we send them (we get their ARP requests but they don't
+ get our replies). Taking eth0 down in the disp VM, then bringing it back up
+ (and re-adding the routes) allows it to work.
+- Cope with writing a frame failing. If a client disconnects suddenly then we
+ may get an error trying to map its grant to send the frame.
+- Survive death of our GUId connection to dom0. We don't need the GUI anyway.
+- Handle `Out_of_memory` adding NAT entries. Because hash tables resize in big
+ steps, this can happen even if we have a fair chunk of free memory.
+- Calculate checksums even for `Accept` action. If packet has been NAT'd then we
+ certainly need to recalculate the checksum, but even for direct pass-through
+ it might have been received with an invalid checksum due to checksum offload.
+ For now, recalculate full checksum in all cases.
+- Log correct destination for redirected packets. Before, we always said it was
+ going to "NetVM".
+- If we can't find a free port, reset the NAT table.
+- Reset NAT table if memory gets low.
+
+Other changes:
+
+- Report current memory use to XenStore.
+- Reduce logging verbosity.
+- Avoid using `Lwt.join` on listening threads.
+ `Lwt.join` only reports an error if _both_ threads fail.
+- Keep track of transmit queue lengths. Log if we have to wait to send a frame.
+- Use mirage-logs library for log reporter.
+- Respond to `WaitForSession` commands (we're always ready!).
+- Log `SetDateTime` messages from dom0 (we still don't actually update our clock,
+ though).
+
+Updates for upstream library changes:
+
+- Updates for mirage 2.9.0.
+ - Use new name for uplink device (`0`, not `tap0`).
+ - Don't configure logging - mirage does that for us now.
+- Remove tcpip pin. The 2.7.0 release has the checksum feature we need.
+- Remove mirage-xen pin. mirage-xen 2.4.0 has been released with the required
+ features (also fixes indentation problem reported by @cfcs).
+- Add ncurses-dev to required yum packages. The ocamlfind package has started
+ listing this as a required dependency for some reason, although it appears
+ not to need it. Reported by cyrinux.
+- Add work-around for Qubes passing Linux kernel arguments. With the new
+ Functoria release of Mirage, these unrecognised arguments prevented the
+ unikernel from booting. See: https://github.com/mirage/mirage/issues/493
+- Remove mirage-logs pin. Now available from the main repository.
+- Remove mirage-qubes pin.
+ mirage-qubes 0.2 has been released, and supports the latests Logs API.
+- Remove mirage-net-xen pin.
+ Version 1.5 has now been released, and includes netback support.
+- Update to new Logs API.
+- Remove pin for mirage-clock-xen. New version has been released now.
+
+### 0.1
+
+Initial release.
From de7d05ebfa6dad7be7aa17f34870f24d53b6f743 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Wed, 29 May 2019 08:37:31 +0100
Subject: [PATCH 067/281] Fix typos in docs
---
CHANGES.md | 2 +-
README.md | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 7fde759..6284c3e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -19,7 +19,7 @@ Changes to rules language:
Now, `from_client` knows that `src` must be a `Client`,
and `from_netvm` knows that `src` is `External` or `NetVM`.
-- Combine `Client_gateway` and `Firewall_uplink` (@talex5, #65).
+- Combine `Client_gateway` and `Firewall_uplink` (@talex5, #64).
Before, we used `Client_gateway` for the IP address of the firewall on the client network
and `Firewall_uplink` for its address on the uplink network.
However, Qubes 4 uses the same IP address for both, so we can't separate these any longer,
diff --git a/README.md b/README.md
index 960e568..33a22a1 100644
--- a/README.md
+++ b/README.md
@@ -170,7 +170,6 @@ Redistribution and use in source and binary forms, with or without modification,
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-gg
[test-mirage]: https://github.com/talex5/qubes-test-mirage
[mirage-qubes]: https://github.com/mirage/mirage-qubes
From 3ab7284a6413043f5e40c592b2907954b126a661 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Wed, 29 May 2019 15:22:15 +0100
Subject: [PATCH 068/281] Note that mirage-firewall cannot be used as UpdateVM
Reported at: https://groups.google.com/forum/#!topic/qubes-users/YPFtbwyoUjc
---
README.md | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/README.md b/README.md
index 960e568..97b8122 100644
--- a/README.md
+++ b/README.md
@@ -86,6 +86,14 @@ qvm-prefs --set my-app-vm netvm mirage-firewall
Alternatively, you can configure `mirage-firewall` to be your default firewall VM.
+Note that by default dom0 uses sys-firewall as its "UpdateVM" (a proxy for downloading updates).
+mirage-firewall cannot be used for this, but any Linux VM should be fine.
+https://www.qubes-os.org/doc/software-update-dom0/ says:
+
+> The role of UpdateVM can be assigned to any VM in the Qubes VM Manager, and
+> there are no significant security implications in this choice. By default,
+> this role is assigned to the firewallvm.
+
### Components
This diagram show the main components (each box corresponds to a source `.ml` file with the same name):
From 0a4b01a8410e8d8c357cf6ce9e3f65f6c422f02b Mon Sep 17 00:00:00 2001
From: jaseg
Date: Fri, 31 May 2019 12:50:33 +0900
Subject: [PATCH 069/281] Fix ln(1) call in build instructions
The arguments were backwards. [```ln``` takes the link target first, then the link name](https://linux.die.net/man/1/ln).
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 0c8aaae..9cd73d7 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ See the [Deploy](#deploy) section below for installation instructions.
Create a new Fedora-29 AppVM (or reuse an existing one). Open a terminal.
Clone this Git repository and run the `build-with-docker.sh` script:
- sudo ln -s /var/lib/docker /home/user/docker
+ sudo ln -s /home/user/docker /var/lib/docker
sudo dnf install docker
sudo systemctl start docker
git clone https://github.com/mirage/qubes-mirage-firewall.git
From d36ecf96af37154ca050b0de2e1c445f41f603a2 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 15 Jun 2019 12:48:01 +0100
Subject: [PATCH 070/281] Remove cmdliner pin as 1.0.4 is now released
Reverts 06511e076f
---
Dockerfile | 5 ++---
build-with-docker.sh | 2 +-
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 1cbe558..41ad029 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,15 +2,14 @@
# It will probably still work on newer images, though, unless Debian
# changes some compiler optimisations (unlikely).
#FROM ocaml/opam2:debian-9-ocaml-4.07
-FROM ocaml/opam2@sha256:f7125924dd6632099ff98b2505536fe5f5c36bf0beb24779431bb62be5748562
+FROM ocaml/opam2@sha256:74fb6e30a95e1569db755b3c061970a8270dfc281c4e69bffe2cf9905d356b38
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN git fetch origin && git reset --hard d1b2a1cbc28d43926b37e61f46fc403b48ab9c23 && opam update
+RUN git fetch origin && git reset --hard d28fedaa8a077a429bd7bd79cbc19eb90e01c040 && opam update
RUN sudo apt-get install -y m4 libxen-dev pkg-config
-RUN opam pin add -yn cmdliner 'https://github.com/talex5/cmdliner.git#repro-builds'
RUN opam install -y vchan mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage mirage-nat mirage-qubes
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 701c686..b484c2f 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 5ee982b12fb3964e7d9e32ca74ce377ec068b3bbef2b6c86c131f8bb422a3134"
+echo "SHA2 last known: b4758e0911acd25c278c5d4bb9feb05daccb5e3d6c3692b5e2274b098971e1b8"
echo "(hashes should match for released versions)"
From f9856a3605ff326520e01c3a26783f0465bed164 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 22 Jun 2019 14:53:25 +0100
Subject: [PATCH 071/281] Remove netchannel pin
Version 1.11.0 has been released now, and the current trunk doesn't
build without updating other things. The error was:
File "lib/xenstore.ml", line 165, characters 19-34:
Error: The module OS is an alias for module Os_xen, which is missing
ocamlopt lib/.netchannel.objs/native/netchannel__Backend.{cmx,o} (exit 2)
(cd _build/default && /home/opam/.opam/4.07/bin/ocamlopt.opt -w -40 -g -I lib/.netchannel.objs/byte -I lib/.netchannel.objs/native -I /home/opam/.opam/4.07/lib/base/caml -I /home/opam/.opam/4.07/lib/bigarray-compat -I /home/opam/.opam/4.07/lib/bytes -I /home/opam/.opam/4.07/lib/cstruct -I /home/opam/.opam/4.07/lib/fmt -I /home/opam/.opam/4.07/lib/io-page -I /home/opam/.opam/4.07/lib/io-page-x[...]
File "lib/backend.ml", line 23, characters 16-29:
Error: The module OS is an alias for module Os_xen, which is missing
Reported by ronpunz in https://groups.google.com/forum/#!topic/qubes-users/PsYUXvypPDs
---
build-with-docker.sh | 2 +-
config.ml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/build-with-docker.sh b/build-with-docker.sh
index b484c2f..ad8d3b7 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: b4758e0911acd25c278c5d4bb9feb05daccb5e3d6c3692b5e2274b098971e1b8"
+echo "SHA2 last known: 9f7d064a194be07301173389a4414266cd5d7ef935b16ed29a978a33cb92884c"
echo "(hashes should match for released versions)"
diff --git a/config.ml b/config.ml
index 4171927..c27223a 100644
--- a/config.ml
+++ b/config.ml
@@ -27,7 +27,7 @@ let main =
package "ethernet";
package "mirage-protocols";
package "shared-memory-ring" ~min:"3.0.0";
- package "netchannel" ~min:"1.11.0" ~pin:"git+https://github.com/mirage/mirage-net-xen.git";
+ package "netchannel" ~min:"1.11.0";
package "mirage-net-xen";
package "ipaddr" ~min:"3.0.0";
package "mirage-qubes";
From cb6d03d83d2d7b1e204c9a36ab7210c35c74a1ec Mon Sep 17 00:00:00 2001
From: xaki23
Date: Sun, 28 Jul 2019 13:07:09 +0200
Subject: [PATCH 072/281] Use OCaml 4.08.0 for qubes-builder builds (was
4.07.1)
---
Makefile.builder | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile.builder b/Makefile.builder
index 098463d..146392e 100644
--- a/Makefile.builder
+++ b/Makefile.builder
@@ -1,2 +1,2 @@
MIRAGE_KERNEL_NAME = qubes_firewall.xen
-OCAML_VERSION ?= 4.07.1
+OCAML_VERSION ?= 4.08.0
From 16231e2e524a53284490346961fc26b11059fe22 Mon Sep 17 00:00:00 2001
From: xaki23
Date: Sun, 28 Jul 2019 13:08:15 +0200
Subject: [PATCH 073/281] Adjust to ipaddr-4.0.0 renaming _bytes to _octets
---
Dockerfile | 2 +-
build-with-docker.sh | 2 +-
client_eth.ml | 2 +-
config.ml | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 41ad029..7544cdb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,7 +7,7 @@ FROM ocaml/opam2@sha256:74fb6e30a95e1569db755b3c061970a8270dfc281c4e69bffe2cf990
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN git fetch origin && git reset --hard d28fedaa8a077a429bd7bd79cbc19eb90e01c040 && opam update
+RUN git fetch origin && git reset --hard 3389beb33b37da54c9f5a41f19291883dfb59bfb && opam update
RUN sudo apt-get install -y m4 libxen-dev pkg-config
RUN opam install -y vchan mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage mirage-nat mirage-qubes
diff --git a/build-with-docker.sh b/build-with-docker.sh
index ad8d3b7..82a6fab 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 9f7d064a194be07301173389a4414266cd5d7ef935b16ed29a978a33cb92884c"
+echo "SHA2 last known: 5707d97d78eb54cad9bade5322c197d8b3706335aa277ccad31fceac564f3319"
echo "(hashes should match for released versions)"
diff --git a/client_eth.ml b/client_eth.ml
index 3aa3a8a..10c84d1 100644
--- a/client_eth.ml
+++ b/client_eth.ml
@@ -70,7 +70,7 @@ module ARP = struct
let lookup t ip =
if ip = t.net.client_gw then Some t.client_link#my_mac
- else if (Ipaddr.V4.to_bytes ip).[3] = '\x01' then (
+ else if (Ipaddr.V4.to_octets ip).[3] = '\x01' then (
Log.info (fun f -> f ~header:t.client_link#log_header
"Request for %a is invalid, but pretending it's me (see Qubes issue #5022)" Ipaddr.V4.pp ip);
Some t.client_link#my_mac
diff --git a/config.ml b/config.ml
index c27223a..ef85b1a 100644
--- a/config.ml
+++ b/config.ml
@@ -29,7 +29,7 @@ let main =
package "shared-memory-ring" ~min:"3.0.0";
package "netchannel" ~min:"1.11.0";
package "mirage-net-xen";
- package "ipaddr" ~min:"3.0.0";
+ package "ipaddr" ~min:"4.0.0";
package "mirage-qubes";
package "mirage-nat" ~min:"1.2.0";
package "mirage-logs";
From 8b411db75145131a11a42a1b662f6de7ae27184d Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sun, 28 Jul 2019 16:49:16 +0100
Subject: [PATCH 074/281] Removed some hard-coded installs from Dockerfile
There's no advantage to installing these manually, and with the current
version of mirage they had to be downgraded again in the next step.
---
Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Dockerfile b/Dockerfile
index 7544cdb..5929b79 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,7 +10,7 @@ FROM ocaml/opam2@sha256:74fb6e30a95e1569db755b3c061970a8270dfc281c4e69bffe2cf990
RUN git fetch origin && git reset --hard 3389beb33b37da54c9f5a41f19291883dfb59bfb && opam update
RUN sudo apt-get install -y m4 libxen-dev pkg-config
-RUN opam install -y vchan mirage-xen-ocaml mirage-xen-minios io-page mirage-xen mirage mirage-nat mirage-qubes
+RUN opam install -y mirage lwt
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
WORKDIR /home/opam/qubes-mirage-firewall
From ce29c09f0f543e2eed02fe55355fd17197027e40 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sun, 28 Jul 2019 17:01:23 +0100
Subject: [PATCH 075/281] Show final sha256 checksum in Travis output
---
.travis.yml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index fb11f9a..77b3499 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,6 +3,8 @@ script:
- echo 'ADD . /home/opam/qubes-mirage-firewall' >> Dockerfile
- echo 'RUN sudo chown -R opam /home/opam/qubes-mirage-firewall' >> Dockerfile
- docker build -t qubes-mirage-firewall .
- - docker run --rm -i qubes-mirage-firewall
+ - docker run --name build -i qubes-mirage-firewall
+ - docker cp build:/home/opam/qubes-mirage-firewall/qubes_firewall.xen .
+ - sha256sum qubes_firewall.xen
sudo: required
dist: trusty
From cac3e53be120fe03cfafe3a221b797bb8fa47a2b Mon Sep 17 00:00:00 2001
From: xaki23
Date: Sun, 28 Jul 2019 13:33:43 +0200
Subject: [PATCH 076/281] README: create the symlink-redirected docker dir
Otherwise, installing the docker package removes the dangling symlink.
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 9cd73d7..7722ff9 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@ See the [Deploy](#deploy) section below for installation instructions.
Create a new Fedora-29 AppVM (or reuse an existing one). Open a terminal.
Clone this Git repository and run the `build-with-docker.sh` script:
+ mkdir /home/user/docker
sudo ln -s /home/user/docker /var/lib/docker
sudo dnf install docker
sudo systemctl start docker
From 3fefba21a78327d243092d3236b19fbf28383bf1 Mon Sep 17 00:00:00 2001
From: xaki23
Date: Sun, 25 Aug 2019 18:12:17 +0200
Subject: [PATCH 077/281] bump OCAML_VERSION to 4.08.1
---
Makefile.builder | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile.builder b/Makefile.builder
index 146392e..8a6355b 100644
--- a/Makefile.builder
+++ b/Makefile.builder
@@ -1,2 +1,2 @@
MIRAGE_KERNEL_NAME = qubes_firewall.xen
-OCAML_VERSION ?= 4.08.0
+OCAML_VERSION ?= 4.08.1
From bc7706cc97531aaf1f4dd0291a26c2307f32d647 Mon Sep 17 00:00:00 2001
From: xaki23
Date: Sun, 25 Aug 2019 18:12:59 +0200
Subject: [PATCH 078/281] rename things for newer mirage-xen versions
---
client_net.ml | 2 +-
dao.ml | 8 ++++----
memory_pressure.ml | 10 +++++-----
3 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/client_net.ml b/client_net.ml
index 68fe6d3..df436be 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -4,7 +4,7 @@
open Lwt.Infix
open Fw_utils
-module Netback = Netchannel.Backend.Make(Netchannel.Xenstore.Make(OS.Xs))
+module Netback = Netchannel.Backend.Make(Netchannel.Xenstore.Make(Os_xen.Xs))
module ClientEth = Ethernet.Make(Netback)
let src = Logs.Src.create "client_net" ~doc:"Client networking"
diff --git a/dao.ml b/dao.ml
index a68cc64..55d901e 100644
--- a/dao.ml
+++ b/dao.ml
@@ -30,7 +30,7 @@ module VifMap = struct
end
let directory ~handle dir =
- OS.Xs.directory handle dir >|= function
+ Os_xen.Xs.directory handle dir >|= function
| [""] -> [] (* XenStore client bug *)
| items -> items
@@ -46,7 +46,7 @@ let vifs ~handle domid =
| Some device_id ->
let vif = { ClientVif.domid; device_id } in
Lwt.try_bind
- (fun () -> OS.Xs.read handle (Printf.sprintf "%s/%d/ip" path device_id))
+ (fun () -> Os_xen.Xs.read handle (Printf.sprintf "%s/%d/ip" path device_id))
(fun client_ip ->
let client_ip = Ipaddr.V4.of_string_exn client_ip in
Lwt.return (Some (vif, client_ip))
@@ -61,10 +61,10 @@ let vifs ~handle domid =
)
let watch_clients fn =
- OS.Xs.make () >>= fun xs ->
+ Os_xen.Xs.make () >>= fun xs ->
let backend_vifs = "backend/vif" in
Log.info (fun f -> f "Watching %s" backend_vifs);
- OS.Xs.wait xs (fun handle ->
+ Os_xen.Xs.wait xs (fun handle ->
begin Lwt.catch
(fun () -> directory ~handle backend_vifs)
(function
diff --git a/memory_pressure.ml b/memory_pressure.ml
index ed5b7e5..92271da 100644
--- a/memory_pressure.ml
+++ b/memory_pressure.ml
@@ -6,7 +6,7 @@ open Lwt
let src = Logs.Src.create "memory_pressure" ~doc:"Memory pressure monitor"
module Log = (val Logs.src_log src : Logs.LOG)
-let total_pages = OS.MM.Heap_pages.total ()
+let total_pages = Os_xen.MM.Heap_pages.total ()
let pagesize_kb = Io_page.page_size / 1024
let meminfo ~used =
@@ -23,7 +23,7 @@ let meminfo ~used =
let report_mem_usage used =
Lwt.async (fun () ->
- let open OS in
+ let open Os_xen in
Xs.make () >>= fun xs ->
Xs.immediate xs (fun h ->
Xs.write h "memory/meminfo" (meminfo ~used)
@@ -32,16 +32,16 @@ let report_mem_usage used =
let init () =
Gc.full_major ();
- let used = OS.MM.Heap_pages.used () in
+ let used = Os_xen.MM.Heap_pages.used () in
report_mem_usage used
let status () =
- let used = OS.MM.Heap_pages.used () |> float_of_int in
+ let used = Os_xen.MM.Heap_pages.used () |> float_of_int in
let frac = used /. float_of_int total_pages in
if frac < 0.9 then `Ok
else (
Gc.full_major ();
- let used = OS.MM.Heap_pages.used () in
+ let used = Os_xen.MM.Heap_pages.used () in
report_mem_usage used;
let frac = float_of_int used /. float_of_int total_pages in
if frac > 0.9 then `Memory_critical
From 49195ed5e18128792f239b500768107ef5e557c2 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sun, 25 Aug 2019 18:41:09 +0100
Subject: [PATCH 079/281] Update Docker build for new mirage-xen
Also, switched to the experimental new OCurrent images, as they are much
smaller:
- Before: 1 GB (ocaml/opam2:debian-10-ocaml-4.08)
- Now: 309 MB (ocurrent/opam:alpine-3.10-ocaml-4.08)
---
.dockerignore | 3 +++
Dockerfile | 9 ++++-----
build-with-docker.sh | 2 +-
config.ml | 1 +
4 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/.dockerignore b/.dockerignore
index 85fe546..72eb1df 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -2,3 +2,6 @@
_build
*.xen
*.bz2
+*.tar.bz2
+*.tgz
+mirage-firewall-bin*
diff --git a/Dockerfile b/Dockerfile
index 5929b79..ba15257 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,16 +1,15 @@
# Pin the base image to a specific hash for maximum reproducibility.
# It will probably still work on newer images, though, unless Debian
# changes some compiler optimisations (unlikely).
-#FROM ocaml/opam2:debian-9-ocaml-4.07
-FROM ocaml/opam2@sha256:74fb6e30a95e1569db755b3c061970a8270dfc281c4e69bffe2cf9905d356b38
+#FROM ocurrent/opam:alpine-3.10-ocaml-4.08
+FROM ocurrent/opam@sha256:4cf6f8a427e7f65a250cd5dbc9f5069e8f8213467376af5136bf67a21d39d6ec
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN git fetch origin && git reset --hard 3389beb33b37da54c9f5a41f19291883dfb59bfb && opam update
+RUN cd ~/opam-repository && git fetch origin master && git reset --hard a83bd077e4e54c41b0664a2e1618670d57b7c79d && opam update
-RUN sudo apt-get install -y m4 libxen-dev pkg-config
-RUN opam install -y mirage lwt
+RUN opam depext -i -y mirage lwt
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
WORKDIR /home/opam/qubes-mirage-firewall
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 82a6fab..01555ba 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 5707d97d78eb54cad9bade5322c197d8b3706335aa277ccad31fceac564f3319"
+echo "SHA2 last known: 3cf9358df911c7bc5a28846087c5359e5b550e5d0c6cf342a6e1c90545518ac6"
echo "(hashes should match for released versions)"
diff --git a/config.ml b/config.ml
index ef85b1a..55d8c42 100644
--- a/config.ml
+++ b/config.ml
@@ -33,6 +33,7 @@ let main =
package "mirage-qubes";
package "mirage-nat" ~min:"1.2.0";
package "mirage-logs";
+ package "mirage-xen" ~min:"4.0.0";
]
"Unikernel.Main" (mclock @-> job)
From 930d209cdb09ec670ad3f28bde15d595c8553c95 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sun, 17 Nov 2019 14:25:42 +0000
Subject: [PATCH 080/281] Fix build
- A new ocaml-migrate-parsetree.1.4.0 was released, replacing the old
1.4.0 with new code. This was rejected by the checksum test.
Fixed by updating to the latest opam-repository.
See: https://github.com/ocaml/opam-repository/pull/15294
- The latest opam-repository pulls in mirage 3.7, which doesn't work
(`No available version of mirage-clock satisfies the constraints`), so
pin the previous mirage 3.5.2 version instead.
- Mirage now generates `.merlin`, so remove it from Git.
---
.gitignore | 1 +
.merlin | 3 ---
Dockerfile | 6 +++---
build-with-docker.sh | 2 +-
4 files changed, 5 insertions(+), 7 deletions(-)
delete mode 100644 .merlin
diff --git a/.gitignore b/.gitignore
index bd2f111..280a547 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ main.native
mir-qubes-test
qubes-firewall.xl.in
qubes-firewall_libvirt.xml
+.merlin
diff --git a/.merlin b/.merlin
deleted file mode 100644
index 2b4d411..0000000
--- a/.merlin
+++ /dev/null
@@ -1,3 +0,0 @@
-S .
-B _build
-PKG vchan.xen lwt mirage mirage-net-xen tcpip mirage-nat
diff --git a/Dockerfile b/Dockerfile
index ba15257..c6ef858 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,14 +2,14 @@
# It will probably still work on newer images, though, unless Debian
# changes some compiler optimisations (unlikely).
#FROM ocurrent/opam:alpine-3.10-ocaml-4.08
-FROM ocurrent/opam@sha256:4cf6f8a427e7f65a250cd5dbc9f5069e8f8213467376af5136bf67a21d39d6ec
+FROM ocurrent/opam@sha256:3f3ce7e577a94942c7f9c63cbdd1ecbfe0ea793f581f69047f3155967bba36f6
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN cd ~/opam-repository && git fetch origin master && git reset --hard a83bd077e4e54c41b0664a2e1618670d57b7c79d && opam update
+RUN cd ~/opam-repository && git fetch origin master && git reset --hard 5eed470abc5c7991e448c9653698c03d6ea146d1 && opam update
-RUN opam depext -i -y mirage lwt
+RUN opam depext -i -y mirage.3.5.2 lwt
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
WORKDIR /home/opam/qubes-mirage-firewall
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 01555ba..31dd331 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 3cf9358df911c7bc5a28846087c5359e5b550e5d0c6cf342a6e1c90545518ac6"
+echo "SHA2 last known: cae3c66d38a50671f694cd529062c538592438b95935d707b97d80b57fbfc186"
echo "(hashes should match for released versions)"
From 315fe4681e52c9b327942d06e93c9e11001fb656 Mon Sep 17 00:00:00 2001
From: Snowy Marmot
Date: Wed, 27 Nov 2019 16:01:58 +0000
Subject: [PATCH 081/281] Note that AppVM Size may need to increase
Add note that AppVM used to build from source may need a private image larger than the default 2048MB.
---
README.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/README.md b/README.md
index 7722ff9..9bd1fef 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,9 @@ See the [Deploy](#deploy) section below for installation instructions.
## Build from source
Create a new Fedora-29 AppVM (or reuse an existing one). Open a terminal.
+Note that you may need more than the default 2GB (2048MB) of storage in the private
+image of the AppVM, so you may need to increase the size in the Qube's Settings.
+
Clone this Git repository and run the `build-with-docker.sh` script:
mkdir /home/user/docker
From dad1f6a723d2ea7ad54db566f30d6896997ea314 Mon Sep 17 00:00:00 2001
From: Snowy Marmot
Date: Sat, 14 Dec 2019 00:24:55 +0000
Subject: [PATCH 082/281] Update per review
Update with suggested wording per talex5
---
README.md | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 9bd1fef..6556705 100644
--- a/README.md
+++ b/README.md
@@ -15,9 +15,8 @@ See the [Deploy](#deploy) section below for installation instructions.
## Build from source
-Create a new Fedora-29 AppVM (or reuse an existing one). Open a terminal.
-Note that you may need more than the default 2GB (2048MB) of storage in the private
-image of the AppVM, so you may need to increase the size in the Qube's Settings.
+
+Create a new Fedora-30 AppVM (or reuse an existing one). In the Qube's Settings (Basic / Disk storage), increase the private storage max size from the default 2048 MiB to 4096 MiB. Open a terminal.
Clone this Git repository and run the `build-with-docker.sh` script:
From 43656be181b8fb6660dca6075c3ba3e3eb2fe7f8 Mon Sep 17 00:00:00 2001
From: xaki23
Date: Fri, 27 Dec 2019 23:19:35 +0100
Subject: [PATCH 083/281] pin mirage to 3.5.2 for qubes-builder builds
---
Makefile.builder | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Makefile.builder b/Makefile.builder
index 8a6355b..23827af 100644
--- a/Makefile.builder
+++ b/Makefile.builder
@@ -1,2 +1,7 @@
MIRAGE_KERNEL_NAME = qubes_firewall.xen
OCAML_VERSION ?= 4.08.1
+SOURCE_BUILD_DEP := firewall-build-dep
+
+firewall-build-dep:
+ opam pin -y add mirage 3.5.2
+
From c66ee54a9fe24e1ffb05261e3b7cef3d9883ffc9 Mon Sep 17 00:00:00 2001
From: Hannes Mehnert
Date: Sat, 11 Jan 2020 14:34:25 +0100
Subject: [PATCH 084/281] revert bc7706cc97531aaf1f4dd0291a26c2307f32d647,
mirage-xen since 5.0.0 reverted the split of OS into Os_xen
---
client_net.ml | 2 +-
config.ml | 2 +-
dao.ml | 8 ++++----
memory_pressure.ml | 10 +++++-----
4 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/client_net.ml b/client_net.ml
index df436be..68fe6d3 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -4,7 +4,7 @@
open Lwt.Infix
open Fw_utils
-module Netback = Netchannel.Backend.Make(Netchannel.Xenstore.Make(Os_xen.Xs))
+module Netback = Netchannel.Backend.Make(Netchannel.Xenstore.Make(OS.Xs))
module ClientEth = Ethernet.Make(Netback)
let src = Logs.Src.create "client_net" ~doc:"Client networking"
diff --git a/config.ml b/config.ml
index 55d8c42..ae4f8f4 100644
--- a/config.ml
+++ b/config.ml
@@ -33,7 +33,7 @@ let main =
package "mirage-qubes";
package "mirage-nat" ~min:"1.2.0";
package "mirage-logs";
- package "mirage-xen" ~min:"4.0.0";
+ package "mirage-xen" ~min:"5.0.0";
]
"Unikernel.Main" (mclock @-> job)
diff --git a/dao.ml b/dao.ml
index 55d901e..a68cc64 100644
--- a/dao.ml
+++ b/dao.ml
@@ -30,7 +30,7 @@ module VifMap = struct
end
let directory ~handle dir =
- Os_xen.Xs.directory handle dir >|= function
+ OS.Xs.directory handle dir >|= function
| [""] -> [] (* XenStore client bug *)
| items -> items
@@ -46,7 +46,7 @@ let vifs ~handle domid =
| Some device_id ->
let vif = { ClientVif.domid; device_id } in
Lwt.try_bind
- (fun () -> Os_xen.Xs.read handle (Printf.sprintf "%s/%d/ip" path device_id))
+ (fun () -> OS.Xs.read handle (Printf.sprintf "%s/%d/ip" path device_id))
(fun client_ip ->
let client_ip = Ipaddr.V4.of_string_exn client_ip in
Lwt.return (Some (vif, client_ip))
@@ -61,10 +61,10 @@ let vifs ~handle domid =
)
let watch_clients fn =
- Os_xen.Xs.make () >>= fun xs ->
+ OS.Xs.make () >>= fun xs ->
let backend_vifs = "backend/vif" in
Log.info (fun f -> f "Watching %s" backend_vifs);
- Os_xen.Xs.wait xs (fun handle ->
+ OS.Xs.wait xs (fun handle ->
begin Lwt.catch
(fun () -> directory ~handle backend_vifs)
(function
diff --git a/memory_pressure.ml b/memory_pressure.ml
index 92271da..ed5b7e5 100644
--- a/memory_pressure.ml
+++ b/memory_pressure.ml
@@ -6,7 +6,7 @@ open Lwt
let src = Logs.Src.create "memory_pressure" ~doc:"Memory pressure monitor"
module Log = (val Logs.src_log src : Logs.LOG)
-let total_pages = Os_xen.MM.Heap_pages.total ()
+let total_pages = OS.MM.Heap_pages.total ()
let pagesize_kb = Io_page.page_size / 1024
let meminfo ~used =
@@ -23,7 +23,7 @@ let meminfo ~used =
let report_mem_usage used =
Lwt.async (fun () ->
- let open Os_xen in
+ let open OS in
Xs.make () >>= fun xs ->
Xs.immediate xs (fun h ->
Xs.write h "memory/meminfo" (meminfo ~used)
@@ -32,16 +32,16 @@ let report_mem_usage used =
let init () =
Gc.full_major ();
- let used = Os_xen.MM.Heap_pages.used () in
+ let used = OS.MM.Heap_pages.used () in
report_mem_usage used
let status () =
- let used = Os_xen.MM.Heap_pages.used () |> float_of_int in
+ let used = OS.MM.Heap_pages.used () |> float_of_int in
let frac = used /. float_of_int total_pages in
if frac < 0.9 then `Ok
else (
Gc.full_major ();
- let used = Os_xen.MM.Heap_pages.used () in
+ let used = OS.MM.Heap_pages.used () in
report_mem_usage used;
let frac = float_of_int used /. float_of_int total_pages in
if frac > 0.9 then `Memory_critical
From 0f476c4d7b99b13527bdb9b6270cec9a9bd2fc13 Mon Sep 17 00:00:00 2001
From: Hannes Mehnert
Date: Sat, 11 Jan 2020 15:36:02 +0100
Subject: [PATCH 085/281] mirage-nat 2.0.0 and mirage-qubes 0.8.0 compatibility
---
client_net.ml | 20 +++++++++++---------
client_net.mli | 10 +++++-----
firewall.ml | 9 ++++++---
my_nat.ml | 8 +++-----
my_nat.mli | 2 +-
unikernel.ml | 21 ++++++++++-----------
uplink.ml | 13 ++++++++-----
uplink.mli | 4 ++--
8 files changed, 46 insertions(+), 41 deletions(-)
diff --git a/client_net.ml b/client_net.ml
index 68fe6d3..5cd819d 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -56,12 +56,13 @@ let input_arp ~fixed_arp ~iface request =
iface#writev `ARP (fun b -> Arp_packet.encode_into response b; Arp_packet.size)
(** Handle an IPv4 packet from the client. *)
-let input_ipv4 ~iface ~router packet =
- match Nat_packet.of_ipv4_packet packet with
+let input_ipv4 get_ts cache ~iface ~router packet =
+ match Nat_packet.of_ipv4_packet cache ~now:(get_ts ()) packet with
| Error e ->
Log.warn (fun f -> f "Ignored unknown IPv4 message: %a" Nat_packet.pp_error e);
Lwt.return ()
- | Ok packet ->
+ | Ok None -> Lwt.return ()
+ | Ok (Some packet) ->
let `IPv4 (ip, _) = packet in
let src = ip.Ipv4_packet.src in
if src = iface#other_ip then Firewall.ipv4_from_client router ~src:iface packet
@@ -72,7 +73,7 @@ let input_ipv4 ~iface ~router packet =
)
(** Connect to a new client's interface and listen for incoming frames. *)
-let add_vif { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks =
+let add_vif get_ts { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks =
Netback.make ~domid ~device_id >>= fun backend ->
Log.info (fun f -> f "Client %d (IP: %s) ready" domid (Ipaddr.V4.to_string client_ip));
ClientEth.connect backend >>= fun eth ->
@@ -83,6 +84,7 @@ let add_vif { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks
Router.add_client router iface >>= fun () ->
Cleanup.on_cleanup cleanup_tasks (fun () -> Router.remove_client router iface);
let fixed_arp = Client_eth.ARP.create ~net:client_eth iface in
+ let fragment_cache = Fragments.Cache.create (256 * 1024) in
Netback.listen backend ~header_size:Ethernet_wire.sizeof_ethernet (fun frame ->
match Ethernet_packet.Unmarshal.of_cstruct frame with
| exception ex ->
@@ -94,18 +96,18 @@ let add_vif { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks
| Ok (eth, payload) ->
match eth.Ethernet_packet.ethertype with
| `ARP -> input_arp ~fixed_arp ~iface payload
- | `IPv4 -> input_ipv4 ~iface ~router payload
+ | `IPv4 -> input_ipv4 get_ts fragment_cache ~iface ~router payload
| `IPv6 -> return () (* TODO: oh no! *)
)
>|= or_raise "Listen on client interface" Netback.pp_error
(** A new client VM has been found in XenStore. Find its interface and connect to it. *)
-let add_client ~router vif client_ip =
+let add_client get_ts ~router vif client_ip =
let cleanup_tasks = Cleanup.create () in
Log.info (fun f -> f "add client vif %a with IP %a" Dao.ClientVif.pp vif Ipaddr.V4.pp client_ip);
Lwt.async (fun () ->
Lwt.catch (fun () ->
- add_vif vif ~client_ip ~router ~cleanup_tasks
+ add_vif get_ts vif ~client_ip ~router ~cleanup_tasks
)
(fun ex ->
Log.warn (fun f -> f "Error with client %a: %s"
@@ -116,7 +118,7 @@ let add_client ~router vif client_ip =
cleanup_tasks
(** Watch XenStore for notifications of new clients. *)
-let listen router =
+let listen get_ts router =
Dao.watch_clients (fun new_set ->
(* Check for removed clients *)
!clients |> Dao.VifMap.iter (fun key cleanup ->
@@ -129,7 +131,7 @@ let listen router =
(* Check for added clients *)
new_set |> Dao.VifMap.iter (fun key ip_addr ->
if not (Dao.VifMap.mem key !clients) then (
- let cleanup = add_client ~router key ip_addr in
+ let cleanup = add_client get_ts ~router key ip_addr in
clients := !clients |> Dao.VifMap.add key cleanup
)
)
diff --git a/client_net.mli b/client_net.mli
index 7bc2660..97ebd68 100644
--- a/client_net.mli
+++ b/client_net.mli
@@ -3,8 +3,8 @@
(** Handling client VMs. *)
-val listen : Router.t -> 'a Lwt.t
-(** [listen router] is a thread that watches for clients being added to and
- removed from XenStore. Clients are connected to the client network and
- packets are sent via [router]. We ensure the source IP address is correct
- before routing a packet. *)
+val listen : (unit -> int64) -> Router.t -> 'a Lwt.t
+(** [listen get_timestamp router] is a thread that watches for clients being
+ added to and removed from XenStore. Clients are connected to the client
+ network and packets are sent via [router]. We ensure the source IP address
+ is correct before routing a packet. *)
diff --git a/firewall.ml b/firewall.ml
index 77656d2..beaa948 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -15,6 +15,7 @@ let transmit_ipv4 packet iface =
(fun () ->
Lwt.catch
(fun () ->
+ let fragments = ref [] in
iface#writev `IPv4 (fun b ->
match Nat_packet.into_cstruct packet b with
| Error e ->
@@ -22,9 +23,11 @@ let transmit_ipv4 packet iface =
Ipaddr.V4.pp iface#other_ip
Nat_packet.pp_error e);
0
- | Ok n -> n
- )
- )
+ | Ok (n, frags) -> fragments := frags ; n) >>= fun () ->
+ Lwt_list.iter_s (fun f ->
+ let size = Cstruct.len f in
+ iface#writev `IPv4 (fun b -> Cstruct.blit f 0 b 0 size ; size))
+ !fragments)
(fun ex ->
Log.warn (fun f -> f "Failed to write packet to %a: %s"
Ipaddr.V4.pp iface#other_ip
diff --git a/my_nat.ml b/my_nat.ml
index bfaf702..02a4b5a 100644
--- a/my_nat.ml
+++ b/my_nat.ml
@@ -15,14 +15,13 @@ module Nat = Mirage_nat_lru
type t = {
table : Nat.t;
- get_time : unit -> Mirage_nat.time;
}
-let create ~get_time ~max_entries =
+let create ~max_entries =
let tcp_size = 7 * max_entries / 8 in
let udp_size = max_entries - tcp_size in
Nat.empty ~tcp_size ~udp_size ~icmp_size:100 >|= fun table ->
- { get_time; table }
+ { table }
let translate t packet =
Nat.translate t.table packet >|= function
@@ -41,10 +40,9 @@ let reset t =
Nat.reset t.table
let add_nat_rule_and_translate t ~xl_host action packet =
- let now = t.get_time () in
let apply_action xl_port =
Lwt.catch (fun () ->
- Nat.add t.table ~now packet (xl_host, xl_port) action
+ Nat.add t.table packet (xl_host, xl_port) action
)
(function
| Out_of_memory -> Lwt.return (Error `Out_of_memory)
diff --git a/my_nat.mli b/my_nat.mli
index 770eaa0..cdc5eda 100644
--- a/my_nat.mli
+++ b/my_nat.mli
@@ -10,7 +10,7 @@ type action = [
| `Redirect of Mirage_nat.endpoint
]
-val create : get_time:(unit -> Mirage_nat.time) -> max_entries:int -> t Lwt.t
+val create : max_entries:int -> t Lwt.t
val reset : t -> unit Lwt.t
val translate : t -> Nat_packet.t -> Nat_packet.t option Lwt.t
val add_nat_rule_and_translate : t -> xl_host:Ipaddr.V4.t ->
diff --git a/unikernel.ml b/unikernel.ml
index 84cac6d..25e4739 100644
--- a/unikernel.ml
+++ b/unikernel.ml
@@ -11,11 +11,11 @@ module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
module Uplink = Uplink.Make(Clock)
(* Set up networking and listen for incoming packets. *)
- let network ~clock nat qubesDB =
+ let network nat qubesDB =
(* Read configuration from QubesDB *)
Dao.read_network_config qubesDB >>= fun config ->
(* Initialise connection to NetVM *)
- Uplink.connect ~clock config >>= fun uplink ->
+ Uplink.connect config >>= fun uplink ->
(* Report success *)
Dao.set_iptables_error qubesDB "" >>= fun () ->
(* Set up client-side networking *)
@@ -29,8 +29,8 @@ module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
in
(* Handle packets from both networks *)
Lwt.choose [
- Client_net.listen router;
- Uplink.listen uplink router
+ Client_net.listen Clock.elapsed_ns router;
+ Uplink.listen uplink Clock.elapsed_ns router
]
(* We don't use the GUI, but it's interesting to keep an eye on it.
@@ -41,7 +41,7 @@ module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
(fun () ->
gui >>= fun gui ->
Log.info (fun f -> f "GUI agent connected");
- GUI.listen gui
+ GUI.listen gui ()
)
(fun `Cant_happen -> assert false)
(fun ex ->
@@ -51,8 +51,8 @@ module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
)
(* Main unikernel entry point (called from auto-generated main.ml). *)
- let start clock =
- let start_time = Clock.elapsed_ns clock in
+ let start _clock =
+ let start_time = Clock.elapsed_ns () in
(* Start qrexec agent, GUI agent and QubesDB agent in parallel *)
let qrexec = RExec.connect ~domid:0 () in
GUI.connect ~domid:0 () |> watch_gui;
@@ -63,7 +63,7 @@ module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
qubesDB >>= fun qubesDB ->
let startup_time =
let (-) = Int64.sub in
- let time_in_ns = Clock.elapsed_ns clock - start_time in
+ let time_in_ns = Clock.elapsed_ns () - start_time in
Int64.to_float time_in_ns /. 1e9
in
Log.info (fun f -> f "QubesDB and qrexec agents connected in %.3f s" startup_time);
@@ -72,10 +72,9 @@ module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
OS.Lifecycle.await_shutdown_request () >>= fun (`Poweroff | `Reboot) ->
return () in
(* Set up networking *)
- let get_time () = Clock.elapsed_ns clock in
let max_entries = Key_gen.nat_table_size () in
- My_nat.create ~get_time ~max_entries >>= fun nat ->
- let net_listener = network ~clock nat qubesDB in
+ My_nat.create ~max_entries >>= fun nat ->
+ let net_listener = network nat qubesDB in
(* Report memory usage to XenStore *)
Memory_pressure.init ();
(* Run until something fails or we get a shutdown request. *)
diff --git a/uplink.ml b/uplink.ml
index 06d4df3..92b46a6 100644
--- a/uplink.ml
+++ b/uplink.ml
@@ -17,6 +17,7 @@ module Make(Clock : Mirage_clock_lwt.MCLOCK) = struct
eth : Eth.t;
arp : Arp.t;
interface : interface;
+ fragments : Fragments.Cache.t;
}
class netvm_iface eth mac ~my_ip ~other_ip : interface = object
@@ -31,13 +32,13 @@ module Make(Clock : Mirage_clock_lwt.MCLOCK) = struct
)
end
- let listen t router =
+ let listen t get_ts router =
Netif.listen t.net ~header_size:Ethernet_wire.sizeof_ethernet (fun frame ->
(* Handle one Ethernet frame from NetVM *)
Eth.input t.eth
~arpv4:(Arp.input t.arp)
~ipv4:(fun ip ->
- match Nat_packet.of_ipv4_packet ip with
+ match Nat_packet.of_ipv4_packet t.fragments ~now:(get_ts ()) ip with
| exception ex ->
Log.err (fun f -> f "Error unmarshalling ethernet frame from uplink: %s@.%a" (Printexc.to_string ex)
Cstruct.hexdump_pp frame
@@ -46,7 +47,8 @@ module Make(Clock : Mirage_clock_lwt.MCLOCK) = struct
| Error e ->
Log.warn (fun f -> f "Ignored unknown IPv4 message from uplink: %a" Nat_packet.pp_error e);
Lwt.return ()
- | Ok packet ->
+ | Ok None -> Lwt.return_unit
+ | Ok (Some packet) ->
Firewall.ipv4_from_netvm router packet
)
~ipv6:(fun _ip -> return ())
@@ -55,7 +57,7 @@ module Make(Clock : Mirage_clock_lwt.MCLOCK) = struct
let interface t = t.interface
- let connect ~clock:_ config =
+ let connect config =
let ip = config.Dao.uplink_our_ip in
Netif.connect "0" >>= fun net ->
Eth.connect net >>= fun eth ->
@@ -67,5 +69,6 @@ module Make(Clock : Mirage_clock_lwt.MCLOCK) = struct
let interface = new netvm_iface eth netvm_mac
~my_ip:ip
~other_ip:config.Dao.uplink_netvm_ip in
- return { net; eth; arp; interface }
+ let fragments = Fragments.Cache.create (256 * 1024) in
+ return { net; eth; arp; interface ; fragments }
end
diff --git a/uplink.mli b/uplink.mli
index 6e2f5f4..14fbd86 100644
--- a/uplink.mli
+++ b/uplink.mli
@@ -8,12 +8,12 @@ open Fw_utils
module Make(Clock : Mirage_clock_lwt.MCLOCK) : sig
type t
- val connect : clock:Clock.t -> Dao.network_config -> t Lwt.t
+ val connect : Dao.network_config -> t Lwt.t
(** Connect to our NetVM (gateway). *)
val interface : t -> interface
(** The network interface to NetVM. *)
- val listen : t -> Router.t -> unit Lwt.t
+ val listen : t -> (unit -> int64) -> Router.t -> unit Lwt.t
(** Handle incoming frames from NetVM. *)
end
From 3fc418e80cafc8b6cc6f137e613d5f04b23aa825 Mon Sep 17 00:00:00 2001
From: Hannes Mehnert
Date: Sat, 11 Jan 2020 15:39:20 +0100
Subject: [PATCH 086/281] qualify all return with Lwt, use Lwt.return_unit
where possible
---
client_net.ml | 18 +++++++++---------
config.ml | 4 ++--
dao.ml | 7 +++----
firewall.ml | 25 ++++++++++++-------------
fw_utils.ml | 3 ---
unikernel.ml | 4 ++--
uplink.ml | 6 +++---
7 files changed, 31 insertions(+), 36 deletions(-)
diff --git a/client_net.ml b/client_net.ml
index 5cd819d..4665aa1 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -23,7 +23,7 @@ let writev eth dst proto fillfn =
(* Usually Netback_shutdown, because the client disconnected *)
Log.err (fun f -> f "uncaught exception trying to send to client: @[%s@]"
(Printexc.to_string ex));
- Lwt.return ()
+ Lwt.return_unit
)
class client_iface eth ~domid ~gateway_ip ~client_ip client_mac : client_link =
@@ -48,10 +48,10 @@ let input_arp ~fixed_arp ~iface request =
match Arp_packet.decode request with
| Error e ->
Log.warn (fun f -> f "Ignored unknown ARP message: %a" Arp_packet.pp_error e);
- Lwt.return ()
+ Lwt.return_unit
| Ok arp ->
match Client_eth.ARP.input fixed_arp arp with
- | None -> return ()
+ | None -> Lwt.return_unit
| Some response ->
iface#writev `ARP (fun b -> Arp_packet.encode_into response b; Arp_packet.size)
@@ -60,8 +60,8 @@ let input_ipv4 get_ts cache ~iface ~router packet =
match Nat_packet.of_ipv4_packet cache ~now:(get_ts ()) packet with
| Error e ->
Log.warn (fun f -> f "Ignored unknown IPv4 message: %a" Nat_packet.pp_error e);
- Lwt.return ()
- | Ok None -> Lwt.return ()
+ Lwt.return_unit
+ | Ok None -> Lwt.return_unit
| Ok (Some packet) ->
let `IPv4 (ip, _) = packet in
let src = ip.Ipv4_packet.src in
@@ -69,7 +69,7 @@ let input_ipv4 get_ts cache ~iface ~router packet =
else (
Log.warn (fun f -> f "Incorrect source IP %a in IP packet from %a (dropping)"
Ipaddr.V4.pp src Ipaddr.V4.pp iface#other_ip);
- return ()
+ Lwt.return_unit
)
(** Connect to a new client's interface and listen for incoming frames. *)
@@ -92,12 +92,12 @@ let add_vif get_ts { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanu
Cstruct.hexdump_pp frame
);
Lwt.return_unit
- | Error err -> Log.warn (fun f -> f "Invalid Ethernet frame: %s" err); return ()
+ | Error err -> Log.warn (fun f -> f "Invalid Ethernet frame: %s" err); Lwt.return_unit
| Ok (eth, payload) ->
match eth.Ethernet_packet.ethertype with
| `ARP -> input_arp ~fixed_arp ~iface payload
| `IPv4 -> input_ipv4 get_ts fragment_cache ~iface ~router payload
- | `IPv6 -> return () (* TODO: oh no! *)
+ | `IPv6 -> Lwt.return_unit (* TODO: oh no! *)
)
>|= or_raise "Listen on client interface" Netback.pp_error
@@ -112,7 +112,7 @@ let add_client get_ts ~router vif client_ip =
(fun ex ->
Log.warn (fun f -> f "Error with client %a: %s"
Dao.ClientVif.pp vif (Printexc.to_string ex));
- return ()
+ Lwt.return_unit
)
);
cleanup_tasks
diff --git a/config.ml b/config.ml
index ae4f8f4..5e284fb 100644
--- a/config.ml
+++ b/config.ml
@@ -30,8 +30,8 @@ let main =
package "netchannel" ~min:"1.11.0";
package "mirage-net-xen";
package "ipaddr" ~min:"4.0.0";
- package "mirage-qubes";
- package "mirage-nat" ~min:"1.2.0";
+ package "mirage-qubes" ~min:"0.8.0";
+ package "mirage-nat" ~min:"2.0.0";
package "mirage-logs";
package "mirage-xen" ~min:"5.0.0";
]
diff --git a/dao.ml b/dao.ml
index a68cc64..a34b8b7 100644
--- a/dao.ml
+++ b/dao.ml
@@ -3,7 +3,6 @@
open Lwt.Infix
open Qubes
-open Fw_utils
open Astring
let src = Logs.Src.create "dao" ~doc:"QubesDB data access"
@@ -68,13 +67,13 @@ let watch_clients fn =
begin Lwt.catch
(fun () -> directory ~handle backend_vifs)
(function
- | Xs_protocol.Enoent _ -> return []
- | ex -> fail ex)
+ | Xs_protocol.Enoent _ -> Lwt.return []
+ | ex -> Lwt.fail ex)
end >>= fun items ->
Lwt_list.map_p (vifs ~handle) items >>= fun items ->
fn (List.concat items |> VifMap.of_list);
(* Wait for further updates *)
- fail Xs_protocol.Eagain
+ Lwt.fail Xs_protocol.Eagain
)
type network_config = {
diff --git a/firewall.ml b/firewall.ml
index beaa948..e80d7a3 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -1,7 +1,6 @@
(* Copyright (C) 2015, Thomas Leonard
See the README file for details. *)
-open Fw_utils
open Packet
open Lwt.Infix
@@ -32,7 +31,7 @@ let transmit_ipv4 packet iface =
Log.warn (fun f -> f "Failed to write packet to %a: %s"
Ipaddr.V4.pp iface#other_ip
(Printexc.to_string ex));
- Lwt.return ()
+ Lwt.return_unit
)
)
(fun ex ->
@@ -40,7 +39,7 @@ let transmit_ipv4 packet iface =
(Printexc.to_string ex)
Nat_packet.pp packet
);
- Lwt.return ()
+ Lwt.return_unit
)
let forward_ipv4 t packet =
@@ -127,19 +126,19 @@ let add_nat_and_forward_ipv4 t packet =
| Ok packet -> forward_ipv4 t packet
| Error e ->
Log.warn (fun f -> f "Failed to add NAT rewrite rule: %s (%a)" e pp_header packet);
- Lwt.return ()
+ Lwt.return_unit
(* Add a NAT rule to redirect this conversation to [host:port] instead of us. *)
let nat_to t ~host ~port packet =
match Router.resolve t host with
- | Ipaddr.V6 _ -> Log.warn (fun f -> f "Cannot NAT with IPv6"); Lwt.return ()
+ | Ipaddr.V6 _ -> Log.warn (fun f -> f "Cannot NAT with IPv6"); Lwt.return_unit
| Ipaddr.V4 target ->
let xl_host = t.Router.uplink#my_ip in
My_nat.add_nat_rule_and_translate t.Router.nat ~xl_host (`Redirect (target, port)) packet >>= function
| Ok packet -> forward_ipv4 t packet
| Error e ->
Log.warn (fun f -> f "Failed to add NAT redirect rule: %s (%a)" e pp_header packet);
- Lwt.return ()
+ Lwt.return_unit
(* Handle incoming packets *)
@@ -150,12 +149,12 @@ let apply_rules t rules ~dst info =
| `Accept, (`External _ | `NetVM) -> transmit_ipv4 packet t.Router.uplink
| `Accept, `Firewall ->
Log.warn (fun f -> f "Bad rule: firewall can't accept packets %a" (pp_packet t) info);
- return ()
+ Lwt.return_unit
| `NAT, _ -> add_nat_and_forward_ipv4 t packet
| `NAT_to (host, port), _ -> nat_to t packet ~host ~port
| `Drop reason, _ ->
Log.info (fun f -> f "Dropped packet (%s) %a" reason (pp_packet t) info);
- return ()
+ Lwt.return_unit
let handle_low_memory t =
match Memory_pressure.status () with
@@ -167,7 +166,7 @@ let handle_low_memory t =
let ipv4_from_client t ~src packet =
handle_low_memory t >>= function
- | `Memory_critical -> return ()
+ | `Memory_critical -> Lwt.return_unit
| `Ok ->
(* Check for existing NAT entry for this packet *)
translate t packet >>= function
@@ -177,23 +176,23 @@ let ipv4_from_client t ~src packet =
let `IPv4 (ip, _transport) = packet in
let dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst) in
match classify ~src:(resolve_client src) ~dst:(resolve_host dst) packet with
- | None -> return ()
+ | None -> Lwt.return_unit
| Some info -> apply_rules t Rules.from_client ~dst info
let ipv4_from_netvm t packet =
handle_low_memory t >>= function
- | `Memory_critical -> return ()
+ | `Memory_critical -> Lwt.return_unit
| `Ok ->
let `IPv4 (ip, _transport) = packet in
let src = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.src) in
let dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst) in
match classify ~src ~dst:(resolve_host dst) packet with
- | None -> return ()
+ | None -> Lwt.return_unit
| Some info ->
match src with
| `Client _ | `Firewall ->
Log.warn (fun f -> f "Frame from NetVM has internal source IP address! %a" (pp_packet t) info);
- return ()
+ Lwt.return_unit
| `External _ | `NetVM as src ->
translate t packet >>= function
| Some frame -> forward_ipv4 t frame
diff --git a/fw_utils.ml b/fw_utils.ml
index c034e72..9c5bab4 100644
--- a/fw_utils.ml
+++ b/fw_utils.ml
@@ -41,9 +41,6 @@ let error fmt =
let err s = Failure s in
Printf.ksprintf err fmt
-let return = Lwt.return
-let fail = Lwt.fail
-
let or_raise msg pp = function
| Ok x -> x
| Error e -> failwith (Fmt.strf "%s: %a" msg pp e)
diff --git a/unikernel.ml b/unikernel.ml
index 25e4739..2b20c9f 100644
--- a/unikernel.ml
+++ b/unikernel.ml
@@ -46,7 +46,7 @@ module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
(fun `Cant_happen -> assert false)
(fun ex ->
Log.warn (fun f -> f "GUI thread failed: %s" (Printexc.to_string ex));
- return ()
+ Lwt.return_unit
)
)
@@ -70,7 +70,7 @@ module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
(* Watch for shutdown requests from Qubes *)
let shutdown_rq =
OS.Lifecycle.await_shutdown_request () >>= fun (`Poweroff | `Reboot) ->
- return () in
+ Lwt.return_unit in
(* Set up networking *)
let max_entries = Key_gen.nat_table_size () in
My_nat.create ~max_entries >>= fun nat ->
diff --git a/uplink.ml b/uplink.ml
index 92b46a6..042fc84 100644
--- a/uplink.ml
+++ b/uplink.ml
@@ -46,12 +46,12 @@ module Make(Clock : Mirage_clock_lwt.MCLOCK) = struct
Lwt.return_unit
| Error e ->
Log.warn (fun f -> f "Ignored unknown IPv4 message from uplink: %a" Nat_packet.pp_error e);
- Lwt.return ()
+ Lwt.return_unit
| Ok None -> Lwt.return_unit
| Ok (Some packet) ->
Firewall.ipv4_from_netvm router packet
)
- ~ipv6:(fun _ip -> return ())
+ ~ipv6:(fun _ip -> Lwt.return_unit)
frame
) >|= or_raise "Uplink listen loop" Netif.pp_error
@@ -70,5 +70,5 @@ module Make(Clock : Mirage_clock_lwt.MCLOCK) = struct
~my_ip:ip
~other_ip:config.Dao.uplink_netvm_ip in
let fragments = Fragments.Cache.create (256 * 1024) in
- return { net; eth; arp; interface ; fragments }
+ Lwt.return { net; eth; arp; interface ; fragments }
end
From 28bda78d209d8a436b3e6eff8a2142cac68a3093 Mon Sep 17 00:00:00 2001
From: Hannes Mehnert
Date: Sat, 11 Jan 2020 15:46:02 +0100
Subject: [PATCH 087/281] fix deprecation warnings (Mirage_clock_lwt ->
Mirage_clock)
---
unikernel.ml | 2 +-
uplink.ml | 2 +-
uplink.mli | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/unikernel.ml b/unikernel.ml
index 2b20c9f..27f772a 100644
--- a/unikernel.ml
+++ b/unikernel.ml
@@ -7,7 +7,7 @@ open Qubes
let src = Logs.Src.create "unikernel" ~doc:"Main unikernel code"
module Log = (val Logs.src_log src : Logs.LOG)
-module Main (Clock : Mirage_clock_lwt.MCLOCK) = struct
+module Main (Clock : Mirage_clock.MCLOCK) = struct
module Uplink = Uplink.Make(Clock)
(* Set up networking and listen for incoming packets. *)
diff --git a/uplink.ml b/uplink.ml
index 042fc84..1fde66b 100644
--- a/uplink.ml
+++ b/uplink.ml
@@ -9,7 +9,7 @@ module Eth = Ethernet.Make(Netif)
let src = Logs.Src.create "uplink" ~doc:"Network connection to NetVM"
module Log = (val Logs.src_log src : Logs.LOG)
-module Make(Clock : Mirage_clock_lwt.MCLOCK) = struct
+module Make(Clock : Mirage_clock.MCLOCK) = struct
module Arp = Arp.Make(Eth)(OS.Time)
type t = {
diff --git a/uplink.mli b/uplink.mli
index 14fbd86..0f494dd 100644
--- a/uplink.mli
+++ b/uplink.mli
@@ -5,7 +5,7 @@
open Fw_utils
-module Make(Clock : Mirage_clock_lwt.MCLOCK) : sig
+module Make(Clock : Mirage_clock.MCLOCK) : sig
type t
val connect : Dao.network_config -> t Lwt.t
From 730957d19b00b66e03f6114915f01c45b13c88c3 Mon Sep 17 00:00:00 2001
From: Hannes Mehnert
Date: Sat, 11 Jan 2020 15:46:22 +0100
Subject: [PATCH 088/281] upgrade opam repository to current head and mirage to
3.7.4
---
Dockerfile | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index c6ef858..3125969 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,9 +7,9 @@ FROM ocurrent/opam@sha256:3f3ce7e577a94942c7f9c63cbdd1ecbfe0ea793f581f69047f3155
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN cd ~/opam-repository && git fetch origin master && git reset --hard 5eed470abc5c7991e448c9653698c03d6ea146d1 && opam update
+RUN cd ~/opam-repository && git fetch origin master && git reset --hard d205c265cee9a86869259180fd2238da98370430 && opam update
-RUN opam depext -i -y mirage.3.5.2 lwt
+RUN opam depext -i -y mirage.3.7.4 lwt
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
WORKDIR /home/opam/qubes-mirage-firewall
From a734bcd2d3d87a93ce7cfd60d04c730520367d70 Mon Sep 17 00:00:00 2001
From: Hannes Mehnert
Date: Sat, 11 Jan 2020 16:01:08 +0100
Subject: [PATCH 089/281] [ci skip] adjust expected sha256
---
build-with-docker.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 31dd331..d2944fe 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: cae3c66d38a50671f694cd529062c538592438b95935d707b97d80b57fbfc186"
+echo "SHA2 last known: 8a337e61e7d093f7c1f0fa5fe277dace4d606bfa06cfde3f2d61d6bdee6eefbc"
echo "(hashes should match for released versions)"
From 48b38fa992cfe2567c21668ff967cc006dfdc73d Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Mon, 13 Jan 2020 09:49:37 +0000
Subject: [PATCH 090/281] Fix Lwt.4.5.0 in the Dockerfile for faster builds
Otherwise, it installs Lwt 5 and then has to downgrade it in the next
step.
---
Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Dockerfile b/Dockerfile
index 3125969..8a9ed27 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -9,7 +9,7 @@ FROM ocurrent/opam@sha256:3f3ce7e577a94942c7f9c63cbdd1ecbfe0ea793f581f69047f3155
# latest versions.
RUN cd ~/opam-repository && git fetch origin master && git reset --hard d205c265cee9a86869259180fd2238da98370430 && opam update
-RUN opam depext -i -y mirage.3.7.4 lwt
+RUN opam depext -i -y mirage.3.7.4 lwt.4.5.0
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
WORKDIR /home/opam/qubes-mirage-firewall
From ab3508a9367dcc69bff871521fcad5090c03eb3a Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Mon, 13 Jan 2020 09:50:48 +0000
Subject: [PATCH 091/281] Remove unused Clock argument to Uplink
---
build-with-docker.sh | 2 +-
unikernel.ml | 2 -
uplink.ml | 118 +++++++++++++++++++++----------------------
uplink.mli | 16 +++---
4 files changed, 66 insertions(+), 72 deletions(-)
diff --git a/build-with-docker.sh b/build-with-docker.sh
index d2944fe..5b1bc30 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 8a337e61e7d093f7c1f0fa5fe277dace4d606bfa06cfde3f2d61d6bdee6eefbc"
+echo "SHA2 last known: 6f8f0f19ba62bf5312039f2904ea8696584f8ff49443dec098facf261449ebf2"
echo "(hashes should match for released versions)"
diff --git a/unikernel.ml b/unikernel.ml
index 27f772a..6eaca4e 100644
--- a/unikernel.ml
+++ b/unikernel.ml
@@ -8,8 +8,6 @@ let src = Logs.Src.create "unikernel" ~doc:"Main unikernel code"
module Log = (val Logs.src_log src : Logs.LOG)
module Main (Clock : Mirage_clock.MCLOCK) = struct
- module Uplink = Uplink.Make(Clock)
-
(* Set up networking and listen for incoming packets. *)
let network nat qubesDB =
(* Read configuration from QubesDB *)
diff --git a/uplink.ml b/uplink.ml
index 1fde66b..039e6bd 100644
--- a/uplink.ml
+++ b/uplink.ml
@@ -9,66 +9,64 @@ module Eth = Ethernet.Make(Netif)
let src = Logs.Src.create "uplink" ~doc:"Network connection to NetVM"
module Log = (val Logs.src_log src : Logs.LOG)
-module Make(Clock : Mirage_clock.MCLOCK) = struct
- module Arp = Arp.Make(Eth)(OS.Time)
+module Arp = Arp.Make(Eth)(OS.Time)
- type t = {
- net : Netif.t;
- eth : Eth.t;
- arp : Arp.t;
- interface : interface;
- fragments : Fragments.Cache.t;
- }
+type t = {
+ net : Netif.t;
+ eth : Eth.t;
+ arp : Arp.t;
+ interface : interface;
+ fragments : Fragments.Cache.t;
+}
- class netvm_iface eth mac ~my_ip ~other_ip : interface = object
- val queue = FrameQ.create (Ipaddr.V4.to_string other_ip)
- method my_mac = Eth.mac eth
- method my_ip = my_ip
- method other_ip = other_ip
- method writev ethertype fillfn =
- FrameQ.send queue (fun () ->
- mac >>= fun dst ->
- Eth.write eth dst ethertype fillfn >|= or_raise "Write to uplink" Eth.pp_error
- )
- end
-
- let listen t get_ts router =
- Netif.listen t.net ~header_size:Ethernet_wire.sizeof_ethernet (fun frame ->
- (* Handle one Ethernet frame from NetVM *)
- Eth.input t.eth
- ~arpv4:(Arp.input t.arp)
- ~ipv4:(fun ip ->
- match Nat_packet.of_ipv4_packet t.fragments ~now:(get_ts ()) ip with
- | exception ex ->
- Log.err (fun f -> f "Error unmarshalling ethernet frame from uplink: %s@.%a" (Printexc.to_string ex)
- Cstruct.hexdump_pp frame
- );
- Lwt.return_unit
- | Error e ->
- Log.warn (fun f -> f "Ignored unknown IPv4 message from uplink: %a" Nat_packet.pp_error e);
- Lwt.return_unit
- | Ok None -> Lwt.return_unit
- | Ok (Some packet) ->
- Firewall.ipv4_from_netvm router packet
- )
- ~ipv6:(fun _ip -> Lwt.return_unit)
- frame
- ) >|= or_raise "Uplink listen loop" Netif.pp_error
-
- let interface t = t.interface
-
- let connect config =
- let ip = config.Dao.uplink_our_ip in
- Netif.connect "0" >>= fun net ->
- Eth.connect net >>= fun eth ->
- Arp.connect eth >>= fun arp ->
- Arp.add_ip arp ip >>= fun () ->
- let netvm_mac =
- Arp.query arp config.Dao.uplink_netvm_ip
- >|= or_raise "Getting MAC of our NetVM" Arp.pp_error in
- let interface = new netvm_iface eth netvm_mac
- ~my_ip:ip
- ~other_ip:config.Dao.uplink_netvm_ip in
- let fragments = Fragments.Cache.create (256 * 1024) in
- Lwt.return { net; eth; arp; interface ; fragments }
+class netvm_iface eth mac ~my_ip ~other_ip : interface = object
+ val queue = FrameQ.create (Ipaddr.V4.to_string other_ip)
+ method my_mac = Eth.mac eth
+ method my_ip = my_ip
+ method other_ip = other_ip
+ method writev ethertype fillfn =
+ FrameQ.send queue (fun () ->
+ mac >>= fun dst ->
+ Eth.write eth dst ethertype fillfn >|= or_raise "Write to uplink" Eth.pp_error
+ )
end
+
+let listen t get_ts router =
+ Netif.listen t.net ~header_size:Ethernet_wire.sizeof_ethernet (fun frame ->
+ (* Handle one Ethernet frame from NetVM *)
+ Eth.input t.eth
+ ~arpv4:(Arp.input t.arp)
+ ~ipv4:(fun ip ->
+ match Nat_packet.of_ipv4_packet t.fragments ~now:(get_ts ()) ip with
+ | exception ex ->
+ Log.err (fun f -> f "Error unmarshalling ethernet frame from uplink: %s@.%a" (Printexc.to_string ex)
+ Cstruct.hexdump_pp frame
+ );
+ Lwt.return_unit
+ | Error e ->
+ Log.warn (fun f -> f "Ignored unknown IPv4 message from uplink: %a" Nat_packet.pp_error e);
+ Lwt.return_unit
+ | Ok None -> Lwt.return_unit
+ | Ok (Some packet) ->
+ Firewall.ipv4_from_netvm router packet
+ )
+ ~ipv6:(fun _ip -> Lwt.return_unit)
+ frame
+ ) >|= or_raise "Uplink listen loop" Netif.pp_error
+
+let interface t = t.interface
+
+let connect config =
+ let ip = config.Dao.uplink_our_ip in
+ Netif.connect "0" >>= fun net ->
+ Eth.connect net >>= fun eth ->
+ Arp.connect eth >>= fun arp ->
+ Arp.add_ip arp ip >>= fun () ->
+ let netvm_mac =
+ Arp.query arp config.Dao.uplink_netvm_ip
+ >|= or_raise "Getting MAC of our NetVM" Arp.pp_error in
+ let interface = new netvm_iface eth netvm_mac
+ ~my_ip:ip
+ ~other_ip:config.Dao.uplink_netvm_ip in
+ let fragments = Fragments.Cache.create (256 * 1024) in
+ Lwt.return { net; eth; arp; interface ; fragments }
diff --git a/uplink.mli b/uplink.mli
index 0f494dd..776b1a4 100644
--- a/uplink.mli
+++ b/uplink.mli
@@ -5,15 +5,13 @@
open Fw_utils
-module Make(Clock : Mirage_clock.MCLOCK) : sig
- type t
+type t
- val connect : Dao.network_config -> t Lwt.t
- (** Connect to our NetVM (gateway). *)
+val connect : Dao.network_config -> t Lwt.t
+(** Connect to our NetVM (gateway). *)
- val interface : t -> interface
- (** The network interface to NetVM. *)
+val interface : t -> interface
+(** The network interface to NetVM. *)
- val listen : t -> (unit -> int64) -> Router.t -> unit Lwt.t
- (** Handle incoming frames from NetVM. *)
-end
+val listen : t -> (unit -> int64) -> Router.t -> unit Lwt.t
+(** Handle incoming frames from NetVM. *)
From 8e714c771244d9830036e05ad71c43a43e64d33f Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Mon, 13 Jan 2020 10:05:38 +0000
Subject: [PATCH 092/281] Removed unreachable Lwt.catch
Spotted by Hannes Mehnert.
---
build-with-docker.sh | 2 +-
firewall.ml | 42 ++++++++++++++++--------------------------
2 files changed, 17 insertions(+), 27 deletions(-)
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 5b1bc30..e8e46cd 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 6f8f0f19ba62bf5312039f2904ea8696584f8ff49443dec098facf261449ebf2"
+echo "SHA2 last known: 91c5bf44a85339aaf14e4763a29c2b64537f5bc41cd7dc2571af954ec9dd3cad"
echo "(hashes should match for released versions)"
diff --git a/firewall.ml b/firewall.ml
index e80d7a3..96ea516 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -12,33 +12,23 @@ module Log = (val Logs.src_log src : Logs.LOG)
let transmit_ipv4 packet iface =
Lwt.catch
(fun () ->
- Lwt.catch
- (fun () ->
- let fragments = ref [] in
- iface#writev `IPv4 (fun b ->
- match Nat_packet.into_cstruct packet b with
- | Error e ->
- Log.warn (fun f -> f "Failed to write packet to %a: %a"
- Ipaddr.V4.pp iface#other_ip
- Nat_packet.pp_error e);
- 0
- | Ok (n, frags) -> fragments := frags ; n) >>= fun () ->
- Lwt_list.iter_s (fun f ->
- let size = Cstruct.len f in
- iface#writev `IPv4 (fun b -> Cstruct.blit f 0 b 0 size ; size))
- !fragments)
- (fun ex ->
- Log.warn (fun f -> f "Failed to write packet to %a: %s"
- Ipaddr.V4.pp iface#other_ip
- (Printexc.to_string ex));
- Lwt.return_unit
- )
- )
+ let fragments = ref [] in
+ iface#writev `IPv4 (fun b ->
+ match Nat_packet.into_cstruct packet b with
+ | Error e ->
+ Log.warn (fun f -> f "Failed to NAT packet to %a: %a"
+ Ipaddr.V4.pp iface#other_ip
+ Nat_packet.pp_error e);
+ 0
+ | Ok (n, frags) -> fragments := frags ; n) >>= fun () ->
+ Lwt_list.iter_s (fun f ->
+ let size = Cstruct.len f in
+ iface#writev `IPv4 (fun b -> Cstruct.blit f 0 b 0 size ; size))
+ !fragments)
(fun ex ->
- Log.err (fun f -> f "Exception in transmit_ipv4: %s for:@.%a"
- (Printexc.to_string ex)
- Nat_packet.pp packet
- );
+ Log.warn (fun f -> f "Failed to write packet to %a: %s"
+ Ipaddr.V4.pp iface#other_ip
+ (Printexc.to_string ex));
Lwt.return_unit
)
From e68962ac483095cd793fcb0442a5a0ae1535a26b Mon Sep 17 00:00:00 2001
From: xaki23
Date: Mon, 13 Jan 2020 20:48:46 +0100
Subject: [PATCH 093/281] support mirage-3.7 via qubes-builder
---
Makefile.builder | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Makefile.builder b/Makefile.builder
index 23827af..30e4cec 100644
--- a/Makefile.builder
+++ b/Makefile.builder
@@ -3,5 +3,6 @@ OCAML_VERSION ?= 4.08.1
SOURCE_BUILD_DEP := firewall-build-dep
firewall-build-dep:
- opam pin -y add mirage 3.5.2
+ opam install -y depext
+ opam depext -i -y mirage.3.7.4 lwt.4.5.0
From 554e73a46d252a7613d986f59718e9127c1aed9a Mon Sep 17 00:00:00 2001
From: Hannes Mehnert
Date: Sat, 8 Feb 2020 15:55:32 +0100
Subject: [PATCH 094/281] cleanup: remove exception cases during Ethernet
decode / Nat.of_ipv4_packet - they do not raise exceptions anymore
---
client_net.ml | 5 -----
uplink.ml | 5 -----
2 files changed, 10 deletions(-)
diff --git a/client_net.ml b/client_net.ml
index 4665aa1..86f9d3a 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -87,11 +87,6 @@ let add_vif get_ts { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanu
let fragment_cache = Fragments.Cache.create (256 * 1024) in
Netback.listen backend ~header_size:Ethernet_wire.sizeof_ethernet (fun frame ->
match Ethernet_packet.Unmarshal.of_cstruct frame with
- | exception ex ->
- Log.err (fun f -> f "Error unmarshalling ethernet frame from client: %s@.%a" (Printexc.to_string ex)
- Cstruct.hexdump_pp frame
- );
- Lwt.return_unit
| Error err -> Log.warn (fun f -> f "Invalid Ethernet frame: %s" err); Lwt.return_unit
| Ok (eth, payload) ->
match eth.Ethernet_packet.ethertype with
diff --git a/uplink.ml b/uplink.ml
index 039e6bd..4683d09 100644
--- a/uplink.ml
+++ b/uplink.ml
@@ -38,11 +38,6 @@ let listen t get_ts router =
~arpv4:(Arp.input t.arp)
~ipv4:(fun ip ->
match Nat_packet.of_ipv4_packet t.fragments ~now:(get_ts ()) ip with
- | exception ex ->
- Log.err (fun f -> f "Error unmarshalling ethernet frame from uplink: %s@.%a" (Printexc.to_string ex)
- Cstruct.hexdump_pp frame
- );
- Lwt.return_unit
| Error e ->
Log.warn (fun f -> f "Ignored unknown IPv4 message from uplink: %a" Nat_packet.pp_error e);
Lwt.return_unit
From 88fec9fa490980c1049a1f5342179b2e8a301926 Mon Sep 17 00:00:00 2001
From: Hannes Mehnert
Date: Sat, 8 Feb 2020 15:58:37 +0100
Subject: [PATCH 095/281] adapt to mirage-nat 2.1.0 API (Nat_packet returns a
Fragments.Cache.t - which is now a Lru.F.t)
---
client_net.ml | 6 ++++--
config.ml | 2 +-
uplink.ml | 10 +++++++---
3 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/client_net.ml b/client_net.ml
index 86f9d3a..5b7b54b 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -57,7 +57,9 @@ let input_arp ~fixed_arp ~iface request =
(** Handle an IPv4 packet from the client. *)
let input_ipv4 get_ts cache ~iface ~router packet =
- match Nat_packet.of_ipv4_packet cache ~now:(get_ts ()) packet with
+ let cache', r = Nat_packet.of_ipv4_packet !cache ~now:(get_ts ()) packet in
+ cache := cache';
+ match r with
| Error e ->
Log.warn (fun f -> f "Ignored unknown IPv4 message: %a" Nat_packet.pp_error e);
Lwt.return_unit
@@ -84,7 +86,7 @@ let add_vif get_ts { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanu
Router.add_client router iface >>= fun () ->
Cleanup.on_cleanup cleanup_tasks (fun () -> Router.remove_client router iface);
let fixed_arp = Client_eth.ARP.create ~net:client_eth iface in
- let fragment_cache = Fragments.Cache.create (256 * 1024) in
+ let fragment_cache = ref (Fragments.Cache.empty (256 * 1024)) in
Netback.listen backend ~header_size:Ethernet_wire.sizeof_ethernet (fun frame ->
match Ethernet_packet.Unmarshal.of_cstruct frame with
| Error err -> Log.warn (fun f -> f "Invalid Ethernet frame: %s" err); Lwt.return_unit
diff --git a/config.ml b/config.ml
index 5e284fb..602fd32 100644
--- a/config.ml
+++ b/config.ml
@@ -31,7 +31,7 @@ let main =
package "mirage-net-xen";
package "ipaddr" ~min:"4.0.0";
package "mirage-qubes" ~min:"0.8.0";
- package "mirage-nat" ~min:"2.0.0";
+ package "mirage-nat" ~min:"2.1.0";
package "mirage-logs";
package "mirage-xen" ~min:"5.0.0";
]
diff --git a/uplink.ml b/uplink.ml
index 4683d09..343eef3 100644
--- a/uplink.ml
+++ b/uplink.ml
@@ -16,7 +16,7 @@ type t = {
eth : Eth.t;
arp : Arp.t;
interface : interface;
- fragments : Fragments.Cache.t;
+ mutable fragments : Fragments.Cache.t;
}
class netvm_iface eth mac ~my_ip ~other_ip : interface = object
@@ -37,7 +37,11 @@ let listen t get_ts router =
Eth.input t.eth
~arpv4:(Arp.input t.arp)
~ipv4:(fun ip ->
- match Nat_packet.of_ipv4_packet t.fragments ~now:(get_ts ()) ip with
+ let cache, r =
+ Nat_packet.of_ipv4_packet t.fragments ~now:(get_ts ()) ip
+ in
+ t.fragments <- cache;
+ match r with
| Error e ->
Log.warn (fun f -> f "Ignored unknown IPv4 message from uplink: %a" Nat_packet.pp_error e);
Lwt.return_unit
@@ -63,5 +67,5 @@ let connect config =
let interface = new netvm_iface eth netvm_mac
~my_ip:ip
~other_ip:config.Dao.uplink_netvm_ip in
- let fragments = Fragments.Cache.create (256 * 1024) in
+ let fragments = Fragments.Cache.empty (256 * 1024) in
Lwt.return { net; eth; arp; interface ; fragments }
From 65324b419761234e197fe2e47c29c55f3da1d957 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Wed, 19 Feb 2020 14:14:26 +0000
Subject: [PATCH 096/281] Update Dockerfile to get new mirage-nat version
---
Dockerfile | 2 +-
build-with-docker.sh | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 8a9ed27..7cbdc98 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,7 +7,7 @@ FROM ocurrent/opam@sha256:3f3ce7e577a94942c7f9c63cbdd1ecbfe0ea793f581f69047f3155
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN cd ~/opam-repository && git fetch origin master && git reset --hard d205c265cee9a86869259180fd2238da98370430 && opam update
+RUN cd ~/opam-repository && git fetch origin master && git reset --hard ebac42783217016bd2c4108bbbef102aab56cdde && opam update
RUN opam depext -i -y mirage.3.7.4 lwt.4.5.0
RUN mkdir /home/opam/qubes-mirage-firewall
diff --git a/build-with-docker.sh b/build-with-docker.sh
index e8e46cd..2a7bb42 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 91c5bf44a85339aaf14e4763a29c2b64537f5bc41cd7dc2571af954ec9dd3cad"
+echo "SHA2 last known: 83b96bd453c3c3cfb282076be81055026eca437b621b3ef3f2642af04ad782e2"
echo "(hashes should match for released versions)"
From 87df5bdcc015b1a9f06aeeadcb8a283e3b1fe100 Mon Sep 17 00:00:00 2001
From: linse
Date: Wed, 29 Apr 2020 15:58:01 +0200
Subject: [PATCH 097/281] Read firewall rules from QubesDB. The module Rules
contains a rule matcher instead of hardcoded rules now.
Co-Authored-By: Mindy Preston
---
Dockerfile | 4 +-
Makefile.builder | 2 +-
Makefile.user | 5 +
README.md | 7 +
client_net.ml | 69 ++++++--
client_net.mli | 10 +-
config.ml | 9 +-
dao.ml | 32 ++++
dao.mli | 7 +
diagrams/components.txt | 12 +-
firewall.ml | 103 +++---------
fw_utils.ml | 2 +
my_nat.ml | 8 +-
my_nat.mli | 5 +-
packet.ml | 65 +++++---
packet.mli | 39 +++++
router.mli | 3 +-
rules.ml | 133 +++++++++------
test/config.ml | 27 +++
test/test.sh | 138 ++++++++++++++++
test/unikernel.ml | 357 ++++++++++++++++++++++++++++++++++++++++
test/update-firewall.sh | 54 ++++++
unikernel.ml | 43 ++---
23 files changed, 928 insertions(+), 206 deletions(-)
create mode 100644 packet.mli
create mode 100644 test/config.ml
create mode 100755 test/test.sh
create mode 100644 test/unikernel.ml
create mode 100644 test/update-firewall.sh
diff --git a/Dockerfile b/Dockerfile
index 7cbdc98..d49cadf 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,9 +7,9 @@ FROM ocurrent/opam@sha256:3f3ce7e577a94942c7f9c63cbdd1ecbfe0ea793f581f69047f3155
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN cd ~/opam-repository && git fetch origin master && git reset --hard ebac42783217016bd2c4108bbbef102aab56cdde && opam update
+RUN cd ~/opam-repository && git fetch origin master && git reset --hard 3548c2a8537029b8165466cd9c5a94bb7bc30405 && opam update
-RUN opam depext -i -y mirage.3.7.4 lwt.4.5.0
+RUN opam depext -i -y mirage.3.7.6 lwt.5.2.0
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
WORKDIR /home/opam/qubes-mirage-firewall
diff --git a/Makefile.builder b/Makefile.builder
index 30e4cec..ee3c966 100644
--- a/Makefile.builder
+++ b/Makefile.builder
@@ -4,5 +4,5 @@ SOURCE_BUILD_DEP := firewall-build-dep
firewall-build-dep:
opam install -y depext
- opam depext -i -y mirage.3.7.4 lwt.4.5.0
+ opam depext -i -y mirage.3.7.6 lwt.5.2.0
diff --git a/Makefile.user b/Makefile.user
index da810cd..cc7a7f4 100644
--- a/Makefile.user
+++ b/Makefile.user
@@ -5,3 +5,8 @@ tar: build
touch _build/mirage-firewall/modules.img
cat /dev/null | gzip -n > _build/mirage-firewall/initramfs
tar cjf mirage-firewall.tar.bz2 -C _build --mtime=./build-with-docker.sh mirage-firewall
+
+fetchmotron: qubes_firewall.xen
+ test-mirage qubes_firewall.xen mirage-fw-test &
+ sleep 1
+ boot-mirage fetchmotron
diff --git a/README.md b/README.md
index 6556705..be85574 100644
--- a/README.md
+++ b/README.md
@@ -165,6 +165,13 @@ This takes a little more setting up the first time, but will be much quicker aft
2017-03-18 11:32:38 -00:00: INF [dao] Watching backend/vif
2017-03-18 11:32:38 -00:00: INF [qubes.db] got update: "/qubes-netvm-domid" = "1"
+# Testing if the firewall works
+
+Build the test unikernel in the test directory.
+Install it to a vm which has the firewall as netvm.
+Set the rules for the testvm to "textfile".
+Run the test unikernel.
+
# Security advisories
See [issues tagged "security"](https://github.com/mirage/qubes-mirage-firewall/issues?utf8=%E2%9C%93&q=label%3Asecurity+) for security advisories affecting the firewall.
diff --git a/client_net.ml b/client_net.ml
index 5b7b54b..31f3f2d 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -30,6 +30,9 @@ class client_iface eth ~domid ~gateway_ip ~client_ip client_mac : client_link =
let log_header = Fmt.strf "dom%d:%a" domid Ipaddr.V4.pp client_ip in
object
val queue = FrameQ.create (Ipaddr.V4.to_string client_ip)
+ val mutable rules = []
+ method get_rules = rules
+ method set_rules new_db = rules <- Dao.read_rules new_db client_ip
method my_mac = ClientEth.mac eth
method other_mac = client_mac
method my_ip = gateway_ip
@@ -74,8 +77,8 @@ let input_ipv4 get_ts cache ~iface ~router packet =
Lwt.return_unit
)
-(** Connect to a new client's interface and listen for incoming frames. *)
-let add_vif get_ts { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks =
+(** Connect to a new client's interface and listen for incoming frames and firewall rule changes. *)
+let add_vif get_ts { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks qubesDB =
Netback.make ~domid ~device_id >>= fun backend ->
Log.info (fun f -> f "Client %d (IP: %s) ready" domid (Ipaddr.V4.to_string client_ip));
ClientEth.connect backend >>= fun eth ->
@@ -83,28 +86,59 @@ let add_vif get_ts { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanu
let client_eth = router.Router.client_eth in
let gateway_ip = Client_eth.client_gw client_eth in
let iface = new client_iface eth ~domid ~gateway_ip ~client_ip client_mac in
+ (* update the rules whenever QubesDB notices a change for this IP *)
+ let qubesdb_updater =
+ Lwt.catch
+ (fun () ->
+ let rec update current_db current_rules =
+ Qubes.DB.got_new_commit qubesDB (Dao.db_root client_ip) current_db >>= fun new_db ->
+ iface#set_rules new_db;
+ let new_rules = iface#get_rules in
+ (if current_rules = new_rules then
+ Log.debug (fun m -> m "Rules did not change for %s" (Ipaddr.V4.to_string client_ip))
+ else begin
+ Log.debug (fun m -> m "New firewall rules for %s@.%a"
+ (Ipaddr.V4.to_string client_ip)
+ Fmt.(list ~sep:(unit "@.") Pf_qubes.Parse_qubes.pp_rule) new_rules);
+ (* empty NAT table if rules are updated: they might deny old connections *)
+ My_nat.remove_connections router.Router.nat client_ip;
+ end);
+ update new_db new_rules
+ in
+ update Qubes.DB.KeyMap.empty [])
+ (function Lwt.Canceled -> Lwt.return_unit | e -> Lwt.fail e)
+ in
+ Cleanup.on_cleanup cleanup_tasks (fun () -> Lwt.cancel qubesdb_updater);
Router.add_client router iface >>= fun () ->
Cleanup.on_cleanup cleanup_tasks (fun () -> Router.remove_client router iface);
let fixed_arp = Client_eth.ARP.create ~net:client_eth iface in
let fragment_cache = ref (Fragments.Cache.empty (256 * 1024)) in
- Netback.listen backend ~header_size:Ethernet_wire.sizeof_ethernet (fun frame ->
- match Ethernet_packet.Unmarshal.of_cstruct frame with
- | Error err -> Log.warn (fun f -> f "Invalid Ethernet frame: %s" err); Lwt.return_unit
- | Ok (eth, payload) ->
- match eth.Ethernet_packet.ethertype with
- | `ARP -> input_arp ~fixed_arp ~iface payload
- | `IPv4 -> input_ipv4 get_ts fragment_cache ~iface ~router payload
- | `IPv6 -> Lwt.return_unit (* TODO: oh no! *)
- )
- >|= or_raise "Listen on client interface" Netback.pp_error
+ let listener =
+ Lwt.catch
+ (fun () ->
+ Netback.listen backend ~header_size:Ethernet_wire.sizeof_ethernet (fun frame ->
+ match Ethernet_packet.Unmarshal.of_cstruct frame with
+ | Error err -> Log.warn (fun f -> f "Invalid Ethernet frame: %s" err); Lwt.return_unit
+ | Ok (eth, payload) ->
+ match eth.Ethernet_packet.ethertype with
+ | `ARP -> input_arp ~fixed_arp ~iface payload
+ | `IPv4 -> input_ipv4 get_ts fragment_cache ~iface ~router payload
+ | `IPv6 -> Lwt.return_unit (* TODO: oh no! *)
+ )
+ >|= or_raise "Listen on client interface" Netback.pp_error)
+ (function Lwt.Canceled -> Lwt.return_unit | e -> Lwt.fail e)
+ in
+ Cleanup.on_cleanup cleanup_tasks (fun () -> Lwt.cancel listener);
+ Lwt.pick [ qubesdb_updater ; listener ]
(** A new client VM has been found in XenStore. Find its interface and connect to it. *)
-let add_client get_ts ~router vif client_ip =
+let add_client get_ts ~router vif client_ip qubesDB =
let cleanup_tasks = Cleanup.create () in
- Log.info (fun f -> f "add client vif %a with IP %a" Dao.ClientVif.pp vif Ipaddr.V4.pp client_ip);
+ Log.info (fun f -> f "add client vif %a with IP %a"
+ Dao.ClientVif.pp vif Ipaddr.V4.pp client_ip);
Lwt.async (fun () ->
Lwt.catch (fun () ->
- add_vif get_ts vif ~client_ip ~router ~cleanup_tasks
+ add_vif get_ts vif ~client_ip ~router ~cleanup_tasks qubesDB
)
(fun ex ->
Log.warn (fun f -> f "Error with client %a: %s"
@@ -115,7 +149,7 @@ let add_client get_ts ~router vif client_ip =
cleanup_tasks
(** Watch XenStore for notifications of new clients. *)
-let listen get_ts router =
+let listen get_ts qubesDB router =
Dao.watch_clients (fun new_set ->
(* Check for removed clients *)
!clients |> Dao.VifMap.iter (fun key cleanup ->
@@ -128,7 +162,8 @@ let listen get_ts router =
(* Check for added clients *)
new_set |> Dao.VifMap.iter (fun key ip_addr ->
if not (Dao.VifMap.mem key !clients) then (
- let cleanup = add_client get_ts ~router key ip_addr in
+ let cleanup = add_client get_ts ~router key ip_addr qubesDB in
+ Log.debug (fun f -> f "client %a arrived" Dao.ClientVif.pp key);
clients := !clients |> Dao.VifMap.add key cleanup
)
)
diff --git a/client_net.mli b/client_net.mli
index 97ebd68..0bfbb01 100644
--- a/client_net.mli
+++ b/client_net.mli
@@ -3,8 +3,8 @@
(** Handling client VMs. *)
-val listen : (unit -> int64) -> Router.t -> 'a Lwt.t
-(** [listen get_timestamp router] is a thread that watches for clients being
- added to and removed from XenStore. Clients are connected to the client
- network and packets are sent via [router]. We ensure the source IP address
- is correct before routing a packet. *)
+val listen : (unit -> int64) -> Qubes.DB.t -> Router.t -> 'a Lwt.t
+(** [listen get_timestamp db router] is a thread that watches for clients being added to and
+ removed from XenStore. Clients are connected to the client network and
+ packets are sent via [router]. We ensure the source IP address is correct
+ before routing a packet. *)
diff --git a/config.ml b/config.ml
index 602fd32..87ba926 100644
--- a/config.ml
+++ b/config.ml
@@ -30,13 +30,14 @@ let main =
package "netchannel" ~min:"1.11.0";
package "mirage-net-xen";
package "ipaddr" ~min:"4.0.0";
- package "mirage-qubes" ~min:"0.8.0";
- package "mirage-nat" ~min:"2.1.0";
+ package "mirage-qubes" ~min:"0.8.2";
+ package "mirage-nat" ~min:"2.2.1";
package "mirage-logs";
package "mirage-xen" ~min:"5.0.0";
+ package "pf-qubes";
]
- "Unikernel.Main" (mclock @-> job)
+ "Unikernel.Main" (random @-> mclock @-> job)
let () =
- register "qubes-firewall" [main $ default_monotonic_clock]
+ register "qubes-firewall" [main $ default_random $ default_monotonic_clock]
~argv:no_argv
diff --git a/dao.ml b/dao.ml
index a34b8b7..8a14c22 100644
--- a/dao.ml
+++ b/dao.ml
@@ -33,6 +33,38 @@ let directory ~handle dir =
| [""] -> [] (* XenStore client bug *)
| items -> items
+let db_root client_ip =
+ "/qubes-firewall/" ^ (Ipaddr.V4.to_string client_ip)
+
+let read_rules rules client_ip =
+ let root = db_root client_ip in
+ let rec get_rule n l : (Pf_qubes.Parse_qubes.rule list, string) result =
+ let pattern = root ^ "/" ^ Printf.sprintf "%04d" n in
+ Log.debug (fun f -> f "reading %s" pattern);
+ match Qubes.DB.KeyMap.find_opt pattern rules with
+ | None ->
+ Log.debug (fun f -> f "rule %d does not exist; won't look for more" n);
+ Ok (List.rev l)
+ | Some rule ->
+ Log.debug (fun f -> f "rule %d: %s" n rule);
+ match Pf_qubes.Parse_qubes.parse_qubes ~number:n rule with
+ | Error e -> Log.warn (fun f -> f "Error parsing rule %d: %s" n e); Error e
+ | Ok rule ->
+ Log.debug (fun f -> f "parsed rule: %a" Pf_qubes.Parse_qubes.pp_rule rule);
+ get_rule (n+1) (rule :: l)
+ in
+ match get_rule 0 [] with
+ | Ok l -> l
+ | Error e ->
+ Log.warn (fun f -> f "Defaulting to deny-all because of rule parse failure (%s)" e);
+ [ Pf_qubes.Parse_qubes.({action = Drop;
+ proto = None;
+ specialtarget = None;
+ dst = `any;
+ dstports = None;
+ icmp_type = None;
+ number = 0;})]
+
let vifs ~handle domid =
match String.to_int domid with
| None -> Log.err (fun f -> f "Invalid domid %S" domid); Lwt.return []
diff --git a/dao.mli b/dao.mli
index b1f56b6..811c2e7 100644
--- a/dao.mli
+++ b/dao.mli
@@ -30,4 +30,11 @@ val read_network_config : Qubes.DB.t -> network_config Lwt.t
(** [read_network_config db] fetches the configuration from QubesDB.
If it isn't there yet, it waits until it is. *)
+val db_root : Ipaddr.V4.t -> string
+(** Returns the root path of the firewall rules in the QubesDB for a given IP address. *)
+
+val read_rules : string Qubes.DB.KeyMap.t -> Ipaddr.V4.t -> Pf_qubes.Parse_qubes.rule list
+(** [read_rules bindings ip] extracts firewall rule information for [ip] from [bindings].
+ If any rules fail to parse, it will return only one rule denying all traffic. *)
+
val set_iptables_error : Qubes.DB.t -> string -> unit Lwt.t
diff --git a/diagrams/components.txt b/diagrams/components.txt
index 62e4f9e..8b7efbf 100644
--- a/diagrams/components.txt
+++ b/diagrams/components.txt
@@ -1,6 +1,12 @@
- +----------+
- | rules |
- +----------+
+ +--------------------+
+ | rules from QubesDB |
+ +--------------------+
+ ^
+ if-not-in-nat | then check
+ |
+ +-----------+
+ | nat-table |
+ +-----------+
^
|checks
|
diff --git a/firewall.ml b/firewall.ml
index 96ea516..48d4fe4 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -16,7 +16,7 @@ let transmit_ipv4 packet iface =
iface#writev `IPv4 (fun b ->
match Nat_packet.into_cstruct packet b with
| Error e ->
- Log.warn (fun f -> f "Failed to NAT packet to %a: %a"
+ Log.warn (fun f -> f "Failed to write packet to %a: %a"
Ipaddr.V4.pp iface#other_ip
Nat_packet.pp_error e);
0
@@ -38,72 +38,6 @@ let forward_ipv4 t packet =
| Some iface -> transmit_ipv4 packet iface
| None -> Lwt.return_unit
-(* Packet classification *)
-
-let parse_ips ips = List.map (fun (ip_str, id) -> (Ipaddr.of_string_exn ip_str, id)) ips
-
-let clients = parse_ips Rules.clients
-let externals = parse_ips Rules.externals
-
-let resolve_client client =
- `Client (try List.assoc (Ipaddr.V4 client#other_ip) clients with Not_found -> `Unknown)
-
-let resolve_host = function
- | `Client c -> resolve_client c
- | `External ip -> `External (try List.assoc ip externals with Not_found -> `Unknown)
- | (`Firewall | `NetVM) as x -> x
-
-let classify ~src ~dst packet =
- let `IPv4 (_ip, transport) = packet in
- let proto =
- match transport with
- | `TCP ({Tcp.Tcp_packet.src_port; dst_port; _}, _) -> `TCP {sport = src_port; dport = dst_port}
- | `UDP ({Udp_packet.src_port; dst_port; _}, _) -> `UDP {sport = src_port; dport = dst_port}
- | `ICMP _ -> `ICMP
- in
- Some {
- packet;
- src;
- dst;
- proto;
- }
-
-let pp_ports fmt {sport; dport} =
- Format.fprintf fmt "sport=%d dport=%d" sport dport
-
-let pp_host fmt = function
- | `Client c -> Ipaddr.V4.pp fmt (c#other_ip)
- | `Unknown_client ip -> Format.fprintf fmt "unknown-client(%a)" Ipaddr.pp ip
- | `NetVM -> Format.pp_print_string fmt "net-vm"
- | `External ip -> Format.fprintf fmt "external(%a)" Ipaddr.pp ip
- | `Firewall -> Format.pp_print_string fmt "firewall"
-
-let pp_proto fmt = function
- | `UDP ports -> Format.fprintf fmt "UDP(%a)" pp_ports ports
- | `TCP ports -> Format.fprintf fmt "TCP(%a)" pp_ports ports
- | `ICMP -> Format.pp_print_string fmt "ICMP"
- | `Unknown -> Format.pp_print_string fmt "UnknownProtocol"
-
-let pp_packet t fmt {src = _; dst = _; proto; packet} =
- let `IPv4 (ip, _transport) = packet in
- let src = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.src) in
- let dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst) in
- Format.fprintf fmt "[src=%a dst=%a proto=%a]"
- pp_host src
- pp_host dst
- pp_proto proto
-
-let pp_transport_headers f = function
- | `ICMP (h, _) -> Icmpv4_packet.pp f h
- | `TCP (h, _) -> Tcp.Tcp_packet.pp f h
- | `UDP (h, _) -> Udp_packet.pp f h
-
-let pp_header f = function
- | `IPv4 (ip, transport) ->
- Fmt.pf f "%a %a"
- Ipv4_packet.pp ip
- pp_transport_headers transport
-
(* NAT *)
let translate t packet =
@@ -115,7 +49,7 @@ let add_nat_and_forward_ipv4 t packet =
My_nat.add_nat_rule_and_translate t.Router.nat ~xl_host `NAT packet >>= function
| Ok packet -> forward_ipv4 t packet
| Error e ->
- Log.warn (fun f -> f "Failed to add NAT rewrite rule: %s (%a)" e pp_header packet);
+ Log.warn (fun f -> f "Failed to add NAT rewrite rule: %s (%a)" e Nat_packet.pp packet);
Lwt.return_unit
(* Add a NAT rule to redirect this conversation to [host:port] instead of us. *)
@@ -127,23 +61,24 @@ let nat_to t ~host ~port packet =
My_nat.add_nat_rule_and_translate t.Router.nat ~xl_host (`Redirect (target, port)) packet >>= function
| Ok packet -> forward_ipv4 t packet
| Error e ->
- Log.warn (fun f -> f "Failed to add NAT redirect rule: %s (%a)" e pp_header packet);
+ Log.warn (fun f -> f "Failed to add NAT redirect rule: %s (%a)" e Nat_packet.pp packet);
Lwt.return_unit
-(* Handle incoming packets *)
-
-let apply_rules t rules ~dst info =
- let packet = info.packet in
- match rules info, dst with
+let apply_rules t (rules : ('a, 'b) Packet.t -> Packet.action Lwt.t) ~dst (annotated_packet : ('a, 'b) Packet.t) : unit Lwt.t =
+ let packet = to_mirage_nat_packet annotated_packet in
+ rules annotated_packet >>= fun action ->
+ match action, dst with
| `Accept, `Client client_link -> transmit_ipv4 packet client_link
| `Accept, (`External _ | `NetVM) -> transmit_ipv4 packet t.Router.uplink
| `Accept, `Firewall ->
- Log.warn (fun f -> f "Bad rule: firewall can't accept packets %a" (pp_packet t) info);
+ Log.warn (fun f -> f "Bad rule: firewall can't accept packets %a" Nat_packet.pp packet);
Lwt.return_unit
- | `NAT, _ -> add_nat_and_forward_ipv4 t packet
+ | `NAT, _ ->
+ Log.debug (fun f -> f "adding NAT rule for %a" Nat_packet.pp packet);
+ add_nat_and_forward_ipv4 t packet
| `NAT_to (host, port), _ -> nat_to t packet ~host ~port
| `Drop reason, _ ->
- Log.info (fun f -> f "Dropped packet (%s) %a" reason (pp_packet t) info);
+ Log.debug (fun f -> f "Dropped packet (%s) %a" reason Nat_packet.pp packet);
Lwt.return_unit
let handle_low_memory t =
@@ -165,9 +100,9 @@ let ipv4_from_client t ~src packet =
(* No existing NAT entry. Check the firewall rules. *)
let `IPv4 (ip, _transport) = packet in
let dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst) in
- match classify ~src:(resolve_client src) ~dst:(resolve_host dst) packet with
+ match of_mirage_nat_packet ~src:(`Client src) ~dst packet with
| None -> Lwt.return_unit
- | Some info -> apply_rules t Rules.from_client ~dst info
+ | Some firewall_packet -> apply_rules t Rules.from_client ~dst firewall_packet
let ipv4_from_netvm t packet =
handle_low_memory t >>= function
@@ -176,15 +111,17 @@ let ipv4_from_netvm t packet =
let `IPv4 (ip, _transport) = packet in
let src = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.src) in
let dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst) in
- match classify ~src ~dst:(resolve_host dst) packet with
+ match Packet.of_mirage_nat_packet ~src ~dst packet with
| None -> Lwt.return_unit
- | Some info ->
+ | Some _ ->
match src with
| `Client _ | `Firewall ->
- Log.warn (fun f -> f "Frame from NetVM has internal source IP address! %a" (pp_packet t) info);
+ Log.warn (fun f -> f "Frame from NetVM has internal source IP address! %a" Nat_packet.pp packet);
Lwt.return_unit
| `External _ | `NetVM as src ->
translate t packet >>= function
| Some frame -> forward_ipv4 t frame
| None ->
- apply_rules t Rules.from_netvm ~dst { info with src }
+ match Packet.of_mirage_nat_packet ~src ~dst packet with
+ | None -> Lwt.return_unit
+ | Some packet -> apply_rules t Rules.from_netvm ~dst packet
diff --git a/fw_utils.ml b/fw_utils.ml
index 9c5bab4..f6d5c7b 100644
--- a/fw_utils.ml
+++ b/fw_utils.ml
@@ -31,6 +31,8 @@ class type client_link = object
inherit interface
method other_mac : Macaddr.t
method log_header : string (* For log messages *)
+ method get_rules: Pf_qubes.Parse_qubes.rule list
+ method set_rules: string Qubes.DB.KeyMap.t -> unit
end
(** An Ethernet header from [src]'s MAC address to [dst]'s with an IPv4 payload. *)
diff --git a/my_nat.ml b/my_nat.ml
index 02a4b5a..9dfcf68 100644
--- a/my_nat.ml
+++ b/my_nat.ml
@@ -39,6 +39,10 @@ let random_user_port () =
let reset t =
Nat.reset t.table
+let remove_connections t ip =
+ let Mirage_nat.{ tcp ; udp } = Nat.remove_connections t.table ip in
+ ignore(tcp, udp)
+
let add_nat_rule_and_translate t ~xl_host action packet =
let apply_action xl_port =
Lwt.catch (fun () ->
@@ -56,13 +60,13 @@ let add_nat_rule_and_translate t ~xl_host action packet =
(* Because hash tables resize in big steps, this can happen even if we have a fair
chunk of free memory. *)
Log.warn (fun f -> f "Out_of_memory adding NAT rule. Dropping NAT table...");
- Nat.reset t.table >>= fun () ->
+ reset t >>= fun () ->
aux ~retries:(retries - 1)
| Error `Overlap when retries < 0 -> Lwt.return (Error "Too many retries")
| Error `Overlap ->
if retries = 0 then (
Log.warn (fun f -> f "Failed to find a free port; resetting NAT table");
- Nat.reset t.table >>= fun () ->
+ reset t >>= fun () ->
aux ~retries:(retries - 1)
) else (
aux ~retries:(retries - 1)
diff --git a/my_nat.mli b/my_nat.mli
index cdc5eda..fc2049d 100644
--- a/my_nat.mli
+++ b/my_nat.mli
@@ -12,6 +12,7 @@ type action = [
val create : max_entries:int -> t Lwt.t
val reset : t -> unit Lwt.t
+val remove_connections : t -> Ipaddr.V4.t -> unit
val translate : t -> Nat_packet.t -> Nat_packet.t option Lwt.t
-val add_nat_rule_and_translate : t -> xl_host:Ipaddr.V4.t ->
- action -> Nat_packet.t -> (Nat_packet.t, string) result Lwt.t
+val add_nat_rule_and_translate : t ->
+ xl_host:Ipaddr.V4.t -> action -> Nat_packet.t -> (Nat_packet.t, string) result Lwt.t
diff --git a/packet.ml b/packet.ml
index 7838a6b..7d8c3c4 100644
--- a/packet.ml
+++ b/packet.ml
@@ -5,33 +5,60 @@ open Fw_utils
type port = int
-type ports = {
- sport : port; (* Source port *)
- dport : port; (* Destination *)
-}
-
-type host =
+type host =
[ `Client of client_link | `Firewall | `NetVM | `External of Ipaddr.t ]
-type ('src, 'dst) info = {
- packet : Nat_packet.t;
+type transport_header = [`TCP of Tcp.Tcp_packet.t
+ |`UDP of Udp_packet.t
+ |`ICMP of Icmpv4_packet.t]
+
+type ('src, 'dst) t = {
+ ipv4_header : Ipv4_packet.t;
+ transport_header : transport_header;
+ transport_payload : Cstruct.t;
src : 'src;
dst : 'dst;
- proto : [ `UDP of ports | `TCP of ports | `ICMP | `Unknown ];
}
+let pp_transport_header f = function
+ | `ICMP h -> Icmpv4_packet.pp f h
+ | `TCP h -> Tcp.Tcp_packet.pp f h
+ | `UDP h -> Udp_packet.pp f h
-(* The first message in a TCP connection has SYN set and ACK clear. *)
-let is_tcp_start = function
- | `IPv4 (_ip, `TCP (hdr, _body)) -> Tcp.Tcp_packet.(hdr.syn && not hdr.ack)
- | _ -> false
+let pp_host fmt = function
+ | `Client c -> Ipaddr.V4.pp fmt (c#other_ip)
+ | `Unknown_client ip -> Format.fprintf fmt "unknown-client(%a)" Ipaddr.pp ip
+ | `NetVM -> Format.pp_print_string fmt "net-vm"
+ | `External ip -> Format.fprintf fmt "external(%a)" Ipaddr.pp ip
+ | `Firewall -> Format.pp_print_string fmt "firewall(client-gw)"
-(* The possible actions we can take for a packet: *)
+let to_mirage_nat_packet t : Nat_packet.t =
+ match t.transport_header with
+ | `TCP h -> `IPv4 (t.ipv4_header, (`TCP (h, t.transport_payload)))
+ | `UDP h -> `IPv4 (t.ipv4_header, (`UDP (h, t.transport_payload)))
+ | `ICMP h -> `IPv4 (t.ipv4_header, (`ICMP (h, t.transport_payload)))
+
+let of_mirage_nat_packet ~src ~dst packet : ('a, 'b) t option =
+ let `IPv4 (ipv4_header, ipv4_payload) = packet in
+ let transport_header, transport_payload = match ipv4_payload with
+ | `TCP (h, p) -> `TCP h, p
+ | `UDP (h, p) -> `UDP h, p
+ | `ICMP (h, p) -> `ICMP h, p
+ in
+ Some {
+ ipv4_header;
+ transport_header;
+ transport_payload;
+ src;
+ dst;
+ }
+
+(* possible actions to take for a packet: *)
type action = [
- | `Accept (* Send the packet to its destination. *)
- | `NAT (* Rewrite the packet's source field so packet appears to
- have come from the firewall, via an unused port.
- Also, add NAT rules so related packets will be translated accordingly. *)
+ | `Accept (* Send to destination, unmodified. *)
+ | `NAT (* Rewrite source field to the firewall's IP, with a fresh source port.
+ Also, add translation rules for future traffic in both directions,
+ between these hosts on these ports, and corresponding ICMP error traffic. *)
| `NAT_to of host * port (* As for [`NAT], but also rewrite the packet's
destination fields so it will be sent to [host:port]. *)
- | `Drop of string (* Drop the packet and log the given reason. *)
+ | `Drop of string (* Drop packet for this reason. *)
]
diff --git a/packet.mli b/packet.mli
new file mode 100644
index 0000000..f7d2876
--- /dev/null
+++ b/packet.mli
@@ -0,0 +1,39 @@
+type port = int
+
+type host =
+ [ `Client of Fw_utils.client_link (** an IP address on the private network *)
+ | `Firewall (** the firewall's IP on the private network *)
+ | `NetVM (** the IP of the firewall's default route *)
+ | `External of Ipaddr.t (** an IP on the public network *)
+ ]
+
+type transport_header = [`TCP of Tcp.Tcp_packet.t
+ |`UDP of Udp_packet.t
+ |`ICMP of Icmpv4_packet.t]
+
+type ('src, 'dst) t = {
+ ipv4_header : Ipv4_packet.t;
+ transport_header : transport_header;
+ transport_payload : Cstruct.t;
+ src : 'src;
+ dst : 'dst;
+}
+
+val pp_transport_header : Format.formatter -> transport_header -> unit
+
+val pp_host : Format.formatter -> host -> unit
+
+val to_mirage_nat_packet : ('a, 'b) t -> Nat_packet.t
+
+val of_mirage_nat_packet : src:'a -> dst:'b -> Nat_packet.t -> ('a, 'b) t option
+
+(* possible actions to take for a packet: *)
+type action = [
+ | `Accept (* Send to destination, unmodified. *)
+ | `NAT (* Rewrite source field to the firewall's IP, with a fresh source port.
+ Also, add translation rules for future traffic in both directions,
+ between these hosts on these ports, and corresponding ICMP error traffic. *)
+ | `NAT_to of host * port (* As for [`NAT], but also rewrite the packet's
+ destination fields so it will be sent to [host:port]. *)
+ | `Drop of string (* Drop packet for this reason. *)
+]
diff --git a/router.mli b/router.mli
index 80678fb..34fa86b 100644
--- a/router.mli
+++ b/router.mli
@@ -10,14 +10,13 @@ type t = private {
nat : My_nat.t;
uplink : interface;
}
-(** A routing table. *)
val create :
client_eth:Client_eth.t ->
uplink:interface ->
nat:My_nat.t ->
t
-(** [create ~client_eth ~uplink] is a new routing table
+(** [create ~client_eth ~uplink ~nat] is a new routing table
that routes packets outside of [client_eth] via [uplink]. *)
val target : t -> Ipv4_packet.t -> interface option
diff --git a/rules.ml b/rules.ml
index ec0c1c3..cb6bb6f 100644
--- a/rules.ml
+++ b/rules.ml
@@ -1,62 +1,101 @@
(* Copyright (C) 2015, Thomas Leonard
See the README file for details. *)
-(** Put your firewall rules in this file. *)
+(** This module applies firewall rules from QubesDB. *)
-open Packet (* Allow us to use definitions in packet.ml *)
+open Packet
+open Lwt.Infix
+module Q = Pf_qubes.Parse_qubes
-(* List your AppVM IP addresses here if you want to match on them in the rules below.
- Any client not listed here will appear as [`Client `Unknown]. *)
-let clients = [
- (*
- "10.137.0.12", `Dev;
- "10.137.0.14", `Untrusted;
- *)
+let src = Logs.Src.create "rules" ~doc:"Firewall rules"
+module Log = (val Logs.src_log src : Logs.LOG)
+
+(* the upstream NetVM will redirect TCP and UDP port 53 traffic with
+ these destination IPs to its upstream nameserver. *)
+let default_dns_servers = [
+ Ipaddr.V4.of_string_exn "10.139.1.1";
+ Ipaddr.V4.of_string_exn "10.139.1.2";
]
+let dns_port = 53
-(* List your external (non-AppVM) IP addresses here if you want to match on them in the rules below.
- Any external machine not listed here will appear as [`External `Unknown]. *)
-let externals = [
- (*
- "8.8.8.8", `GoogleDNS;
- *)
-]
+module Classifier = struct
-(* OCaml normally warns if you don't match all fields, but that's OK here. *)
-[@@@ocaml.warning "-9"]
+ let matches_port dstports (port : int) = match dstports with
+ | None -> true
+ | Some (Q.Range_inclusive (min, max)) -> min <= port && port <= max
-(** This function decides what to do with a packet from a client VM.
+ let matches_proto rule packet = match rule.Q.proto, rule.Q.specialtarget with
+ | None, None -> true
+ | None, Some `dns when List.mem packet.ipv4_header.Ipv4_packet.dst default_dns_servers -> begin
+ (* specialtarget=dns applies only to the specialtarget destination IPs, and
+ specialtarget=dns is also implicitly tcp/udp port 53 *)
+ match packet.transport_header with
+ | `TCP header -> header.Tcp.Tcp_packet.dst_port = dns_port
+ | `UDP header -> header.Udp_packet.dst_port = dns_port
+ | _ -> false
+ end
+ (* DNS rules can only match traffic headed to the specialtarget hosts, so any other destination
+ isn't a match for DNS rules *)
+ | None, Some `dns -> false
+ | Some rule_proto, _ -> match rule_proto, packet.transport_header with
+ | `tcp, `TCP header -> matches_port rule.Q.dstports header.Tcp.Tcp_packet.dst_port
+ | `udp, `UDP header -> matches_port rule.Q.dstports header.Udp_packet.dst_port
+ | `icmp, `ICMP header ->
+ begin
+ match rule.Q.icmp_type with
+ | None -> true
+ | Some rule_icmp_type ->
+ 0 = compare rule_icmp_type @@ Icmpv4_wire.ty_to_int header.Icmpv4_packet.ty
+ end
+ | _, _ -> false
- It takes as input an argument [info] (of type [Packet.info]) describing the
- packet, and returns an action (of type [Packet.action]) to perform.
+ let matches_dest rule packet =
+ let ip = packet.ipv4_header.Ipv4_packet.dst in
+ match rule.Q.dst with
+ | `any -> Lwt.return @@ `Match rule
+ | `hosts subnet ->
+ Lwt.return @@ if (Ipaddr.Prefix.mem Ipaddr.(V4 ip) subnet) then `Match rule else `No_match
+ | `dnsname name ->
+ Log.warn (fun f -> f "Resolving %a" Domain_name.pp name);
+ Lwt.return @@ `No_match
- See packet.ml for the definitions of [info] and [action].
+end
- Note: If the packet matched an existing NAT rule then this isn't called. *)
-let from_client (info : ([`Client of _], _) Packet.info) : Packet.action =
- match info with
- (* Examples (add your own rules here):
+let find_first_match packet acc rule =
+ match acc with
+ | `No_match ->
+ if Classifier.matches_proto rule packet
+ then Classifier.matches_dest rule packet
+ else Lwt.return `No_match
+ | q -> Lwt.return q
- 1. Allows Dev to send SSH packets to Untrusted.
- Note: responses are not covered by this!
- 2. Allows Untrusted to reply to Dev.
- 3. Blocks an external site.
+(* Does the packet match our rules? *)
+let classify_client_packet (packet : ([`Client of Fw_utils.client_link], _) Packet.t) =
+ let (`Client client_link) = packet.src in
+ let rules = client_link#get_rules in
+ Lwt_list.fold_left_s (find_first_match packet) `No_match rules >|= function
+ | `No_match -> `Drop "No matching rule; assuming default drop"
+ | `Match {Q.action = Q.Accept; _} -> `Accept
+ | `Match ({Q.action = Q.Drop; _} as rule) ->
+ `Drop (Format.asprintf "rule number %a explicitly drops this packet" Q.pp_rule rule)
- In all cases, make sure you've added the VM name to [clients] or [externals] above, or it won't
- match anything! *)
- (*
- | { src = `Client `Dev; dst = `Client `Untrusted; proto = `TCP { dport = 22 } } -> `Accept
- | { src = `Client `Untrusted; dst = `Client `Dev; proto = `TCP _; packet }
- when not (is_tcp_start packet) -> `Accept
- | { dst = `External `GoogleDNS } -> `Drop "block Google DNS"
- *)
- | { dst = (`External _ | `NetVM) } -> `NAT
- | { dst = `Firewall; proto = `UDP { dport = 53 } } -> `NAT_to (`NetVM, 53)
- | { dst = `Firewall } -> `Drop "packet addressed to firewall itself"
- | { dst = `Client _ } -> `Drop "prevent communication between client VMs by default"
+let translate_accepted_packets packet =
+ classify_client_packet packet >|= function
+ | `Accept -> `NAT
+ | `Drop s -> `Drop s
-(** Decide what to do with a packet received from the outside world.
- Note: If the packet matched an existing NAT rule then this isn't called. *)
-let from_netvm (info : ([`NetVM | `External of _], _) Packet.info) : Packet.action =
- match info with
- | _ -> `Drop "drop by default"
+(** Packets from the private interface that don't match any NAT table entry are being checked against the fw rules here *)
+let from_client (packet : ([`Client of Fw_utils.client_link], _) Packet.t) : Packet.action Lwt.t =
+ match packet with
+ | { dst = `Firewall; transport_header = `UDP header; _ } ->
+ if header.Udp_packet.dst_port = dns_port
+ then Lwt.return @@ `NAT_to (`NetVM, dns_port)
+ else Lwt.return @@ `Drop "packet addressed to client gateway"
+ | { dst = `External _ ; _ } | { dst = `NetVM; _ } -> translate_accepted_packets packet
+ | { dst = `Firewall ; _ } -> Lwt.return @@ `Drop "packet addressed to firewall itself"
+ | { dst = `Client _ ; _ } -> classify_client_packet packet
+ | _ -> Lwt.return @@ `Drop "could not classify packet"
+
+(** Packets from the outside world that don't match any NAT table entry are being dropped by default *)
+let from_netvm (_packet : ([`NetVM | `External of _], _) Packet.t) : Packet.action Lwt.t =
+ Lwt.return @@ `Drop "drop by default"
diff --git a/test/config.ml b/test/config.ml
new file mode 100644
index 0000000..d8695e4
--- /dev/null
+++ b/test/config.ml
@@ -0,0 +1,27 @@
+open Mirage
+
+let pin = "git+https://github.com/roburio/alcotest.git#mirage"
+
+let packages = [
+ package "ethernet";
+ package "arp";
+ package "arp-mirage";
+ package "ipaddr";
+ package "tcpip" ~sublibs:["stack-direct"; "icmpv4"; "ipv4"; "udp"; "tcp"];
+ package "mirage-qubes";
+ package "mirage-qubes-ipv4";
+ package "dns-client" ~sublibs:["mirage"];
+ package ~pin "alcotest";
+ package ~pin "alcotest-mirage";
+]
+
+let client =
+ foreign ~packages
+ "Unikernel.Client" @@ random @-> time @-> mclock @-> network @-> qubesdb @-> job
+
+let db = default_qubesdb
+let network = default_network
+
+let () =
+ let job = [ client $ default_random $ default_time $ default_monotonic_clock $ network $ db ] in
+ register "http-fetch" job
diff --git a/test/test.sh b/test/test.sh
new file mode 100755
index 0000000..2971207
--- /dev/null
+++ b/test/test.sh
@@ -0,0 +1,138 @@
+#!/bin/bash
+function explain_commands {
+ echo "1) Set up test qubes:"
+echo "First, set up the test-mirage script from https://github.com/talex5/qubes-test-mirage.git"
+
+echo "Then, use `qubes-manager` to create two new AppVMs called `mirage-fw-test` and `fetchmotron`.
+You can make it standalone or not and use any template (it doesn't matter
+because unikernels already contain all their code and don't need to use a disk
+to boot)."
+
+echo "Next, still in dom0, create a new `mirage-fw-test` and `fetchmotron` kernels, with an empty `modules.img` and `vmlinuz` and a compressed empty file for the initramfs, and then set that as the kernel for the new VMs:
+
+ mkdir /var/lib/qubes/vm-kernels/mirage-fw-test
+ cd /var/lib/qubes/vm-kernels/mirage-fw-test
+ touch modules.img vmlinuz test-mirage-ok
+ cat /dev/null | gzip > initramfs
+ qvm-prefs -s mirage-fw-test kernel mirage-fw-test
+
+ mkdir /var/lib/qubes/vm-kernels/fetchmotron
+ cd /var/lib/qubes/vm-kernels/fetchmotron
+ touch modules.img vmlinuz test-mirage-ok
+ cat /dev/null | gzip > initramfs
+ qvm-prefs -s fetchmotron kernel fetchmotron
+"
+}
+
+function explain_service {
+echo "2) Set up rule update service:"
+echo "In dom0, make a new service:
+
+sudo bash
+echo /usr/local/bin/update-firewall > /etc/qubes-rpc/yomimono.updateFirewall
+
+Make a policy file for this service, YOUR_DEV_VM being the qube from which you build (e.g. ocamldev):
+
+cd /etc/qubes-rpc/policy
+cat << EOF >> yomimono.updateFirewall
+YOUR_DEV_VM dom0 allow
+
+copy the update-firewall script:
+
+cd /usr/local/bin
+qvm-run -p YOUR_DEV_VM 'cat /path/to/qubes-mirage-firewall/test/update-firewall.sh' > update-firewall
+chmod +x update-firewall
+
+Now, back to YOUR_DEV_VM. Let's test to change fetchmotron's firewall rules:
+
+qrexec-client-vm dom0 yomimono.updateFirewall"
+}
+
+function explain_upstream {
+echo "Also, start the test services on the upstream NetVM (which is available at 10.137.0.5 from the test unikernel).
+For the UDP and TCP reply services:
+Install nmap-ncat (to persist this package, install it in your sys-net template VM):
+
+sudo dnf install nmap-ncat
+
+Allow incoming traffic from local virtual interfaces on the appropriate ports,
+then run the services:
+
+sudo iptables -I INPUT -i vif+ -p udp --dport $udp_echo_port -j ACCEPT
+sudo iptables -I INPUT -i vif+ -p tcp --dport $tcp_echo_port_lower -j ACCEPT
+sudo iptables -I INPUT -i vif+ -p tcp --dport $tcp_echo_port_upper -j ACCEPT
+ncat -e /bin/cat -k -u -l $udp_echo_port &
+ncat -e /bin/cat -k -l $tcp_echo_port_lower &
+ncat -e /bin/cat -k -l $tcp_echo_port_upper &
+"
+}
+
+if ! [ -x "$(command -v test-mirage)" ]; then
+ echo 'Error: test-mirage is not installed.' >&2
+ explain_commands >&2
+ exit 1
+fi
+qrexec-client-vm dom0 yomimono.updateFirewall
+if [ $? -ne 0 ]; then
+ echo "Error: can't update firewall rules." >&2
+ explain_service >&2
+ exit 1
+fi
+echo_host=10.137.0.5
+udp_echo_port=1235
+tcp_echo_port_lower=6668
+tcp_echo_port_upper=6670
+
+# Pretest that checks if our echo servers work.
+# NOTE: we assume the dev qube has the same netvm as fetchmotron.
+# If yours is different, this test will fail (comment it out)
+function pretest {
+ protocol=$1
+ port=$2
+ if [ "$protocol" = "udp" ]; then
+ udp_arg="-u"
+ else
+ udp_arg=""
+ fi
+ reply=$(echo hi | nc $udp_arg $echo_host -w 1 $port)
+ if [ "$reply" != "hi" ]; then
+ echo "echo hi | nc $udp_arg $echo_host -w 1 $port"
+ echo "echo services not reachable at $protocol $echo_host:$port" >&2
+ explain_upstream >&2
+ exit 1
+ fi
+}
+
+pretest "udp" "$udp_echo_port"
+pretest "tcp" "$tcp_echo_port_lower"
+pretest "tcp" "$tcp_echo_port_upper"
+
+echo "We're gonna set up a unikernel for the mirage-fw-test qube"
+cd ..
+make clean && \
+#mirage configure -t xen -l "application:error,net-xen xenstore:error,firewall:debug,frameQ:debug,uplink:debug,rules:debug,udp:debug,ipv4:debug,fw-resolver:debug" && \
+mirage configure -t xen -l "net-xen xenstore:error,application:warning,qubes.db:warning" && \
+#mirage configure -t xen -l "*:debug" && \
+make depend && \
+make
+if [ $? -ne 0 ]; then
+ echo "Could not build unikernel for mirage-fw-test qube" >&2
+ exit 1
+fi
+cd test
+
+echo "We're gonna set up a unikernel for fetchmotron qube"
+make clean && \
+mirage configure -t qubes -l "net-xen frontend:error,firewall test:debug" && \
+#mirage configure -t qubes -l "*:error" && \
+make depend && \
+make
+if [ $? -ne 0 ]; then
+ echo "Could not build unikernel for fetchmotron qube" >&2
+ exit 1
+fi
+
+cd ..
+test-mirage qubes_firewall.xen mirage-fw-test &
+cd test
+test-mirage http_fetch.xen fetchmotron
diff --git a/test/unikernel.ml b/test/unikernel.ml
new file mode 100644
index 0000000..9c347f3
--- /dev/null
+++ b/test/unikernel.ml
@@ -0,0 +1,357 @@
+open Lwt.Infix
+(* https://www.qubes-os.org/doc/vm-interface/#firewall-rules-in-4x *)
+let src = Logs.Src.create "firewall test" ~doc:"Firewalltest"
+module Log = (val Logs.src_log src : Logs.LOG)
+
+(* TODO
+ * things we can have in rule
+ * - action:
+ x accept (UDP fetch test)
+ x drop (TCP connect denied test)
+ * - proto:
+ x None (TCP connect denied test)
+ x TCP (TCP connect test)
+ x UDP (UDP fetch test)
+ x ICMP (ping test)
+ * - specialtarget:
+ x None (UDP fetch test, TCP connect denied test)
+ x DNS (TCP connect test, TCP connect denied test)
+ * - destination:
+ x Any (TCP connect denied test)
+ x Some ipv4 host (UDP fetch test)
+ Some ipv6 host (we can't do this right now)
+ Some hostname (need a bunch of DNS stuff for that)
+ * - destination ports:
+ x none (TCP connect denied test)
+ x range is one port (UDP fetch test)
+ x range has different ports in pair
+ * - icmp type:
+ x None (TCP connect denied, UDP fetch test)
+ x query type (ping test)
+ error type
+ x - errors related to allowed traffic (does it have a host waiting for it?)
+ x - directly allowed outbound icmp errors (e.g. for forwarding)
+ * - number (ordering over rules, to resolve conflicts by precedence)
+ no overlap between rules, i.e. ordering unimportant
+ error case: multiple rules with same number?
+ x conflicting rules (specific accept rules with low numbers, drop all with high number)
+*)
+
+(* Point-to-point links out of a netvm always have this IP TODO clarify with Marek *)
+let netvm = "10.137.0.5"
+(* default "nameserver"s, which netvm redirects to whatever its real nameservers are *)
+let nameserver_1, nameserver_2 = "10.139.1.1", "10.139.1.2"
+
+module Client (R: Mirage_random.S) (Time: Mirage_time.S) (Clock : Mirage_clock.MCLOCK) (NET: Mirage_net.S) (DB : Qubes.S.DB) = struct
+ module E = Ethernet.Make(NET)
+ module A = Arp.Make(E)(Time)
+ module I = Qubesdb_ipv4.Make(DB)(R)(Clock)(E)(A)
+ module Icmp = Icmpv4.Make(I)
+ module U = Udp.Make(I)(R)
+ module T = Tcp.Flow.Make(I)(Time)(Clock)(R)
+
+ module Alcotest = Alcotest_mirage.Make(Clock)
+
+ module Stack = struct
+ (* A Mirage_stack.V4 implementation which diverts DHCP messages to a DHCP
+ server. The DHCP server needs to get the entire Ethernet frame, because
+ the Ethernet source address is the address to send replies to, its IPv4
+ addresses (source, destination) do not matter (since the DHCP client that
+ sent this request does not have an IP address yet). ARP cannot be used
+ by DHCP, because the client does not have an IP address (and thus no ARP
+ replies). *)
+
+ module UDPV4 = U
+ module TCPV4 = T
+ module IPV4 = I
+
+ type t = {
+ net : NET.t ; eth : E.t ; arp : A.t ;
+ ip : I.t ; icmp : Icmp.t ; udp : U.t ; tcp : T.t ;
+ udp_listeners : (int, U.callback) Hashtbl.t ;
+ tcp_listeners : (int, T.listener) Hashtbl.t ;
+ mutable icmp_listener : (src:Ipaddr.V4.t -> dst:Ipaddr.V4.t -> Cstruct.t -> unit Lwt.t) option ;
+ }
+
+ let ipv4 { ip ; _ } = ip
+ let udpv4 { udp ; _ } = udp
+ let tcpv4 { tcp ; _ } = tcp
+ let icmpv4 { icmp ; _ } = icmp
+
+ let listener h port = Hashtbl.find_opt h port
+ let udp_listener h ~dst_port = listener h dst_port
+
+ let listen_udpv4 { udp_listeners ; _ } ~port cb =
+ Hashtbl.replace udp_listeners port cb
+
+ let stop_listen_udpv4 { udp_listeners ; _ } ~port =
+ Hashtbl.remove udp_listeners port
+
+ let listen_tcpv4 ?keepalive { tcp_listeners ; _ } ~port cb =
+ Hashtbl.replace tcp_listeners port { T.process = cb ; T.keepalive }
+
+ let stop_listen_tcpv4 { tcp_listeners ; _ } ~port =
+ Hashtbl.remove tcp_listeners port
+
+ let listen_icmp t cb = t.icmp_listener <- cb
+
+ let listen t =
+ let ethif_listener =
+ E.input
+ ~arpv4:(A.input t.arp)
+ ~ipv4:(
+ I.input
+ ~tcp:(T.input t.tcp ~listeners:(listener t.tcp_listeners))
+ ~udp:(U.input t.udp ~listeners:(udp_listener t.udp_listeners))
+ ~default:(fun ~proto ~src ~dst buf ->
+ match proto with
+ | 1 ->
+ begin match t.icmp_listener with
+ | None -> Icmp.input t.icmp ~src ~dst buf
+ | Some cb -> cb ~src ~dst buf
+ end
+ | _ -> Lwt.return_unit)
+ t.ip)
+ ~ipv6:(fun _ -> Lwt.return_unit)
+ t.eth
+ in
+ NET.listen t.net ~header_size:Ethernet_wire.sizeof_ethernet ethif_listener
+ >>= function
+ | Error e ->
+ Logs.warn (fun p -> p "%a" NET.pp_error e) ;
+ Lwt.return_unit
+ | Ok _res -> Lwt.return_unit
+
+ let connect net eth arp ip icmp udp tcp =
+ { net ; eth ; arp ; ip ; icmp ; udp ; tcp ;
+ udp_listeners = Hashtbl.create 2 ;
+ tcp_listeners = Hashtbl.create 2 ;
+ icmp_listener = None ;
+ }
+
+ let disconnect _ =
+ Logs.warn (fun m -> m "ignoring disconnect");
+ Lwt.return_unit
+ end
+
+ module Dns = Dns_client_mirage.Make(R)(Time)(Clock)(Stack)
+
+ let make_ping_packet payload =
+ let echo_request = { Icmpv4_packet.code = 0; (* constant for echo request/reply *)
+ ty = Icmpv4_wire.Echo_request;
+ subheader = Icmpv4_packet.(Id_and_seq (0, 0)); } in
+ Icmpv4_packet.Marshal.make_cstruct echo_request ~payload
+
+ let is_ping_reply src server packet =
+ 0 = Ipaddr.V4.(compare src @@ of_string_exn server) &&
+ packet.Icmpv4_packet.code = 0 &&
+ packet.Icmpv4_packet.ty = Icmpv4_wire.Echo_reply &&
+ packet.Icmpv4_packet.subheader = Icmpv4_packet.(Id_and_seq (0, 0))
+
+ let ping_denied_listener server resp_received stack =
+ let icmp_listener ~src ~dst:_ buf =
+ (* hopefully this is a reply to an ICMP echo request we sent *)
+ Log.info (fun f -> f "ping test: ICMP message received from %a: %a" I.pp_ipaddr src Cstruct.hexdump_pp buf);
+ match Icmpv4_packet.Unmarshal.of_cstruct buf with
+ | Error e -> Log.err (fun f -> f "couldn't parse ICMP packet: %s" e);
+ Lwt.return_unit
+ | Ok (packet, _payload) ->
+ Log.info (fun f -> f "ICMP message: %a" Icmpv4_packet.pp packet);
+ if is_ping_reply src server packet then resp_received := true;
+ Lwt.return_unit
+ in
+ Stack.listen_icmp stack (Some icmp_listener)
+
+ let ping_expect_failure server stack () =
+ let resp_received = ref false in
+ Log.info (fun f -> f "Entering ping test: %s" server);
+ ping_denied_listener server resp_received stack;
+ Icmp.write (Stack.icmpv4 stack) ~dst:(Ipaddr.V4.of_string_exn server) (make_ping_packet (Cstruct.of_string "hi")) >>= function
+ | Error e -> Log.err (fun f -> f "ping test: error sending ping: %a" Icmp.pp_error e); Lwt.return_unit
+ | Ok () ->
+ Log.info (fun f -> f "ping test: sent ping to %s" server);
+ Time.sleep_ns 2_000_000_000L >>= fun () ->
+ (if !resp_received then
+ Log.err (fun f -> f "ping test failed: server %s got a response, block expected :(" server)
+ else
+ Log.err (fun f -> f "ping test passed: successfully blocked :)")
+ );
+ Stack.listen_icmp stack None;
+ Lwt.return_unit
+
+ let icmp_error_type stack () =
+ let resp_correct = ref false in
+ let echo_server = Ipaddr.V4.of_string_exn netvm in
+ let icmp_callback ~src ~dst:_ buf =
+ if Ipaddr.V4.compare src echo_server = 0 then begin
+ (* TODO: check that packet is error packet *)
+ match Icmpv4_packet.Unmarshal.of_cstruct buf with
+ | Error e -> Log.err (fun f -> f "Error parsing icmp packet %s" e)
+ | Ok (packet, _) ->
+ (* TODO don't hardcode the numbers, make a datatype *)
+ if packet.Icmpv4_packet.code = 10 (* unreachable, admin prohibited *)
+ then resp_correct := true
+ else Log.debug (fun f -> f "Unrelated icmp packet %a" Icmpv4_packet.pp packet)
+ end;
+ Lwt.return_unit
+ in
+ let content = Cstruct.of_string "important data" in
+ Stack.listen_icmp stack (Some icmp_callback);
+ U.write ~src_port:1337 ~dst:echo_server ~dst_port:1338 (Stack.udpv4 stack) content >>= function
+ | Ok () -> (* .. listener: test with accept rule, if we get reply we're good *)
+ Time.sleep_ns 1_000_000_000L >>= fun () ->
+ if !resp_correct
+ then Log.info (fun m -> m "UDP fetch test to port %d succeeded :)" 1338)
+ else Log.err (fun f -> f "UDP fetch test to port %d: failed. :( correct response not received" 1338);
+ Stack.listen_icmp stack None;
+ Lwt.return_unit
+ | Error e ->
+ Log.err (fun f -> f "UDP fetch test to port %d failed: :( couldn't write the packet: %a"
+ 1338 U.pp_error e);
+ Lwt.return_unit
+
+ let tcp_connect msg server port tcp () =
+ Log.info (fun f -> f "Entering tcp connect test: %s:%d" server port);
+ let ip = Ipaddr.V4.of_string_exn server in
+ let msg' = Printf.sprintf "TCP connect test %s to %s:%d" msg server port in
+ T.create_connection tcp (ip, port) >>= function
+ | Ok flow ->
+ Log.info (fun f -> f "%s passed :)" msg');
+ T.close flow
+ | Error e -> Log.err (fun f -> f "%s failed: Connection failed (%a) :(" msg' T.pp_error e);
+ Lwt.return_unit
+
+ let tcp_connect_denied msg server port tcp () =
+ let ip = Ipaddr.V4.of_string_exn server in
+ let msg' = Printf.sprintf "TCP connect denied test %s to %s:%d" msg server port in
+ let connect = (T.create_connection tcp (ip, port) >>= function
+ | Ok flow ->
+ Log.err (fun f -> f "%s failed: Connection should be denied, but was not. :(" msg');
+ T.close flow
+ | Error e -> Log.info (fun f -> f "%s passed (error text: %a) :)" msg' T.pp_error e);
+ Lwt.return_unit)
+ in
+ let timeout = (
+ Time.sleep_ns 1_000_000_000L >>= fun () ->
+ Log.info (fun f -> f "%s passed :)" msg');
+ Lwt.return_unit)
+ in
+ Lwt.pick [ connect ; timeout ]
+
+ let udp_fetch ~src_port ~echo_server_port stack () =
+ Log.info (fun f -> f "Entering udp fetch test: %d -> %s:%d"
+ src_port netvm echo_server_port);
+ let resp_correct = ref false in
+ let echo_server = Ipaddr.V4.of_string_exn netvm in
+ let content = Cstruct.of_string "important data" in
+ let udp_listener : U.callback = (fun ~src ~dst:_ ~src_port buf ->
+ Log.debug (fun f -> f "listen_udpv4 function invoked for packet: %a" Cstruct.hexdump_pp buf);
+ if ((0 = Ipaddr.V4.compare echo_server src) && src_port = echo_server_port) then
+ match Cstruct.equal buf content with
+ | true -> (* yay *)
+ Log.info (fun f -> f "UDP fetch test to port %d: passed :)" echo_server_port);
+ resp_correct := true;
+ Lwt.return_unit
+ | false -> (* oh no *)
+ Log.err (fun f -> f "UDP fetch test to port %d: failed. :( Packet corrupted; expected %a but got %a"
+ echo_server_port Cstruct.hexdump_pp content Cstruct.hexdump_pp buf);
+ Lwt.return_unit
+ else
+ begin
+ (* disregard this packet *)
+ Log.debug (fun f -> f "packet is not from the echo server or has the wrong source port (%d but we wanted %d)"
+ src_port echo_server_port);
+ (* don't cancel the listener, since we want to keep listening *)
+ Lwt.return_unit
+ end
+ )
+ in
+ Stack.listen_udpv4 stack ~port:src_port udp_listener;
+ U.write ~src_port ~dst:echo_server ~dst_port:echo_server_port (Stack.udpv4 stack) content >>= function
+ | Ok () -> (* .. listener: test with accept rule, if we get reply we're good *)
+ Time.sleep_ns 1_000_000_000L >>= fun () ->
+ Stack.stop_listen_udpv4 stack ~port:src_port;
+ if !resp_correct then Lwt.return_unit else begin
+ Log.err (fun f -> f "UDP fetch test to port %d: failed. :( correct response not received" echo_server_port);
+ Lwt.return_unit
+ end
+ | Error e ->
+ Log.err (fun f -> f "UDP fetch test to port %d failed: :( couldn't write the packet: %a"
+ echo_server_port U.pp_error e);
+ Lwt.return_unit
+
+ let dns_expect_failure ~nameserver ~hostname stack () =
+ let lookup = Domain_name.(of_string_exn hostname |> host_exn) in
+ let nameserver' = `UDP, (Ipaddr.V4.of_string_exn nameserver, 53) in
+ let dns = Dns.create ~nameserver:nameserver' stack in
+ Dns.gethostbyname dns lookup >>= function
+ | Error (`Msg s) when String.compare s "Truncated UDP response" <> 0 -> Log.debug (fun f -> f "DNS test to %s failed as expected: %s"
+ nameserver s);
+ Log.info (fun f -> f "DNS traffic to %s correctly blocked :)" nameserver);
+ Lwt.return_unit
+ | Error (`Msg s) ->
+ Log.debug (fun f -> f "DNS test to %s failed unexpectedly (truncated response): %s :("
+ nameserver s);
+ Lwt.return_unit
+ | Ok addr -> Log.err (fun f -> f "DNS test to %s should have been blocked, but looked up %s:%a" nameserver hostname Ipaddr.V4.pp addr);
+ Lwt.return_unit
+
+ let dns_then_tcp_denied server stack () =
+ let parsed_server = Domain_name.(of_string_exn server |> host_exn) in
+ (* ask dns about server *)
+ Log.debug (fun f -> f "going to make a dns thing using nameserver %s" nameserver_1);
+ let dns = Dns.create ~nameserver:(`UDP, ((Ipaddr.V4.of_string_exn nameserver_1), 53)) stack in
+ Log.debug (fun f -> f "OK, going to look up %s now" server);
+ Dns.gethostbyname dns parsed_server >>= function
+ | Error (`Msg s) -> Log.err (fun f -> f "couldn't look up ip for %s: %s" server s); Lwt.return_unit
+ | Ok addr ->
+ Log.debug (fun f -> f "looked up ip for %s: %a" server Ipaddr.V4.pp addr);
+ Log.err (fun f -> f "Do more stuff here!!!! :(");
+ Lwt.return_unit
+
+ let start _random _time _clock network db =
+ E.connect network >>= fun ethernet ->
+ A.connect ethernet >>= fun arp ->
+ I.connect db ethernet arp >>= fun ipv4 ->
+ Icmp.connect ipv4 >>= fun icmp ->
+ U.connect ipv4 >>= fun udp ->
+ T.connect ipv4 >>= fun tcp ->
+
+ let stack = Stack.connect network ethernet arp ipv4 icmp udp tcp in
+ Lwt.async (fun () -> Stack.listen stack);
+
+ (* put this first because tcp_connect_denied tests also generate icmp messages *)
+ let general_tests : unit Alcotest.test = ("firewall tests", [
+ ("UDP fetch", `Quick, udp_fetch ~src_port:9090 ~echo_server_port:1235 stack);
+ ("Ping expect failure", `Quick, ping_expect_failure "8.8.8.8" stack );
+ (* TODO: ping_expect_success to the netvm, for which we have an icmptype rule in update-firewall.sh *)
+ ("ICMP error type", `Quick, icmp_error_type stack)
+ ] ) in
+ Alcotest.run ~and_exit:false "name" [ general_tests ] >>= fun () ->
+ let tcp_tests : unit Alcotest.test = ("tcp tests", [
+ (* this test fails on 4.0R3
+ ("TCP connect", `Quick, tcp_connect "when trying specialtarget" nameserver_1 53 tcp); *)
+ ("TCP connect", `Quick, tcp_connect_denied "" netvm 53 tcp);
+ ("TCP connect", `Quick, tcp_connect_denied "when trying below range" netvm 6667 tcp);
+ ("TCP connect", `Quick, tcp_connect "when trying lower bound in range" netvm 6668 tcp);
+ ("TCP connect", `Quick, tcp_connect "when trying upper bound in range" netvm 6670 tcp);
+ ("TCP connect", `Quick, tcp_connect_denied "when trying above range" netvm 6671 tcp);
+ ("TCP connect", `Quick, tcp_connect_denied "" netvm 8082 tcp);
+ ] ) in
+
+ (* replace the udp-related listeners with the right one for tcp *)
+ Alcotest.run "name" [ tcp_tests ] >>= fun () ->
+ (* use the stack abstraction only after the other tests have run, since it's not friendly with outside use of its modules *)
+ let stack_tests = "stack tests", [
+ ("DNS expect failure", `Quick, dns_expect_failure ~nameserver:"8.8.8.8" ~hostname:"mirage.io" stack);
+
+ (* the test below won't work on @linse's internet,
+ * because the nameserver there doesn't answer on TCP port 53,
+ * only UDP port 53. Dns_mirage_client.ml disregards our request
+ * to use UDP and uses TCP anyway, so this request can never work there. *)
+ (* If we can figure out a way to have this test unikernel do a UDP lookup with minimal pain,
+ * we should re-enable this test. *)
+ ("DNS lookup + TCP connect", `Quick, dns_then_tcp_denied "google.com" stack);
+ ] in
+ Alcotest.run "name" [ stack_tests ]
+end
diff --git a/test/update-firewall.sh b/test/update-firewall.sh
new file mode 100644
index 0000000..fcfaac4
--- /dev/null
+++ b/test/update-firewall.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+# this script sets a deny-all rule for a particular VM, set here as TEST_VM.
+# it is intended to be used as part of a test suite which analyzes whether
+# an upstream FirewallVM correctly applies rule changes when they occur.
+
+# Copy this script into dom0 at /usr/local/bin/update-firewall.sh so it can be
+# remotely triggered by your development VM as part of the firewall testing
+# script.
+
+TEST_VM=fetchmotron
+
+#echo "Current $TEST_VM firewall rules:"
+#qvm-firewall $TEST_VM list
+
+echo "Removing $TEST_VM rules..."
+rc=0
+while [ "$rc" = "0" ]; do
+ qvm-firewall $TEST_VM del --rule-no 0
+ rc=$?
+done
+
+#echo "$TEST_VM firewall rules are now:"
+#qvm-firewall $TEST_VM list
+
+#echo "Setting $TEST_VM specialtarget=dns rule:"
+qvm-firewall $TEST_VM add accept specialtarget=dns
+
+#echo "Setting $TEST_VM allow rule for UDP port 1235 to 10.137.0.5:"
+qvm-firewall $TEST_VM add accept 10.137.0.5 udp 1235
+
+#echo "Setting $TEST_VM allow rule for UDP port 1338 to 10.137.0.5:"
+qvm-firewall $TEST_VM add accept 10.137.0.5 udp 1338
+
+#echo "Setting $TEST_VM allow rule for TCP port 6668-6670 to 10.137.0.5:"
+qvm-firewall $TEST_VM add accept 10.137.0.5 tcp 6668-6670
+
+#echo "Setting $TEST_VM allow rule for ICMP type 8 (ping) to 10.137.0.5:"
+qvm-firewall $TEST_VM add accept 10.137.0.5 icmp icmptype=8
+
+#echo "Setting $TEST_VM allow rule for bogus.linse.me:"
+qvm-firewall $TEST_VM add accept dsthost=bogus.linse.me
+
+#echo "Setting deny rule to host google.com:"
+qvm-firewall $TEST_VM add drop dsthost=google.com
+
+#echo "Setting allow-all on port 443 rule:"
+qvm-firewall $TEST_VM add accept proto=tcp dstports=443-443
+
+#echo "Setting $TEST_VM deny-all rule:"
+qvm-firewall $TEST_VM add drop
+
+echo "$TEST_VM firewall rules are now:"
+qvm-firewall $TEST_VM list
diff --git a/unikernel.ml b/unikernel.ml
index 6eaca4e..7a3b1d7 100644
--- a/unikernel.ml
+++ b/unikernel.ml
@@ -7,27 +7,15 @@ open Qubes
let src = Logs.Src.create "unikernel" ~doc:"Main unikernel code"
module Log = (val Logs.src_log src : Logs.LOG)
-module Main (Clock : Mirage_clock.MCLOCK) = struct
+module Main (R : Mirage_random.S)(Clock : Mirage_clock.MCLOCK) = struct
+
(* Set up networking and listen for incoming packets. *)
- let network nat qubesDB =
- (* Read configuration from QubesDB *)
- Dao.read_network_config qubesDB >>= fun config ->
- (* Initialise connection to NetVM *)
- Uplink.connect config >>= fun uplink ->
+ let network uplink qubesDB router =
(* Report success *)
Dao.set_iptables_error qubesDB "" >>= fun () ->
- (* Set up client-side networking *)
- let client_eth = Client_eth.create
- ~client_gw:config.Dao.clients_our_ip in
- (* Set up routing between networks and hosts *)
- let router = Router.create
- ~client_eth
- ~uplink:(Uplink.interface uplink)
- ~nat
- in
(* Handle packets from both networks *)
Lwt.choose [
- Client_net.listen Clock.elapsed_ns router;
+ Client_net.listen Clock.elapsed_ns qubesDB router;
Uplink.listen uplink Clock.elapsed_ns router
]
@@ -49,17 +37,18 @@ module Main (Clock : Mirage_clock.MCLOCK) = struct
)
(* Main unikernel entry point (called from auto-generated main.ml). *)
- let start _clock =
+ let start _random _clock =
let start_time = Clock.elapsed_ns () in
(* Start qrexec agent, GUI agent and QubesDB agent in parallel *)
let qrexec = RExec.connect ~domid:0 () in
GUI.connect ~domid:0 () |> watch_gui;
let qubesDB = DB.connect ~domid:0 () in
+
(* Wait for clients to connect *)
qrexec >>= fun qrexec ->
let agent_listener = RExec.listen qrexec Command.handler in
qubesDB >>= fun qubesDB ->
- let startup_time =
+ let startup_time =
let (-) = Int64.sub in
let time_in_ns = Clock.elapsed_ns () - start_time in
Int64.to_float time_in_ns /. 1e9
@@ -72,7 +61,23 @@ module Main (Clock : Mirage_clock.MCLOCK) = struct
(* Set up networking *)
let max_entries = Key_gen.nat_table_size () in
My_nat.create ~max_entries >>= fun nat ->
- let net_listener = network nat qubesDB in
+
+ (* Read network configuration from QubesDB *)
+ Dao.read_network_config qubesDB >>= fun config ->
+
+ Uplink.connect config >>= fun uplink ->
+ (* Set up client-side networking *)
+ let client_eth = Client_eth.create
+ ~client_gw:config.Dao.clients_our_ip in
+ (* Set up routing between networks and hosts *)
+ let router = Router.create
+ ~client_eth
+ ~uplink:(Uplink.interface uplink)
+ ~nat
+ in
+
+ let net_listener = network uplink qubesDB router in
+
(* Report memory usage to XenStore *)
Memory_pressure.init ();
(* Run until something fails or we get a shutdown request. *)
From 2d78d47591b18fc147479f90edd0e8b2bac53ff7 Mon Sep 17 00:00:00 2001
From: linse
Date: Wed, 29 Apr 2020 16:06:48 +0200
Subject: [PATCH 098/281] Support firewall rules with hostnames.
Co-Authored-By: Mindy Preston
Co-Authored-By: Olle Jonsson
Co-Authored-By: hannes
Co-Authored-By: cfcs
---
client_net.ml | 18 ++++++------
client_net.mli | 6 ++--
config.ml | 1 +
firewall.ml | 18 ++++++------
firewall.mli | 4 ++-
my_dns.ml | 57 ++++++++++++++++++++++++++++++++++++++
my_nat.ml | 46 ++++++++++++++++++++++++-------
my_nat.mli | 15 ++++++++--
ports.ml | 16 +++++++++++
router.ml | 5 +++-
router.mli | 1 +
rules.ml | 33 ++++++++++++++--------
unikernel.ml | 15 +++++++---
uplink.ml | 74 ++++++++++++++++++++++++++++++++++----------------
uplink.mli | 19 ++++++++-----
15 files changed, 247 insertions(+), 81 deletions(-)
create mode 100644 my_dns.ml
create mode 100644 ports.ml
diff --git a/client_net.ml b/client_net.ml
index 31f3f2d..10d4412 100644
--- a/client_net.ml
+++ b/client_net.ml
@@ -59,7 +59,7 @@ let input_arp ~fixed_arp ~iface request =
iface#writev `ARP (fun b -> Arp_packet.encode_into response b; Arp_packet.size)
(** Handle an IPv4 packet from the client. *)
-let input_ipv4 get_ts cache ~iface ~router packet =
+let input_ipv4 get_ts cache ~iface ~router dns_client packet =
let cache', r = Nat_packet.of_ipv4_packet !cache ~now:(get_ts ()) packet in
cache := cache';
match r with
@@ -70,7 +70,7 @@ let input_ipv4 get_ts cache ~iface ~router packet =
| Ok (Some packet) ->
let `IPv4 (ip, _) = packet in
let src = ip.Ipv4_packet.src in
- if src = iface#other_ip then Firewall.ipv4_from_client router ~src:iface packet
+ if src = iface#other_ip then Firewall.ipv4_from_client dns_client router ~src:iface packet
else (
Log.warn (fun f -> f "Incorrect source IP %a in IP packet from %a (dropping)"
Ipaddr.V4.pp src Ipaddr.V4.pp iface#other_ip);
@@ -78,7 +78,7 @@ let input_ipv4 get_ts cache ~iface ~router packet =
)
(** Connect to a new client's interface and listen for incoming frames and firewall rule changes. *)
-let add_vif get_ts { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks qubesDB =
+let add_vif get_ts { Dao.ClientVif.domid; device_id } dns_client ~client_ip ~router ~cleanup_tasks qubesDB =
Netback.make ~domid ~device_id >>= fun backend ->
Log.info (fun f -> f "Client %d (IP: %s) ready" domid (Ipaddr.V4.to_string client_ip));
ClientEth.connect backend >>= fun eth ->
@@ -101,7 +101,7 @@ let add_vif get_ts { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanu
(Ipaddr.V4.to_string client_ip)
Fmt.(list ~sep:(unit "@.") Pf_qubes.Parse_qubes.pp_rule) new_rules);
(* empty NAT table if rules are updated: they might deny old connections *)
- My_nat.remove_connections router.Router.nat client_ip;
+ My_nat.remove_connections router.Router.nat router.Router.ports client_ip;
end);
update new_db new_rules
in
@@ -122,7 +122,7 @@ let add_vif get_ts { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanu
| Ok (eth, payload) ->
match eth.Ethernet_packet.ethertype with
| `ARP -> input_arp ~fixed_arp ~iface payload
- | `IPv4 -> input_ipv4 get_ts fragment_cache ~iface ~router payload
+ | `IPv4 -> input_ipv4 get_ts fragment_cache ~iface ~router dns_client payload
| `IPv6 -> Lwt.return_unit (* TODO: oh no! *)
)
>|= or_raise "Listen on client interface" Netback.pp_error)
@@ -132,13 +132,13 @@ let add_vif get_ts { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanu
Lwt.pick [ qubesdb_updater ; listener ]
(** A new client VM has been found in XenStore. Find its interface and connect to it. *)
-let add_client get_ts ~router vif client_ip qubesDB =
+let add_client get_ts dns_client ~router vif client_ip qubesDB =
let cleanup_tasks = Cleanup.create () in
Log.info (fun f -> f "add client vif %a with IP %a"
Dao.ClientVif.pp vif Ipaddr.V4.pp client_ip);
Lwt.async (fun () ->
Lwt.catch (fun () ->
- add_vif get_ts vif ~client_ip ~router ~cleanup_tasks qubesDB
+ add_vif get_ts vif dns_client ~client_ip ~router ~cleanup_tasks qubesDB
)
(fun ex ->
Log.warn (fun f -> f "Error with client %a: %s"
@@ -149,7 +149,7 @@ let add_client get_ts ~router vif client_ip qubesDB =
cleanup_tasks
(** Watch XenStore for notifications of new clients. *)
-let listen get_ts qubesDB router =
+let listen get_ts dns_client qubesDB router =
Dao.watch_clients (fun new_set ->
(* Check for removed clients *)
!clients |> Dao.VifMap.iter (fun key cleanup ->
@@ -162,7 +162,7 @@ let listen get_ts qubesDB router =
(* Check for added clients *)
new_set |> Dao.VifMap.iter (fun key ip_addr ->
if not (Dao.VifMap.mem key !clients) then (
- let cleanup = add_client get_ts ~router key ip_addr qubesDB in
+ let cleanup = add_client get_ts dns_client ~router key ip_addr qubesDB in
Log.debug (fun f -> f "client %a arrived" Dao.ClientVif.pp key);
clients := !clients |> Dao.VifMap.add key cleanup
)
diff --git a/client_net.mli b/client_net.mli
index 0bfbb01..fc1953a 100644
--- a/client_net.mli
+++ b/client_net.mli
@@ -3,8 +3,10 @@
(** Handling client VMs. *)
-val listen : (unit -> int64) -> Qubes.DB.t -> Router.t -> 'a Lwt.t
-(** [listen get_timestamp db router] is a thread that watches for clients being added to and
+val listen : (unit -> int64) ->
+ ([ `host ] Domain_name.t -> (int32 * Dns.Rr_map.Ipv4_set.t, [> `Msg of string ]) result Lwt.t) ->
+ Qubes.DB.t -> Router.t -> 'a Lwt.t
+(** [listen get_timestamp resolver db router] is a thread that watches for clients being added to and
removed from XenStore. Clients are connected to the client network and
packets are sent via [router]. We ensure the source IP address is correct
before routing a packet. *)
diff --git a/config.ml b/config.ml
index 87ba926..3075006 100644
--- a/config.ml
+++ b/config.ml
@@ -34,6 +34,7 @@ let main =
package "mirage-nat" ~min:"2.2.1";
package "mirage-logs";
package "mirage-xen" ~min:"5.0.0";
+ package ~min:"4.5.0" "dns-client";
package "pf-qubes";
]
"Unikernel.Main" (random @-> mclock @-> job)
diff --git a/firewall.ml b/firewall.ml
index 48d4fe4..9b1587c 100644
--- a/firewall.ml
+++ b/firewall.ml
@@ -45,8 +45,9 @@ let translate t packet =
(* Add a NAT rule for the endpoints in this frame, via a random port on the firewall. *)
let add_nat_and_forward_ipv4 t packet =
- let xl_host = t.Router.uplink#my_ip in
- My_nat.add_nat_rule_and_translate t.Router.nat ~xl_host `NAT packet >>= function
+ let open Router in
+ let xl_host = t.uplink#my_ip in
+ My_nat.add_nat_rule_and_translate t.nat t.ports ~xl_host `NAT packet >>= function
| Ok packet -> forward_ipv4 t packet
| Error e ->
Log.warn (fun f -> f "Failed to add NAT rewrite rule: %s (%a)" e Nat_packet.pp packet);
@@ -54,11 +55,12 @@ let add_nat_and_forward_ipv4 t packet =
(* Add a NAT rule to redirect this conversation to [host:port] instead of us. *)
let nat_to t ~host ~port packet =
- match Router.resolve t host with
+ let open Router in
+ match resolve t host with
| Ipaddr.V6 _ -> Log.warn (fun f -> f "Cannot NAT with IPv6"); Lwt.return_unit
| Ipaddr.V4 target ->
- let xl_host = t.Router.uplink#my_ip in
- My_nat.add_nat_rule_and_translate t.Router.nat ~xl_host (`Redirect (target, port)) packet >>= function
+ let xl_host = t.uplink#my_ip in
+ My_nat.add_nat_rule_and_translate t.nat t.ports ~xl_host (`Redirect (target, port)) packet >>= function
| Ok packet -> forward_ipv4 t packet
| Error e ->
Log.warn (fun f -> f "Failed to add NAT redirect rule: %s (%a)" e Nat_packet.pp packet);
@@ -85,11 +87,11 @@ let handle_low_memory t =
match Memory_pressure.status () with
| `Memory_critical -> (* TODO: should happen before copying and async *)
Log.warn (fun f -> f "Memory low - dropping packet and resetting NAT table");
- My_nat.reset t.Router.nat >|= fun () ->
+ My_nat.reset t.Router.nat t.Router.ports >|= fun () ->
`Memory_critical
| `Ok -> Lwt.return `Ok
-let ipv4_from_client t ~src packet =
+let ipv4_from_client resolver t ~src packet =
handle_low_memory t >>= function
| `Memory_critical -> Lwt.return_unit
| `Ok ->
@@ -102,7 +104,7 @@ let ipv4_from_client t ~src packet =
let dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst) in
match of_mirage_nat_packet ~src:(`Client src) ~dst packet with
| None -> Lwt.return_unit
- | Some firewall_packet -> apply_rules t Rules.from_client ~dst firewall_packet
+ | Some firewall_packet -> apply_rules t (Rules.from_client resolver) ~dst firewall_packet
let ipv4_from_netvm t packet =
handle_low_memory t >>= function
diff --git a/firewall.mli b/firewall.mli
index 9900f56..88f02ba 100644
--- a/firewall.mli
+++ b/firewall.mli
@@ -6,6 +6,8 @@
val ipv4_from_netvm : Router.t -> Nat_packet.t -> unit Lwt.t
(** Handle a packet from the outside world (this module will validate the source IP). *)
-val ipv4_from_client : Router.t -> src:Fw_utils.client_link -> Nat_packet.t -> unit Lwt.t
+(* TODO the function type is a workaround, rework the module dependencies / functors to get rid of it *)
+val ipv4_from_client : ([ `host ] Domain_name.t -> (int32 * Dns.Rr_map.Ipv4_set.t, [> `Msg of string ]) result Lwt.t) ->
+ Router.t -> src:Fw_utils.client_link -> Nat_packet.t -> unit Lwt.t
(** Handle a packet from a client. Caller must check the source IP matches the client's
before calling this. *)
diff --git a/my_dns.ml b/my_dns.ml
new file mode 100644
index 0000000..c94cbb1
--- /dev/null
+++ b/my_dns.ml
@@ -0,0 +1,57 @@
+open Lwt.Infix
+
+module Transport (R : Mirage_random.S) (C : Mirage_clock.MCLOCK) = struct
+ type +'a io = 'a Lwt.t
+ type io_addr = Ipaddr.V4.t * int
+ type ns_addr = [ `TCP | `UDP ] * io_addr
+ type stack = Router.t * (src_port:int -> dst:Ipaddr.V4.t -> dst_port:int -> Cstruct.t -> (unit, [ `Msg of string ]) result Lwt.t) * (Udp_packet.t * Cstruct.t) Lwt_mvar.t
+
+ type t = {
+ nameserver : ns_addr ;
+ stack : stack ;
+ timeout_ns : int64 ;
+ }
+ type context = { t : t ; timeout_ns : int64 ref; mutable src_port : int }
+
+ let nameserver t = t.nameserver
+ let rng = R.generate ?g:None
+ let clock = C.elapsed_ns
+
+ let create ?(nameserver = `UDP, (Ipaddr.V4.of_string_exn "91.239.100.100", 53)) ~timeout stack =
+ { nameserver ; stack ; timeout_ns = timeout }
+
+ let with_timeout ctx f =
+ let timeout = OS.Time.sleep_ns !(ctx.timeout_ns) >|= fun () -> Error (`Msg "DNS request timeout") in
+ let start = clock () in
+ Lwt.pick [ f ; timeout ] >|= fun result ->
+ let stop = clock () in
+ ctx.timeout_ns := Int64.sub !(ctx.timeout_ns) (Int64.sub stop start);
+ result
+
+ let connect ?nameserver:_ (t : t) = Lwt.return (Ok { t ; timeout_ns = ref t.timeout_ns ; src_port = 0 })
+
+ let send (ctx : context) buf : (unit, [> `Msg of string ]) result Lwt.t =
+ let open Router in
+ let open My_nat in
+ let dst, dst_port = snd ctx.t.nameserver in
+ let router, send_udp, _ = ctx.t.stack in
+ let src_port = Ports.pick_free_port ~consult:router.ports.nat_udp router.ports.dns_udp in
+ ctx.src_port <- src_port;
+ with_timeout ctx (send_udp ~src_port ~dst ~dst_port buf >|= Rresult.R.open_error_msg)
+
+ let recv ctx =
+ let open Router in
+ let open My_nat in
+ let router, _, answers = ctx.t.stack in
+ with_timeout ctx
+ (Lwt_mvar.take answers >|= fun (_, dns_response) -> Ok dns_response) >|= fun result ->
+ router.ports.dns_udp := Ports.remove ctx.src_port !(router.ports.dns_udp);
+ result
+
+ let close _ = Lwt.return_unit
+
+ let bind = Lwt.bind
+
+ let lift = Lwt.return
+end
+
diff --git a/my_nat.ml b/my_nat.ml
index 9dfcf68..2652ff5 100644
--- a/my_nat.ml
+++ b/my_nat.ml
@@ -11,6 +11,20 @@ type action = [
| `Redirect of Mirage_nat.endpoint
]
+type ports = {
+ nat_tcp : Ports.t ref;
+ nat_udp : Ports.t ref;
+ nat_icmp : Ports.t ref;
+ dns_udp : Ports.t ref;
+}
+
+let empty_ports () =
+ let nat_tcp = ref Ports.empty in
+ let nat_udp = ref Ports.empty in
+ let nat_icmp = ref Ports.empty in
+ let dns_udp = ref Ports.empty in
+ { nat_tcp ; nat_udp ; nat_icmp ; dns_udp }
+
module Nat = Mirage_nat_lru
type t = {
@@ -33,17 +47,23 @@ let translate t packet =
None
| Ok packet -> Some packet
-let random_user_port () =
- 1024 + Random.int (0xffff - 1024)
+let pick_free_port ~nat_ports ~dns_ports =
+ Ports.pick_free_port ~consult:dns_ports nat_ports
-let reset t =
+(* just clears the nat ports, dns ports stay as is *)
+let reset t ports =
+ ports.nat_tcp := Ports.empty;
+ ports.nat_udp := Ports.empty;
+ ports.nat_icmp := Ports.empty;
Nat.reset t.table
-let remove_connections t ip =
- let Mirage_nat.{ tcp ; udp } = Nat.remove_connections t.table ip in
- ignore(tcp, udp)
+let remove_connections t ports ip =
+ let freed_ports = Nat.remove_connections t.table ip in
+ ports.nat_tcp := Ports.diff !(ports.nat_tcp) (Ports.of_list freed_ports.Mirage_nat.tcp);
+ ports.nat_udp := Ports.diff !(ports.nat_udp) (Ports.of_list freed_ports.Mirage_nat.udp);
+ ports.nat_icmp := Ports.diff !(ports.nat_icmp) (Ports.of_list freed_ports.Mirage_nat.icmp)
-let add_nat_rule_and_translate t ~xl_host action packet =
+let add_nat_rule_and_translate t ports ~xl_host action packet =
let apply_action xl_port =
Lwt.catch (fun () ->
Nat.add t.table packet (xl_host, xl_port) action
@@ -54,19 +74,25 @@ let add_nat_rule_and_translate t ~xl_host action packet =
)
in
let rec aux ~retries =
- let xl_port = random_user_port () in
+ let nat_ports, dns_ports =
+ match packet with
+ | `IPv4 (_, `TCP _) -> ports.nat_tcp, ref Ports.empty
+ | `IPv4 (_, `UDP _) -> ports.nat_udp, ports.dns_udp
+ | `IPv4 (_, `ICMP _) -> ports.nat_icmp, ref Ports.empty
+ in
+ let xl_port = pick_free_port ~nat_ports ~dns_ports in
apply_action xl_port >>= function
| Error `Out_of_memory ->
(* Because hash tables resize in big steps, this can happen even if we have a fair
chunk of free memory. *)
Log.warn (fun f -> f "Out_of_memory adding NAT rule. Dropping NAT table...");
- reset t >>= fun () ->
+ reset t ports >>= fun () ->
aux ~retries:(retries - 1)
| Error `Overlap when retries < 0 -> Lwt.return (Error "Too many retries")
| Error `Overlap ->
if retries = 0 then (
Log.warn (fun f -> f "Failed to find a free port; resetting NAT table");
- reset t >>= fun () ->
+ reset t ports >>= fun () ->
aux ~retries:(retries - 1)
) else (
aux ~retries:(retries - 1)
diff --git a/my_nat.mli b/my_nat.mli
index fc2049d..2ee21e0 100644
--- a/my_nat.mli
+++ b/my_nat.mli
@@ -3,6 +3,15 @@
(* Abstract over NAT interface (todo: remove this) *)
+type ports = private {
+ nat_tcp : Ports.t ref;
+ nat_udp : Ports.t ref;
+ nat_icmp : Ports.t ref;
+ dns_udp : Ports.t ref;
+}
+
+val empty_ports : unit -> ports
+
type t
type action = [
@@ -11,8 +20,8 @@ type action = [
]
val create : max_entries:int -> t Lwt.t
-val reset : t -> unit Lwt.t
-val remove_connections : t -> Ipaddr.V4.t -> unit
+val reset : t -> ports -> unit Lwt.t
+val remove_connections : t -> ports -> Ipaddr.V4.t -> unit
val translate : t -> Nat_packet.t -> Nat_packet.t option Lwt.t
-val add_nat_rule_and_translate : t ->
+val add_nat_rule_and_translate : t -> ports ->
xl_host:Ipaddr.V4.t -> action -> Nat_packet.t -> (Nat_packet.t, string) result Lwt.t
diff --git a/ports.ml b/ports.ml
new file mode 100644
index 0000000..59d3205
--- /dev/null
+++ b/ports.ml
@@ -0,0 +1,16 @@
+module Set = Set.Make(struct
+ type t = int
+ let compare a b = compare a b
+end)
+
+include Set
+
+let rec pick_free_port ?(retries = 10) ~consult add_to =
+ let p = 1024 + Random.int (0xffff - 1024) in
+ if (mem p !consult || mem p !add_to) && retries <> 0
+ then pick_free_port ~retries:(retries - 1) ~consult add_to
+ else
+ begin
+ add_to := add p !add_to;
+ p
+ end
diff --git a/router.ml b/router.ml
index 4d7ed90..b91da74 100644
--- a/router.ml
+++ b/router.ml
@@ -9,10 +9,13 @@ type t = {
client_eth : Client_eth.t;
nat : My_nat.t;
uplink : interface;
+ (* NOTE: do not try to make this pure, it relies on mvars / side effects *)
+ ports : My_nat.ports;
}
let create ~client_eth ~uplink ~nat =
- { client_eth; nat; uplink }
+ let ports = My_nat.empty_ports () in
+ { client_eth; nat; uplink; ports }
let target t buf =
let dst_ip = buf.Ipv4_packet.dst in
diff --git a/router.mli b/router.mli
index 34fa86b..610bddd 100644
--- a/router.mli
+++ b/router.mli
@@ -9,6 +9,7 @@ type t = private {
client_eth : Client_eth.t;
nat : My_nat.t;
uplink : interface;
+ ports : My_nat.ports;
}
val create :
diff --git a/rules.ml b/rules.ml
index cb6bb6f..da4706c 100644
--- a/rules.ml
+++ b/rules.ml
@@ -49,51 +49,60 @@ module Classifier = struct
end
| _, _ -> false
- let matches_dest rule packet =
+ let matches_dest dns_client rule packet =
let ip = packet.ipv4_header.Ipv4_packet.dst in
match rule.Q.dst with
| `any -> Lwt.return @@ `Match rule
| `hosts subnet ->
Lwt.return @@ if (Ipaddr.Prefix.mem Ipaddr.(V4 ip) subnet) then `Match rule else `No_match
| `dnsname name ->
- Log.warn (fun f -> f "Resolving %a" Domain_name.pp name);
- Lwt.return @@ `No_match
+ Log.debug (fun f -> f "Resolving %a" Domain_name.pp name);
+ dns_client name >|= function
+ | Ok (_ttl, found_ips) ->
+ if Dns.Rr_map.Ipv4_set.mem ip found_ips
+ then `Match rule
+ else `No_match
+ | Error (`Msg m) ->
+ Log.warn (fun f -> f "Ignoring rule %a, could not resolve" Q.pp_rule rule);
+ Log.debug (fun f -> f "%s" m);
+ `No_match
+ | Error _ -> assert false (* TODO: fix type of dns_client so that this case can go *)
end
-let find_first_match packet acc rule =
+let find_first_match dns_client packet acc rule =
match acc with
| `No_match ->
if Classifier.matches_proto rule packet
- then Classifier.matches_dest rule packet
+ then Classifier.matches_dest dns_client rule packet
else Lwt.return `No_match
| q -> Lwt.return q
(* Does the packet match our rules? *)
-let classify_client_packet (packet : ([`Client of Fw_utils.client_link], _) Packet.t) =
+let classify_client_packet dns_client (packet : ([`Client of Fw_utils.client_link], _) Packet.t) =
let (`Client client_link) = packet.src in
let rules = client_link#get_rules in
- Lwt_list.fold_left_s (find_first_match packet) `No_match rules >|= function
+ Lwt_list.fold_left_s (find_first_match dns_client packet) `No_match rules >|= function
| `No_match -> `Drop "No matching rule; assuming default drop"
| `Match {Q.action = Q.Accept; _} -> `Accept
| `Match ({Q.action = Q.Drop; _} as rule) ->
`Drop (Format.asprintf "rule number %a explicitly drops this packet" Q.pp_rule rule)
-let translate_accepted_packets packet =
- classify_client_packet packet >|= function
+let translate_accepted_packets dns_client packet =
+ classify_client_packet dns_client packet >|= function
| `Accept -> `NAT
| `Drop s -> `Drop s
(** Packets from the private interface that don't match any NAT table entry are being checked against the fw rules here *)
-let from_client (packet : ([`Client of Fw_utils.client_link], _) Packet.t) : Packet.action Lwt.t =
+let from_client dns_client (packet : ([`Client of Fw_utils.client_link], _) Packet.t) : Packet.action Lwt.t =
match packet with
| { dst = `Firewall; transport_header = `UDP header; _ } ->
if header.Udp_packet.dst_port = dns_port
then Lwt.return @@ `NAT_to (`NetVM, dns_port)
else Lwt.return @@ `Drop "packet addressed to client gateway"
- | { dst = `External _ ; _ } | { dst = `NetVM; _ } -> translate_accepted_packets packet
+ | { dst = `External _ ; _ } | { dst = `NetVM; _ } -> translate_accepted_packets dns_client packet
| { dst = `Firewall ; _ } -> Lwt.return @@ `Drop "packet addressed to firewall itself"
- | { dst = `Client _ ; _ } -> classify_client_packet packet
+ | { dst = `Client _ ; _ } -> classify_client_packet dns_client packet
| _ -> Lwt.return @@ `Drop "could not classify packet"
(** Packets from the outside world that don't match any NAT table entry are being dropped by default *)
diff --git a/unikernel.ml b/unikernel.ml
index 7a3b1d7..72f2c83 100644
--- a/unikernel.ml
+++ b/unikernel.ml
@@ -8,15 +8,18 @@ let src = Logs.Src.create "unikernel" ~doc:"Main unikernel code"
module Log = (val Logs.src_log src : Logs.LOG)
module Main (R : Mirage_random.S)(Clock : Mirage_clock.MCLOCK) = struct
+ module Uplink = Uplink.Make(R)(Clock)
+ module Dns_transport = My_dns.Transport(R)(Clock)
+ module Dns_client = Dns_client.Make(Dns_transport)
(* Set up networking and listen for incoming packets. *)
- let network uplink qubesDB router =
+ let network dns_client dns_responses uplink qubesDB router =
(* Report success *)
Dao.set_iptables_error qubesDB "" >>= fun () ->
(* Handle packets from both networks *)
Lwt.choose [
- Client_net.listen Clock.elapsed_ns qubesDB router;
- Uplink.listen uplink Clock.elapsed_ns router
+ Client_net.listen Clock.elapsed_ns dns_client qubesDB router;
+ Uplink.listen uplink Clock.elapsed_ns dns_responses router
]
(* We don't use the GUI, but it's interesting to keep an eye on it.
@@ -76,7 +79,11 @@ module Main (R : Mirage_random.S)(Clock : Mirage_clock.MCLOCK) = struct
~nat
in
- let net_listener = network uplink qubesDB router in
+ let send_dns_query = Uplink.send_dns_client_query uplink in
+ let dns_mvar = Lwt_mvar.create_empty () in
+ let dns_client = Dns_client.create (router, send_dns_query, dns_mvar) in
+
+ let net_listener = network (Dns_client.getaddrinfo dns_client Dns.Rr_map.A) dns_mvar uplink qubesDB router in
(* Report memory usage to XenStore *)
Memory_pressure.init ();
diff --git a/uplink.ml b/uplink.ml
index 343eef3..d4372b3 100644
--- a/uplink.ml
+++ b/uplink.ml
@@ -9,15 +9,20 @@ module Eth = Ethernet.Make(Netif)
let src = Logs.Src.create "uplink" ~doc:"Network connection to NetVM"
module Log = (val Logs.src_log src : Logs.LOG)
-module Arp = Arp.Make(Eth)(OS.Time)
+module Make (R:Mirage_random.S) (Clock : Mirage_clock.MCLOCK) = struct
+ module Arp = Arp.Make(Eth)(OS.Time)
+ module I = Static_ipv4.Make(R)(Clock)(Eth)(Arp)
+ module U = Udp.Make(I)(R)
-type t = {
- net : Netif.t;
- eth : Eth.t;
- arp : Arp.t;
- interface : interface;
- mutable fragments : Fragments.Cache.t;
-}
+ type t = {
+ net : Netif.t;
+ eth : Eth.t;
+ arp : Arp.t;
+ interface : interface;
+ mutable fragments : Fragments.Cache.t;
+ ip : I.t;
+ udp: U.t;
+ }
class netvm_iface eth mac ~my_ip ~other_ip : interface = object
val queue = FrameQ.create (Ipaddr.V4.to_string other_ip)
@@ -31,10 +36,26 @@ class netvm_iface eth mac ~my_ip ~other_ip : interface = object
)
end
-let listen t get_ts router =
- Netif.listen t.net ~header_size:Ethernet_wire.sizeof_ethernet (fun frame ->
- (* Handle one Ethernet frame from NetVM *)
- Eth.input t.eth
+ let send_dns_client_query t ~src_port ~dst ~dst_port buf =
+ U.write ~src_port ~dst ~dst_port t.udp buf >|= function
+ | Error s -> Log.err (fun f -> f "error sending udp packet: %a" U.pp_error s); Error (`Msg "failure")
+ | Ok () -> Ok ()
+
+ let listen t get_ts dns_responses router =
+ let handle_packet ip_header ip_packet =
+ let open Udp_packet in
+
+ Log.debug (fun f -> f "received ipv4 packet from %a on uplink" Ipaddr.V4.pp ip_header.Ipv4_packet.src);
+ match ip_packet with
+ | `UDP (header, packet) when Ports.mem header.dst_port !(router.Router.ports.My_nat.dns_udp) ->
+ Log.debug (fun f -> f "found a DNS packet whose dst_port (%d) was in the list of dns_client ports" header.dst_port);
+ Lwt_mvar.put dns_responses (header, packet)
+ | _ ->
+ Firewall.ipv4_from_netvm router (`IPv4 (ip_header, ip_packet))
+ in
+ Netif.listen t.net ~header_size:Ethernet_wire.sizeof_ethernet (fun frame ->
+ (* Handle one Ethernet frame from NetVM *)
+ Eth.input t.eth
~arpv4:(Arp.input t.arp)
~ipv4:(fun ip ->
let cache, r =
@@ -42,30 +63,35 @@ let listen t get_ts router =
in
t.fragments <- cache;
match r with
- | Error e ->
- Log.warn (fun f -> f "Ignored unknown IPv4 message from uplink: %a" Nat_packet.pp_error e);
- Lwt.return_unit
- | Ok None -> Lwt.return_unit
- | Ok (Some packet) ->
- Firewall.ipv4_from_netvm router packet
- )
+ | Error e ->
+ Log.warn (fun f -> f "Ignored unknown IPv4 message from uplink: %a" Nat_packet.pp_error e);
+ Lwt.return ()
+ | Ok None -> Lwt.return_unit
+ | Ok (Some (`IPv4 (header, packet))) -> handle_packet header packet
+ )
~ipv6:(fun _ip -> Lwt.return_unit)
frame
) >|= or_raise "Uplink listen loop" Netif.pp_error
+
let interface t = t.interface
let connect config =
- let ip = config.Dao.uplink_our_ip in
+ let my_ip = config.Dao.uplink_our_ip in
+ let gateway = config.Dao.uplink_netvm_ip in
Netif.connect "0" >>= fun net ->
Eth.connect net >>= fun eth ->
Arp.connect eth >>= fun arp ->
- Arp.add_ip arp ip >>= fun () ->
+ Arp.add_ip arp my_ip >>= fun () ->
+ let network = Ipaddr.V4.Prefix.make 0 Ipaddr.V4.any in
+ I.connect ~ip:(network, my_ip) ~gateway eth arp >>= fun ip ->
+ U.connect ip >>= fun udp ->
let netvm_mac =
- Arp.query arp config.Dao.uplink_netvm_ip
+ Arp.query arp gateway
>|= or_raise "Getting MAC of our NetVM" Arp.pp_error in
let interface = new netvm_iface eth netvm_mac
- ~my_ip:ip
+ ~my_ip
~other_ip:config.Dao.uplink_netvm_ip in
let fragments = Fragments.Cache.empty (256 * 1024) in
- Lwt.return { net; eth; arp; interface ; fragments }
+ Lwt.return { net; eth; arp; interface ; fragments ; ip ; udp }
+end
diff --git a/uplink.mli b/uplink.mli
index 776b1a4..438e04a 100644
--- a/uplink.mli
+++ b/uplink.mli
@@ -5,13 +5,18 @@
open Fw_utils
-type t
+[@@@ocaml.warning "-67"]
+module Make (R: Mirage_random.S)(Clock : Mirage_clock.MCLOCK) : sig
+ type t
-val connect : Dao.network_config -> t Lwt.t
-(** Connect to our NetVM (gateway). *)
+ val connect : Dao.network_config -> t Lwt.t
+ (** Connect to our NetVM (gateway). *)
-val interface : t -> interface
-(** The network interface to NetVM. *)
+ val interface : t -> interface
+ (** The network interface to NetVM. *)
-val listen : t -> (unit -> int64) -> Router.t -> unit Lwt.t
-(** Handle incoming frames from NetVM. *)
+ val listen : t -> (unit -> int64) -> (Udp_packet.t * Cstruct.t) Lwt_mvar.t -> Router.t -> unit Lwt.t
+ (** Handle incoming frames from NetVM. *)
+
+ val send_dns_client_query: t -> src_port:int-> dst:Ipaddr.V4.t -> dst_port:int -> Cstruct.t -> (unit, [`Msg of string]) result Lwt.t
+end
From 8927a45f43029a226c8a4dcba64666979f8283fe Mon Sep 17 00:00:00 2001
From: linse
Date: Fri, 15 May 2020 17:31:30 +0200
Subject: [PATCH 099/281] [ci skip] Edit CHANGES
---
CHANGES.md | 15 +++++++++++++++
build-with-docker.sh | 2 +-
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/CHANGES.md b/CHANGES.md
index 6284c3e..5d4f268 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,18 @@
+### master branch
+
+This version adapts qubes-mirage-firewall with
+- dynamic rulesets via QubesDB (as defined in Qubes 4.0), and
+- adds support for DNS hostnames in rules, using the pf-qubes library for parsing.
+
+The DNS client is provided by DNS (>= 4.2.0) which uses a cache for name lookups. Not every packet will lead to a DNS lookup if DNS rules are in place.
+
+A test unikernel is available in the test subdirectory.
+
+This project was done by @linse and @yomimono in summer 2019, see PR #96.
+
+Additional changes and bugfixes:
+TODO: describe based on commit log de7d05e .. 02e515d
+
### 0.6
Changes to rules language:
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 2a7bb42..4cefbb6 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 83b96bd453c3c3cfb282076be81055026eca437b621b3ef3f2642af04ad782e2"
+echo "SHA2 last known: 7a6b003e712256cce7ac8741239f6d8d5a0db4b71656396f7ee734568282c72d"
echo "(hashes should match for released versions)"
From 6a1b012527b98a3c1c7e7ce7703f584b67ae2422 Mon Sep 17 00:00:00 2001
From: xaki23
Date: Fri, 15 May 2020 18:36:03 +0200
Subject: [PATCH 100/281] bump qubes-builder ocaml-version to 4.10.0 for gcc-10
compatibility
---
Makefile.builder | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile.builder b/Makefile.builder
index ee3c966..2c049cd 100644
--- a/Makefile.builder
+++ b/Makefile.builder
@@ -1,5 +1,5 @@
MIRAGE_KERNEL_NAME = qubes_firewall.xen
-OCAML_VERSION ?= 4.08.1
+OCAML_VERSION ?= 4.10.0
SOURCE_BUILD_DEP := firewall-build-dep
firewall-build-dep:
From 470160dcb2bad01e21a0df6dbb6f03d8d289fce4 Mon Sep 17 00:00:00 2001
From: Thomas Leonard
Date: Sat, 16 May 2020 15:19:05 +0100
Subject: [PATCH 101/281] Update changelog
---
CHANGES.md | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/CHANGES.md b/CHANGES.md
index 5d4f268..12153de 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -13,6 +13,42 @@ This project was done by @linse and @yomimono in summer 2019, see PR #96.
Additional changes and bugfixes:
TODO: describe based on commit log de7d05e .. 02e515d
+- Support Mirage 3.7 and mirage-nat 2.0.0 (@hannesm, #89).
+ The main improvement is fragmentation and reassembly support.
+
+- Use the smaller OCurrent images as the base for building the Docker images (@talex5, #80).
+ - Before: 1 GB (ocaml/opam2:debian-10-ocaml-4.08)
+ - Now: 309 MB (ocurrent/opam:alpine-3.10-ocaml-4.08)
+
+- Removed unreachable `Lwt.catch` (@hannesm, #90).
+
+Documentation:
+
+- Add note that AppVM used to build from source may need a private image larger than the default 2048MB (@marmot1791, #83).
+
+- README: create the symlink-redirected docker dir (@xaki23, #75). Otherwise, installing the docker package removes the dangling symlink.
+
+- Note that mirage-firewall cannot be used as UpdateVM (@talex5, #68).
+
+- Fix ln(1) call in build instructions (@jaseg, #69). The arguments were backwards.
+
+Keeping up with upstream changes:
+
+- Support mirage-3.7 via qubes-builder (@xaki23, #91).
+
+- Remove unused `Clock` argument to `Uplink` (@talex5, #90).
+
+- Rename things for newer mirage-xen versions (@xaki23, #80).
+
+- Adjust to ipaddr-4.0.0 renaming `_bytes` to `_octets` (@xaki23, #75).
+
+- Use OCaml 4.08.0 for qubes-builder builds (was 4.07.1) (@xaki23, #75).
+
+- Remove netchannel pin as 1.11.0 is now released (@talex5, #72).
+
+- Remove cmdliner pin as 1.0.4 is now released (@talex5, #71).
+
+
### 0.6
Changes to rules language:
From cc534d9618edbdd909e2a1bc8e840d83a3043ed0 Mon Sep 17 00:00:00 2001
From: linse
Date: Tue, 19 May 2020 11:07:25 +0200
Subject: [PATCH 102/281] Update changes for release.
---
CHANGES.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 12153de..a9a3bc7 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,4 +1,4 @@
-### master branch
+### 0.7
This version adapts qubes-mirage-firewall with
- dynamic rulesets via QubesDB (as defined in Qubes 4.0), and
@@ -11,7 +11,6 @@ A test unikernel is available in the test subdirectory.
This project was done by @linse and @yomimono in summer 2019, see PR #96.
Additional changes and bugfixes:
-TODO: describe based on commit log de7d05e .. 02e515d
- Support Mirage 3.7 and mirage-nat 2.0.0 (@hannesm, #89).
The main improvement is fragmentation and reassembly support.
From 53bf4f960cd910349e4fe7a097eb854f79c94be5 Mon Sep 17 00:00:00 2001
From: linse
Date: Tue, 19 May 2020 14:35:22 +0200
Subject: [PATCH 103/281] update to ocaml 4.10 and mirage 3.7.7
---
Dockerfile | 8 ++++----
Makefile.builder | 2 +-
build-with-docker.sh | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index d49cadf..5bd2d95 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,15 +1,15 @@
# Pin the base image to a specific hash for maximum reproducibility.
# It will probably still work on newer images, though, unless Debian
# changes some compiler optimisations (unlikely).
-#FROM ocurrent/opam:alpine-3.10-ocaml-4.08
-FROM ocurrent/opam@sha256:3f3ce7e577a94942c7f9c63cbdd1ecbfe0ea793f581f69047f3155967bba36f6
+#FROM ocurrent/opam:alpine-3.10-ocaml-4.10
+FROM ocurrent/opam@sha256:d30098ff92b5ee10cf7c11c17f2351705e5226a6b05aa8b9b7280b3d87af9cde
# Pin last known-good version for reproducible builds.
# Remove this line (and the base image pin above) if you want to test with the
# latest versions.
-RUN cd ~/opam-repository && git fetch origin master && git reset --hard 3548c2a8537029b8165466cd9c5a94bb7bc30405 && opam update
+RUN cd ~/opam-repository && git fetch origin master && git reset --hard 4dd2620bcc821418bae53669a6c6163964c090a2 && opam update
-RUN opam depext -i -y mirage.3.7.6 lwt.5.2.0
+RUN opam depext -i -y mirage.3.7.7 lwt.5.3.0
RUN mkdir /home/opam/qubes-mirage-firewall
ADD config.ml /home/opam/qubes-mirage-firewall/config.ml
WORKDIR /home/opam/qubes-mirage-firewall
diff --git a/Makefile.builder b/Makefile.builder
index 2c049cd..f93d74c 100644
--- a/Makefile.builder
+++ b/Makefile.builder
@@ -4,5 +4,5 @@ SOURCE_BUILD_DEP := firewall-build-dep
firewall-build-dep:
opam install -y depext
- opam depext -i -y mirage.3.7.6 lwt.5.2.0
+ opam depext -i -y mirage.3.7.7 lwt.5.3.0
diff --git a/build-with-docker.sh b/build-with-docker.sh
index 4cefbb6..3e7eb33 100755
--- a/build-with-docker.sh
+++ b/build-with-docker.sh
@@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall .
echo Building Firewall...
docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall
echo "SHA2 of build: $(sha256sum qubes_firewall.xen)"
-echo "SHA2 last known: 7a6b003e712256cce7ac8741239f6d8d5a0db4b71656396f7ee734568282c72d"
+echo "SHA2 last known: 4f4456b5fe7c8ae1ba2f6934cf89749cf6aae9a90cce899cf744c89d311467a3"
echo "(hashes should match for released versions)"
From 60ebd61b72856b5ff17cc31efac5ebe56297851e Mon Sep 17 00:00:00 2001
From: linse
Date: Tue, 19 May 2020 16:48:48 +0200
Subject: [PATCH 104/281] Update documentation.
---
README.md | 14 +-
diagrams/components.svg | 326 +++++++++++++++++++++++-----------------
2 files changed, 193 insertions(+), 147 deletions(-)
diff --git a/README.md b/README.md
index be85574..0c22988 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,6 @@
A unikernel that can run as a QubesOS ProxyVM, replacing `sys-firewall`.
It uses the [mirage-qubes][] library to implement the Qubes protocols.
-Note: This firewall *ignores the rules set in the Qubes GUI*. See `rules.ml` for the actual policy.
-
See [A Unikernel Firewall for QubesOS][] for more details.
@@ -63,8 +61,8 @@ Run this command in dom0 to create a `mirage-firewall` VM using the `mirage-fire
qvm-create \
--property kernel=mirage-firewall \
--property kernelopts=None \
- --property memory=32 \
- --property maxmem=32 \
+ --property memory=64 \
+ --property maxmem=64 \
--property netvm=sys-net \
--property provides_network=True \
--property vcpus=1 \
@@ -106,7 +104,7 @@ This diagram show the main components (each box corresponds to a source `.ml` fi
Ethernet frames arrives from client qubes (such as `work` or `personal`) or from `sys-net`.
-Internet (IP) packets are sent to `firewall`, which consults `rules` to decide what to do with the packet.
+Internet (IP) packets are sent to `firewall`, which consults the NAT table and the rules from QubesDB to decide what to do with the packet.
If it should be sent on, it uses `router` to send it to the chosen destination.
`client_net` watches the XenStore database provided by dom0
to find out when clients need to be added or removed.
@@ -167,10 +165,8 @@ This takes a little more setting up the first time, but will be much quicker aft
# Testing if the firewall works
-Build the test unikernel in the test directory.
-Install it to a vm which has the firewall as netvm.
-Set the rules for the testvm to "textfile".
-Run the test unikernel.
+A unikernel which tests the firewall is available in the `test/` subdirectory.
+To use it, run `test.sh` and follow the instructions to set up the test environment.
# Security advisories
diff --git a/diagrams/components.svg b/diagrams/components.svg
index 1e996b1..2d69f9d 100644
--- a/diagrams/components.svg
+++ b/diagrams/components.svg
@@ -1,149 +1,199 @@
-