From 6ae7f4d20a5b0f8a958b53c0fff978aa1b198bfa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Burak=20Varl=C4=B1?= <burakvar@amazon.co.uk>
Date: Tue, 7 Jan 2025 18:22:59 +0000
Subject: [PATCH 1/2] Create a shorter symlink for Unix socket in
 `mountoptions` package
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Go returns `invalid argument` if you try to `net.{Dial, Listen}` on a
Unix socket if the path is longer than 108 characters.

This limitation is only to the argument passed to those functions, if
we receive a Unix socket path longer than 108 characters, we create a
symlink for that path in tempdir for that platform and use that path
in network operations to workaround this limitation.

Signed-off-by: Burak Varlı <burakvar@amazon.co.uk>
---
 pkg/podmounter/mountoptions/mount_options.go | 52 +++++++++++++++++---
 1 file changed, 44 insertions(+), 8 deletions(-)

diff --git a/pkg/podmounter/mountoptions/mount_options.go b/pkg/podmounter/mountoptions/mount_options.go
index aa555872..290c49d2 100644
--- a/pkg/podmounter/mountoptions/mount_options.go
+++ b/pkg/podmounter/mountoptions/mount_options.go
@@ -8,7 +8,10 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"io/fs"
 	"net"
+	"os"
+	"path/filepath"
 	"syscall"
 
 	"k8s.io/klog/v2"
