277 lines
7.7 KiB
Go
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
|
|
}
|