Don't allow port in CIFS URL
**- What I did**

When creating a CIFS volume, generate an error if the device URL includes a port number, for example:
   `--opt device="//some.server.com:2345/thepath"`

The port must be specified in the port option instead, for example:
   ` --opt o=username=USERNAME,password=PASSWORD,vers=3,sec=ntlmsspi,port=1234`

As discussed in https://github.com/moby/moby/pull/46863#issuecomment-1833646398

**- How I did it**

Check the CIFS URL during the 'volume create', revert to using the whole Host part of the URL when mounting the volume.

**- How to verify it**

Updated unit test, and ...

```
# docker volume create --driver local --opt type=cifs --opt o=username=auser,password=verysecret,vers=3,sec=ntlmsspi,port=1234 --opt device="//some.server.com:2345/thepath" myvol
Error response from daemon: create myvol: port not allowed in CIFS device URL, include 'port' in 'o='

# docker volume create --driver local --opt type=cifs --opt o=username=auser,password=verysecret,vers=3,sec=ntlmsspi,port=1234 --opt device=":::" myvol
Error response from daemon: create myvol: error parsing mount device url: parse ":::": missing protocol scheme

# docker volume create --driver local --opt type=cifs --opt o=username=auser,password=verysecret,vers=3,sec=ntlmsspi,port=1234 --opt device="//some.server.com/thepath" myvol
myvol
```

**- Description for the changelog**

Don't allow port in CIFS URL
volume/local/local_linux_test.go
//go:build linux

package local // import "github.com/docker/docker/volume/local"

import (
	"os"
	"path/filepath"
	"strconv"
	"testing"

	"github.com/docker/docker/errdefs"
	"github.com/docker/docker/pkg/idtools"
	"github.com/docker/docker/quota"
	"gotest.tools/v3/assert"
	is "gotest.tools/v3/assert/cmp"
)

const (
	quotaSize        = 1024 * 1024
	quotaSizeLiteral = "1M"
)

func TestQuota(t *testing.T) {
	if msg, ok := quota.CanTestQuota(); !ok {
		t.Skip(msg)
	}

	// get sparse xfs test image
	imageFileName, err := quota.PrepareQuotaTestImage(t)
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(imageFileName)

	t.Run("testVolWithQuota", quota.WrapMountTest(imageFileName, true, testVolWithQuota))
	t.Run("testVolQuotaUnsupported", quota.WrapMountTest(imageFileName, false, testVolQuotaUnsupported))
}

func testVolWithQuota(t *testing.T, mountPoint, backingFsDev, testDir string) {
	r, err := New(testDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
	if err != nil {
		t.Fatal(err)
	}
	assert.Assert(t, r.quotaCtl != nil)

	vol, err := r.Create("testing", map[string]string{"size": quotaSizeLiteral})
	if err != nil {
		t.Fatal(err)
	}

	dir, err := vol.Mount("1234")
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		if err := vol.Unmount("1234"); err != nil {
			t.Fatal(err)
		}
	}()

	testfile := filepath.Join(dir, "testfile")

	// test writing file smaller than quota
	assert.NilError(t, os.WriteFile(testfile, make([]byte, quotaSize/2), 0o644))
	assert.NilError(t, os.Remove(testfile))

	// test writing fiel larger than quota
	err = os.WriteFile(testfile, make([]byte, quotaSize+1), 0o644)
	assert.ErrorContains(t, err, "")
	if _, err := os.Stat(testfile); err == nil {
		assert.NilError(t, os.Remove(testfile))
	}
}

