Skip to content

Commit 546f4d1

Browse files
committed
dial: add the ability to connect via socket fd
This patch introduces `FdDialer`, which connects to Tarantool using an existing socket file descriptor. `FdDialer` is not authenticated when creating a connection. Closes #321
1 parent 1f4b104 commit 546f4d1

File tree

6 files changed

+260
-0
lines changed

6 files changed

+260
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
work_dir*
55
.rocks
66
bench*
7+
testdata/sidecar/main

dial.go

+56
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"io"
1010
"net"
11+
"os"
1112
"strings"
1213
"time"
1314

@@ -252,6 +253,61 @@ func (d OpenSslDialer) Dial(ctx context.Context, opts DialOpts) (Conn, error) {
252253
return conn, nil
253254
}
254255

256+
// FdDialer allows to use an existing socket fd for connection.
257+
type FdDialer struct {
258+
// Fd is a socket file descrpitor.
259+
Fd uintptr
260+
// RequiredProtocol contains minimal protocol version and
261+
// list of protocol features that should be supported by
262+
// Tarantool server. By default, there are no restrictions.
263+
RequiredProtocolInfo ProtocolInfo
264+
}
265+
266+
type fdAddr struct {
267+
Fd uintptr
268+
}
269+
270+
func (a fdAddr) Network() string {
271+
return "fd"
272+
}
273+
274+
func (a fdAddr) String() string {
275+
return fmt.Sprintf("fd://%d", a.Fd)
276+
}
277+
278+
type fdConn struct {
279+
net.Conn
280+
Addr fdAddr
281+
}
282+
283+
func (c *fdConn) RemoteAddr() net.Addr {
284+
return c.Addr
285+
}
286+
287+
// Dial makes FdDialer satisfy the Dialer interface.
288+
func (d FdDialer) Dial(ctx context.Context, opts DialOpts) (Conn, error) {
289+
file := os.NewFile(d.Fd, "")
290+
c, err := net.FileConn(file)
291+
if err != nil {
292+
return nil, fmt.Errorf("failed to dial: %w", err)
293+
}
294+
295+
conn := new(tntConn)
296+
conn.net = &fdConn{Conn: c, Addr: fdAddr{Fd: d.Fd}}
297+
298+
dc := &deadlineIO{to: opts.IoTimeout, c: conn.net}
299+
conn.reader = bufio.NewReaderSize(dc, bufSize)
300+
conn.writer = bufio.NewWriterSize(dc, bufSize)
301+
302+
_, err = rawDial(conn, d.RequiredProtocolInfo)
303+
if err != nil {
304+
conn.net.Close()
305+
return nil, err
306+
}
307+
308+
return conn, nil
309+
}
310+
255311
// Addr makes tntConn satisfy the Conn interface.
256312
func (c *tntConn) Addr() net.Addr {
257313
return c.net.RemoteAddr()

dial_test.go

+50
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,7 @@ type testDialOpts struct {
442442
isIdUnsupported bool
443443
isPapSha256Auth bool
444444
isErrAuth bool
445+
isEmptyAuth bool
445446
}
446447

447448
type dialServerActual struct {
@@ -483,6 +484,8 @@ func testDialAccept(t *testing.T, opts testDialOpts, l net.Listener) chan dialSe
483484
authRequestExpected := authRequestExpectedChapSha1
484485
if opts.isPapSha256Auth {
485486
authRequestExpected = authRequestExpectedPapSha256
487+
} else if opts.isEmptyAuth {
488+
authRequestExpected = []byte{}
486489
}
487490
authRequestActual := make([]byte, len(authRequestExpected))
488491
client.Read(authRequestActual)
@@ -525,6 +528,8 @@ func testDialer(t *testing.T, l net.Listener, dialer tarantool.Dialer,
525528
authRequestExpected := authRequestExpectedChapSha1
526529
if opts.isPapSha256Auth {
527530
authRequestExpected = authRequestExpectedPapSha256
531+
} else if opts.isEmptyAuth {
532+
authRequestExpected = []byte{}
528533
}
529534
require.Equal(t, authRequestExpected, actual.AuthRequest)
530535
conn.Close()
@@ -769,3 +774,48 @@ func TestOpenSslDialer_Dial_ctx_cancel(t *testing.T) {
769774
_, err := dialer.Dial(ctx, tarantool.DialOpts{})
770775
require.Error(t, err)
771776
}
777+
778+
func TestFdDialer_Dial(t *testing.T) {
779+
l, err := net.Listen("tcp", "127.0.0.1:0")
780+
require.NoError(t, err)
781+
addr := l.Addr().String()
782+
783+
cases := []testDialOpts{
784+
{
785+
name: "all is ok",
786+
expectedProtocolInfo: idResponseTyped.Clone(),
787+
isEmptyAuth: true,
788+
},
789+
{
790+
name: "id request unsupported",
791+
expectedProtocolInfo: tarantool.ProtocolInfo{},
792+
isIdUnsupported: true,
793+
isEmptyAuth: true,
794+
},
795+
{
796+
name: "greeting response error",
797+
wantErr: true,
798+
expectedErr: "failed to read greeting",
799+
isErrGreeting: true,
800+
},
801+
{
802+
name: "id response error",
803+
wantErr: true,
804+
expectedErr: "failed to identify",
805+
isErrId: true,
806+
},
807+
}
808+
809+
for _, tc := range cases {
810+
t.Run(tc.name, func(t *testing.T) {
811+
sock, err := net.Dial("tcp", addr)
812+
require.NoError(t, err)
813+
f, err := sock.(*net.TCPConn).File()
814+
require.NoError(t, err)
815+
dialer := tarantool.FdDialer{
816+
Fd: f.Fd(),
817+
}
818+
testDialer(t, l, dialer, tc)
819+
})
820+
}
821+
}

example_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package tarantool_test
33
import (
44
"context"
55
"fmt"
6+
"net"
67
"time"
78

89
"github.com/tarantool/go-iproto"
@@ -1330,3 +1331,34 @@ func ExampleWatchOnceRequest() {
13301331
fmt.Println(resp.Data)
13311332
}
13321333
}
1334+
1335+
// This example demonstrates how to use an existing socket file descriptor
1336+
// to establish a connection with Tarantool. This can be useful if the socket fd
1337+
// was inherited from the Tarantool process itself.
1338+
// For details, please see TestFdDialer in tarantool_test.go.
1339+
func ExampleFdDialer() {
1340+
addr := dialer.Address
1341+
c, err := net.Dial("tcp", addr)
1342+
if err != nil {
1343+
fmt.Printf("can't establish connection: %v\n", err)
1344+
return
1345+
}
1346+
f, err := c.(*net.TCPConn).File()
1347+
if err != nil {
1348+
fmt.Printf("unexpected error: %v\n", err)
1349+
return
1350+
}
1351+
dialer := tarantool.FdDialer{
1352+
Fd: f.Fd(),
1353+
}
1354+
// Use an existing socket fd to create connection with Tarantool.
1355+
conn, err := tarantool.Connect(context.Background(), dialer, opts)
1356+
if err != nil {
1357+
fmt.Printf("connect error: %v\n", err)
1358+
return
1359+
}
1360+
resp, err := conn.Do(tarantool.NewPingRequest()).Get()
1361+
fmt.Println(resp.Code, err)
1362+
// Output:
1363+
// 0 <nil>
1364+
}

tarantool_test.go

+84
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"log"
99
"math"
1010
"os"
11+
"os/exec"
12+
"path/filepath"
1113
"reflect"
1214
"runtime"
1315
"strings"
@@ -77,6 +79,7 @@ func (m *Member) DecodeMsgpack(d *msgpack.Decoder) error {
7779
}
7880

7981
var server = "127.0.0.1:3013"
82+
var fdDialerTestServer = "127.0.0.1:3014"
8083
var spaceNo = uint32(617)
8184
var spaceName = "test"
8285
var indexNo = uint32(0)
@@ -3927,6 +3930,87 @@ func TestConnect_context_cancel(t *testing.T) {
39273930
}
39283931
}
39293932

3933+
func buildSidecar(dir string) error {
3934+
goPath, err := exec.LookPath("go")
3935+
if err != nil {
3936+
return err
3937+
}
3938+
cmd := exec.Command(goPath, "build", "main.go")
3939+
cmd.Dir = filepath.Join(dir, "testdata", "sidecar")
3940+
return cmd.Run()
3941+
}
3942+
3943+
func TestFdDialer(t *testing.T) {
3944+
isLess, err := test_helpers.IsTarantoolVersionLess(3, 0, 0)
3945+
if err != nil || isLess {
3946+
t.Skip("box.session.new present in Tarantool since version 3.0")
3947+
}
3948+
3949+
wd, err := os.Getwd()
3950+
require.NoError(t, err)
3951+
3952+
err = buildSidecar(wd)
3953+
require.NoErrorf(t, err, "failed to build sidecar: %v", err)
3954+
3955+
instOpts := startOpts
3956+
instOpts.Listen = fdDialerTestServer
3957+
instOpts.Dialer = NetDialer{
3958+
Address: fdDialerTestServer,
3959+
User: "test",
3960+
Password: "test",
3961+
}
3962+
3963+
inst, err := test_helpers.StartTarantool(instOpts)
3964+
require.NoError(t, err)
3965+
defer test_helpers.StopTarantoolWithCleanup(inst)
3966+
3967+
conn := test_helpers.ConnectWithValidation(t, dialer, opts)
3968+
defer conn.Close()
3969+
3970+
sidecarExe := filepath.Join(wd, "testdata", "sidecar", "main")
3971+
3972+
evalBody := fmt.Sprintf(`
3973+
local socket = require('socket')
3974+
local popen = require('popen')
3975+
local os = require('os')
3976+
local s1, s2 = socket.socketpair('AF_UNIX', 'SOCK_STREAM', 0)
3977+
3978+
--[[ Tell sidecar which fd use to connect. --]]
3979+
os.setenv('SOCKET_FD', tostring(s2:fd()))
3980+
3981+
box.session.new({
3982+
type = 'binary',
3983+
fd = s1:fd(),
3984+
user = 'test',
3985+
})
3986+
s1:detach()
3987+
3988+
local ph, err = popen.new({'%s'}, {
3989+
stdout = popen.opts.PIPE,
3990+
stderr = popen.opts.PIPE,
3991+
inherit_fds = {s2:fd()},
3992+
})
3993+
3994+
if err ~= nil then
3995+
return 1, err
3996+
end
3997+
3998+
ph:wait()
3999+
4000+
local status_code = ph:info().status.exit_code
4001+
local stderr = ph:read({stderr=true}):rstrip()
4002+
local stdout = ph:read({stdout=true}):rstrip()
4003+
return status_code, stderr, stdout
4004+
`, sidecarExe)
4005+
4006+
var resp []interface{}
4007+
err = conn.EvalTyped(evalBody, []interface{}{}, &resp)
4008+
require.NoError(t, err)
4009+
require.Equal(t, "", resp[1], resp[1])
4010+
require.Equal(t, "", resp[2], resp[2])
4011+
require.Equal(t, int8(0), resp[0])
4012+
}
4013+
39304014
// runTestMain is a body of TestMain function
39314015
// (see https://pkg.go.dev/testing#hdr-Main).
39324016
// Using defer + os.Exit is not works so TestMain body

testdata/sidecar/main.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"os"
6+
"strconv"
7+
8+
"github.com/tarantool/go-tarantool/v2"
9+
)
10+
11+
func main() {
12+
fd, err := strconv.Atoi(os.Getenv("SOCKET_FD"))
13+
if err != nil {
14+
panic(err)
15+
}
16+
dialer := tarantool.FdDialer{
17+
Fd: uintptr(fd),
18+
}
19+
conn, err := tarantool.Connect(context.Background(), dialer, tarantool.Opts{})
20+
if err != nil {
21+
panic(err)
22+
}
23+
if _, err := conn.Do(tarantool.NewPingRequest()).Get(); err != nil {
24+
panic(err)
25+
}
26+
// Insert new tuple.
27+
if _, err := conn.Do(tarantool.NewInsertRequest("test").
28+
Tuple([]interface{}{239})).Get(); err != nil {
29+
panic(err)
30+
}
31+
// Delete inserted tuple.
32+
if _, err := conn.Do(tarantool.NewDeleteRequest("test").
33+
Index("primary").
34+
Key([]interface{}{239})).Get(); err != nil {
35+
panic(err)
36+
}
37+
}

0 commit comments

Comments
 (0)