Files
gotgt/pkg/port/iscsit/packet.go
2015-12-14 10:23:35 +08:00

277 lines
7.7 KiB
Go

/*
Copyright 2015 The GoStor Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package packet implements the iSCSI PDU packet format as specified in
// rfc7143 section 11.
package iscsit
import (
"fmt"
"io"
"strings"
)
type OpCode int
const (
// Defined on the initiator.
OpNoopOut OpCode = 0x00
OpSCSICmd = 0x01
OpSCSITaskReq = 0x02
OpLoginReq = 0x03
OpTextReq = 0x04
OpSCSIOut = 0x05
OpLogoutReq = 0x06
OpSNACKReq = 0x10
// Defined on the target.
OpNoopIn OpCode = 0x20
OpSCSIResp = 0x21
OpSCSITaskResp = 0x22
OpLoginResp = 0x23
OpTextResp = 0x24
OpSCSIIn = 0x25
OpLogoutResp = 0x26
OpReady = 0x31
OpAsync = 0x32
OpReject = 0x3f
)
var opCodeMap = map[OpCode]string{
OpNoopOut: "NOP-Out",
OpSCSICmd: "SCSI Command",
OpSCSITaskReq: "SCSI Task Management FunctionRequest",
OpLoginReq: "Login Request",
OpTextReq: "Text Request",
OpSCSIOut: "SCSI Data-Out (write)",
OpLogoutReq: "Logout Request",
OpSNACKReq: "SNACK Request",
OpNoopIn: "NOP-In",
OpSCSIResp: "SCSI Response",
OpSCSITaskResp: "SCSI Task Management Function Response",
OpLoginResp: "Login Response",
OpTextResp: "Text Response",
OpSCSIIn: "SCSI Data-In (read)",
OpLogoutResp: "Logout Response",
OpReady: "Ready To Transfer (R2T)",
OpAsync: "Asynchronous Message",
OpReject: "Reject",
}
func (c OpCode) String() string {
s := opCodeMap[c]
if s == "" {
s = fmt.Sprintf("Unknown Code: %x", int(c))
}
return s
}
type Message struct {
OpCode OpCode
RawHeader []byte
DataLen int
RawData []byte
Final bool
Immediate bool
TaskTag uint32
ExpCmdSN, MaxCmdSN uint32
AHSLen int
ConnID uint16 // Connection ID.
CmdSN uint32 // Command serial number.
ExpStatSN uint32 // Expected status serial.
Read, Write bool
LUN uint8
Transit bool // Transit bit.
Cont bool // Continue bit.
CSG, NSG Stage // Current Stage, Next Stage.
ISID uint64 // Initiator part of the SSID.
TSIH uint16 // Target-assigned Session Identifying Handle.
StatSN uint32 // Status serial number.
// For login response.
StatusClass uint8
StatusDetail uint8
// SCSI commands
ExpectedDataLen uint32
CDB []byte
Status Status
SCSIResponse Response
// Data-In
HasStatus bool
DataSN uint32
BufferOffset uint32
}
func (m *Message) Bytes() []byte {
switch m.OpCode {
case OpLoginResp:
return m.loginRespBytes()
case OpLogoutResp:
return m.logoutRespBytes()
case OpSCSIResp:
return m.scsiCmdRespBytes()
case OpSCSIIn:
return m.dataInBytes()
}
return nil
}
func (m *Message) String() string {
var s []string
s = append(s, fmt.Sprintf("Op: %v", m.OpCode))
s = append(s, fmt.Sprintf("Final = %v", m.Final))
s = append(s, fmt.Sprintf("Immediate = %v", m.Immediate))
s = append(s, fmt.Sprintf("Data Segment Length = %d", m.DataLen))
s = append(s, fmt.Sprintf("Task Tag = %x", m.TaskTag))
s = append(s, fmt.Sprintf("AHS Length = %d", m.AHSLen))
switch m.OpCode {
case OpLoginReq:
s = append(s, fmt.Sprintf("ISID = %x", m.ISID))
s = append(s, fmt.Sprintf("Transit = %v", m.Transit))
s = append(s, fmt.Sprintf("Continue = %v", m.Cont))
s = append(s, fmt.Sprintf("Current Stage = %v", m.CSG))
s = append(s, fmt.Sprintf("Next Stage = %v", m.NSG))
case OpLoginResp:
s = append(s, fmt.Sprintf("ISID = %x", m.ISID))
s = append(s, fmt.Sprintf("Transit = %v", m.Transit))
s = append(s, fmt.Sprintf("Continue = %v", m.Cont))
s = append(s, fmt.Sprintf("Current Stage = %v", m.CSG))
s = append(s, fmt.Sprintf("Next Stage = %v", m.NSG))
s = append(s, fmt.Sprintf("Status Class = %d", m.StatusClass))
s = append(s, fmt.Sprintf("Status Detail = %d", m.StatusDetail))
case OpSCSICmd:
s = append(s, fmt.Sprintf("LUN = %d", m.LUN))
s = append(s, fmt.Sprintf("ExpectedDataLen = %d", m.ExpectedDataLen))
s = append(s, fmt.Sprintf("CmdSN = %d", m.CmdSN))
s = append(s, fmt.Sprintf("ExpStatSN = %d", m.ExpStatSN))
s = append(s, fmt.Sprintf("Read = %v", m.Read))
s = append(s, fmt.Sprintf("Write = %v", m.Write))
s = append(s, fmt.Sprintf("CDB = %x", m.CDB))
case OpSCSIResp:
s = append(s, fmt.Sprintf("StatSN = %d", m.StatSN))
s = append(s, fmt.Sprintf("ExpCmdSN = %d", m.ExpCmdSN))
s = append(s, fmt.Sprintf("MaxCmdSN = %d", m.MaxCmdSN))
}
return strings.Join(s, "\n")
}
// Response composes a reply to the given message with the appropriate bits set.
func (m *Message) Response(r *Message) {
r.TaskTag = m.TaskTag
r.ConnID = m.ConnID
r.ISID = m.ISID
}
func Next(r io.Reader) (*Message, error) {
buf := make([]byte, 48) // TODO: sync.Pool
if _, err := io.ReadFull(r, buf); err != nil {
return nil, err
}
m, err := parseHeader(buf)
if err != nil {
return nil, err
}
m.RawHeader = buf
if m.DataLen > 0 {
dl := m.DataLen
for dl%4 > 0 {
dl++
}
data := make([]byte, dl)
if _, err := io.ReadFull(r, data); err != nil {
return nil, err
}
m.RawData = data[:m.DataLen]
}
return m, nil
}
// parseUint parses the given slice as a network-byte-ordered integer. If
// there are more than 8 bytes in data, it overflows.
func ParseUint(data []byte) uint64 {
var out uint64
for i := 0; i < len(data); i++ {
out += uint64(data[len(data)-i-1]) << uint(8*i)
}
return out
}
func MarshalUint64(i uint64) []byte {
var data []byte
for j := 0; j < 8; j++ {
b := byte(i >> uint(8*(7-j)) & 0xff)
data = append(data, b)
}
return data
}
func parseHeader(data []byte) (*Message, error) {
if len(data) != 48 {
return nil, fmt.Errorf("garbled header")
}
// TODO: sync.Pool
m := &Message{}
m.Immediate = 0x40&data[0] == 0x40
m.OpCode = OpCode(data[0] & 0x3f)
m.Final = 0x80&data[1] == 0x80
m.AHSLen = int(data[4]) * 4
m.DataLen = int(ParseUint(data[5:8]))
m.TaskTag = uint32(ParseUint(data[16:20]))
switch m.OpCode {
case OpSCSICmd:
m.LUN = uint8(data[9])
m.ExpectedDataLen = uint32(ParseUint(data[20:24]))
m.CmdSN = uint32(ParseUint(data[24:28]))
m.Read = data[1]&0x40 == 0x40
m.Write = data[1]&0x20 == 0x20
m.CDB = data[32:48]
m.ExpStatSN = uint32(ParseUint(data[28:32]))
case OpSCSIResp:
case OpLoginReq:
m.Transit = m.Final
m.Cont = data[1]&0x40 == 0x40
if m.Cont && m.Transit {
// rfc7143 11.12.2
return nil, fmt.Errorf("transit and continue bits set in same login request")
}
m.CSG = Stage(data[1]&0xc) >> 2
m.NSG = Stage(data[1] & 0x3)
m.ISID = uint64(ParseUint(data[8:14]))
m.TSIH = uint16(ParseUint(data[14:16]))
m.ConnID = uint16(ParseUint(data[20:22]))
m.CmdSN = uint32(ParseUint(data[24:28]))
m.ExpStatSN = uint32(ParseUint(data[28:32]))
case OpLoginResp:
m.Transit = m.Final
m.Cont = data[1]&0x40 == 0x40
if m.Cont && m.Transit {
// rfc7143 11.12.2
return nil, fmt.Errorf("transit and continue bits set in same login request")
}
m.CSG = Stage(data[1]&0xc) >> 2
m.NSG = Stage(data[1] & 0x3)
m.StatSN = uint32(ParseUint(data[24:28]))
m.ExpCmdSN = uint32(ParseUint(data[28:32]))
m.MaxCmdSN = uint32(ParseUint(data[32:36]))
m.StatusClass = uint8(data[36])
m.StatusDetail = uint8(data[37])
}
return m, nil
}