Skip to content

Commit 37fb64f

Browse files
committed
Merge branch 'release/v2.26' into release/v2
2 parents 6660024 + 8f297d0 commit 37fb64f

File tree

10 files changed

+189
-17
lines changed

10 files changed

+189
-17
lines changed

CHANGELOG.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
# For older changes, see CHANGELOG.OLD.md
2626
items:
2727
- version: 2.27.0
28-
date: (TBD)
28+
date: 2026-02-14
2929
notes:
3030
- type: feature
3131
title: Add macOS package installer with root daemon as a system service
@@ -53,7 +53,7 @@ items:
5353
due to dependency constraints.
5454
docs: install/client
5555
- version: 2.26.2
56-
date: (TBD)
56+
date: 2026-02-14
5757
notes:
5858
- type: bugfix
5959
title: Dial 127.0.0.1 instead of 0.0.0.0 when connecting to local daemons
@@ -77,6 +77,14 @@ items:
7777
loglevel failed with "root daemon is embedded". The user daemon now correctly delegates to the in-process
7878
root daemon session instead of returning an error.
7979
docs: https://github.com/telepresenceio/telepresence/issues/4049
80+
- type: bugfix
81+
title: Fix h2c (HTTP/2 cleartext) prior knowledge not working on transport
82+
body: >-
83+
Since Go 1.24, having both HTTP1 and UnencryptedHTTP2 enabled on an HTTP transport causes Go to
84+
default to HTTP/1.1 instead of using h2c prior knowledge. The transport now explicitly disables
85+
HTTP1 when UnencryptedHTTP2 is enabled, fixing h2c communication for both the default forwarding
86+
handler and intercepted traffic.
87+
docs: https://github.com/telepresenceio/telepresence/issues/4056
8088
- version: 2.26.1
8189
date: 2026-01-26
8290
notes:

build-aux/main.mk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ bindir ?= $(or $(shell go env GOBIN),$(shell go env GOPATH|cut -d: -f1)/bin)
3131
# https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/syntax.md.
3232
export DOCKER_BUILDKIT := 1
3333

34-
GOLANGCI_VERSION:=v2.8.0
34+
GOLANGCI_VERSION:=v2.9.0
3535

3636
.PHONY: FORCE
3737
FORCE:

