use std::net::SocketAddr;
use std::sync::Arc;
use eggress_core::{ClientIdentity, TargetAddr, TargetHost};
use eggress_protocol_reverse::client::{TargetResolution, TargetResolver};
use eggress_routing::{
RouteDecision, RouteRequest, RouteService, SharedRoutingService, TransportKind,
};
use tracing::warn;
pub struct RouteEngineTargetResolver {
router: Arc<SharedRoutingService>,
target: TargetAddr,
reverse_listener: Arc<str>,
client_addr: Option<SocketAddr>,
}
impl RouteEngineTargetResolver {
pub fn new(
router: Arc<SharedRoutingService>,
host: String,
port: u16,
reverse_listener: Arc<str>,
client_addr: Option<SocketAddr>,
) -> Self {
let target = TargetAddr {
host: TargetHost::Domain(host),
port,
};
Self {
router,
target,
reverse_listener,
client_addr,
}
}
pub fn target(&self) -> &TargetAddr {
&self.target
}
}
impl TargetResolver for RouteEngineTargetResolver {
fn resolve(&self) -> TargetResolution {
let request = RouteRequest {
target: &self.target,
source: self.client_addr,
listener: self.reverse_listener.as_ref(),
inbound_protocol: eggress_core::ProtocolId::Reverse,
identity: &ClientIdentity::Anonymous,
transport: TransportKind::ReverseTcp,
};
match self.router.decide(&request) {
RouteDecision::Direct { .. } | RouteDecision::UpstreamGroup { .. } => {
TargetResolution::Connect {
host: self.target.host.to_string(),
port: self.target.port,
}
}
RouteDecision::Reject { reason, .. } => {
warn!(
listener = %self.reverse_listener,
target = %self.target,
?reason,
"reverse route rejected by policy",
);
TargetResolution::Reject {
reason: format!("route rejected: {:?}", reason),
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use eggress_core::RejectReason;
use eggress_routing::{CompiledRule, RouteActionSpec, Router, RuleId, SharedRoutingService};
fn router_with_default(action: RouteActionSpec) -> Arc<SharedRoutingService> {
Arc::new(SharedRoutingService::new(Router::new(Vec::new(), action)))
}
#[test]
fn allows_direct_route() {
let r = router_with_default(RouteActionSpec::Direct);
let resolver = RouteEngineTargetResolver::new(
r,
"127.0.0.1".to_string(),
8080,
Arc::from("rev-1"),
None,
);
match resolver.resolve() {
TargetResolution::Connect { host, port } => {
assert_eq!(host, "127.0.0.1");
assert_eq!(port, 8080);
}
other => panic!("expected Connect, got {:?}", other),
}
}
#[test]
fn allows_upstream_group_route() {
let r = router_with_default(RouteActionSpec::UpstreamGroup(
eggress_routing::UpstreamGroupId(Arc::from("group-1")),
));
let resolver = RouteEngineTargetResolver::new(
r,
"127.0.0.1".to_string(),
9090,
Arc::from("rev-1"),
None,
);
match resolver.resolve() {
TargetResolution::Connect { port, .. } => assert_eq!(port, 9090),
other => panic!("expected Connect, got {:?}", other),
}
}
#[test]
fn rejects_reject_route() {
let rule = CompiledRule {
id: RuleId(Arc::from("deny-all")),
matcher: eggress_routing::MatchExpr::Any,
action: RouteActionSpec::Reject(RejectReason::AccessDenied),
};
let router = Arc::new(SharedRoutingService::new(Router::new(
vec![rule],
RouteActionSpec::Direct,
)));
let resolver = RouteEngineTargetResolver::new(
router,
"127.0.0.1".to_string(),
80,
Arc::from("rev-1"),
None,
);
match resolver.resolve() {
TargetResolution::Reject { reason } => {
assert!(reason.contains("route rejected"), "reason: {}", reason);
}
other => panic!("expected Reject, got {:?}", other),
}
}
#[test]
fn reverse_listener_matcher_only_matches_reverse_tcp() {
let rule = CompiledRule {
id: RuleId(Arc::from("reverse-only")),
matcher: eggress_routing::MatchExpr::ReverseListener(Arc::from("rev-1")),
action: RouteActionSpec::Direct,
};
let router = Arc::new(SharedRoutingService::new(Router::new(
vec![rule],
RouteActionSpec::Reject(RejectReason::AccessDenied),
)));
let resolver = RouteEngineTargetResolver::new(
router,
"127.0.0.1".to_string(),
8080,
Arc::from("rev-1"),
None,
);
assert!(matches!(
resolver.resolve(),
TargetResolution::Connect { .. }
));
}
}