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 }