cmd/traffic/cmd/agent/fwd/http.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ func (f *tcp) protocols(ctx context.Context, plainText bool) *http.Protocols {
4040
func (f *tcp) configureTransport(ctx context.Context, plainText bool) *http.Transport {
4141
trn := http.DefaultTransport.(*http.Transport).Clone()
4242
trn.Protocols = f.protocols(ctx, plainText)
43+
if trn.Protocols.UnencryptedHTTP2() {
44+
trn.Protocols.SetHTTP1(false)
45+
}
4346
return trn
4447
}
4548

@@ -177,15 +180,11 @@ func shouldInterceptRequest(req *http.Request, headerFilters map[string]string,
177180
return matcher.NewRequest(pathFilters, headerFilters).Matches(req)
178181
}
179182

180-
func (f *tcp) configureUpstreamTransport(ctx context.Context, requestProto int, plaintext bool) *http.Transport {
183+
func (f *tcp) configureUpstreamTransport(ctx context.Context, plaintext bool) *http.Transport {
181184
tm := f.tlsManager
182185
tp := f.Target().Port()
183186
trn := f.configureTransport(ctx, plaintext)
184187
if plaintext {
185-
if requestProto == 2 && trn.Protocols.UnencryptedHTTP2() {
186-
// Force upstream use of clear-text HTTP/2
187-
trn.Protocols.SetHTTP1(false)
188-
}
189188
return trn
190189
}
191190
if tm == nil || !tm.UseTLS(ctx, tp) {
@@ -215,7 +214,7 @@ func (f *tcp) serveHTTPIntercept(ctx context.Context, src netip.AddrPort, writer
215214
ingressBytes = tunnel.NewCounterProbe("FromClientBytes")
216215
egressBytes = tunnel.NewCounterProbe("ToClientBytes")
217216
}
218-
trn := f.configureUpstreamTransport(ctx, request.ProtoMajor, spec.Plaintext)
217+
trn := f.configureUpstreamTransport(ctx, spec.Plaintext)
219218
trn.DialContext = func(context.Context, string, string) (net.Conn, error) {
220219
s, err := f.createStream(ctx, src, ii)
221220
if err != nil {

cmd/traffic/cmd/agent/state_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ const (
2626
var appTarget = netip.AddrPortFrom(netip.MustParseAddr("192.168.1.100"), appPort)
2727

2828
func makeFS(t *testing.T, ctx context.Context) (fwd.Interceptor, agent.State) {
29+
ctx, cancel := context.WithCancel(ctx)
30+
t.Cleanup(cancel)
2931
f := fwd.NewInterceptor(ctx, types.PortAndProto{Proto: types.ProtoTCP, Port: 1111}, tunnel.AgentToProxied, appTarget)
3032
go func() {
31-
if err := f.Serve(context.Background(), nil); err != nil {
33+
if err := f.Serve(ctx, nil); err != nil {
3234
clog.Error(ctx, err)
3335
}
3436
}()

docs/reference/cli/telepresence_genyaml_config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Generate YAML for the agent's entry in the telepresence-agents configmap. See ge
1717

1818
### Flags:
1919
```
20-
--agent-image string The qualified name of the agent image (default "ghcr.io/telepresenceio/tel2:2.27.0")
20+
--agent-image string The qualified name of the agent image (default "ghcr.io/telepresenceio/tel2:2.26.2")
2121
--agent-port uint16 The port number you wish the agent to listen on. (default 9900)
2222
-h, --help help for config
2323
-i, --input string Path to the yaml containing the workload definition (i.e. Deployment, StatefulSet, etc). Pass '-' for stdin.. Mutually exclusive to --workload

docs/release-notes.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
[comment]: # (Code generated by relnotesgen. DO NOT EDIT.)
33
# <img src="images/logo.png" height="64px"/> Telepresence Release Notes
4-
## Version 2.27.0
4+
## Version 2.27.0 <span style="font-size: 16px;">(February 14)</span>
55
## <div style="display:flex;"><img src="images/feature.png" alt="feature" style="width:30px;height:fit-content;"/><div style="display:flex;margin-left:7px;">[Add macOS package installer with root daemon as a system service](install/client)</div></div>
66
<div style="margin-left: 15px">
77

@@ -20,7 +20,7 @@ New Linux package installers (.deb for Debian/Ubuntu and .rpm for Fedora/RHEL) a
2020
A new Windows installer (.exe) is now available that installs Telepresence with the root daemon configured as a Windows service. The installer bundles WinFSP and SSHFS-Win dependencies for volume mount support, adds Telepresence to the system PATH, and optionally installs the TelepresenceDaemon service. This eliminates the need for elevated privileges when using Telepresence. Currently available for amd64 architecture only due to dependency constraints.
2121
</div>
2222

23-
## Version 2.26.2
23+
## Version 2.26.2 <span style="font-size: 16px;">(February 14)</span>
2424
## <div style="display:flex;"><img src="images/bugfix.png" alt="bugfix" style="width:30px;height:fit-content;"/><div style="display:flex;margin-left:7px;">[Dial 127.0.0.1 instead of 0.0.0.0 when connecting to local daemons](https://github.com/telepresenceio/telepresence/issues/4048)</div></div>
2525
<div style="margin-left: 15px">
2626

@@ -39,6 +39,12 @@ The compose extension set HeaderFilters and PathFilters on the intercept spec bu
3939
When running Telepresence in an elevated (administrator) terminal on Windows, commands like connect and loglevel failed with "root daemon is embedded". The user daemon now correctly delegates to the in-process root daemon session instead of returning an error.
4040
</div>
4141

42+
## <div style="display:flex;"><img src="images/bugfix.png" alt="bugfix" style="width:30px;height:fit-content;"/><div style="display:flex;margin-left:7px;">[Fix h2c (HTTP/2 cleartext) prior knowledge not working on transport](https://github.com/telepresenceio/telepresence/issues/4056)</div></div>
43+
<div style="margin-left: 15px">
44+
45+
Since Go 1.24, having both HTTP1 and UnencryptedHTTP2 enabled on an HTTP transport causes Go to default to HTTP/1.1 instead of using h2c prior knowledge. The transport now explicitly disables HTTP1 when UnencryptedHTTP2 is enabled, fixing h2c communication for both the default forwarding handler and intercepted traffic.
46+
</div>
47+
4248
## Version 2.26.1 <span style="font-size: 16px;">(January 26)</span>
4349
## <div style="display:flex;"><img src="images/bugfix.png" alt="bugfix" style="width:30px;height:fit-content;"/><div style="display:flex;margin-left:7px;">[Add support for "warning" as an alias for "warn" in log levels](https://github.com/telepresenceio/telepresence/issues/4043)</div></div>
4450
<div style="margin-left: 15px">

docs/release-notes.mdx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Note, Title, Body } from '@site/src/components/ReleaseNotes'
77
[comment]: # (Code generated by relnotesgen. DO NOT EDIT.)
88

99
# Telepresence Release Notes
10-
## Version 2.27.0
10+
## Version 2.27.0 <span style={{fontSize:'16px'}}>(February 14)</span>
1111
<Note>
1212
<Title type="feature" docs="install/client">Add macOS package installer with root daemon as a system service</Title>
1313
<Body>
@@ -26,7 +26,7 @@ New Linux package installers (.deb for Debian/Ubuntu and .rpm for Fedora/RHEL) a
2626
A new Windows installer (.exe) is now available that installs Telepresence with the root daemon configured as a Windows service. The installer bundles WinFSP and SSHFS-Win dependencies for volume mount support, adds Telepresence to the system PATH, and optionally installs the TelepresenceDaemon service. This eliminates the need for elevated privileges when using Telepresence. Currently available for amd64 architecture only due to dependency constraints.
2727
</Body>
2828
</Note>
29-
## Version 2.26.2
29+
## Version 2.26.2 <span style={{fontSize:'16px'}}>(February 14)</span>
3030
<Note>
3131
<Title type="bugfix" docs="https://github.com/telepresenceio/telepresence/issues/4048">Dial 127.0.0.1 instead of 0.0.0.0 when connecting to local daemons</Title>
3232
<Body>
@@ -45,6 +45,12 @@ The compose extension set HeaderFilters and PathFilters on the intercept spec bu
4545
When running Telepresence in an elevated (administrator) terminal on Windows, commands like connect and loglevel failed with "root daemon is embedded". The user daemon now correctly delegates to the in-process root daemon session instead of returning an error.
4646
</Body>
4747
</Note>
48+
<Note>
49+
<Title type="bugfix" docs="https://github.com/telepresenceio/telepresence/issues/4056">Fix h2c (HTTP/2 cleartext) prior knowledge not working on transport</Title>
50+
<Body>
51+
Since Go 1.24, having both HTTP1 and UnencryptedHTTP2 enabled on an HTTP transport causes Go to default to HTTP/1.1 instead of using h2c prior knowledge. The transport now explicitly disables HTTP1 when UnencryptedHTTP2 is enabled, fixing h2c communication for both the default forwarding handler and intercepted traffic.
52+
</Body>
53+
</Note>
4854
## Version 2.26.1 <span style={{fontSize:'16px'}}>(January 26)</span>
4955
<Note>
5056
<Title type="bugfix" docs="https://github.com/telepresenceio/telepresence/issues/4043">Add support for "warning" as an alias for "warn" in log levels</Title>

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ require (
4141
github.com/telepresenceio/go-fuseftp v1.0.1
4242
github.com/telepresenceio/go-fuseftp/rpc v1.0.1
4343
github.com/telepresenceio/telepresence/cmd/cobraparser/v2 v2.0.0-20260209025653-93f96f1a660a
44-
github.com/telepresenceio/telepresence/rpc/v2 v2.26.1
44+
github.com/telepresenceio/telepresence/rpc/v2 v2.26.2
4545
github.com/vishvananda/netlink v1.3.1
4646
golang.org/x/net v0.50.0
4747
golang.org/x/sync v0.19.0
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package integration_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"net"
8+
"net/http"
9+
"strconv"
10+
"strings"
11+
"time"
12+
13+
"github.com/telepresenceio/clog"
14+
"github.com/telepresenceio/telepresence/v2/integration_test/itest"
15+
"github.com/telepresenceio/telepresence/v2/pkg/iputil"
16+
)
17+
18+
type h2cInterceptSuite struct {
19+
itest.Suite
20+
itest.TrafficManager
21+
}
22+
23+
func (s *h2cInterceptSuite) SuiteName() string {
24+
return "H2CIntercept"
25+
}
26+
27+
func init() {
28+
itest.AddConnectedSuite("", func(h itest.TrafficManager) itest.TestingSuite {
29+
return &h2cInterceptSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}
30+
})
31+
}
32+
33+
func (s *h2cInterceptSuite) SetupSuite() {
34+
if !(s.ManagerIsVersion(">2.26.x") && s.ClientIsVersion(">2.26.x")) {
35+
s.T().Skip("H2C intercepts require Telepresence 2.27.0 or later")
36+
}
37+
s.Suite.SetupSuite()
38+
}
39+
40+
// startLocalH2CEchoServer starts a local HTTP server that supports h2c (HTTP/2 cleartext)
41+
// and echoes a line with the given name and the current URL path.
42+
func startLocalH2CEchoServer(ctx context.Context, name string) (int, context.CancelFunc) {
43+
ctx, cancel := context.WithCancel(ctx)
44+
lc := net.ListenConfig{}
45+
l, err := lc.Listen(ctx, "tcp", "localhost:0")
46+
if err != nil {
47+
cancel()
48+
panic(fmt.Sprintf("failed to listen: %v", err))
49+
}
50+
pr := new(http.Protocols)
51+
pr.SetHTTP1(true)
52+
pr.SetUnencryptedHTTP2(true)
53+
sc := &http.Server{
54+
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
55+
fmt.Fprintf(w, "%s from intercept at %s", name, r.URL.Path)
56+
}),
57+
Protocols: pr,
58+
}
59+
go func() {
60+
_ = sc.Serve(l)
61+
}()
62+
go func() {
63+
<-ctx.Done()
64+
sdCtx, sdCancel := context.WithTimeout(context.WithoutCancel(ctx), time.Second)
65+
defer sdCancel()
66+
_ = sc.Shutdown(sdCtx)
67+
}()
68+
return l.Addr().(*net.TCPAddr).Port, cancel
69+
}
70+
71+
func (s *h2cInterceptSuite) Test_H2CInterceptPreservesProtocol() {
72+
ctx := s.Context()
73+
require := s.Require()
74+
75+
const svc = "echo-h2c"
76+
77+
// Deploy echo-server with appProtocol: kubernetes.io/h2c
78+
itest.ApplyAppTemplate(ctx, s.AppNamespace(), &itest.AppData{
79+
AppName: svc,
80+
Ports: []itest.AppPort{
81+
{
82+
ServicePortNumber: 80,
83+
TargetPortNumber: 8080,
84+
AppProtocol: "kubernetes.io/h2c",
85+
},
86+
},
87+
Env: map[string]string{"PORTS": "8080:http"},
88+
})
89+
defer itest.DeleteSvcAndWorkload(ctx, "deploy", svc, s.AppNamespace())
90+
91+
// Start a local h2c-capable echo server to receive intercepted traffic.
92+
// The upstream handler must support the same protocol as the app.
93+
localPort, cancel := startLocalH2CEchoServer(ctx, svc)
94+
defer cancel()
95+
96+
// Create a personal HTTP intercept with a header filter
97+
stdout, stderr, err := itest.Telepresence(ctx, "intercept", svc,
98+
"--http-header", "x-test=h2c",
99+
"--port", strconv.Itoa(localPort)+":80",
100+
"--mount=false")
101+
require.NoError(err, "stderr: %s", stderr)
102+
require.Contains(stdout, "Using Deployment")
103+
104+
// Capture traffic-agent logs for debugging
105+
s.CapturePodLogs(ctx, svc, "traffic-agent", s.AppNamespace())
106+
107+
// Verify non-intercepted traffic uses HTTP/2 (h2c).
108+
// The intercept is active so traffic goes through the agent's HTTP handler.
109+
// Requests without the x-test header don't match the intercept and are forwarded
110+
// by the default reverse proxy. The fix ensures that this proxy uses h2c prior
111+
// knowledge, so the echo-server sees HTTP/2.0.
112+
require.Eventually(func() bool {
113+
ips, err := net.DefaultResolver.LookupIP(ctx, "ip", svc)
114+
if err != nil {
115+
clog.Info(ctx, err)
116+
return false
117+
}
118+
ips = iputil.UniqueSorted(ips)
119+
if len(ips) != 1 {
120+
clog.Infof(ctx, "Lookup for %s returned %v", svc, ips)
121+
return false
122+
}
123+
hc := http.Client{Timeout: 2 * time.Second}
124+
rq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://%s", net.JoinHostPort(ips[0].String(), "80")), nil)
125+
if err != nil {
126+
clog.Info(ctx, err)
127+
return false
128+
}
129+
resp, err := hc.Do(rq)
130+
if err != nil {
131+
clog.Info(ctx, err)
132+
return false
133+
}
134+
defer resp.Body.Close()
135+
body, err := io.ReadAll(resp.Body)
136+
if err != nil {
137+
clog.Info(ctx, err)
138+
return false
139+
}
140+
r := string(body)
141+
clog.Infof(ctx, "non-intercepted response: %s", r)
142+
return strings.Contains(r, "HTTP/2.0 GET /")
143+
}, 30*time.Second, 3*time.Second, "expected HTTP/2.0 in non-intercepted response")
144+
145+
// Verify intercepted traffic reaches the local h2c handler
146+
itest.PingInterceptedEchoServer(ctx, svc, "80", "x-test=h2c")
147+
148+
// Leave the intercept
149+
_, _, err = itest.Telepresence(ctx, "leave", svc)
150+
require.NoError(err)
151+
}

pkg/vif/testdata/router/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ require (
2323
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
2424
github.com/puzpuzpuz/xsync/v4 v4.4.0 // indirect
2525
github.com/rogpeppe/go-internal v1.14.1 // indirect
26-
github.com/telepresenceio/telepresence/rpc/v2 v2.26.1 // indirect
26+
github.com/telepresenceio/telepresence/rpc/v2 v2.26.2 // indirect
2727
github.com/vishvananda/netlink v1.3.1 // indirect
2828
github.com/vishvananda/netns v0.0.5 // indirect
2929
github.com/x448/float16 v0.8.4 // indirect

0 commit comments

Comments
 (0)