go-hep.org/x/hep@v0.38.1/xrootd/session.go (about)

     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 xrootd // import "go-hep.org/x/hep/xrootd"
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"net"
    11  	"os"
    12  	"sync"
    13  	"sync/atomic"
    14  	"time"
    15  
    16  	"go-hep.org/x/hep/xrootd/internal/mux"
    17  	"go-hep.org/x/hep/xrootd/internal/xrdenc"
    18  	"go-hep.org/x/hep/xrootd/xrdproto"
    19  	"go-hep.org/x/hep/xrootd/xrdproto/signing"
    20  	"go-hep.org/x/hep/xrootd/xrdproto/sigver"
    21  )
    22  
    23  // cliSession is a connection to the specific XRootD server
    24  // which allows to send requests and receive responses.
    25  // Concurrent requests are supported.
    26  // Zero value is invalid, cliSession should be instantiated using newSession.
    27  //
    28  // The cliSession is used by the Client to send requests to the particular server
    29  // specified by the name and port. If the current server cannot
    30  // handle a request, it responds with the redirect to the new server.
    31  // After that, Client obtains a session associated with that server and
    32  // re-issues the request. Stream ID may be different during these 2 requests
    33  // because it is used to identify requests among one particular server
    34  // and is not shared between servers in any way.
    35  //
    36  // If the request that supports sending data over a separate socket is issued,
    37  // the session tries to obtain a sub-session to the same server using a `bind` request.
    38  // If the connection is successful, the request is sent specifying that socket for the data exchange.
    39  // Otherwise, a default socket connected to the server is used.
    40  type cliSession struct {
    41  	ctx              context.Context
    42  	cancel           context.CancelFunc
    43  	conn             net.Conn
    44  	mux              *mux.Mux
    45  	protocolVersion  int32
    46  	signRequirements signing.Requirements
    47  	seqID            int64
    48  	mu               sync.RWMutex
    49  	requests         map[xrdproto.StreamID]pendingRequest
    50  
    51  	subCreateMu sync.Mutex   // subCreateMu is used to serialize the creation of sub-sessions.
    52  	subsMu      sync.RWMutex // subsMu is used to serialize the access to the subs map.
    53  	subs        map[xrdproto.PathID]*cliSession
    54  
    55  	maxSubs   int
    56  	freeSubs  chan xrdproto.PathID
    57  	isSub     bool // indicates whether this session is a sub-session.
    58  	client    *Client
    59  	sessionID string
    60  	addr      string
    61  	loginID   [16]byte
    62  	pathID    xrdproto.PathID
    63  }
    64  
    65  // pendingRequest is a request that has been sent to the remote server.
    66  type pendingRequest struct {
    67  	// Header is the header part of the request.
    68  	// It may contain all of the request content if there is no data that is
    69  	// intended to be sent over a separate socket.
    70  	Header []byte
    71  
    72  	// Data is the data part of the request that is intended to be sent over a separate socket.
    73  	Data []byte
    74  
    75  	// PathID is the identifier of the socket which should be used to read or write a data.
    76  	PathID xrdproto.PathID
    77  }
    78  
    79  func newSession(ctx context.Context, address, username, token string, client *Client) (*cliSession, error) {
    80  	ctx, cancel := context.WithCancel(ctx)
    81  
    82  	var d net.Dialer
    83  	addr := parseAddr(address)
    84  	conn, err := d.DialContext(ctx, "tcp", addr)
    85  	if err != nil {
    86  		cancel()
    87  		return nil, err
    88  	}
    89  
    90  	sess := &cliSession{
    91  		ctx:       ctx,
    92  		cancel:    cancel,
    93  		conn:      conn,
    94  		mux:       mux.New(),
    95  		subs:      make(map[xrdproto.PathID]*cliSession),
    96  		freeSubs:  make(chan xrdproto.PathID),
    97  		requests:  make(map[xrdproto.StreamID]pendingRequest),
    98  		client:    client,
    99  		sessionID: addr,
   100  		addr:      addr,
   101  		maxSubs:   8, // TODO: The value of 8 is just a guess. Change it?
   102  	}
   103  
   104  	go sess.consume()
   105  
   106  	if err := sess.handshake(ctx); err != nil {
   107  		sess.Close()
   108  		return nil, err
   109  	}
   110  
   111  	securityInfo, err := sess.Login(ctx, username, token)
   112  	if err != nil {
   113  		sess.Close()
   114  		return nil, err
   115  	}
   116  
   117  	sess.loginID = securityInfo.SessionID
   118  
   119  	if len(securityInfo.SecurityInformation) > 0 {
   120  		err = sess.auth(ctx, securityInfo.SecurityInformation)
   121  		if err != nil {
   122  			sess.Close()
   123  			return nil, err
   124  		}
   125  	}
   126  
   127  	protocolInfo, err := sess.Protocol(ctx)
   128  	if err != nil {
   129  		sess.Close()
   130  		return nil, err
   131  	}
   132  
   133  	sess.signRequirements = signing.New(protocolInfo.SecurityLevel, protocolInfo.SecurityOverrides)
   134  
   135  	return sess, nil
   136  }
   137  
   138  // Close closes the connection. Any blocked operation will be unblocked and return error.
   139  func (sess *cliSession) Close() error {
   140  	if sess == nil {
   141  		return os.ErrInvalid
   142  	}
   143  
   144  	sess.cancel()
   145  
   146  	var errs []error
   147  	for _, child := range sess.subs {
   148  		err := child.Close()
   149  		if err != nil {
   150  			errs = append(errs, err)
   151  		}
   152  	}
   153  
   154  	if !sess.isSub {
   155  		sess.mux.Close()
   156  	}
   157  
   158  	// TODO: should we remove session here somehow?
   159  	err := sess.conn.Close()
   160  	if err != nil {
   161  		errs = append(errs, err)
   162  	}
   163  	if errs != nil {
   164  		return fmt.Errorf("xrootd: errors occured during closing of the session: %v", errs)
   165  	}
   166  	return nil
   167  }
   168  
   169  // handleReadError handles an error encountered while reading and parsing a response.
   170  // If the current session is equal to the initial, the error is considered critical and handleReadError panics.
   171  // Otherwise, the current session is closed and all requests are redirected to the initial session.
   172  // See http://xrootd.org/doc/dev45/XRdv310.pdf, p. 11 for details.
   173  func (sess *cliSession) handleReadError(err error) {
   174  	if sess.sessionID == sess.client.initialSessionID {
   175  		// TODO: what should we do in case initial session is aborted?
   176  		// Should we try to reconnect to the server and re-issue all requests?
   177  		panic(err)
   178  	}
   179  	sess.mu.RLock()
   180  	resp := mux.ServerResponse{Redirection: &mux.Redirection{Addr: sess.client.initialSessionID}}
   181  	for streamID := range sess.requests {
   182  		err := sess.mux.SendData(streamID, resp)
   183  		// TODO: should we log error somehow? We have nowhere to send it.
   184  		_ = err
   185  	}
   186  	sess.mu.RUnlock()
   187  	sess.Close()
   188  }
   189  
   190  // handleWaitResponse handles a "kXR_wait" response by re-issuing the request with streamID
   191  // after the number of seconds encoded in data.
   192  // See http://xrootd.org/doc/dev45/XRdv310.pdf, p. 35 for the specification of the response.
   193  func (sess *cliSession) handleWaitResponse(streamID xrdproto.StreamID, data []byte) error {
   194  	var resp xrdproto.WaitResponse
   195  	rBuffer := xrdenc.NewRBuffer(data)
   196  	if err := resp.UnmarshalXrd(rBuffer); err != nil {
   197  		return err
   198  	}
   199  
   200  	sess.mu.RLock()
   201  	req, ok := sess.requests[streamID]
   202  	sess.mu.RUnlock()
   203  	if !ok {
   204  		return fmt.Errorf("xrootd: could not find a request with stream id equal to %v", streamID)
   205  	}
   206  
   207  	go func(req pendingRequest) {
   208  		time.Sleep(resp.Duration)
   209  		if err := sess.writeRequest(req); err != nil {
   210  			resp := mux.ServerResponse{Err: fmt.Errorf("xrootd: could not send data to the server: %w", err)}
   211  			err := sess.mux.SendData(streamID, resp)
   212  			// TODO: should we log error somehow? We have nowhere to send it.
   213  			_ = err
   214  			sess.cleanupRequest(streamID)
   215  		}
   216  	}(req)
   217  
   218  	return nil
   219  }
   220  
   221  func (sess *cliSession) consume() {
   222  	var header xrdproto.ResponseHeader
   223  	var headerBytes = make([]byte, xrdproto.ResponseHeaderLength)
   224  	var resp mux.ServerResponse
   225  
   226  	for {
   227  		select {
   228  		case <-sess.ctx.Done():
   229  			// TODO: Should wait for active requests to be completed?
   230  			return
   231  		default:
   232  			var err error
   233  			resp.Data, err = xrdproto.ReadResponseWithReuse(sess.conn, headerBytes, &header)
   234  			if err != nil {
   235  				if sess.ctx.Err() != nil {
   236  					// something happened to the context.
   237  					// ignore this error.
   238  					return
   239  				}
   240  				sess.handleReadError(err)
   241  			}
   242  			resp.Err = nil
   243  			resp.Redirection = nil
   244  
   245  			switch header.Status {
   246  			case xrdproto.Error:
   247  				resp.Err = header.Error(resp.Data)
   248  			case xrdproto.Wait:
   249  				resp.Err = sess.handleWaitResponse(header.StreamID, resp.Data)
   250  				if resp.Err == nil {
   251  					continue
   252  				}
   253  			case xrdproto.Redirect:
   254  				resp.Redirection, resp.Err = mux.ParseRedirection(resp.Data)
   255  			}
   256  
   257  			if err := sess.mux.SendData(header.StreamID, resp); err != nil {
   258  				if sess.ctx.Err() != nil {
   259  					// something happened to the context.
   260  					// ignore this error.
   261  					continue
   262  				}
   263  				panic(err)
   264  				// TODO: should we just ignore responses to unclaimed stream IDs?
   265  			}
   266  
   267  			if header.Status != xrdproto.OkSoFar {
   268  				sess.cleanupRequest(header.StreamID)
   269  			}
   270  		}
   271  	}
   272  }
   273  
   274  func (sess *cliSession) cleanupRequest(streamID xrdproto.StreamID) {
   275  	sess.mux.Unclaim(streamID)
   276  	sess.mu.Lock()
   277  	delete(sess.requests, streamID)
   278  	sess.mu.Unlock()
   279  }
   280  
   281  func (sess *cliSession) writeRequest(request pendingRequest) error {
   282  	if request.PathID == 0 {
   283  		request.Header = append(request.Header, request.Data...)
   284  	}
   285  
   286  	if _, err := sess.conn.Write(request.Header); err != nil {
   287  		return err
   288  	}
   289  
   290  	if request.PathID != 0 && len(request.Data) > 0 {
   291  		sess.subsMu.RLock()
   292  		conn, ok := sess.subs[request.PathID]
   293  		sess.subsMu.RUnlock()
   294  		if !ok {
   295  			return fmt.Errorf("xrootd: connection with wrong pathID = %v was requested", request.PathID)
   296  		}
   297  		if _, err := conn.conn.Write(request.Data); err != nil {
   298  			return err
   299  		}
   300  	}
   301  	return nil
   302  }
   303  
   304  func (sess *cliSession) send(ctx context.Context, streamID xrdproto.StreamID, responseChannel mux.DataRecvChan, header, body []byte, pathID xrdproto.PathID) ([]byte, *mux.Redirection, error) {
   305  	if pathID == 0 {
   306  		header = append(header, body...)
   307  	}
   308  	request := pendingRequest{Header: header, Data: body, PathID: pathID}
   309  	sess.mu.Lock()
   310  	sess.requests[streamID] = request
   311  	sess.mu.Unlock()
   312  
   313  	if err := sess.writeRequest(request); err != nil {
   314  		return nil, nil, err
   315  	}
   316  
   317  	var data []byte
   318  
   319  	for {
   320  		select {
   321  		case resp, more := <-responseChannel:
   322  			if !more {
   323  				return data, nil, nil
   324  			}
   325  
   326  			if resp.Err != nil {
   327  				return nil, resp.Redirection, resp.Err
   328  			}
   329  
   330  			if resp.Redirection != nil {
   331  				return nil, resp.Redirection, nil
   332  			}
   333  
   334  			data = append(data, resp.Data...)
   335  		case <-ctx.Done():
   336  			if err := ctx.Err(); err != nil {
   337  				return nil, nil, err
   338  			}
   339  		}
   340  	}
   341  }
   342  
   343  // Send sends the request to the server and stores the response inside the resp.
   344  func (sess *cliSession) Send(ctx context.Context, resp xrdproto.Response, req xrdproto.Request) (*mux.Redirection, error) {
   345  	streamID, responseChannel, err := sess.mux.Claim()
   346  	if err != nil {
   347  		return nil, err
   348  	}
   349  
   350  	var wBuffer xrdenc.WBuffer
   351  	header := xrdproto.RequestHeader{StreamID: streamID, RequestID: req.ReqID()}
   352  	if err = header.MarshalXrd(&wBuffer); err != nil {
   353  		return nil, err
   354  	}
   355  
   356  	var pathID xrdproto.PathID = 0
   357  	var pathData []byte
   358  	if dr, ok := req.(xrdproto.DataRequest); ok {
   359  		var err error
   360  		pathID, err = sess.claimPathID(ctx)
   361  		if err != nil {
   362  			// Should we log error somehow?
   363  			// Fallback to sending the data over a single connection.
   364  			pathID = 0
   365  		}
   366  		defer sess.unclaimPathID(pathID)
   367  		dr.SetPathID(pathID)
   368  		pathData = dr.PathData()
   369  	}
   370  
   371  	if err = req.MarshalXrd(&wBuffer); err != nil {
   372  		return nil, err
   373  	}
   374  	data := wBuffer.Bytes()
   375  
   376  	if sess.signRequirements.Needed(req) {
   377  		data, err = sess.sign(streamID, req.ReqID(), data)
   378  		if err != nil {
   379  			return nil, err
   380  		}
   381  	}
   382  
   383  	data, redirection, err := sess.send(ctx, streamID, responseChannel, data, pathData, pathID)
   384  	if err != nil || redirection != nil || resp == nil {
   385  		return redirection, err
   386  	}
   387  
   388  	return nil, resp.UnmarshalXrd(xrdenc.NewRBuffer(data))
   389  }
   390  
   391  func (sess *cliSession) claimPathID(ctx context.Context) (xrdproto.PathID, error) {
   392  	select {
   393  	case child := <-sess.freeSubs:
   394  		return child, nil
   395  	default:
   396  		sess.subCreateMu.Lock()
   397  		defer sess.subCreateMu.Unlock()
   398  
   399  		sess.subsMu.RLock()
   400  		if len(sess.subs) >= sess.maxSubs {
   401  			sess.subsMu.RUnlock()
   402  			return 0, fmt.Errorf("xrootd: could not claimPathID: all of %d connections are taken", sess.maxSubs)
   403  		}
   404  		sess.subsMu.RUnlock()
   405  
   406  		ds, err := newSubSession(ctx, sess)
   407  		if err != nil {
   408  			return 0, err
   409  		}
   410  		sess.subsMu.Lock()
   411  		sess.subs[ds.pathID] = ds
   412  		sess.subsMu.Unlock()
   413  
   414  		return ds.pathID, nil
   415  	}
   416  }
   417  
   418  func (sess *cliSession) unclaimPathID(pathID xrdproto.PathID) {
   419  	if pathID == 0 {
   420  		return
   421  	}
   422  	go func() {
   423  		select {
   424  		case <-sess.ctx.Done():
   425  			return
   426  		case sess.freeSubs <- pathID:
   427  		}
   428  	}()
   429  }
   430  
   431  func (sess *cliSession) sign(streamID xrdproto.StreamID, requestID uint16, data []byte) ([]byte, error) {
   432  	seqID := atomic.AddInt64(&sess.seqID, 1)
   433  	signRequest := sigver.NewRequest(requestID, seqID, data)
   434  	header := xrdproto.RequestHeader{StreamID: streamID, RequestID: signRequest.ReqID()}
   435  
   436  	var wBuffer xrdenc.WBuffer
   437  	if err := header.MarshalXrd(&wBuffer); err != nil {
   438  		return nil, err
   439  	}
   440  	if err := signRequest.MarshalXrd(&wBuffer); err != nil {
   441  		return nil, err
   442  	}
   443  	wBuffer.WriteBytes(data)
   444  
   445  	return wBuffer.Bytes(), nil
   446  }
   447  
   448  func newSubSession(ctx context.Context, parent *cliSession) (*cliSession, error) {
   449  	ctx, cancel := context.WithCancel(ctx)
   450  
   451  	var d net.Dialer
   452  	conn, err := d.DialContext(ctx, "tcp", parent.addr)
   453  	if err != nil {
   454  		cancel()
   455  		return nil, err
   456  	}
   457  
   458  	sess := &cliSession{
   459  		ctx:       ctx,
   460  		cancel:    cancel,
   461  		conn:      conn,
   462  		mux:       parent.mux,
   463  		subs:      make(map[xrdproto.PathID]*cliSession),
   464  		requests:  make(map[xrdproto.StreamID]pendingRequest),
   465  		client:    parent.client,
   466  		sessionID: parent.addr,
   467  		addr:      parent.addr,
   468  		isSub:     true,
   469  	}
   470  
   471  	go sess.consume()
   472  
   473  	if err := sess.handshake(ctx); err != nil {
   474  		sess.Close()
   475  		return nil, err
   476  	}
   477  
   478  	pathID, err := sess.bind(ctx, parent.loginID)
   479  	if err != nil {
   480  		sess.Close()
   481  		return nil, err
   482  	}
   483  
   484  	sess.pathID = pathID
   485  	return sess, nil
   486  }