@@ -15,41 +15,47 @@ use k8s_openapi::{
1515 } ,
1616 apimachinery:: pkg:: apis:: meta:: v1:: LabelSelector ,
1717} ;
18- use kube:: { api :: ResourceExt , core:: ObjectMeta , error:: ErrorResponse , Resource } ;
18+ use kube:: { core:: ObjectMeta , error:: ErrorResponse , Resource } ;
1919use tracing:: { info, instrument, trace} ;
2020
2121const CHISEL_IMAGE : & str = "jpillora/chisel" ;
2222
23- /// The function takes a ServicePort struct and returns a string representation of the port number and
24- /// protocol (if specified).
23+ /// The function takes a ServicePort struct and returns a string representation of the target port
24+ /// and protocol (if specified).
2525///
2626/// Arguments:
2727///
28- /// * `svcport`: `svcport` is a variable of type `ServicePort`, which is likely a struct or enum that
29- /// represents a service port in a network application. The function `convert_service_port` takes this
30- /// `svcport` as input and returns a string representation of the port number and protocol (if
31- /// specified).
28+ /// * `svcport`: `svcport` is a variable of type `ServicePort`, which represents a service port in
29+ /// Kubernetes. The function extracts the target port (what pods listen on) for use in chisel tunnels.
3230///
3331/// Returns:
3432///
35- /// a string that represents the service port. The string contains the port number and, if applicable,
36- /// the protocol (TCP or UDP) in the format "port/protocol".
37- fn convert_service_port ( svcport : ServicePort ) -> String {
38- let mut port = String :: new ( ) ;
39-
40- // get port number
41- port. push_str ( & svcport. port . to_string ( ) ) ;
42-
43- if let Some ( protocol) = svcport. protocol {
44- match protocol. as_str ( ) {
45- // todo: we probably want to imply none by default
46- "TCP" => port. push_str ( "/tcp" ) ,
47- "UDP" => port. push_str ( "/udp" ) ,
48- _ => ( ) ,
49- } ;
33+ /// a string that represents the target port with protocol suffix. If a numeric target_port is specified,
34+ /// it is used; otherwise falls back to the service port. Named target ports (strings) fall back to
35+ /// the service port since they cannot be resolved without pod container port information.
36+ fn get_target_port ( svcport : & ServicePort ) -> i32 {
37+ use k8s_openapi:: apimachinery:: pkg:: util:: intstr:: IntOrString ;
38+
39+ // Use numeric target_port if specified, otherwise fall back to the service port.
40+ // Named ports (strings like "web", "http") cannot be resolved here since we'd need
41+ // to look up the Pod's container ports, so we fall back to service port.
42+ match & svcport. target_port {
43+ Some ( IntOrString :: Int ( p) ) => * p,
44+ Some ( IntOrString :: String ( _) ) => svcport. port , // Can't resolve named ports
45+ None => svcport. port ,
5046 }
47+ }
5148
52- port
49+ fn get_protocol_suffix ( svcport : & ServicePort ) -> & ' static str {
50+ svcport
51+ . protocol
52+ . as_ref ( )
53+ . map ( |p| match p. as_str ( ) {
54+ "TCP" => "/tcp" ,
55+ "UDP" => "/udp" ,
56+ _ => "" ,
57+ } )
58+ . unwrap_or ( "" )
5359}
5460
5561/// This function generates a remote argument string using an ExitNode's host and port information.
@@ -98,28 +104,20 @@ pub fn generate_remote_arg(node: &ExitNode) -> String {
98104/// a `Result` containing a `Vec` of `String`s. The `Vec` contains arguments for a tunnel, which are
99105/// generated based on the input `Service`.
100106pub fn generate_tunnel_args ( svc : & Service ) -> Result < Vec < String > , ReconcileError > {
101- // We can unwrap safely since Service is guaranteed to have a name
102- let service_name = svc. metadata . name . clone ( ) . unwrap ( ) ;
103- // We can unwrap safely since Service is namespaced scoped
104- let service_namespace = svc. namespace ( ) . unwrap ( ) ;
105-
106- // this feels kind of janky, will need to refactor this later
107-
108- // check if there's a custom IP set
109- // let target_ip = svc
110- // .spec
111- // .as_ref()
112- // .map(|spec| spec.load_balancer_ip.clone())
113- // .flatten()
114- // .unwrap_or_else(|| "R".to_string());
115-
116107 let proxy_protocol = svc. metadata . annotations . as_ref ( ) . and_then ( |annotations| {
117108 annotations
118109 . get ( EXIT_NODE_PROXY_PROTOCOL_ANNOTATION )
119110 . map ( String :: as_ref)
120111 } ) == Some ( "true" ) ;
121112 let target_ip = if proxy_protocol { "RP" } else { "R" } ;
122113
114+ // Use ClusterIP directly instead of DNS name for more reliable routing
115+ let cluster_ip = svc
116+ . spec
117+ . as_ref ( )
118+ . and_then ( |spec| spec. cluster_ip . as_ref ( ) )
119+ . ok_or ( ReconcileError :: NoClusterIP ) ?;
120+
123121 // We can unwrap safely since Service is guaranteed to have a spec
124122 let ports = svc
125123 . spec
@@ -130,13 +128,12 @@ pub fn generate_tunnel_args(svc: &Service) -> Result<Vec<String>, ReconcileError
130128 . ok_or ( ReconcileError :: NoPortsSet ) ?
131129 . iter ( )
132130 . map ( |p| {
131+ // The target port is what we expose externally and what the backend listens on
132+ let target_port = get_target_port ( p) ;
133+ let protocol = get_protocol_suffix ( p) ;
133134 format ! (
134- "{}:{}:{}.{}:{}" ,
135- target_ip,
136- p. port,
137- service_name,
138- service_namespace,
139- convert_service_port( p. clone( ) )
135+ "{}:{}:{}:{}{}" ,
136+ target_ip, target_port, cluster_ip, target_port, protocol
140137 )
141138 } )
142139 . collect ( ) ;
0 commit comments