Skip to content

Commit 74a1632

Browse files
authored
Fix caching in ingress mode (#965)
When the proxy is in `ingress` mode, requests with different `l5d-dst-override` headers but the same address share the same key in the cache. This causes consecutive requests to the same address to all go to the value of the first `l5d-dst-override` header, ignoring the header values of later requests—unless the services are evicted from the cache. This occurs because each key into the cache is calculated based off `LogicalAddr`'s `orig_dst` and `protocol` fields. When the proxy is in `ingress` mode, the value of `orig_dst` is overridden to `0.0.0.0:port` This results in an easy key conflict: two different requests with the same port and protocol with result in the same key. To fix this, the `logical_addr` field is added to `LogicalAddr` and is used as a third field in calculating the cache key. `logical_addr` is the value of the `l5d-dst-override` header which means requests to the same address but with different header values will result in different keys. Fixes linkerd/linkerd2#5975 Signed-off-by: Kevin Leimkuhler <[email protected]>
1 parent b401746 commit 74a1632

File tree

6 files changed

+45
-28
lines changed

6 files changed

+45
-28
lines changed

linkerd/app/gateway/src/gateway.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ where
6161
None => return Gateway::NoIdentity,
6262
};
6363

64-
let dst = match profile.as_ref().and_then(|p| p.borrow().addr.clone()) {
65-
Some(profiles::LogicalAddr(addr)) => addr,
64+
let addr = match profile.as_ref().and_then(|p| p.borrow().addr.clone()) {
65+
Some(addr) => addr,
6666
None => return Gateway::BadDomain(http.target.name().clone()),
6767
};
6868

@@ -73,7 +73,8 @@ where
7373
let svc = self.outbound.new_service(outbound::http::Logical {
7474
profile,
7575
protocol: http.version,
76-
orig_dst: OrigDstAddr(([0, 0, 0, 0], dst.port()).into()),
76+
orig_dst: OrigDstAddr(([0, 0, 0, 0], addr.0.port()).into()),
77+
logical_addr: Some(addr),
7778
});
7879

7980
Gateway::new(svc, http.target, local_id)

linkerd/app/gateway/src/lib.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,15 @@ where
116116
.push_tcp_logical(resolve.clone())
117117
.into_stack()
118118
.push_request_filter(|(p, _): (Option<profiles::Receiver>, _)| match p {
119-
Some(rx) if rx.borrow().addr.is_some() => Ok(outbound::tcp::Logical {
120-
profile: Some(rx),
121-
orig_dst: OrigDstAddr(std::net::SocketAddr::from(([0, 0, 0, 0], 0))),
122-
protocol: (),
123-
}),
119+
Some(rx) if rx.borrow().addr.is_some() => {
120+
let logical_addr = rx.borrow().addr.clone();
121+
Ok(outbound::tcp::Logical {
122+
profile: Some(rx),
123+
orig_dst: OrigDstAddr(std::net::SocketAddr::from(([0, 0, 0, 0], 0))),
124+
protocol: (),
125+
logical_addr,
126+
})
127+
}
124128
_ => Err(discovery_rejected()),
125129
})
126130
.push(profiles::discover::layer(profiles.clone(), {

linkerd/app/outbound/src/http/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ impl From<(Version, tcp::Logical)> for Logical {
6565
protocol,
6666
orig_dst: logical.orig_dst,
6767
profile: logical.profile,
68+
logical_addr: logical.logical_addr,
6869
}
6970
}
7071
}

linkerd/app/outbound/src/ingress.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,13 @@ impl From<(Option<profiles::Receiver>, Target)> for http::Logical {
183183
} else {
184184
OrigDstAddr(([0, 0, 0, 0], dst.port()).into())
185185
};
186+
let logical_addr = profile.as_ref().and_then(|p| p.borrow().addr.clone());
186187

187188
Self {
188189
orig_dst,
189190
profile,
190191
protocol: version,
192+
logical_addr,
191193
}
192194
}
193195
}

