From b8a09667d94d972d3547a85cb3c428d513de9a75 Mon Sep 17 00:00:00 2001 From: Thomas Hallgren Date: Thu, 16 Jan 2025 17:43:55 +0100 Subject: [PATCH 1/7] Fix a problem causing brew release update to be omitted. Signed-off-by: Thomas Hallgren --- .github/workflows/release.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 16506e8b7a..a59e67bf30 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -148,9 +148,9 @@ jobs: ![Assets](https://static.scarf.sh/a.png?x-pxid=d842651a-2e4d-465a-98e1-4808722c01ab) - uses: actions/checkout@v4 - if: needs.publish-release.semver_check.outputs.make_latest + if: steps.semver_check.outputs.make_latest == true - name: Update Homebrew - if: needs.publish-release.semver_check.outputs.make_latest + if: steps.semver_check.outputs.make_latest == true run: | v=${{ github.ref_name }} packaging/homebrew-package.sh "${v#v}" tel2oss "${{ vars.GH_BOT_USER }}" "${{ vars.GH_BOT_EMAIL }}" "${{ secrets.HOMEBREW_TAP_TOKEN }}" From 212be58ff8c3034090046f3fd33c10666dd37735 Mon Sep 17 00:00:00 2001 From: Thomas Hallgren Date: Mon, 20 Jan 2025 12:29:36 +0100 Subject: [PATCH 2/7] Fix panic when agentpf.client creates a Tunnel A race could occur where several requests where made to `agentpf.client.Tunnel` on a client that had errored when creating its port-forward to the agent. The implementation could handle one such requests but not several, resulting in a panic in situations where multiple simultaneous requests were made to the same client during a very short time period, Signed-off-by: Thomas Hallgren --- CHANGELOG.yml | 10 ++++++++++ docs/release-notes.md | 7 +++++++ docs/release-notes.mdx | 5 +++++ pkg/client/agentpf/clients.go | 27 ++++++++++++++++++--------- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.yml b/CHANGELOG.yml index 47dcae5324..2a05fa5da1 100644 --- a/CHANGELOG.yml +++ b/CHANGELOG.yml @@ -33,6 +33,16 @@ docDescription: >- environments, access to instantaneous feedback loops, and highly customizable development environments. items: + - version: 2.21.2 + date: (TBD) + notes: + - type: bugfix + title: Fix panic when agentpf.client creates a Tunnel + body: >- + A race could occur where several requests where made to `agentpf.client.Tunnel` on a client that had errored + when creating its port-forward to the agent. The implementation could handle one such requests but not + several, resulting in a panic in situations where multiple simultaneous requests were made to the same client + during a very short time period, - version: 2.21.1 date: 2024-12-17 notes: diff --git a/docs/release-notes.md b/docs/release-notes.md index 874cebc7f0..c2adbe31e2 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,13 @@ [comment]: # (Code generated by relnotesgen. DO NOT EDIT.) # Telepresence Release Notes +## Version 2.21.2 +##
bugfix
Fix panic when agentpf.client creates a Tunnel
+
+ +A race could occur where several requests where made to `agentpf.client.Tunnel` on a client that had errored when creating its port-forward to the agent. The implementation could handle one such requests but not several, resulting in a panic in situations where multiple simultaneous requests were made to the same client during a very short time period, +
+ ## Version 2.21.1 (December 17) ##
bugfix
[Allow ingest of serverless deployments without specifying an inject-container-ports annotation](https://github.com/telepresenceio/telepresence/issues/3741)
diff --git a/docs/release-notes.mdx b/docs/release-notes.mdx index 45fa434b15..525d3bf628 100644 --- a/docs/release-notes.mdx +++ b/docs/release-notes.mdx @@ -7,6 +7,11 @@ import { Note, Title, Body } from '@site/src/components/ReleaseNotes' [comment]: # (Code generated by relnotesgen. DO NOT EDIT.) # Telepresence Release Notes +## Version 2.21.2 + + Fix panic when agentpf.client creates a Tunnel + A race could occur where several requests where made to `agentpf.client.Tunnel` on a client that had errored when creating its port-forward to the agent. The implementation could handle one such requests but not several, resulting in a panic in situations where multiple simultaneous requests were made to the same client during a very short time period, + ## Version 2.21.1 (December 17) Allow ingest of serverless deployments without specifying an inject-container-ports annotation diff --git a/pkg/client/agentpf/clients.go b/pkg/client/agentpf/clients.go index d601d1621f..812e9d5b83 100644 --- a/pkg/client/agentpf/clients.go +++ b/pkg/client/agentpf/clients.go @@ -53,6 +53,8 @@ func (ac *client) Tunnel(ctx context.Context, opts ...grpc.CallOption) (tunnel.C select { case err, ok := <-ac.ready: if ok { + // Put error back on channel in case this Tunnel is used again before it's deleted. + ac.ready <- err return nil, err } // ready channel is closed. We are ready to go. @@ -74,14 +76,24 @@ func (ac *client) Tunnel(ctx context.Context, opts ...grpc.CallOption) (tunnel.C } func (ac *client) connect(ctx context.Context, deleteMe func()) { - defer close(ac.ready) dialCtx, dialCancel := context.WithTimeout(ctx, 5*time.Second) defer dialCancel() - conn, cli, _, err := k8sclient.ConnectToAgent(dialCtx, ac.info.PodName, ac.info.Namespace, uint16(ac.info.ApiPort)) + var err error + defer func() { + if err == nil { + close(ac.ready) + } else { + deleteMe() + ac.ready <- err + } + }() + + var conn *grpc.ClientConn + var cli agent.AgentClient + + conn, cli, _, err = k8sclient.ConnectToAgent(dialCtx, ac.info.PodName, ac.info.Namespace, uint16(ac.info.ApiPort)) if err != nil { - deleteMe() - ac.ready <- err return } @@ -94,10 +106,7 @@ func (ac *client) connect(ctx context.Context, deleteMe func()) { intercepted := ac.info.Intercepted ac.Unlock() if intercepted { - if err = ac.startDialWatcherReady(ctx); err != nil { - deleteMe() - ac.ready <- err - } + err = ac.startDialWatcherReady(ctx) } } @@ -495,7 +504,7 @@ func (s *clients) updateClients(ctx context.Context, ais []*manager.AgentPodInfo return oldValue, false } ac := &client{ - ready: make(chan error), + ready: make(chan error, 1), session: s.session, info: ai, } From 0f9d66941cda123ee81bdc7e005245d359f66a7e Mon Sep 17 00:00:00 2001 From: Thomas Hallgren Date: Mon, 20 Jan 2025 12:46:11 +0100 Subject: [PATCH 3/7] Fix goroutine leak in dialer. The context passed to the `Tunnel` call that creates a stream for a dialer, was not cancelled when the dialer was finished, so the stream was never properly closed, leading to one dormant goroutine for each stream. Signed-off-by: Thomas Hallgren --- CHANGELOG.yml | 5 +++++ docs/release-notes.md | 6 ++++++ docs/release-notes.mdx | 4 ++++ pkg/tunnel/dialer.go | 3 ++- 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.yml b/CHANGELOG.yml index 2a05fa5da1..73c33ed61f 100644 --- a/CHANGELOG.yml +++ b/CHANGELOG.yml @@ -43,6 +43,11 @@ items: when creating its port-forward to the agent. The implementation could handle one such requests but not several, resulting in a panic in situations where multiple simultaneous requests were made to the same client during a very short time period, + - type: bugfix + title: Fix goroutine leak in dialer. + body: >- + The context passed to the `Tunnel` call that creates a stream for a dialer, was not cancelled when the dialer + was finished, so the stream was never properly closed, leading to one dormant goroutine for each stream. - version: 2.21.1 date: 2024-12-17 notes: diff --git a/docs/release-notes.md b/docs/release-notes.md index c2adbe31e2..f3b418f2fd 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,6 +8,12 @@ A race could occur where several requests where made to `agentpf.client.Tunnel` on a client that had errored when creating its port-forward to the agent. The implementation could handle one such requests but not several, resulting in a panic in situations where multiple simultaneous requests were made to the same client during a very short time period,
+##
bugfix
Fix goroutine leak in dialer.
+
+ +The context passed to the `Tunnel` call that creates a stream for a dialer, was not cancelled when the dialer was finished, so the stream was never properly closed, leading to one dormant goroutine for each stream. +
+ ## Version 2.21.1 (December 17) ##
bugfix
[Allow ingest of serverless deployments without specifying an inject-container-ports annotation](https://github.com/telepresenceio/telepresence/issues/3741)
diff --git a/docs/release-notes.mdx b/docs/release-notes.mdx index 525d3bf628..d58ae9a8c3 100644 --- a/docs/release-notes.mdx +++ b/docs/release-notes.mdx @@ -12,6 +12,10 @@ import { Note, Title, Body } from '@site/src/components/ReleaseNotes' Fix panic when agentpf.client creates a Tunnel A race could occur where several requests where made to `agentpf.client.Tunnel` on a client that had errored when creating its port-forward to the agent. The implementation could handle one such requests but not several, resulting in a panic in situations where multiple simultaneous requests were made to the same client during a very short time period, + + Fix goroutine leak in dialer. + The context passed to the `Tunnel` call that creates a stream for a dialer, was not cancelled when the dialer was finished, so the stream was never properly closed, leading to one dormant goroutine for each stream. + ## Version 2.21.1 (December 17) Allow ingest of serverless deployments without specifying an inject-container-ports annotation diff --git a/pkg/tunnel/dialer.go b/pkg/tunnel/dialer.go index ed1d72c0de..f2e3fa6cb2 100644 --- a/pkg/tunnel/dialer.go +++ b/pkg/tunnel/dialer.go @@ -347,12 +347,13 @@ func DialWaitLoop( func dialRespond(ctx context.Context, tunnelProvider Provider, dr *rpc.DialRequest, sessionID string) { id := ConnID(dr.ConnId) + ctx, cancel := context.WithCancel(ctx) mt, err := tunnelProvider.Tunnel(ctx) if err != nil { dlog.Errorf(ctx, "!! CONN %s, call to manager Tunnel failed: %v", id, err) + cancel() return } - ctx, cancel := context.WithCancel(ctx) s, err := NewClientStream(ctx, mt, id, sessionID, time.Duration(dr.RoundtripLatency), time.Duration(dr.DialTimeout)) if err != nil { dlog.Error(ctx, err) From bece8bafad04110ee14b6bd31075b0aa2f149410 Mon Sep 17 00:00:00 2001 From: Thomas Hallgren Date: Sun, 19 Jan 2025 23:29:37 +0100 Subject: [PATCH 4/7] Make the API between Telepresence tun-device and gVisor tighter. This commit removes some redundant byte copying when gVisors network stack reads and writes from the tun-device. This is especially true on Linux where the actual LinkEndpoint now is file descriptor based. Signed-off-by: Thomas Hallgren --- DEPENDENCIES.md | 2 +- build-aux/main.mk | 2 +- go.mod | 2 +- go.sum | 4 +- pkg/client/agentpf/clients.go | 3 + pkg/tunnel/dialer.go | 5 +- pkg/tunnel/stream.go | 15 +++- pkg/vif/buffer/data_darwin.go | 50 ------------ pkg/vif/buffer/data_other.go | 40 --------- pkg/vif/device.go | 141 ++++---------------------------- pkg/vif/device_darwin.go | 133 +++++++++++++++++------------- pkg/vif/device_linux.go | 143 +++++++++++++-------------------- pkg/vif/device_notlinux.go | 102 +++++++++++++++++++++++ pkg/vif/device_unix.go | 2 +- pkg/vif/device_windows.go | 102 ++++++++++++++--------- pkg/vif/stack.go | 88 ++++++++++---------- pkg/vif/testdata/router/go.mod | 2 +- pkg/vif/testdata/router/go.sum | 4 +- pkg/vif/tunneling_device.go | 9 ++- 19 files changed, 393 insertions(+), 456 deletions(-) delete mode 100644 pkg/vif/buffer/data_darwin.go delete mode 100644 pkg/vif/buffer/data_other.go create mode 100644 pkg/vif/device_notlinux.go diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index d035d53ccf..e8b7135af6 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -162,7 +162,7 @@ following Free and Open Source software: gopkg.in/evanphx/json-patch.v4 v4.12.0 3-clause BSD license gopkg.in/inf.v0 v0.9.1 3-clause BSD license gopkg.in/yaml.v3 v3.0.1 Apache License 2.0, MIT license - gvisor.dev/gvisor v0.0.0-20241210225239-aa8ecac76a04 Apache License 2.0, MIT license + gvisor.dev/gvisor v0.0.0-20250115195935-26653e7d8816 Apache License 2.0, MIT license helm.sh/helm/v3 v3.16.3 Apache License 2.0 k8s.io/api v0.32.0 Apache License 2.0 k8s.io/apiextensions-apiserver v0.32.0 Apache License 2.0 diff --git a/build-aux/main.mk b/build-aux/main.mk index 4f61b43148..699d94d010 100644 --- a/build-aux/main.mk +++ b/build-aux/main.mk @@ -379,7 +379,7 @@ lint: lint-rpc lint-go lint-go: lint-deps ## (QA) Run the golangci-lint $(eval badimports = $(shell find cmd integration_test pkg -name '*.go' | grep -v '/mocks/' | xargs $(tools/gosimports) --local github.com/datawire/,github.com/telepresenceio/ -l)) $(if $(strip $(badimports)), echo "The following files have bad import ordering (use make format to fix): " $(badimports) && false) -ifeq ($(GOHOSTOS),windows) +ifeq ($(GOOS),windows) CGO_ENABLED=$(CGO_ENABLED) $(tools/golangci-lint) run --timeout 8m ./cmd/telepresence/... ./integration_test/... ./pkg/... else CGO_ENABLED=$(CGO_ENABLED) $(tools/golangci-lint) run --timeout 8m ./... diff --git a/go.mod b/go.mod index cf8cdc2ca2..4b497f5a99 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( golang.zx2c4.com/wireguard/windows v0.5.3 google.golang.org/grpc v1.69.0 google.golang.org/protobuf v1.35.2 - gvisor.dev/gvisor v0.0.0-20241210225239-aa8ecac76a04 + gvisor.dev/gvisor v0.0.0-20250115195935-26653e7d8816 helm.sh/helm/v3 v3.16.3 k8s.io/api v0.32.0 k8s.io/apimachinery v0.32.0 diff --git a/go.sum b/go.sum index f28a12237e..17bc110bae 100644 --- a/go.sum +++ b/go.sum @@ -589,8 +589,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -gvisor.dev/gvisor v0.0.0-20241210225239-aa8ecac76a04 h1:OvxeUgwtB7WYf2yMVpJTpFMekpztPTYbSQXpvEJoCKk= -gvisor.dev/gvisor v0.0.0-20241210225239-aa8ecac76a04/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= +gvisor.dev/gvisor v0.0.0-20250115195935-26653e7d8816 h1:ZIPfC6hWCapUHskzT+Hqq648v+os3ZnruhEA0NNQsQc= +gvisor.dev/gvisor v0.0.0-20250115195935-26653e7d8816/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= helm.sh/helm/v3 v3.16.3 h1:kb8bSxMeRJ+knsK/ovvlaVPfdis0X3/ZhYCSFRP+YmY= helm.sh/helm/v3 v3.16.3/go.mod h1:zeVWGDR4JJgiRbT3AnNsjYaX8OTJlIE9zC+Q7F7iUSU= k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= diff --git a/pkg/client/agentpf/clients.go b/pkg/client/agentpf/clients.go index 812e9d5b83..256da6b9fd 100644 --- a/pkg/client/agentpf/clients.go +++ b/pkg/client/agentpf/clients.go @@ -248,6 +248,9 @@ func NewClients(session *manager.SessionInfo) Clients { // // The function returns nil when there are no agents in the connected namespace. func (s *clients) GetClient(ip netip.Addr) (pvd tunnel.Provider) { + if s.disabled.Load() { + return nil + } var primary, secondary, ternary tunnel.Provider s.clients.Range(func(_ string, c *client) bool { podIP, ok := netip.AddrFromSlice(c.info.PodIp) diff --git a/pkg/tunnel/dialer.go b/pkg/tunnel/dialer.go index f2e3fa6cb2..06b9a68132 100644 --- a/pkg/tunnel/dialer.go +++ b/pkg/tunnel/dialer.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net" + "strings" "sync" "sync/atomic" "time" @@ -219,8 +220,10 @@ func (h *dialer) connToStreamLoop(ctx context.Context, wg *sync.WaitGroup) { case errors.Is(err, net.ErrClosed): endReason = "the connection was closed" h.startDisconnect(ctx, endReason) + case strings.Contains(err.Error(), "connection aborted"): + endReason = "the connection was aborted" default: - endReason = fmt.Sprintf("a read error occurred: %v", err) + endReason = fmt.Sprintf("a read error occurred: %T %v", err, err) endLevel = dlog.LogLevelError } return diff --git a/pkg/tunnel/stream.go b/pkg/tunnel/stream.go index c798e63315..298e0f5791 100644 --- a/pkg/tunnel/stream.go +++ b/pkg/tunnel/stream.go @@ -105,13 +105,20 @@ func ReadLoop(ctx context.Context, s Stream, p *CounterProbe) (<-chan Message, < endReason = "EOF on input" case errors.Is(err, net.ErrClosed): endReason = "stream closed" - case errors.Is(err, context.Canceled), status.Code(err) == codes.Canceled: + case errors.Is(err, context.Canceled): endReason = err.Error() default: - endReason = err.Error() - select { - case errCh <- fmt.Errorf("!! %s %s, read from grpc.ClientStream failed: %w", s.Tag(), s.ID(), err): + switch status.Code(err) { + case codes.NotFound: + endReason = "session closed" + case codes.Canceled: + endReason = err.Error() default: + endReason = err.Error() + select { + case errCh <- fmt.Errorf("!! %s %s, read from grpc.ClientStream failed: %w", s.Tag(), s.ID(), err): + default: + } } } break diff --git a/pkg/vif/buffer/data_darwin.go b/pkg/vif/buffer/data_darwin.go deleted file mode 100644 index ce23fdbff7..0000000000 --- a/pkg/vif/buffer/data_darwin.go +++ /dev/null @@ -1,50 +0,0 @@ -package buffer - -const PrefixLen = 4 - -// Data on a macOS consists of two slices that share the same underlying byte array. The -// raw data points to the beginning of the array and the buf points PrefixLen into the array. -// All data manipulation is then done using the buf, except reads/writes to the tun device which -// uses the raw. This setup enables the read/write to receive and write the required 4-byte -// header that macOS TUN socket uses without copying data. -type Data struct { - buf []byte - raw []byte -} - -// Buf returns this Data's buffer. This is the buffer that should be used everywhere -// except for the tun.Device ReadPacket and WritePacket methods. -func (d *Data) Buf() []byte { - return d.buf -} - -// Copy copies n bytes from the given Data buffer into a new Data and returns it. -func (d *Data) Copy(n int) *Data { - c := NewData(n) - c.buf = c.buf[:n] - c.raw = c.raw[:n+PrefixLen] - copy(c.raw, d.raw) - return c -} - -// Raw returns this Data's raw buffer. This is the buffer that should be used by the tun.Device -// ReadPacket and WritePacket methods. It uses the same underlying byte array as Buf but might be -// offset before Buf to allow for leading bytes that are provided before the IP header. -func (d *Data) Raw() []byte { - return d.raw -} - -func NewData(sz int) *Data { - raw := make([]byte, PrefixLen+sz) - return &Data{buf: raw[PrefixLen:], raw: raw} -} - -func (d *Data) Resize(size int) { - if size <= cap(d.buf) { - d.buf = d.buf[:size] - d.raw = d.raw[:size+PrefixLen] - } else { - d.raw = make([]byte, size+PrefixLen) - d.buf = d.raw[PrefixLen:] - } -} diff --git a/pkg/vif/buffer/data_other.go b/pkg/vif/buffer/data_other.go deleted file mode 100644 index 4a030e656a..0000000000 --- a/pkg/vif/buffer/data_other.go +++ /dev/null @@ -1,40 +0,0 @@ -//go:build !darwin -// +build !darwin - -package buffer - -type Data struct { - buf []byte -} - -// Buf returns this Data's buffer. This is the buffer that should be used everywhere -// except for the tun.Device ReadPacket and WritePacket methods. -func (d *Data) Buf() []byte { - return d.buf -} - -// Copy copies n bytes from the given Data buffer into a new Data and returns it. -func (d *Data) Copy(n int) *Data { - c := NewData(n) - copy(c.buf, d.buf) - return c -} - -// Raw returns this Data's raw buffer. This is the buffer that should be used by the tun.Device -// ReadPacket and WritePacket methods. It uses the same underlying byte array as Buf but might be -// offset before Buf to allow for leading bytes that are provided before the IP header. -func (d *Data) Raw() []byte { - return d.buf -} - -func NewData(sz int) *Data { - return &Data{buf: make([]byte, sz)} -} - -func (d *Data) Resize(size int) { - if size <= cap(d.buf) { - d.buf = d.buf[:size] - } else { - d.buf = make([]byte, size) - } -} diff --git a/pkg/vif/device.go b/pkg/vif/device.go index 3207759e5e..87a7898555 100644 --- a/pkg/vif/device.go +++ b/pkg/vif/device.go @@ -3,28 +3,14 @@ package vif import ( "context" "net/netip" - "sync" - "gvisor.dev/gvisor/pkg/buffer" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/link/channel" "gvisor.dev/gvisor/pkg/tcpip/stack" - - "github.com/datawire/dlib/dlog" - vifBuffer "github.com/telepresenceio/telepresence/v2/pkg/vif/buffer" ) -type device struct { - *channel.Endpoint - ctx context.Context - wg sync.WaitGroup - dev *nativeDevice -} - type Device interface { - stack.LinkEndpoint - Index() int32 + Close() // Overrides stack.LinkEndpoint.Close. Must not return error. + NewLinkEndpoint() (stack.LinkEndpoint, error) + Index() uint32 Name() string AddSubnet(context.Context, netip.Prefix) error RemoveSubnet(context.Context, netip.Prefix) error @@ -32,139 +18,40 @@ type Device interface { WaitForDevice() } -const defaultDevMtu = 1500 - -// Queue length for outbound packet, arriving at fd side for read. Overflow -// causes packet drops. gVisor implementation-specific. -const defaultDevOutQueueLen = 1024 - var _ Device = (*device)(nil) // OpenTun creates a new TUN device and ensures that it is up and running. func OpenTun(ctx context.Context) (Device, error) { - dev, err := openTun(ctx) - if err != nil { - return nil, err - } - - return &device{ - Endpoint: channel.New(defaultDevOutQueueLen, defaultDevMtu, ""), - ctx: ctx, - dev: dev, - }, nil -} - -func (d *device) Attach(dp stack.NetworkDispatcher) { - go func() { - d.Endpoint.Attach(dp) - if dp == nil { - // Stack is closing - return - } - dlog.Info(d.ctx, "Starting Endpoint") - ctx, cancel := context.WithCancel(d.ctx) - d.wg.Add(2) - go d.tunToDispatch(cancel) - d.dispatchToTun(ctx) - }() + return openTun(ctx) } // AddSubnet adds a subnet to this TUN device and creates a route for that subnet which // is associated with the device (removing the device will automatically remove the route). func (d *device) AddSubnet(ctx context.Context, subnet netip.Prefix) (err error) { - return d.dev.addSubnet(ctx, subnet) -} - -func (d *device) Close() { - _ = d.dev.Close() + return d.addSubnet(ctx, subnet) } // Index returns the index of this device. -func (d *device) Index() int32 { - return d.dev.index() +func (d *device) Index() uint32 { + return d.index() } // Name returns the name of this device, e.g. "tun0". func (d *device) Name() string { - return d.dev.name + return d.name } -// SetDNS sets the DNS configuration for the device on the windows platform. -func (d *device) SetDNS(ctx context.Context, clusterDomain string, server netip.Addr, domains []string) (err error) { - return d.dev.setDNS(ctx, clusterDomain, server, domains) +func (d *device) NewLinkEndpoint() (stack.LinkEndpoint, error) { + return d.createLinkEndpoint() } -func (d *device) SetMTU(mtu uint32) { - _ = d.dev.setMTU(int(mtu)) +// SetDNS sets the DNS configuration for the device on the windows platform. +func (d *device) SetDNS(ctx context.Context, clusterDomain string, server netip.Addr, domains []string) (err error) { + return d.setDNS(ctx, clusterDomain, server, domains) } // RemoveSubnet removes a subnet from this TUN device and also removes the route for that subnet which // is associated with the device. func (d *device) RemoveSubnet(ctx context.Context, subnet netip.Prefix) (err error) { - return d.dev.removeSubnet(ctx, subnet) -} - -func (d *device) WaitForDevice() { - d.wg.Wait() - dlog.Info(d.ctx, "Endpoint done") -} - -func (d *device) tunToDispatch(cancel context.CancelFunc) { - defer func() { - cancel() - d.wg.Done() - }() - buf := vifBuffer.NewData(0x10000) - data := buf.Buf() - for ok := true; ok; { - n, err := d.dev.readPacket(buf) - if err != nil { - ok = d.IsAttached() - if ok && d.ctx.Err() == nil { - dlog.Errorf(d.ctx, "read packet error: %v", err) - } - return - } - if n == 0 { - continue - } - - var ipv tcpip.NetworkProtocolNumber - switch header.IPVersion(data) { - case header.IPv4Version: - ipv = header.IPv4ProtocolNumber - case header.IPv6Version: - ipv = header.IPv6ProtocolNumber - default: - continue - } - - pb := stack.NewPacketBuffer(stack.PacketBufferOptions{ - Payload: buffer.MakeWithData(data[:n]), - }) - - d.InjectInbound(ipv, pb) - pb.DecRef() - } -} - -func (d *device) dispatchToTun(ctx context.Context) { - defer d.wg.Done() - buf := vifBuffer.NewData(0x10000) - for { - pb := d.ReadContext(ctx) - if pb == nil { - break - } - buf.Resize(pb.Size()) - b := buf.Buf() - for _, s := range pb.AsSlices() { - copy(b, s) - b = b[len(s):] - } - pb.DecRef() - if _, err := d.dev.writePacket(buf, 0); err != nil { - dlog.Errorf(ctx, "WritePacket failed: %v", err) - } - } + return d.removeSubnet(ctx, subnet) } diff --git a/pkg/vif/device_darwin.go b/pkg/vif/device_darwin.go index d7912ebebb..4e62248005 100644 --- a/pkg/vif/device_darwin.go +++ b/pkg/vif/device_darwin.go @@ -1,6 +1,7 @@ package vif import ( + "bytes" "context" "errors" "fmt" @@ -8,14 +9,16 @@ import ( "net/netip" "os" "runtime" + "sync" "unsafe" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/tcpip/link/channel" + "gvisor.dev/gvisor/pkg/tcpip/stack" "github.com/telepresenceio/telepresence/v2/pkg/routing" - "github.com/telepresenceio/telepresence/v2/pkg/vif/buffer" ) const ( @@ -24,12 +27,16 @@ const ( uTunControlName = "com.apple.net.utun_control" ) -type nativeDevice struct { - *os.File +type device struct { + *channel.Endpoint + file *os.File + ctx context.Context name string + wb bytes.Buffer + wg sync.WaitGroup } -func openTun(_ context.Context) (*nativeDevice, error) { +func openTun(ctx context.Context) (*device, error) { fd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, sysProtoControl) if err != nil { return nil, fmt.Errorf("failed to open DGRAM socket: %w", err) @@ -59,64 +66,83 @@ func openTun(_ context.Context) (*nativeDevice, error) { if err != nil { return nil, err } - return &nativeDevice{ - File: os.NewFile(uintptr(fd), ""), - name: name, + mtu, err := getMTU(name) + if err != nil { + return nil, err + } + return &device{ + file: os.NewFile(uintptr(fd), ""), + ctx: ctx, + name: name, + Endpoint: channel.New(defaultDevOutQueueLen, mtu, ""), }, nil } -func (t *nativeDevice) addSubnet(_ context.Context, subnet netip.Prefix) error { +func getMTU(name string) (mtu uint32, err error) { + err = withSocket(unix.AF_INET, func(fd int) error { + ifr, err := unix.IoctlGetIfreqMTU(fd, name) + if err == nil { + mtu = uint32(ifr.MTU) + } + return err + }) + return mtu, err +} + +// Close closes both the tun-device and the Endpoint. This function overrides the LinkEndpoint.Close so +// it can not return an error. +func (d *device) Close() { + d.Endpoint.Close() + _ = d.file.Close() +} + +func (d *device) addSubnet(_ context.Context, subnet netip.Prefix) error { to := subnet.Addr().AsSlice() to[len(to)-1] = 1 dest, _ := netip.AddrFromSlice(to) - if err := t.setAddr(subnet, dest); err != nil { + if err := d.setAddr(subnet, dest); err != nil { return err } return routing.Add(1, subnet, dest) } -func (t *nativeDevice) index() int32 { +func (d *device) index() uint32 { panic("not implemented") } -func (t *nativeDevice) removeSubnet(_ context.Context, subnet netip.Prefix) error { +func (d *device) removeSubnet(_ context.Context, subnet netip.Prefix) error { to := subnet.Addr().AsSlice() to[len(to)-1] = 1 dest, _ := netip.AddrFromSlice(to) - if err := t.removeAddr(subnet, dest); err != nil { + if err := d.removeAddr(subnet, dest); err != nil { return err } return routing.Clear(1, subnet, dest) } -func (t *nativeDevice) setMTU(mtu int) error { - return withSocket(unix.AF_INET, func(fd int) error { - var ifr unix.IfreqMTU - copy(ifr.Name[:], t.name) - ifr.MTU = int32(mtu) - err := unix.IoctlSetIfreqMTU(fd, &ifr) - if err != nil { - err = fmt.Errorf("set MTU on %s failed: %w", t.name, err) - } - return err - }) +func (d *device) readPacket(buf []byte) (int, error) { + return d.file.Read(buf) } -func (t *nativeDevice) readPacket(into *buffer.Data) (int, error) { - n, err := t.File.Read(into.Raw()) - if n >= buffer.PrefixLen { - n -= buffer.PrefixLen - } - return n, err +const prefixLen = 4 + +func (d *device) headerSkip() int { + return prefixLen } -func (t *nativeDevice) writePacket(from *buffer.Data, offset int) (n int, err error) { - raw := from.Raw() - if len(raw) <= buffer.PrefixLen { - return 0, unix.EIO +func (d *device) writePacket(from *stack.PacketBuffer) (err error) { + ss := from.AsSlices() + var first []byte + for _, s := range ss { + if len(s) > 0 { + first = s + break + } } - - ipVer := raw[buffer.PrefixLen] >> 4 + if first == nil { + return nil + } + ipVer := first[0] >> 4 var af byte switch ipVer { case ipv4.Version: @@ -124,21 +150,20 @@ func (t *nativeDevice) writePacket(from *buffer.Data, offset int) (n int, err er case ipv6.Version: af = unix.AF_INET6 default: - return 0, errors.New("unable to determine IP version from packet") + return errors.New("unable to determine IP version from packet") } - - if offset > 0 { - raw = raw[offset:] - // Temporarily move AF_INET/AF_INET6 into the offset position. - r3 := raw[3] - raw[3] = af - n, err = t.File.Write(raw) - raw[3] = r3 - } else { - raw[3] = af - n, err = t.File.Write(raw) + wb := &d.wb + wb.Reset() + wb.WriteByte(0) + wb.WriteByte(0) + wb.WriteByte(0) + wb.WriteByte(af) + wb.Write(first) + for i := 1; i < len(ss); i++ { + wb.Write(ss[i]) } - return n - buffer.PrefixLen, err + _, err = d.file.Write(wb.Bytes()) + return err } // Address structure for the SIOCAIFADDR ioctlHandle request @@ -182,7 +207,7 @@ const ( // SIOCDIFADDR_IN6 is the same ioctlHandle identifier as unix.SIOCDIFADDR adjusted with size of addrIfReq6. const SIOCDIFADDR_IN6 = (unix.SIOCDIFADDR & 0xe000ffff) | (uint(unsafe.Sizeof(addrIfReq6{})) << 16) -func (t *nativeDevice) setAddr(subnet netip.Prefix, to netip.Addr) error { +func (d *device) setAddr(subnet netip.Prefix, to netip.Addr) error { if to.Is4() && subnet.Addr().Is4() { return withSocket(unix.AF_INET, func(fd int) error { ifreq := &addrIfReq{ @@ -190,7 +215,7 @@ func (t *nativeDevice) setAddr(subnet netip.Prefix, to netip.Addr) error { dest: unix.RawSockaddrInet4{Len: unix.SizeofSockaddrInet4, Family: unix.AF_INET}, mask: unix.RawSockaddrInet4{Len: unix.SizeofSockaddrInet4, Family: unix.AF_INET}, } - copy(ifreq.name[:], t.name) + copy(ifreq.name[:], d.name) copy(ifreq.mask.Addr[:], net.CIDRMask(subnet.Bits(), 32)) ifreq.addr.Addr = subnet.Addr().As4() ifreq.dest.Addr = to.As4() @@ -208,7 +233,7 @@ func (t *nativeDevice) setAddr(subnet netip.Prefix, to netip.Addr) error { ifreq.addrLifetime.validLifeTime = ND6_INFINITE_LIFETIME ifreq.addrLifetime.prefixLifeTime = ND6_INFINITE_LIFETIME - copy(ifreq.name[:], t.name) + copy(ifreq.name[:], d.name) copy(ifreq.mask.Addr[:], net.CIDRMask(subnet.Bits(), 128)) ifreq.addr.Addr = subnet.Addr().As16() err := ioctl(fd, SIOCAIFADDR_IN6, unsafe.Pointer(ifreq)) @@ -218,7 +243,7 @@ func (t *nativeDevice) setAddr(subnet netip.Prefix, to netip.Addr) error { } } -func (t *nativeDevice) removeAddr(subnet netip.Prefix, to netip.Addr) error { +func (d *device) removeAddr(subnet netip.Prefix, to netip.Addr) error { if to.Is4() && subnet.Addr().Is4() { return withSocket(unix.AF_INET, func(fd int) error { ifreq := &addrIfReq{ @@ -226,7 +251,7 @@ func (t *nativeDevice) removeAddr(subnet netip.Prefix, to netip.Addr) error { dest: unix.RawSockaddrInet4{Len: unix.SizeofSockaddrInet6, Family: unix.AF_INET}, mask: unix.RawSockaddrInet4{Len: unix.SizeofSockaddrInet6, Family: unix.AF_INET}, } - copy(ifreq.name[:], t.name) + copy(ifreq.name[:], d.name) copy(ifreq.mask.Addr[:], net.CIDRMask(subnet.Bits(), 32)) ifreq.addr.Addr = subnet.Addr().As4() ifreq.dest.Addr = to.As4() @@ -244,7 +269,7 @@ func (t *nativeDevice) removeAddr(subnet netip.Prefix, to netip.Addr) error { ifreq.addrLifetime.validLifeTime = ND6_INFINITE_LIFETIME ifreq.addrLifetime.prefixLifeTime = ND6_INFINITE_LIFETIME - copy(ifreq.name[:], t.name) + copy(ifreq.name[:], d.name) copy(ifreq.mask.Addr[:], net.CIDRMask(subnet.Bits(), 128)) ifreq.addr.Addr = subnet.Addr().As16() err := ioctl(fd, SIOCDIFADDR_IN6, unsafe.Pointer(ifreq)) diff --git a/pkg/vif/device_linux.go b/pkg/vif/device_linux.go index a3c66fcacf..e5786f22ec 100644 --- a/pkg/vif/device_linux.go +++ b/pkg/vif/device_linux.go @@ -4,26 +4,25 @@ import ( "context" "fmt" "net/netip" - "os" - "runtime" "unsafe" "github.com/vishvananda/netlink" "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/tcpip/link/fdbased" + "gvisor.dev/gvisor/pkg/tcpip/stack" "github.com/telepresenceio/telepresence/v2/pkg/subnet" - "github.com/telepresenceio/telepresence/v2/pkg/vif/buffer" ) const devicePath = "/dev/net/tun" -type nativeDevice struct { - *os.File +type device struct { + fd int name string - interfaceIndex int32 + interfaceIndex uint32 } -func openTun(_ context.Context) (*nativeDevice, error) { +func openTun(_ context.Context) (*device, error) { // https://www.kernel.org/doc/html/latest/networking/tuntap.html fd, err := unix.Open(devicePath, unix.O_RDWR, 0) @@ -37,30 +36,16 @@ func openTun(_ context.Context) (*nativeDevice, error) { } }() - var flagsRequest struct { - name [unix.IFNAMSIZ]byte - flags int16 - } - copy(flagsRequest.name[:], "tel%d") - flagsRequest.flags = unix.IFF_TUN | unix.IFF_NO_PI + ifr, err := unix.NewIfreq("tel%d") + ifr.SetUint16(unix.IFF_TUN | unix.IFF_NO_PI) - err = unix.IoctlSetInt(fd, unix.TUNSETIFF, int(uintptr(unsafe.Pointer(&flagsRequest)))) + err = unix.IoctlSetInt(fd, unix.TUNSETIFF, int(uintptr(unsafe.Pointer(ifr)))) if err != nil { return nil, fmt.Errorf("failed to set TUN device flags: %w", err) } - // Retrieve the name that was generated based on the "tel%d" template. The - // name is zero terminated. - var name string - for i := 0; i < unix.IFNAMSIZ; i++ { - if flagsRequest.name[i] == 0 { - name = string(flagsRequest.name[0:i]) - break - } - } - if name == "" { - name = string(flagsRequest.name[:]) - } + // Retrieve the name that was generated based on the "tel%d" template. + name := ifr.Name() // Set non-blocking so that ReadPacket() doesn't hang for several seconds when the // fd is Closed. ReadPacket() will still wait for data to arrive. @@ -68,52 +53,44 @@ func openTun(_ context.Context) (*nativeDevice, error) { // See: https://github.com/golang/go/issues/30426#issuecomment-470044803 _ = unix.SetNonblock(fd, true) - // Bring the device up. This is how it's done in ifconfig. - provisioningSocket, err := unix.Socket(unix.AF_PACKET, unix.SOCK_DGRAM, unix.IPPROTO_IP) - if err != nil { - return nil, fmt.Errorf("failed to open provisioning socket: %w", err) - } - defer unix.Close(provisioningSocket) - - flagsRequest.flags = 0 - if err = ioctl(provisioningSocket, unix.SIOCGIFFLAGS, unsafe.Pointer(&flagsRequest)); err != nil { - return nil, fmt.Errorf("failed to get flags for %s: %w", name, err) - } + var index uint32 + err = withSocket(unix.AF_INET, func(provisioningSocket int) error { + // Bring the device up. This is how it's done in ifconfig. + if err = ioctl(provisioningSocket, unix.SIOCGIFFLAGS, unsafe.Pointer(ifr)); err != nil { + return fmt.Errorf("failed to get flags for %s: %w", name, err) + } - flagsRequest.flags |= unix.IFF_UP | unix.IFF_RUNNING - if err = ioctl(provisioningSocket, unix.SIOCSIFFLAGS, unsafe.Pointer(&flagsRequest)); err != nil { - return nil, fmt.Errorf("failed to set flags for %s: %w", name, err) - } + ifr.SetUint16(ifr.Uint16() | unix.IFF_UP | unix.IFF_RUNNING) + if err = ioctl(provisioningSocket, unix.SIOCSIFFLAGS, unsafe.Pointer(ifr)); err != nil { + return fmt.Errorf("failed to set flags for %s: %w", name, err) + } - index, err := getInterfaceIndex(provisioningSocket, name) + if err = ioctl(provisioningSocket, unix.SIOCGIFINDEX, unsafe.Pointer(ifr)); err != nil { + return fmt.Errorf("get interface index on %s failed: %w", name, err) + } + index = ifr.Uint32() + return nil + }) if err != nil { return nil, err } - return &nativeDevice{File: os.NewFile(uintptr(fd), devicePath), name: name, interfaceIndex: index}, nil + return &device{fd: fd, name: name, interfaceIndex: index}, nil } -func (t *nativeDevice) Close() error { - err := t.File.Close() +func (d *device) addSubnet(_ context.Context, pfx netip.Prefix) error { + link, err := netlink.LinkByIndex(int(d.interfaceIndex)) if err != nil { - return err - } - return nil -} - -func (t *nativeDevice) addSubnet(_ context.Context, pfx netip.Prefix) error { - link, err := netlink.LinkByIndex(int(t.interfaceIndex)) - if err != nil { - return fmt.Errorf("failed to find link for interface %s: %w", t.name, err) + return fmt.Errorf("failed to find link for interface %s: %w", d.name, err) } addr := &netlink.Addr{IPNet: subnet.PrefixToIPNet(pfx)} if err := netlink.AddrAdd(link, addr); err != nil { - return fmt.Errorf("failed to add address %s to interface %s: %w", pfx, t.name, err) + return fmt.Errorf("failed to add address %s to interface %s: %w", pfx, d.name, err) } return nil } -func (t *nativeDevice) removeSubnet(ctx context.Context, pfx netip.Prefix) error { - link, err := netlink.LinkByIndex(int(t.interfaceIndex)) +func (d *device) removeSubnet(ctx context.Context, pfx netip.Prefix) error { + link, err := netlink.LinkByIndex(int(d.interfaceIndex)) if err != nil { return err } @@ -121,43 +98,39 @@ func (t *nativeDevice) removeSubnet(ctx context.Context, pfx netip.Prefix) error return netlink.AddrDel(link, addr) } -func (t *nativeDevice) index() int32 { - return t.interfaceIndex +func (d *device) index() uint32 { + return d.interfaceIndex } -func (t *nativeDevice) setMTU(mtu int) error { - return withSocket(unix.AF_INET, func(fd int) error { - var mtuRequest struct { - name [unix.IFNAMSIZ]byte - mtu int32 - } - copy(mtuRequest.name[:], t.name) - mtuRequest.mtu = int32(mtu) - err := ioctl(fd, unix.SIOCSIFMTU, unsafe.Pointer(&mtuRequest)) - runtime.KeepAlive(&mtuRequest) - if err != nil { - err = fmt.Errorf("set MTU on %s failed: %w", t.name, err) +func (d *device) getMTU() (mtu uint32, err error) { + err = withSocket(unix.AF_INET, func(fd int) error { + ifr, err := unix.NewIfreq(d.name) + if err == nil { + err = ioctl(fd, unix.SIOCGIFMTU, unsafe.Pointer(ifr)) + if err == nil { + mtu = ifr.Uint32() + } } return err }) + return mtu, err } -func (t *nativeDevice) readPacket(into *buffer.Data) (int, error) { - return t.File.Read(into.Raw()) +func (d *device) createLinkEndpoint() (stack.LinkEndpoint, error) { + mtu, err := d.getMTU() + if err != nil { + return nil, err + } + return fdbased.New(&fdbased.Options{ + FDs: []int{d.fd}, + MTU: mtu, + PacketDispatchMode: fdbased.RecvMMsg, + }) } -func (t *nativeDevice) writePacket(from *buffer.Data, offset int) (int, error) { - return t.File.Write(from.Raw()[offset:]) +func (d *device) Close() { + _ = unix.Close(d.fd) } -func getInterfaceIndex(fd int, name string) (int32, error) { - var indexRequest struct { - name [unix.IFNAMSIZ]byte - index int32 - } - copy(indexRequest.name[:], name) - if err := ioctl(fd, unix.SIOCGIFINDEX, unsafe.Pointer(&indexRequest)); err != nil { - return 0, fmt.Errorf("get interface index on %s failed: %w", name, err) - } - return indexRequest.index, nil +func (d *device) WaitForDevice() { } diff --git a/pkg/vif/device_notlinux.go b/pkg/vif/device_notlinux.go new file mode 100644 index 0000000000..3b5d96e6e6 --- /dev/null +++ b/pkg/vif/device_notlinux.go @@ -0,0 +1,102 @@ +//go:build !linux +// +build !linux + +package vif + +import ( + "context" + + "gvisor.dev/gvisor/pkg/buffer" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/stack" + + "github.com/datawire/dlib/dlog" +) + +// Queue length for outbound packets, arriving at fd side for read. Overflow +// causes packet drops. gVisor implementation-specific. +const defaultDevOutQueueLen = 1024 + +// This can be fairly large. We're only allocating one such buffer, and the +// reads are non-blocking. +const ioBufferSize = 1 << 22 + +func (d *device) createLinkEndpoint() (stack.LinkEndpoint, error) { + return d, nil +} + +func (d *device) WaitForDevice() { + d.wg.Wait() +} + +func (d *device) Attach(dp stack.NetworkDispatcher) { + go func() { + d.Endpoint.Attach(dp) + if dp == nil { + // Stack is closing + return + } + dlog.Info(d.ctx, "Starting Endpoint") + ctx, cancel := context.WithCancel(d.ctx) + d.wg.Add(2) + go d.tunToDispatch(cancel) + d.dispatchToTun(ctx) + }() +} + +func (d *device) tunToDispatch(cancel context.CancelFunc) { + defer func() { + cancel() + d.wg.Done() + }() + buf := make([]byte, ioBufferSize) + skip := d.headerSkip() + for { + dlog.Trace(d.ctx, "readPacket") + n, err := d.readPacket(buf) + if err != nil { + if d.IsAttached() && d.ctx.Err() == nil { + dlog.Errorf(d.ctx, "read packet error: %v", err) + } + return + } + if n-skip <= 0 { + continue + } + data := buf[skip:n] + + var ipv tcpip.NetworkProtocolNumber + switch header.IPVersion(data) { + case header.IPv4Version: + ipv = header.IPv4ProtocolNumber + case header.IPv6Version: + ipv = header.IPv6ProtocolNumber + default: + continue + } + + pb := stack.NewPacketBuffer(stack.PacketBufferOptions{ + Payload: buffer.MakeWithData(data), + }) + + dlog.Tracef(d.ctx, "injectInbound %d", n-skip) + d.InjectInbound(ipv, pb) + pb.DecRef() + } +} + +func (d *device) dispatchToTun(ctx context.Context) { + defer d.wg.Done() + for { + dlog.Trace(d.ctx, "ReadContext") + pb := d.ReadContext(ctx) + if pb == nil { + break + } + if err := d.writePacket(pb); err != nil { + dlog.Errorf(ctx, "WritePacket failed: %v", err) + } + pb.DecRef() + } +} diff --git a/pkg/vif/device_unix.go b/pkg/vif/device_unix.go index 4e5260a1a3..e26760ba5f 100644 --- a/pkg/vif/device_unix.go +++ b/pkg/vif/device_unix.go @@ -11,7 +11,7 @@ import ( "golang.org/x/sys/unix" ) -func (t *nativeDevice) setDNS(context.Context, string, netip.Addr, []string) (err error) { +func (d *device) setDNS(context.Context, string, netip.Addr, []string) (err error) { // DNS is configured by other means than through the actual device return nil } diff --git a/pkg/vif/device_windows.go b/pkg/vif/device_windows.go index 6466940a63..9244000f6b 100644 --- a/pkg/vif/device_windows.go +++ b/pkg/vif/device_windows.go @@ -2,33 +2,38 @@ package vif import ( "context" - "errors" "fmt" "io" "net" "net/netip" "slices" "strings" + "sync" "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" + "gvisor.dev/gvisor/pkg/tcpip/link/channel" + "gvisor.dev/gvisor/pkg/tcpip/stack" "github.com/datawire/dlib/derror" "github.com/datawire/dlib/dlog" - "github.com/telepresenceio/telepresence/v2/pkg/vif/buffer" ) -// This nativeDevice will require that wintun.dll is available to the loader. +// This device will require that wintun.dll is available to the loader. // See: https://www.wintun.net/ for more info. -type nativeDevice struct { - tun.Device +type device struct { + *channel.Endpoint + dev tun.Device + ctx context.Context name string dns netip.Addr - interfaceIndex int32 + interfaceIndex uint32 + luid winipcfg.LUID + wg sync.WaitGroup } -func openTun(ctx context.Context) (td *nativeDevice, err error) { +func openTun(ctx context.Context) (td *device, err error) { defer func() { if r := recover(); r != nil { err = derror.PanicToError(r) @@ -52,24 +57,41 @@ func openTun(ctx context.Context) (td *nativeDevice, err error) { } } interfaceName := fmt.Sprintf(interfaceFmt, ifaceNumber) + dlog.Infof(ctx, "Creating interface %s", interfaceName) - td = &nativeDevice{} - if td.Device, err = tun.CreateTUN(interfaceName, 0); err != nil { + dev, err := tun.CreateTUN(interfaceName, 0) + if err != nil { return nil, fmt.Errorf("failed to create TUN device: %w", err) } - if td.name, err = td.Device.Name(); err != nil { + name, err := dev.Name() + if err != nil { return nil, fmt.Errorf("failed to get real name of TUN device: %w", err) } - iface, err := td.getLUID().Interface() + luid := winipcfg.LUID(dev.(*tun.NativeTun).LUID()) + iface, err := luid.Interface() if err != nil { return nil, fmt.Errorf("failed to get interface for TUN device: %w", err) } - td.interfaceIndex = int32(iface.InterfaceIndex) - return td, nil + mtu, err := dev.MTU() + if err != nil { + return nil, fmt.Errorf("failed to get MTU for TUN device: %w", err) + } + return &device{ + Endpoint: channel.New(defaultDevOutQueueLen, uint32(mtu), ""), + dev: dev, + ctx: ctx, + name: name, + interfaceIndex: iface.InterfaceIndex, + luid: luid, + }, nil } -func (t *nativeDevice) Close() error { +// Close closes both the Device and the Endpoint. This function overrides the LinkEndpoint.Close so +// it cannot return an error. +func (d *device) Close() { + d.Endpoint.Close() + // The tun.NativeTun device has a closing mutex which is read locked during // a call to Read(). The read lock prevents a call to Close() to proceed // until Read() actually receives something. To resolve that "deadlock", @@ -79,7 +101,7 @@ func (t *nativeDevice) Close() error { go func() { // first message is just to indicate that this goroutine has started closeCh <- nil - closeCh <- t.Device.Close() + closeCh <- d.dev.Close() close(closeCh) }() @@ -91,42 +113,42 @@ func (t *nativeDevice) Close() error { // Send something to the TUN device so that the Read // unlocks the NativeTun.closing mutex and let the actual // Close call continue - conn, err := net.Dial("udp", net.JoinHostPort(t.dns.String(), "53")) + conn, err := net.Dial("udp", net.JoinHostPort(d.dns.String(), "53")) if err == nil { _, _ = conn.Write([]byte("bogus")) } - return <-closeCh + <-closeCh } -func (t *nativeDevice) getLUID() winipcfg.LUID { - return winipcfg.LUID(t.Device.(*tun.NativeTun).LUID()) +func (d *device) getLUID() winipcfg.LUID { + return d.luid } -func (t *nativeDevice) index() int32 { - return t.interfaceIndex +func (d *device) index() uint32 { + return d.interfaceIndex } -func (t *nativeDevice) addSubnet(_ context.Context, subnet netip.Prefix) error { - return t.getLUID().AddIPAddress(subnet) +func (d *device) addSubnet(_ context.Context, subnet netip.Prefix) error { + return d.getLUID().AddIPAddress(subnet) } -func (t *nativeDevice) removeSubnet(_ context.Context, subnet netip.Prefix) error { - return t.getLUID().DeleteIPAddress(subnet) +func (d *device) removeSubnet(_ context.Context, subnet netip.Prefix) error { + return d.getLUID().DeleteIPAddress(subnet) } -func (t *nativeDevice) setDNS(ctx context.Context, clusterDomain string, server netip.Addr, searchList []string) (err error) { +func (d *device) setDNS(ctx context.Context, clusterDomain string, server netip.Addr, searchList []string) (err error) { // This function must not be interrupted by a context cancellation, so we give it a timeout instead. dlog.Debugf(ctx, "SetDNS server: %s, searchList: %v, domain: %q", server, searchList, clusterDomain) defer dlog.Debug(ctx, "SetDNS done") - luid := t.getLUID() + luid := d.getLUID() family := addressFamily(server) - if t.dns.IsValid() { - if oldFamily := addressFamily(t.dns); oldFamily != family { + if d.dns.IsValid() { + if oldFamily := addressFamily(d.dns); oldFamily != family { _ = luid.FlushDNS(oldFamily) } } - t.dns = server + d.dns = server clusterDomain = strings.TrimSuffix(clusterDomain, ".") cdi := slices.Index(searchList, clusterDomain) switch cdi { @@ -139,7 +161,7 @@ func (t *nativeDevice) setDNS(ctx context.Context, clusterDomain string, server // put clusterDomain first in list, but retain the order of remaining elements searchList = slices.Insert(slices.Delete(searchList, cdi, cdi+1), 0, clusterDomain) } - return luid.SetDNS(family, []netip.Addr{t.dns}, searchList) + return luid.SetDNS(family, []netip.Addr{d.dns}, searchList) } func addressFamily(ip netip.Addr) winipcfg.AddressFamily { @@ -150,13 +172,13 @@ func addressFamily(ip netip.Addr) winipcfg.AddressFamily { return f } -func (t *nativeDevice) setMTU(int) error { - return errors.New("not implemented") +func (d *device) headerSkip() int { + return 0 } -func (t *nativeDevice) readPacket(into *buffer.Data) (int, error) { +func (d *device) readPacket(buf []byte) (int, error) { sz := make([]int, 1) - packetsN, err := t.Device.Read([][]byte{into.Raw()}, sz, 0) + packetsN, err := d.dev.Read([][]byte{buf}, sz, 0) if err != nil { return 0, err } @@ -166,13 +188,13 @@ func (t *nativeDevice) readPacket(into *buffer.Data) (int, error) { return sz[0], nil } -func (t *nativeDevice) writePacket(from *buffer.Data, offset int) (int, error) { - packetsN, err := t.Device.Write([][]byte{from.Raw()}, offset) +func (d *device) writePacket(from *stack.PacketBuffer) error { + packetsN, err := d.dev.Write(from.AsSlices(), 0) if err != nil { - return 0, err + return err } if packetsN == 0 { - return 0, io.EOF + return io.EOF } - return len(from.Raw()), nil + return nil } diff --git a/pkg/vif/stack.go b/pkg/vif/stack.go index a864500af8..d59872c96c 100644 --- a/pkg/vif/stack.go +++ b/pkg/vif/stack.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "runtime" "time" "gvisor.dev/gvisor/pkg/tcpip" @@ -22,7 +23,7 @@ import ( "github.com/telepresenceio/telepresence/v2/pkg/tunnel" ) -func NewStack(ctx context.Context, dev stack.LinkEndpoint, streamCreator tunnel.StreamCreator) (*stack.Stack, error) { +func NewStack(ctx context.Context, ep stack.LinkEndpoint, streamCreator tunnel.StreamCreator) (*stack.Stack, error) { s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ ipv4.NewProtocol, @@ -39,7 +40,8 @@ func NewStack(ctx context.Context, dev stack.LinkEndpoint, streamCreator tunnel. if err := setDefaultOptions(s); err != nil { return nil, err } - if err := setNIC(ctx, s, dev); err != nil { + _, err := setNIC(s, ep) + if err != nil { return nil, err } setTCPHandler(ctx, s, streamCreator) @@ -49,11 +51,11 @@ func NewStack(ctx context.Context, dev stack.LinkEndpoint, streamCreator tunnel. const ( myWindowScale = 6 - maxReceiveWindow = 1 << (myWindowScale + 14) // 1MiB + maxReceiveWindow = 4 << (myWindowScale + 14) // 4MiB ) // maxInFlight specifies the max number of in-flight connection attempts. -const maxInFlight = 512 +const maxInFlight = 16 // keepAliveIdle is used as the very first alive interval. Subsequent intervals // use keepAliveInterval. @@ -75,7 +77,33 @@ func (i idStringer) String() string { } func setDefaultOptions(s *stack.Stack) error { - // Forwarding + sa := tcpip.TCPSACKEnabled(true) + if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sa); err != nil { + return fmt.Errorf("SetTransportProtocolOption(tcp, TCPSACKEnabled(%t): %s", sa, err) + } + + if runtime.GOOS == "windows" { + // Windows w/RACK performs poorly. ACKs do not appear to be handled in a + // timely manner, leading to spurious retransmissions and a reduced + // congestion window. + tr := tcpip.TCPRecovery(0) + if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &tr); err != nil { + return fmt.Errorf("SetTransportProtocolOption(tcp, TCPRecovery(%d): %s", tr, err) + } + } + + // Enable Receive Buffer Auto-Tuning, see: + // https://github.com/google/gvisor/issues/1666 + mo := tcpip.TCPModerateReceiveBufferOption(true) + if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &mo); err != nil { + return fmt.Errorf("SetTransportProtocolOption(tcp, TCPModerateReceiveBufferOption(%t): %s", mo, err) + } + + cco := tcpip.CongestionControlOption("cubic") + if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &cco); err != nil { + return fmt.Errorf("SetTransportProtocolOption(tcp, CongestionControlOption(%s): %s", cco, err) + } + if err := s.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, true); err != nil { return fmt.Errorf("SetForwardingDefaultAndAllNICs(ipv4, %t): %s", true, err) } @@ -92,28 +120,24 @@ func setDefaultOptions(s *stack.Stack) error { return nil } -func setNIC(ctx context.Context, s *stack.Stack, ep stack.LinkEndpoint) error { +func setNIC(s *stack.Stack, ep stack.LinkEndpoint) (tcpip.NICID, error) { nicID := s.NextNICID() - if err := s.CreateNICWithOptions(nicID, ep, stack.NICOptions{Name: "tel", Context: ctx}); err != nil { - return fmt.Errorf("create NIC failed: %s", err) + if tcpErr := s.CreateNIC(nicID, ep); tcpErr != nil { + return 0, fmt.Errorf("create NIC failed: %s", tcpErr) } if err := s.SetPromiscuousMode(nicID, true); err != nil { - return fmt.Errorf("SetPromiscuousMode(%d, %t): %s", nicID, true, err) + return 0, fmt.Errorf("SetPromiscuousMode(%d, %t): %s", nicID, true, err) } if err := s.SetSpoofing(nicID, true); err != nil { - return fmt.Errorf("SetSpoofing(%d, %t): %s", nicID, true, err) + return 0, fmt.Errorf("SetSpoofing(%d, %t): %s", nicID, true, err) } s.SetRouteTable([]tcpip.Route{ { Destination: header.IPv4EmptySubnet, NIC: nicID, }, - { - Destination: header.IPv6EmptySubnet, - NIC: nicID, - }, }) - return nil + return nicID, nil } func forwardTCP(ctx context.Context, streamCreator tunnel.StreamCreator, fr *tcp.ForwarderRequest) { @@ -127,59 +151,37 @@ func forwardTCP(ctx context.Context, streamCreator tunnel.StreamCreator, fr *tcp } }() - wq := waiter.Queue{} + var wq waiter.Queue if ep, err = fr.CreateEndpoint(&wq); err != nil { fr.Complete(true) + dlog.Error(ctx, err) return } - defer fr.Complete(false) + fr.Complete(false) so := ep.SocketOptions() so.SetKeepAlive(true) idle := tcpip.KeepaliveIdleOption(keepAliveIdle) if err = ep.SetSockOpt(&idle); err != nil { + dlog.Error(ctx, err) return } ivl := tcpip.KeepaliveIntervalOption(keepAliveInterval) if err = ep.SetSockOpt(&ivl); err != nil { + dlog.Error(ctx, err) return } if err = ep.SetSockOptInt(tcpip.KeepaliveCountOption, keepAliveCount); err != nil { + dlog.Error(ctx, err) return } dispatchToStream(ctx, newConnID(header.TCPProtocolNumber, id), gonet.NewTCPConn(&wq, ep), streamCreator) } func setTCPHandler(ctx context.Context, s *stack.Stack, streamCreator tunnel.StreamCreator) { - if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, - &tcpip.TCPSendBufferSizeRangeOption{ - Min: tcp.MinBufferSize, - Default: tcp.DefaultSendBufferSize, - Max: tcp.MaxBufferSize, - }); err != nil { - return - } - - if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, - &tcpip.TCPReceiveBufferSizeRangeOption{ - Min: tcp.MinBufferSize, - Default: tcp.DefaultSendBufferSize, - Max: tcp.MaxBufferSize, - }); err != nil { - return - } - - sa := tcpip.TCPSACKEnabled(true) - s.SetTransportProtocolOption(tcp.ProtocolNumber, &sa) - - // Enable Receive Buffer Auto-Tuning, see: - // https://github.com/google/gvisor/issues/1666 - mo := tcpip.TCPModerateReceiveBufferOption(true) - s.SetTransportProtocolOption(tcp.ProtocolNumber, &mo) - f := tcp.NewForwarder(s, maxReceiveWindow, maxInFlight, func(fr *tcp.ForwarderRequest) { forwardTCP(ctx, streamCreator, fr) }) diff --git a/pkg/vif/testdata/router/go.mod b/pkg/vif/testdata/router/go.mod index 0f66e3db63..9ad36c1966 100644 --- a/pkg/vif/testdata/router/go.mod +++ b/pkg/vif/testdata/router/go.mod @@ -72,7 +72,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gvisor.dev/gvisor v0.0.0-20241210225239-aa8ecac76a04 // indirect + gvisor.dev/gvisor v0.0.0-20250115195935-26653e7d8816 // indirect k8s.io/api v0.32.0 // indirect k8s.io/apimachinery v0.32.0 // indirect k8s.io/cli-runtime v0.32.0 // indirect diff --git a/pkg/vif/testdata/router/go.sum b/pkg/vif/testdata/router/go.sum index 7437cdf78a..960cc99879 100644 --- a/pkg/vif/testdata/router/go.sum +++ b/pkg/vif/testdata/router/go.sum @@ -219,8 +219,8 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gvisor.dev/gvisor v0.0.0-20241210225239-aa8ecac76a04 h1:OvxeUgwtB7WYf2yMVpJTpFMekpztPTYbSQXpvEJoCKk= -gvisor.dev/gvisor v0.0.0-20241210225239-aa8ecac76a04/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= +gvisor.dev/gvisor v0.0.0-20250115195935-26653e7d8816 h1:ZIPfC6hWCapUHskzT+Hqq648v+os3ZnruhEA0NNQsQc= +gvisor.dev/gvisor v0.0.0-20250115195935-26653e7d8816/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= diff --git a/pkg/vif/tunneling_device.go b/pkg/vif/tunneling_device.go index 153133bea6..31c1d359e4 100644 --- a/pkg/vif/tunneling_device.go +++ b/pkg/vif/tunneling_device.go @@ -28,13 +28,17 @@ func NewTunnelingDevice(ctx context.Context, tunnelStreamCreator tunnel.StreamCr if err != nil { return nil, err } - stack, err := NewStack(ctx, dev, tunnelStreamCreator) + ep, err := dev.NewLinkEndpoint() + if err != nil { + return nil, err + } + netStack, err := NewStack(ctx, ep, tunnelStreamCreator) if err != nil { return nil, err } router := NewRouter(dev, routingTable) return &TunnelingDevice{ - stack: stack, + stack: netStack, Device: dev, Router: router, table: routingTable, @@ -62,6 +66,5 @@ func (vif *TunnelingDevice) Run(ctx context.Context) (err error) { }() vif.stack.Wait() - vif.Device.Wait() return nil } From dd04651e2916bdea9c73b073334c2477b25b85a4 Mon Sep 17 00:00:00 2001 From: Thomas Hallgren Date: Mon, 20 Jan 2025 17:11:13 +0100 Subject: [PATCH 5/7] Prepare v2.21.2-rc.0 Signed-off-by: Thomas Hallgren --- go.mod | 2 +- pkg/vif/testdata/router/go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 4b497f5a99..90818a2bc5 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.10.0 github.com/telepresenceio/go-fuseftp/rpc v0.5.0 - github.com/telepresenceio/telepresence/rpc/v2 v2.21.1 + github.com/telepresenceio/telepresence/rpc/v2 v2.21.2-rc.0 github.com/vishvananda/netlink v1.3.0 golang.org/x/exp v0.0.0-20241210194714-1829a127f884 golang.org/x/net v0.32.0 diff --git a/pkg/vif/testdata/router/go.mod b/pkg/vif/testdata/router/go.mod index 9ad36c1966..66e5b992b9 100644 --- a/pkg/vif/testdata/router/go.mod +++ b/pkg/vif/testdata/router/go.mod @@ -50,7 +50,7 @@ require ( github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/telepresenceio/telepresence/rpc/v2 v2.21.1 // indirect + github.com/telepresenceio/telepresence/rpc/v2 v2.21.2-rc.0 // indirect github.com/vishvananda/netlink v1.3.0 // indirect github.com/vishvananda/netns v0.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect From 442d5b4cbc707534fa80be38815020ded4a63d3b Mon Sep 17 00:00:00 2001 From: Thomas Hallgren Date: Sun, 26 Jan 2025 23:49:17 +0100 Subject: [PATCH 6/7] Don't send multiple buffers to Windows TUN device write. It doesn't work. So Windows uses the same strategy as macOS and flatten the package buffers into grow-only buffer and then write just once. Signed-off-by: Thomas Hallgren --- pkg/vif/device_notlinux.go | 1 - pkg/vif/device_windows.go | 15 +++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/pkg/vif/device_notlinux.go b/pkg/vif/device_notlinux.go index 3b5d96e6e6..550dc24fce 100644 --- a/pkg/vif/device_notlinux.go +++ b/pkg/vif/device_notlinux.go @@ -80,7 +80,6 @@ func (d *device) tunToDispatch(cancel context.CancelFunc) { Payload: buffer.MakeWithData(data), }) - dlog.Tracef(d.ctx, "injectInbound %d", n-skip) d.InjectInbound(ipv, pb) pb.DecRef() } diff --git a/pkg/vif/device_windows.go b/pkg/vif/device_windows.go index 9244000f6b..bb71181a56 100644 --- a/pkg/vif/device_windows.go +++ b/pkg/vif/device_windows.go @@ -1,6 +1,7 @@ package vif import ( + "bytes" "context" "fmt" "io" @@ -30,6 +31,7 @@ type device struct { dns netip.Addr interfaceIndex uint32 luid winipcfg.LUID + wb bytes.Buffer wg sync.WaitGroup } @@ -77,6 +79,10 @@ func openTun(ctx context.Context) (td *device, err error) { if err != nil { return nil, fmt.Errorf("failed to get MTU for TUN device: %w", err) } + if mtu < 1500 { + mtu = 1500 + } + dlog.Debugf(ctx, "using MTU = %d", mtu) return &device{ Endpoint: channel.New(defaultDevOutQueueLen, uint32(mtu), ""), dev: dev, @@ -121,7 +127,7 @@ func (d *device) Close() { } func (d *device) getLUID() winipcfg.LUID { - return d.luid + return winipcfg.LUID(d.dev.(*tun.NativeTun).LUID()) } func (d *device) index() uint32 { @@ -189,7 +195,12 @@ func (d *device) readPacket(buf []byte) (int, error) { } func (d *device) writePacket(from *stack.PacketBuffer) error { - packetsN, err := d.dev.Write(from.AsSlices(), 0) + wb := &d.wb + wb.Reset() + for _, s := range from.AsSlices() { + wb.Write(s) + } + packetsN, err := d.dev.Write([][]byte{wb.Bytes()}, 0) if err != nil { return err } From 9bc0279d72ec1b081236a8151541100ae2838baf Mon Sep 17 00:00:00 2001 From: Thomas Hallgren Date: Sun, 26 Jan 2025 23:53:41 +0100 Subject: [PATCH 7/7] Prepare v2.21.2 Signed-off-by: Thomas Hallgren --- CHANGELOG.yml | 2 +- docs/release-notes.md | 2 +- docs/release-notes.mdx | 2 +- go.mod | 2 +- pkg/vif/testdata/router/go.mod | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.yml b/CHANGELOG.yml index 73c33ed61f..139b920b34 100644 --- a/CHANGELOG.yml +++ b/CHANGELOG.yml @@ -34,7 +34,7 @@ docDescription: >- customizable development environments. items: - version: 2.21.2 - date: (TBD) + date: 2025-01-26 notes: - type: bugfix title: Fix panic when agentpf.client creates a Tunnel diff --git a/docs/release-notes.md b/docs/release-notes.md index f3b418f2fd..a46747fc49 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,7 +1,7 @@ [comment]: # (Code generated by relnotesgen. DO NOT EDIT.) # Telepresence Release Notes -## Version 2.21.2 +## Version 2.21.2 (January 26) ##
bugfix
Fix panic when agentpf.client creates a Tunnel
diff --git a/docs/release-notes.mdx b/docs/release-notes.mdx index d58ae9a8c3..a1c29c82e8 100644 --- a/docs/release-notes.mdx +++ b/docs/release-notes.mdx @@ -7,7 +7,7 @@ import { Note, Title, Body } from '@site/src/components/ReleaseNotes' [comment]: # (Code generated by relnotesgen. DO NOT EDIT.) # Telepresence Release Notes -## Version 2.21.2 +## Version 2.21.2 (January 26) Fix panic when agentpf.client creates a Tunnel A race could occur where several requests where made to `agentpf.client.Tunnel` on a client that had errored when creating its port-forward to the agent. The implementation could handle one such requests but not several, resulting in a panic in situations where multiple simultaneous requests were made to the same client during a very short time period, diff --git a/go.mod b/go.mod index 90818a2bc5..ca138870d2 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.10.0 github.com/telepresenceio/go-fuseftp/rpc v0.5.0 - github.com/telepresenceio/telepresence/rpc/v2 v2.21.2-rc.0 + github.com/telepresenceio/telepresence/rpc/v2 v2.21.2 github.com/vishvananda/netlink v1.3.0 golang.org/x/exp v0.0.0-20241210194714-1829a127f884 golang.org/x/net v0.32.0 diff --git a/pkg/vif/testdata/router/go.mod b/pkg/vif/testdata/router/go.mod index 66e5b992b9..d691070a0f 100644 --- a/pkg/vif/testdata/router/go.mod +++ b/pkg/vif/testdata/router/go.mod @@ -50,7 +50,7 @@ require ( github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/telepresenceio/telepresence/rpc/v2 v2.21.2-rc.0 // indirect + github.com/telepresenceio/telepresence/rpc/v2 v2.21.2 // indirect github.com/vishvananda/netlink v1.3.0 // indirect github.com/vishvananda/netns v0.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect