diff --git a/libs/protomask-metrics/Cargo.toml b/libs/protomask-metrics/Cargo.toml
index 7a27df5..4b1a816 100644
--- a/libs/protomask-metrics/Cargo.toml
+++ b/libs/protomask-metrics/Cargo.toml
@@ -13,6 +13,7 @@ keywords = []
categories = []
[dependencies]
+hyper = { version = "0.14.27", features = ["server", "http1", "tcp"] }
log = "^0.4"
prometheus = "0.13.3"
lazy_static = "1.4.0"
\ No newline at end of file
diff --git a/libs/protomask-metrics/src/http.rs b/libs/protomask-metrics/src/http.rs
new file mode 100644
index 0000000..0124f54
--- /dev/null
+++ b/libs/protomask-metrics/src/http.rs
@@ -0,0 +1,44 @@
+use hyper::{
+ service::{make_service_fn, service_fn},
+ Body, Method, Request, Response, Server,
+};
+use prometheus::{Encoder, TextEncoder};
+use std::{convert::Infallible, net::SocketAddr};
+
+/// Handle an HTTP request
+#[allow(clippy::unused_async)]
+async fn handle_request(request: Request
) -> Result, Infallible> {
+ // If the request is targeting the metrics endpoint
+ if request.method() == Method::GET && request.uri().path() == "/metrics" {
+ // Gather metrics
+ let metric_families = prometheus::gather();
+ let body = {
+ let mut buffer = Vec::new();
+ let encoder = TextEncoder::new();
+ encoder.encode(&metric_families, &mut buffer).unwrap();
+ String::from_utf8(buffer).unwrap()
+ };
+
+ // Return the response
+ return Ok(Response::new(Body::from(body)));
+ }
+
+ // Otherwise, just return a 404
+ Ok(Response::builder()
+ .status(404)
+ .body(Body::from("Not found"))
+ .unwrap())
+}
+
+/// Bring up an HTTP server that listens for metrics requests
+pub async fn serve_metrics(bind_addr: SocketAddr) {
+ // Set up the server
+ let make_service =
+ make_service_fn(|_| async { Ok::<_, Infallible>(service_fn(handle_request)) });
+ let server = Server::bind(&bind_addr).serve(make_service);
+
+ // Run the server
+ if let Err(e) = server.await {
+ eprintln!("Metrics server error: {e}");
+ }
+}
diff --git a/libs/protomask-metrics/src/lib.rs b/libs/protomask-metrics/src/lib.rs
index e79c974..775643d 100644
--- a/libs/protomask-metrics/src/lib.rs
+++ b/libs/protomask-metrics/src/lib.rs
@@ -5,6 +5,7 @@
#![allow(clippy::missing_panics_doc)]
#![allow(clippy::doc_markdown)]
+pub mod http;
pub mod metrics;
#[macro_use]
diff --git a/src/protomask-clat.rs b/src/protomask-clat.rs
index 9e3c3dd..b3a866c 100644
--- a/src/protomask-clat.rs
+++ b/src/protomask-clat.rs
@@ -10,7 +10,10 @@ use interproto::protocols::ip::{translate_ipv4_to_ipv6, translate_ipv6_to_ipv4};
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
use nix::unistd::Uid;
use rfc6052::{embed_ipv4_addr_unchecked, extract_ipv4_addr_unchecked};
-use std::io::{Read, Write};
+use std::{
+ io::{Read, Write},
+ net::SocketAddr,
+};
use crate::common::packet_handler::handle_packet;
@@ -19,14 +22,18 @@ mod common;
#[derive(Debug, Parser)]
#[clap(author, version, about="IPv4 to IPv6 Customer-side transLATor (CLAT)", long_about = None)]
struct Args {
- /// RFC6052 IPv6 prefix to encapsulate IPv4 packets within
- #[clap(long="via", default_value_t = ("64:ff9b::/96").parse().unwrap(), value_parser = parse_network_specific_prefix)]
- embed_prefix: Ipv6Net,
-
/// One or more customer-side IPv4 prefixes to allow through CLAT
#[clap(short = 'c', long = "customer-prefix", required = true)]
customer_pool: Vec,
+ /// Enable prometheus metrics on a given address
+ #[clap(long = "prometheus")]
+ prom_bind_addr: Option,
+
+ /// RFC6052 IPv6 prefix to encapsulate IPv4 packets within
+ #[clap(long="via", default_value_t = ("64:ff9b::/96").parse().unwrap(), value_parser = parse_network_specific_prefix)]
+ embed_prefix: Ipv6Net,
+
/// Explicitly set the interface name to use
#[clap(short, long, default_value_t = ("clat%d").to_string())]
interface: String,
@@ -93,6 +100,12 @@ pub async fn main() {
.unwrap();
}
+ // If we are configured to serve prometheus metrics, start the server
+ if let Some(bind_addr) = args.prom_bind_addr {
+ log::info!("Starting prometheus server on {}", bind_addr);
+ tokio::spawn(protomask_metrics::http::serve_metrics(bind_addr));
+ }
+
// Translate all incoming packets
log::info!("Translating packets on {}", tun.name());
let mut buffer = vec![0u8; 1500];
diff --git a/src/protomask.rs b/src/protomask.rs
index 308e7a1..7e99b54 100644
--- a/src/protomask.rs
+++ b/src/protomask.rs
@@ -9,7 +9,7 @@ use rfc6052::{embed_ipv4_addr_unchecked, extract_ipv4_addr_unchecked};
use std::{
cell::RefCell,
io::{BufRead, Read, Write},
- net::{Ipv4Addr, Ipv6Addr},
+ net::{Ipv4Addr, Ipv6Addr, SocketAddr},
path::PathBuf,
time::Duration,
};
@@ -21,10 +21,6 @@ mod common;
#[derive(Parser)]
#[clap(author, version, about="Fast and simple NAT64", long_about = None)]
struct Args {
- /// RFC6052 IPv6 translation prefix
- #[clap(long, default_value_t = ("64:ff9b::/96").parse().unwrap(), value_parser = parse_network_specific_prefix)]
- translation_prefix: Ipv6Net,
-
#[command(flatten)]
pool: PoolArgs,
@@ -32,6 +28,14 @@ struct Args {
#[clap(long = "static-file")]
static_file: Option,
+ /// Enable prometheus metrics on a given address
+ #[clap(long = "prometheus")]
+ prom_bind_addr: Option,
+
+ /// RFC6052 IPv6 translation prefix
+ #[clap(long, default_value_t = ("64:ff9b::/96").parse().unwrap(), value_parser = parse_network_specific_prefix)]
+ translation_prefix: Ipv6Net,
+
/// NAT reservation timeout in seconds
#[clap(long, default_value = "7200")]
reservation_timeout: u64,
@@ -150,6 +154,12 @@ pub async fn main() {
.unwrap();
}
+ // If we are configured to serve prometheus metrics, start the server
+ if let Some(bind_addr) = args.prom_bind_addr {
+ log::info!("Starting prometheus server on {}", bind_addr);
+ tokio::spawn(protomask_metrics::http::serve_metrics(bind_addr));
+ }
+
// Translate all incoming packets
log::info!("Translating packets on {}", tun.name());
let mut buffer = vec![0u8; 1500];