Skip to content

Commit 849df9e

Browse files
EgorMatirovsbinet
authored andcommitted
xrootd: add protocol request
Protocol request is used for obtaining the protocol version number, type of server and possible security requirements. These security requirements determine which requests should be signed. This info is provided to Client via protocol.SignRequirements. The Client will use sr.Needed to determine if the request should be signed via Sigver request. Actual sign requirements depending on the security level will be added as part of the request implementation in the following form: if level >= protocol.Pedantic { sr.requirements[dirlist.RequestID] = protocol.SignNeeded // ... } Updates #170.
1 parent 39a3dac commit 849df9e

File tree

9 files changed

+562
-16
lines changed

9 files changed

+562
-16
lines changed

xrootd/client/client.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ import (
3535
// Concurrent requests are supported.
3636
// Zero value is invalid, Client should be instantiated using NewClient.
3737
type Client struct {
38-
cancel context.CancelFunc
39-
conn net.Conn
40-
mux *mux.Mux
41-
protocolVersion int32
38+
cancel context.CancelFunc
39+
conn net.Conn
40+
mux *mux.Mux
41+
protocolVersion int32
42+
signRequirements protocol.SignRequirements
4243
}
4344

4445
// NewClient creates a new xrootd client that connects to the given address.
@@ -54,7 +55,7 @@ func NewClient(ctx context.Context, address string) (*Client, error) {
5455
return nil, err
5556
}
5657

57-
client := &Client{cancel, conn, mux.New(), 0}
58+
client := &Client{cancel: cancel, conn: conn, mux: mux.New()}
5859

5960
go client.consume(ctx)
6061

@@ -63,6 +64,14 @@ func NewClient(ctx context.Context, address string) (*Client, error) {
6364
return nil, err
6465
}
6566

67+
protocolInfo, err := client.Protocol(ctx)
68+
if err != nil {
69+
client.Close()
70+
return nil, err
71+
}
72+
73+
client.signRequirements = protocol.NewSignRequirements(protocolInfo.SecurityLevel, protocolInfo.SecurityOverrides)
74+
6675
return client, nil
6776
}
6877

xrootd/client/main_mock_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright 2018 The go-hep Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package client // import "go-hep.org/x/hep/xrootd/client"
6+
7+
import (
8+
"context"
9+
"encoding/binary"
10+
"io"
11+
"net"
12+
13+
"github.com/pkg/errors"
14+
"go-hep.org/x/hep/xrootd/internal/mux"
15+
"go-hep.org/x/hep/xrootd/protocol"
16+
)
17+
18+
func testClientWithMockServer(serverFunc func(cancel func(), conn net.Conn), clientFunc func(cancel func(), client *Client)) {
19+
ctx, cancel := context.WithCancel(context.Background())
20+
defer cancel()
21+
22+
server, conn := net.Pipe()
23+
defer server.Close()
24+
defer conn.Close()
25+
26+
client := &Client{cancel: cancel, conn: conn, mux: mux.New(), signRequirements: protocol.DefaultSignRequirements()}
27+
defer client.Close()
28+
29+
go serverFunc(func() { client.Close() }, server)
30+
go client.consume(ctx)
31+
32+
clientFunc(cancel, client)
33+
}
34+
35+
func readRequest(conn net.Conn) ([]byte, error) {
36+
// 16 is for the request options and 4 is for the data length
37+
const requestSize = protocol.RequestHeaderLength + 16 + 4
38+
var request = make([]byte, requestSize)
39+
if _, err := io.ReadFull(conn, request); err != nil {
40+
return nil, err
41+
}
42+
43+
dataLength := binary.BigEndian.Uint32(request[protocol.RequestHeaderLength+16:])
44+
if dataLength == 0 {
45+
return request, nil
46+
}
47+
48+
var data = make([]byte, dataLength)
49+
if _, err := io.ReadFull(conn, data); err != nil {
50+
return nil, err
51+
}
52+
53+
return append(request, data...), nil
54+
}
55+
56+
func writeResponse(conn net.Conn, data []byte) error {
57+
n, err := conn.Write(data)
58+
if err != nil {
59+
return err
60+
}
61+
if n != len(data) {
62+
return errors.Errorf("could not write all %d bytes: wrote %d", len(data), n)
63+
}
64+
return nil
65+
}
66+
67+
// TODO: move marshalResponse outside of main_mock_test.go and use it for server implementation.
68+
func marshalResponse(responseParts ...interface{}) ([]byte, error) {
69+
var data []byte
70+
for _, p := range responseParts {
71+
pData, err := protocol.Marshal(p)
72+
if err != nil {
73+
return nil, err
74+
}
75+
data = append(data, pData...)
76+
}
77+
return data, nil
78+
}
79+
80+
// TODO: move unmarshalRequest outside of main_mock_test.go and use it for server implementation.
81+
func unmarshalRequest(data []byte, request interface{}) (protocol.RequestHeader, error) {
82+
var header protocol.RequestHeader
83+
if err := protocol.Unmarshal(data[:protocol.RequestHeaderLength], &header); err != nil {
84+
return protocol.RequestHeader{}, err
85+
}
86+
if err := protocol.Unmarshal(data[protocol.RequestHeaderLength:], request); err != nil {
87+
return protocol.RequestHeader{}, err
88+
}
89+
90+
return header, nil
91+
}

xrootd/client/protocol.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2018 The go-hep Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package client // import "go-hep.org/x/hep/xrootd/client"
6+
7+
import (
8+
"context"
9+
10+
xrdproto "go-hep.org/x/hep/xrootd/protocol"
11+
"go-hep.org/x/hep/xrootd/protocol/protocol"
12+
)
13+
14+
// ProtocolInfo is a response for the `Protocol` request. See details in the xrootd protocol specification.
15+
type ProtocolInfo struct {
16+
BinaryProtocolVersion int32
17+
ServerType xrdproto.ServerType
18+
IsManager bool
19+
IsServer bool
20+
IsMeta bool
21+
IsProxy bool
22+
IsSupervisor bool
23+
SecurityVersion byte
24+
ForceSecurity bool
25+
SecurityLevel protocol.SecurityLevel
26+
SecurityOverrides []protocol.SecurityOverride
27+
}
28+
29+
// Protocol obtains the protocol version number, type of the server and security information, such as:
30+
// the security version, the security options, the security level, and the list of alterations
31+
// needed to the specified predefined security level.
32+
func (client *Client) Protocol(ctx context.Context) (ProtocolInfo, error) {
33+
resp, err := client.call(ctx, protocol.RequestID, protocol.NewRequest(client.protocolVersion, true))
34+
if err != nil {
35+
return ProtocolInfo{}, err
36+
}
37+
38+
var generalResp protocol.GeneralResponse
39+
40+
if err = xrdproto.Unmarshal(resp, &generalResp); err != nil {
41+
return ProtocolInfo{}, err
42+
}
43+
44+
var securityInfo *protocol.SecurityInfo
45+
if len(resp) > protocol.GeneralResponseLength {
46+
securityInfo = &protocol.SecurityInfo{}
47+
err = xrdproto.Unmarshal(resp[protocol.GeneralResponseLength:], securityInfo)
48+
if err != nil {
49+
return ProtocolInfo{}, err
50+
}
51+
}
52+
53+
var info = ProtocolInfo{
54+
BinaryProtocolVersion: generalResp.BinaryProtocolVersion,
55+
ServerType: extractServerType(generalResp.Flags),
56+
57+
// TODO: implement bit-encoded fields support in Unmarshal.
58+
IsManager: generalResp.Flags&protocol.IsManager != 0,
59+
IsServer: generalResp.Flags&protocol.IsServer != 0,
60+
IsMeta: generalResp.Flags&protocol.IsMeta != 0,
61+
IsProxy: generalResp.Flags&protocol.IsProxy != 0,
62+
IsSupervisor: generalResp.Flags&protocol.IsSupervisor != 0,
63+
}
64+
65+
if securityInfo != nil {
66+
info.SecurityVersion = securityInfo.SecurityVersion
67+
info.ForceSecurity = securityInfo.SecurityOptions&protocol.ForceSecurity != 0
68+
info.SecurityLevel = securityInfo.SecurityLevel
69+
70+
if securityInfo.SecurityOverridesSize > 0 {
71+
info.SecurityOverrides = make([]protocol.SecurityOverride, securityInfo.SecurityOverridesSize)
72+
73+
const offset = protocol.GeneralResponseLength + protocol.SecurityInfoLength
74+
const elementSize = protocol.SecurityOverrideLength
75+
76+
for i := byte(0); i < securityInfo.SecurityOverridesSize; i++ {
77+
err = xrdproto.Unmarshal(resp[offset+elementSize*int(i):], &info.SecurityOverrides[i])
78+
if err != nil {
79+
return ProtocolInfo{}, err
80+
}
81+
}
82+
}
83+
}
84+
85+
return info, nil
86+
}
87+
88+
func extractServerType(flags protocol.Flags) xrdproto.ServerType {
89+
if int32(flags)&int32(xrdproto.DataServer) != 0 {
90+
return xrdproto.DataServer
91+
}
92+
return xrdproto.LoadBalancingServer
93+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2018 The go-hep Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package client
6+
7+
import (
8+
"context"
9+
"net"
10+
"reflect"
11+
"testing"
12+
13+
xrdproto "go-hep.org/x/hep/xrootd/protocol"
14+
"go-hep.org/x/hep/xrootd/protocol/protocol"
15+
)
16+
17+
func TestClient_Protocol_WithSecurityInfo(t *testing.T) {
18+
var protocolVersion int32 = 0x310
19+
20+
serverFunc := func(cancel func(), conn net.Conn) {
21+
defer cancel()
22+
23+
data, err := readRequest(conn)
24+
if err != nil {
25+
t.Fatalf("could not read request: %v", err)
26+
}
27+
28+
var gotRequest protocol.Request
29+
gotHeader, err := unmarshalRequest(data, &gotRequest)
30+
if err != nil {
31+
t.Fatalf("could not unmarshal request: %v", err)
32+
}
33+
34+
if gotHeader.RequestID != protocol.RequestID {
35+
t.Fatalf("invalid request id was specified:\nwant = %d\ngot = %d\n", protocol.RequestID, gotHeader.RequestID)
36+
}
37+
38+
if gotRequest.ClientProtocolVersion != protocolVersion {
39+
t.Fatalf("invalid client protocol version was specified:\nwant = %d\ngot = %d\n", protocolVersion, gotRequest.ClientProtocolVersion)
40+
}
41+
42+
flags := protocol.IsManager | protocol.IsServer | protocol.IsMeta | protocol.IsProxy | protocol.IsSupervisor
43+
44+
responseHeader := xrdproto.ResponseHeader{
45+
StreamID: gotHeader.StreamID,
46+
DataLength: protocol.GeneralResponseLength + protocol.SecurityInfoLength + protocol.SecurityOverrideLength,
47+
}
48+
49+
protocolResponse := protocol.GeneralResponse{protocolVersion, flags}
50+
51+
protocolSecurityInfo := protocol.SecurityInfo{
52+
SecurityOptions: protocol.None,
53+
SecurityLevel: protocol.Pedantic,
54+
SecurityOverridesSize: 1,
55+
}
56+
57+
securityOverride := protocol.SecurityOverride{1, protocol.SignNeeded}
58+
59+
response, err := marshalResponse(responseHeader, protocolResponse, protocolSecurityInfo, securityOverride)
60+
if err != nil {
61+
t.Fatalf("could not marshal response: %v", err)
62+
}
63+
64+
if err := writeResponse(conn, response); err != nil {
65+
t.Fatalf("invalid write: %s", err)
66+
}
67+
}
68+
69+
var want = ProtocolInfo{
70+
BinaryProtocolVersion: protocolVersion,
71+
ServerType: xrdproto.DataServer,
72+
IsManager: true,
73+
IsServer: true,
74+
IsMeta: true,
75+
IsProxy: true,
76+
IsSupervisor: true,
77+
SecurityLevel: protocol.Pedantic,
78+
SecurityOverrides: []protocol.SecurityOverride{{1, protocol.SignNeeded}},
79+
}
80+
81+
clientFunc := func(cancel func(), client *Client) {
82+
client.protocolVersion = protocolVersion
83+
got, err := client.Protocol(context.Background())
84+
if err != nil {
85+
t.Fatalf("invalid protocol call: %v", err)
86+
}
87+
if !reflect.DeepEqual(got, want) {
88+
t.Fatalf("protocol info does not match:\ngot = %v\nwant = %v", got, want)
89+
}
90+
}
91+
92+
testClientWithMockServer(serverFunc, clientFunc)
93+
}

xrootd/client/protocol_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2018 The go-hep Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build xrootd_test_with_server
6+
7+
package client
8+
9+
import (
10+
"context"
11+
"reflect"
12+
"testing"
13+
14+
"go-hep.org/x/hep/xrootd/protocol"
15+
)
16+
17+
func testClient_Protocol(t *testing.T, addr string) {
18+
var want = ProtocolInfo{
19+
BinaryProtocolVersion: 784,
20+
ServerType: protocol.DataServer,
21+
IsServer: true,
22+
}
23+
24+
client, err := NewClient(context.Background(), addr)
25+
if err != nil {
26+
t.Fatalf("could not create client: %v", err)
27+
}
28+
29+
got, err := client.Protocol(context.Background())
30+
if err != nil {
31+
t.Fatalf("invalid protocol call: %v", err)
32+
}
33+
if !reflect.DeepEqual(got, want) {
34+
t.Errorf("Client.Protocol()\ngot = %v\nwant = %v", got, want)
35+
}
36+
37+
client.Close()
38+
}
39+
40+
func TestClient_Protocol(t *testing.T) {
41+
for _, addr := range testClientAddrs {
42+
t.Run(addr, func(t *testing.T) {
43+
testClient_Protocol(t, addr)
44+
})
45+
}
46+
}

xrootd/protocol/handshake/handshake.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,13 @@
66
// for handshake request (see XRootD specification).
77
package handshake // import "go-hep.org/x/hep/xrootd/protocol/handshake"
88

9-
// ServerType is the general server type kept for compatibility
10-
// with 2.0 protocol version (see xrootd protocol specification v3.1.0, p. 5).
11-
type ServerType int32
12-
13-
const (
14-
// LoadBalancingServer indicates whether this is a load-balancing server.
15-
LoadBalancingServer ServerType = iota
16-
// DataServer indicates whether this is a data server.
17-
DataServer ServerType = iota
18-
)
9+
import "go-hep.org/x/hep/xrootd/protocol"
1910

2011
// Response is a response for the handshake request,
2112
// which contains protocol version and server type.
2213
type Response struct {
2314
ProtocolVersion int32
24-
ServerType ServerType
15+
ServerType protocol.ServerType
2516
}
2617

2718
// Request holds the handshake request parameters.

0 commit comments

Comments
 (0)