@@ -25,7 +28,11 @@ type Options struct {
 
 // Send sends given mount `options` to given `sockPath` to be received by `Recv` function on the other end.
 func Send(ctx context.Context, sockPath string, options Options) error {
-	warnAboutLongUnixSocketPath(sockPath)
+	sockPath, cleanSockPath, err := ensureSocketPathLengthIsUnderLimit(sockPath)
+	if err != nil {
+		return err
+	}
+	defer cleanSockPath()
 
 	message, err := json.Marshal(&options)
 	if err != nil {
@@ -61,7 +68,11 @@ var (
 
 // Recv receives passed mount options via `Send` function through given `sockPath`.
 func Recv(ctx context.Context, sockPath string) (Options, error) {
-	warnAboutLongUnixSocketPath(sockPath)
+	sockPath, cleanSockPath, err := ensureSocketPathLengthIsUnderLimit(sockPath)
+	if err != nil {
+		return Options{}, err
+	}
+	defer cleanSockPath()
 
 	l, err := net.Listen("unix", sockPath)
 	if err != nil {
@@ -135,12 +146,37 @@ func parseUnixRights(buf []byte) ([]int, error) {
 	return fds, nil
 }
 
-// warnAboutLongUnixSocketPath emits a warning if `path` is longer than 108 characters.
-// There is a limit on Unix domain socket path on some platforms and
-// Go returns `bind: invalid argument` in this case which is hard to debug.
+// ensureSocketPathLengthIsUnderLimit ensures `sockPath` is not longer than 108 characters,
+// which Go returns `invalid argument` errors if you try to do `net.Listen()` or `net.Dial()`.
 // See https://github.com/golang/go/issues/6895 for more details.
-func warnAboutLongUnixSocketPath(path string) {
-	if len(path) > 108 {
-		klog.Warningf("Length of Unix domain socket is larger than 108 characters and it might not work in some platforms, see https://github.com/golang/go/issues/6895. Fullpath: %q", path)
+//
+// If `sockPath` is longer than 108 characters, this function creates a shorter symlink to it in the temp dir,
+// and returns that symlink to use as `sockPath`.
+// It also returns a clean up function to clean up this temporary symlink at the end.
+func ensureSocketPathLengthIsUnderLimit(sockPath string) (string, func(), error) {
+	if len(sockPath) < 108 {
+		return sockPath, func() {}, nil
+	}
+
+	klog.Infof("Unix socket path %q is longer than 108 characters which known to be cause problems in some platforms. Creating a shorter symlink to it to use.", sockPath)
+
+	tempDir, err := os.MkdirTemp(os.TempDir(), "mountoptions")
+	if err != nil {
+		return "", nil, fmt.Errorf("failed to create a temp dir for a shorter symlink to %s: %w", sockPath, err)
+	}
+	shortSockPath := filepath.Join(tempDir, filepath.Base(sockPath))
+
+	err = os.Symlink(sockPath, shortSockPath)
+	if err != nil {
+		return "", nil, fmt.Errorf("failed to create a symlink from %s to %s: %w", sockPath, shortSockPath, err)
 	}
+
+	klog.Infof("Created %q as a symlink to %q and will be used to do Unix socket recv/send operations", shortSockPath, sockPath)
+
+	return shortSockPath, func() {
+		err := os.Remove(shortSockPath)
+		if err != nil && !errors.Is(err, fs.ErrNotExist) {
+			klog.Infof("Failed to remove symlink %s: %v\n", shortSockPath, err)
+		}
+	}, nil
 }

From 8f044d48f3519765735849a5af5cf12856c4d7e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Burak=20Varl=C4=B1?= <burakvar@amazon.co.uk>
Date: Fri, 17 Jan 2025 13:50:29 +0000
Subject: [PATCH 2/2] Ensure to test roundtrip operation both short and long
 socket paths
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Burak Varlı <burakvar@amazon.co.uk>
---
 .../mountoptions/mount_options_test.go        | 31 +++++++++++++++++--
 1 file changed, 28 insertions(+), 3 deletions(-)

diff --git a/pkg/podmounter/mountoptions/mount_options_test.go b/pkg/podmounter/mountoptions/mount_options_test.go
index 8ffcf139..81670711 100644
--- a/pkg/podmounter/mountoptions/mount_options_test.go
+++ b/pkg/podmounter/mountoptions/mount_options_test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"os"
 	"path/filepath"
+	"strings"
 	"syscall"
 	"testing"
 	"time"
@@ -13,7 +14,33 @@ import (
 	"github.com/awslabs/aws-s3-csi-driver/pkg/util/testutil/assert"
 )
 
-func TestSendingAndReceivingMountOptions(t *testing.T) {
+func TestMountOptions(t *testing.T) {
+	// Go returns `invalid argument` errors if you try to do `net.Listen()` or `net.Dial()` with a Unix socket
+	// path thats longer than 108 characters by default. We're creating symlinks automatically and use those Unix
+	// socket paths in this case. Here we add test cases for both short and long Unix socket paths.
+	// See https://github.com/golang/go/issues/6895 for more details.
+
+	t.Run("Short Path", func(t *testing.T) {
+		mountSock := filepath.Join(t.TempDir(), "m")
+		if len(mountSock) >= 108 {
+			t.Fatalf("test Unix socket path %q must be shorter than 108 characters", mountSock)
+		}
+		testRoundtrip(t, mountSock)
+	})
+
+	t.Run("Long Path", func(t *testing.T) {
+		basepath := filepath.Join(t.TempDir(), "long"+strings.Repeat("g", 108))
+		assert.NoError(t, os.Mkdir(basepath, 0700))
+
+		mountSock := filepath.Join(basepath, "mount.sock")
+		if len(mountSock) <= 108 {
+			t.Fatalf("test Unix socket path %q must be longer than 108 characters", mountSock)
+		}
+		testRoundtrip(t, mountSock)
+	})
+}
+
+func testRoundtrip(t *testing.T, mountSock string) {
 	file, err := os.Open(os.DevNull)
 	assert.NoError(t, err)
 	defer file.Close()
@@ -22,8 +49,6 @@ func TestSendingAndReceivingMountOptions(t *testing.T) {
 	err = syscall.Fstat(int(file.Fd()), wantStat)
 	assert.NoError(t, err)
 
-	mountSock := filepath.Join(t.TempDir(), "m")
-
 	c := make(chan mountoptions.Options)
 	go func() {
 		mountOptions, err := mountoptions.Recv(defaultContext(t), mountSock)