func testVolQuotaUnsupported(t *testing.T, mountPoint, backingFsDev, testDir string) {
	r, err := New(testDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
	if err != nil {
		t.Fatal(err)
	}
	assert.Assert(t, is.Nil(r.quotaCtl))

	_, err = r.Create("testing", map[string]string{"size": quotaSizeLiteral})
	assert.ErrorContains(t, err, "no quota support")

	vol, err := r.Create("testing", nil)
	if err != nil {
		t.Fatal(err)
	}

	// this could happen if someone moves volumes from storage with
	// quota support to some place without
	lv, ok := vol.(*localVolume)
	assert.Assert(t, ok)
	lv.opts = &optsConfig{
		Quota: quota.Quota{Size: quotaSize},
	}

	_, err = vol.Mount("1234")
	assert.ErrorContains(t, err, "no quota support")
}

func TestVolCreateValidation(t *testing.T) {
	r, err := New(t.TempDir(), idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
	if err != nil {
		t.Fatal(err)
	}

	mandatoryOpts = map[string][]string{
		"device": {"type"},
		"type":   {"device"},
		"o":      {"device", "type"},
	}

	tests := []struct {
		doc         string
		name        string
		opts        map[string]string
		expectedErr string
	}{
		{
			doc:  "invalid: name too short",
			name: "a",
			opts: map[string]string{
				"type":   "foo",
				"device": "foo",
			},
			expectedErr: `volume name is too short, names should be at least two alphanumeric characters`,
		},
		{
			doc:  "invalid: name invalid characters",
			name: "hello world",
			opts: map[string]string{
				"type":   "foo",
				"device": "foo",
			},
			expectedErr: `"hello world" includes invalid characters for a local volume name, only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed. If you intended to pass a host directory, use absolute path`,
		},
		{
			doc:         "invalid: unknown option",
			opts:        map[string]string{"hello": "world"},
			expectedErr: `invalid option: "hello"`,
		},
		{
			doc:         "invalid: invalid size",
			opts:        map[string]string{"size": "hello"},
			expectedErr: `invalid size: 'hello'`,
		},
		{
			doc:         "invalid: size, but no quotactl",
			opts:        map[string]string{"size": "1234"},
			expectedErr: `quota size requested but no quota support`,
		},
		{
			doc: "invalid: device without type",
			opts: map[string]string{
				"device": "foo",
			},
			expectedErr: `missing required option: "type"`,
		},
		{
			doc: "invalid: type without device",
			opts: map[string]string{
				"type": "foo",
			},
			expectedErr: `missing required option: "device"`,
		},
		{
			doc: "invalid: o without device",
			opts: map[string]string{
				"o":    "foo",
				"type": "foo",
			},
			expectedErr: `missing required option: "device"`,
		},
		{
			doc: "invalid: o without type",
			opts: map[string]string{
				"o":      "foo",
				"device": "foo",
			},
			expectedErr: `missing required option: "type"`,
		},
		{
			doc:  "valid: short name, no options",
			name: "ab",
		},
		{
			doc: "valid: device and type",
			opts: map[string]string{
				"type":   "foo",
				"device": "foo",
			},
		},
		{
			doc: "valid: device, type, and o",
			opts: map[string]string{
				"type":   "foo",
				"device": "foo",
				"o":      "foo",
			},
		},
	}

	for i, tc := range tests {
		tc := tc
		t.Run(tc.doc, func(t *testing.T) {
			if tc.name == "" {
				tc.name = "vol-" + strconv.Itoa(i)
			}
			v, err := r.Create(tc.name, tc.opts)
			if v != nil {
				defer assert.Check(t, r.Remove(v))
			}
			if tc.expectedErr == "" {
				assert.NilError(t, err)
			} else {
				assert.Check(t, errdefs.IsInvalidParameter(err), "got: %T", err)
				assert.ErrorContains(t, err, tc.expectedErr)
			}
		})
	}
}

//go:build linux

package local // import "github.com/docker/docker/volume/local"

import (
	"os"
	"path/filepath"
	"strconv"
	"testing"

	"github.com/docker/docker/errdefs"
	"github.com/docker/docker/pkg/idtools"
	"github.com/docker/docker/quota"
	"gotest.tools/v3/assert"
	is "gotest.tools/v3/assert/cmp"
)

const (
	quotaSize        = 1024 * 1024
	quotaSizeLiteral = "1M"
)

func TestQuota(t *testing.T) {
	if msg, ok := quota.CanTestQuota(); !ok {
		t.Skip(msg)
	}

	// get sparse xfs test image
	imageFileName, err := quota.PrepareQuotaTestImage(t)
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(imageFileName)

	t.Run("testVolWithQuota", quota.WrapMountTest(imageFileName, true, testVolWithQuota))
	t.Run("testVolQuotaUnsupported", quota.WrapMountTest(imageFileName, false, testVolQuotaUnsupported))
}

func testVolWithQuota(t *testing.T, mountPoint, backingFsDev, testDir string) {
	r, err := New(testDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
	if err != nil {
		t.Fatal(err)
	}
	assert.Assert(t, r.quotaCtl != nil)

	vol, err := r.Create("testing", map[string]string{"size": quotaSizeLiteral})
	if err != nil {
		t.Fatal(err)
	}

	dir, err := vol.Mount("1234")
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		if err := vol.Unmount("1234"); err != nil {
			t.Fatal(err)
		}
	}()

	testfile := filepath.Join(dir, "testfile")

	// test writing file smaller than quota
	assert.NilError(t, os.WriteFile(testfile, make([]byte, quotaSize/2), 0o644))
	assert.NilError(t, os.Remove(testfile))

	// test writing fiel larger than quota
	err = os.WriteFile(testfile, make([]byte, quotaSize+1), 0o644)
	assert.ErrorContains(t, err, "")
	if _, err := os.Stat(testfile); err == nil {
		assert.NilError(t, os.Remove(testfile))
	}
}

func testVolQuotaUnsupported(t *testing.T, mountPoint, backingFsDev, testDir string) {
	r, err := New(testDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
	if err != nil {
		t.Fatal(err)
	}
	assert.Assert(t, is.Nil(r.quotaCtl))

	_, err = r.Create("testing", map[string]string{"size": quotaSizeLiteral})
	assert.ErrorContains(t, err, "no quota support")

	vol, err := r.Create("testing", nil)
	if err != nil {
		t.Fatal(err)
	}

	// this could happen if someone moves volumes from storage with
	// quota support to some place without
	lv, ok := vol.(*localVolume)
	assert.Assert(t, ok)
	lv.opts = &optsConfig{
		Quota: quota.Quota{Size: quotaSize},
	}

	_, err = vol.Mount("1234")
	assert.ErrorContains(t, err, "no quota support")
}

func TestVolCreateValidation(t *testing.T) {
	r, err := New(t.TempDir(), idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
	if err != nil {
		t.Fatal(err)
	}

	mandatoryOpts = map[string][]string{
		"device": {"type"},
		"type":   {"device"},
		"o":      {"device", "type"},
	}

	tests := []struct {
		doc         string
		name        string
		opts        map[string]string
		expectedErr string
	}{
		{
			doc:  "invalid: name too short",
			name: "a",
			opts: map[string]string{
				"type":   "foo",
				"device": "foo",
			},
			expectedErr: `volume name is too short, names should be at least two alphanumeric characters`,
		},
		{
			doc:  "invalid: name invalid characters",
			name: "hello world",
			opts: map[string]string{
				"type":   "foo",
				"device": "foo",
			},
			expectedErr: `"hello world" includes invalid characters for a local volume name, only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed. If you intended to pass a host directory, use absolute path`,
		},
		{
			doc:         "invalid: unknown option",
			opts:        map[string]string{"hello": "world"},
			expectedErr: `invalid option: "hello"`,
		},
		{
			doc:         "invalid: invalid size",
			opts:        map[string]string{"size": "hello"},
			expectedErr: `invalid size: 'hello'`,
		},
		{
			doc:         "invalid: size, but no quotactl",
			opts:        map[string]string{"size": "1234"},
			expectedErr: `quota size requested but no quota support`,
		},
		{
			doc: "invalid: device without type",
			opts: map[string]string{
				"device": "foo",
			},
			expectedErr: `missing required option: "type"`,
		},
		{
			doc: "invalid: type without device",
			opts: map[string]string{
				"type": "foo",
			},
			expectedErr: `missing required option: "device"`,
		},
		{
			doc: "invalid: o without device",
			opts: map[string]string{
				"o":    "foo",
				"type": "foo",
			},
			expectedErr: `missing required option: "device"`,
		},
		{
			doc: "invalid: o without type",
			opts: map[string]string{
				"o":      "foo",
				"device": "foo",
			},
			expectedErr: `missing required option: "type"`,
		},
		{
			doc:  "valid: short name, no options",
			name: "ab",
		},
		{
			doc: "valid: device and type",
			opts: map[string]string{
				"type":   "foo",
				"device": "foo",
			},
		},
		{
			doc: "valid: device, type, and o",
			opts: map[string]string{
				"type":   "foo",
				"device": "foo",
				"o":      "foo",
			},
		},
		{
			doc: "cifs",
			opts: map[string]string{
				"type":   "cifs",
				"device": "//some.example.com/thepath",
				"o":      "foo",
			},
		},
		{
			doc: "cifs with port in url",
			opts: map[string]string{
				"type":   "cifs",
				"device": "//some.example.com:2345/thepath",
				"o":      "foo",
			},
			expectedErr: "port not allowed in CIFS device URL, include 'port' in 'o='",
		},
		{
			doc: "cifs with bad url",
			opts: map[string]string{
				"type":   "cifs",
				"device": ":::",
				"o":      "foo",
			},
			expectedErr: `error parsing mount device url: parse ":::": missing protocol scheme`,
		},
	}

	for i, tc := range tests {
		tc := tc
		t.Run(tc.doc, func(t *testing.T) {
			if tc.name == "" {
				tc.name = "vol-" + strconv.Itoa(i)
			}
			v, err := r.Create(tc.name, tc.opts)
			if v != nil {
				defer assert.Check(t, r.Remove(v))
			}
			if tc.expectedErr == "" {
				assert.NilError(t, err)
			} else {
				assert.Check(t, errdefs.IsInvalidParameter(err), "got: %T", err)
				assert.ErrorContains(t, err, tc.expectedErr)
			}
		})
	}
}

Ah, yes - done.