linkerd/app/outbound/src/target.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub struct Accept<P> {
2424
#[derive(Clone)]
2525
pub struct Logical<P> {
2626
pub orig_dst: OrigDstAddr,
27+
pub logical_addr: Option<LogicalAddr>,
2728
pub profile: Option<profiles::Receiver>,
2829
pub protocol: P,
2930
}
@@ -72,10 +73,12 @@ impl<P> From<(Option<profiles::Receiver>, Accept<P>)> for Logical<P> {
7273
},
7374
): (Option<profiles::Receiver>, Accept<P>),
7475
) -> Self {
76+
let logical_addr = profile.as_ref().and_then(|p| p.borrow().addr.clone());
7577
Self {
7678
profile,
7779
orig_dst,
7880
protocol,
81+
logical_addr,
7982
}
8083
}
8184
}
@@ -96,17 +99,18 @@ impl<P> Param<profiles::LookupAddr> for Logical<P> {
9699

97100
impl<P> Logical<P> {
98101
pub fn addr(&self) -> Addr {
99-
self.profile
102+
self.logical_addr
100103
.as_ref()
101-
.and_then(|p| p.borrow().addr.clone())
102-
.map(|LogicalAddr(a)| Addr::from(a))
104+
.map(|LogicalAddr(a)| Addr::from(a.clone()))
103105
.unwrap_or_else(|| self.orig_dst.0.into())
104106
}
105107
}
106108

107109
impl<P: PartialEq> PartialEq<Logical<P>> for Logical<P> {
108110
fn eq(&self, other: &Logical<P>) -> bool {
109-
self.orig_dst == other.orig_dst && self.protocol == other.protocol
111+
self.orig_dst == other.orig_dst
112+
&& self.logical_addr == other.logical_addr
113+
&& self.protocol == other.protocol
110114
}
111115
}
112116

@@ -115,6 +119,7 @@ impl<P: Eq> Eq for Logical<P> {}
115119
impl<P: std::hash::Hash> std::hash::Hash for Logical<P> {
116120
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
117121
self.orig_dst.hash(state);
122+
self.logical_addr.hash(state);
118123
self.protocol.hash(state);
119124
}
120125
}
@@ -135,6 +140,7 @@ impl<P: std::fmt::Debug> std::fmt::Debug for Logical<P> {
135140
}
136141
),
137142
)
143+
.field("logical_addr", &self.logical_addr)
138144
.finish()
139145
}
140146
}

linkerd/app/outbound/src/tcp/tests.rs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ use crate::{
1010
Config, Outbound,
1111
};
1212
use linkerd_app_core::{
13-
io, svc,
13+
io,
14+
profiles::Receiver,
15+
svc,
1416
svc::NewService,
1517
tls,
1618
transport::{addrs::*, listen, orig_dst},
@@ -37,11 +39,8 @@ async fn plaintext_tcp() {
3739
// ports or anything. These will just be used so that the proxy has a socket
3840
// address to resolve, etc.
3941
let target_addr = SocketAddr::new([0, 0, 0, 0].into(), 666);
40-
let logical = Logical {
41-
orig_dst: OrigDstAddr(target_addr),
42-
profile: Some(profile::only_default()),
43-
protocol: (),
44-
};
42+
let profile_recv = profile::only_default();
43+
let logical = build_logical(target_addr, profile_recv);
4544

4645
// Configure mock IO for the upstream "server". It will read "hello" and
4746
// then write "world".
@@ -80,18 +79,12 @@ async fn tls_when_hinted() {
8079
let _trace = support::trace_init();
8180

8281
let tls_addr = SocketAddr::new([0, 0, 0, 0].into(), 5550);
83-
let tls = Logical {
84-
orig_dst: OrigDstAddr(tls_addr),
85-
profile: Some(profile::only_with_addr("tls:5550")),
86-
protocol: (),
87-
};
82+
let tls_recv = profile::only_with_addr("tls:5550");
83+
let tls = build_logical(tls_addr, tls_recv);
8884

8985
let plain_addr = SocketAddr::new([0, 0, 0, 0].into(), 5551);
90-
let plain = Logical {
91-
orig_dst: OrigDstAddr(plain_addr),
92-
profile: Some(profile::only_with_addr("plain:5551")),
93-
protocol: (),
94-
};
86+
let plain_recv = profile::only_with_addr("plain:5551");
87+
let plain = build_logical(plain_addr, plain_recv);
9588

9689
let id_name = tls::ServerId::from_str("foo.ns1.serviceaccount.identity.linkerd.cluster.local")
9790
.expect("hostname is valid");
@@ -822,3 +815,13 @@ where
822815
}
823816
.instrument(span)
824817
}
818+
819+
fn build_logical(addr: SocketAddr, profile_recv: Receiver) -> Logical {
820+
let logical_addr = profile_recv.borrow().addr.clone();
821+
Logical {
822+
orig_dst: OrigDstAddr(addr),
823+
profile: Some(profile_recv),
824+
protocol: (),
825+
logical_addr,
826+
}
827+
}

0 commit comments

Comments
 (0)