diff --git a/pkg/port/interfaces.go b/pkg/port/interfaces.go index b2e6b94..777c77d 100644 --- a/pkg/port/interfaces.go +++ b/pkg/port/interfaces.go @@ -1,9 +1,22 @@ +/* +Copyright 2016 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 port -import ( - "github.com/gostor/gotgt/pkg/api" - "github.com/gostor/gotgt/pkg/port/iscsit" -) +import "github.com/gostor/gotgt/pkg/api" type SCSITargetDriver interface { Init() error @@ -16,7 +29,6 @@ type SCSITargetDriver interface { CreateLu(lu *api.SCSILu) error GetLu(lun uint8) (uint64, error) - ProcessCommand(buf []byte) ([]byte, error) CommandNotify(nid uint64, result int, cmd *api.SCSICommand) error } @@ -58,13 +70,3 @@ func (fake *fakeSCSITargetDriver) GetLun(lun uint8) (uint64, error) { func (fake *fakeSCSITargetDriver) CommandNotify(nid uint64, result int, cmd *api.SCSICommand) error { return nil } -func (fake *fakeSCSITargetDriver) ProcessCommand(buf []byte) ([]byte, error) { - return []byte(""), nil -} - -func NewTargetDriver(driver string, tgt *api.SCSITarget) SCSITargetDriver { - if driver == "iscsi" { - return iscsit.NewISCSITarget(tgt) - } - return nil -} diff --git a/pkg/port/iscsit/auth.go b/pkg/port/iscsit/auth.go index 96aa347..41074bc 100644 --- a/pkg/port/iscsit/auth.go +++ b/pkg/port/iscsit/auth.go @@ -1,5 +1,5 @@ /* -Copyright 2015 The GoStor Authors All rights reserved. +Copyright 2016 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. diff --git a/pkg/port/iscsit/cmd.go b/pkg/port/iscsit/cmd.go index 69b674a..b690bd1 100644 --- a/pkg/port/iscsit/cmd.go +++ b/pkg/port/iscsit/cmd.go @@ -4,6 +4,8 @@ import ( "bytes" "fmt" "strings" + + "github.com/gostor/gotgt/pkg/util" ) type OpCode int @@ -52,6 +54,8 @@ var opCodeMap = map[OpCode]string{ OpReject: "Reject", } +const DataPadding = 4 + type ISCSICommand struct { OpCode OpCode RawHeader []byte @@ -68,7 +72,7 @@ type ISCSICommand struct { ExpStatSN uint32 // Expected status serial. Read, Write bool - LUN uint8 + LUN [8]uint8 Transit bool // Transit bit. Cont bool // Continue bit. CSG, NSG Stage // Current Stage, Next Stage. @@ -102,6 +106,10 @@ func (cmd *ISCSICommand) Bytes() []byte { return cmd.scsiCmdRespBytes() case OpSCSIIn: return cmd.dataInBytes() + case OpTextResp: + return cmd.textRespBytes() + case OpNoopIn: + return cmd.noopInBytes() } return nil } @@ -155,16 +163,8 @@ func ParseUint(data []byte) uint64 { 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) (*ISCSICommand, error) { - if len(data) != 48 { + if len(data) != BHS_SIZE { return nil, fmt.Errorf("garbled header") } // TODO: sync.Pool @@ -177,7 +177,7 @@ func parseHeader(data []byte) (*ISCSICommand, error) { m.TaskTag = uint32(ParseUint(data[16:20])) switch m.OpCode { case OpSCSICmd: - m.LUN = uint8(data[9]) + m.LUN = [8]uint8{data[9]} m.ExpectedDataLen = uint32(ParseUint(data[20:24])) m.CmdSN = uint32(ParseUint(data[24:28])) m.Read = data[1]&0x40 == 0x40 @@ -185,7 +185,7 @@ func parseHeader(data []byte) (*ISCSICommand, error) { m.CDB = data[32:48] m.ExpStatSN = uint32(ParseUint(data[28:32])) case OpSCSIResp: - case OpLoginReq: + case OpLoginReq, OpTextReq, OpNoopOut, OpLogoutReq: m.Transit = m.Final m.Cont = data[1]&0x40 == 0x40 if m.Cont && m.Transit { @@ -229,13 +229,13 @@ func (m *ISCSICommand) scsiCmdRespBytes() []byte { for i := 0; i < 3*4; i++ { buf.WriteByte(0x00) } - buf.Write(MarshalUint64(uint64(m.TaskTag))[4:]) + buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:]) for i := 0; i < 4; i++ { buf.WriteByte(0x00) } - buf.Write(MarshalUint64(uint64(m.StatSN))[4:]) - buf.Write(MarshalUint64(uint64(m.ExpCmdSN))[4:]) - buf.Write(MarshalUint64(uint64(m.MaxCmdSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:]) for i := 0; i < 3*4; i++ { buf.WriteByte(0x00) } @@ -259,24 +259,22 @@ func (m *ISCSICommand) dataInBytes() []byte { } buf.WriteByte(b) - buf.WriteByte(0x00) // 4 - buf.Write(MarshalUint64(uint64(len(m.RawData)))[5:]) // 5-8 - buf.WriteByte(0x00) - buf.WriteByte(byte(m.LUN)) + buf.WriteByte(0x00) // 4 + buf.Write(util.MarshalUint64(uint64(len(m.RawData)))[5:]) // 5-8 // Skip through to byte 16 - for i := 0; i < 6; i++ { - buf.WriteByte(0x00) + for i := 0; i < 8; i++ { + buf.WriteByte(byte(m.LUN[i])) } - buf.Write(MarshalUint64(uint64(m.TaskTag))[4:]) + buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:]) for i := 0; i < 4; i++ { // 11.7.4 buf.WriteByte(0xff) } - buf.Write(MarshalUint64(uint64(m.StatSN))[4:]) - buf.Write(MarshalUint64(uint64(m.ExpCmdSN))[4:]) - buf.Write(MarshalUint64(uint64(m.MaxCmdSN))[4:]) - buf.Write(MarshalUint64(uint64(m.DataSN))[4:]) - buf.Write(MarshalUint64(uint64(m.BufferOffset))[4:]) + buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.DataSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.BufferOffset))[4:]) for i := 0; i < 4; i++ { buf.WriteByte(0x00) } @@ -290,123 +288,79 @@ func (m *ISCSICommand) dataInBytes() []byte { return buf.Bytes() } -type InquiryData struct { - PeripheralQualifier int - PeripheralType int - Removable bool - Version int - SupportsACA bool - Hierarchical bool - SupportsSCC bool - HasACC bool - TargetGroupSupport int - ThirdPartyCopy bool - Protect bool - EnclosureServices bool - Multiport bool - MediaChanger bool - Vendor [8]byte - Product [16]byte - RevisionLevel [4]byte - SerialNumber uint64 -} - -func (id *InquiryData) bytes() []byte { +func (m *ISCSICommand) textRespBytes() []byte { buf := &bytes.Buffer{} + + buf.WriteByte(byte(OpTextResp)) var b byte - b = (uint8(id.PeripheralQualifier) << 5) & 0xe0 - b |= uint8(id.PeripheralType) & 0x1f - buf.WriteByte(b) - b = 0 - if id.Removable { - b = 0x80 - } - buf.WriteByte(b) - buf.WriteByte(byte(id.Version)) - b = 0x02 - if id.SupportsACA { - b |= 0x20 - } - if id.Hierarchical { - b |= 0x10 - } - buf.WriteByte(b) - buf.WriteByte(0x00) - // byte 5 - b = 0 - if id.SupportsSCC { + if m.Final { b |= 0x80 } - if id.HasACC { + if m.Cont { b |= 0x40 } - b |= byte(id.TargetGroupSupport) << 4 & 0x30 - if id.ThirdPartyCopy { - b |= 0x08 - } - if id.Protect { - b |= 0x01 - } + // byte 1 buf.WriteByte(b) - // byte 6 + b = 0 - if id.EnclosureServices { - b |= 0x40 - } - if id.Multiport { - b |= 0x10 - } - if id.MediaChanger { - b |= 0x08 - } buf.WriteByte(b) - buf.WriteByte(0x02) - buf.Write(id.Vendor[:]) - buf.Write(id.Product[:]) - buf.Write(id.RevisionLevel[:]) - buf.Write(MarshalUint64(id.SerialNumber)) - for i := 0; i < 12; i++ { + buf.WriteByte(b) + buf.WriteByte(b) + buf.Write(util.MarshalUint64(uint64(len(m.RawData)))[5:]) // 5-8 + // Skip through to byte 12 + for i := 0; i < 2*4; i++ { buf.WriteByte(0x00) } - data := buf.Bytes() - data[4] = byte(len(data) - 4) - return data -} - -type Capacity struct { - LBA uint64 - Blocksize uint32 - ProtectionType uint8 - PIExponent uint8 - LogicalExponent uint8 - ThinProvisioned bool - ThinProvReturnsZeros bool - LowestLBA uint16 -} - -func (c *Capacity) bytes() []byte { - // table 111 - // http://www.seagate.com/staticfiles/support/disc/manuals/Interface%20manuals/100293068c.pdf - buf := &bytes.Buffer{} - buf.Write(MarshalUint64(c.LBA)) - buf.Write(MarshalUint64(uint64(c.Blocksize))[4:]) - var b byte - if c.ProtectionType > 0 { - b |= 0x01 - b |= c.ProtectionType << 1 - b &= 0x0f + buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:]) + for i := 0; i < 4; i++ { + buf.WriteByte(0xff) } - buf.WriteByte(b) - b = c.PIExponent << 4 - b |= c.LogicalExponent - buf.WriteByte(b) - lowLBA := MarshalUint64(uint64(c.LowestLBA))[6:] - lowLBA[0] &= 0x3f - if c.ThinProvisioned { - lowLBA[0] &= 0x80 + buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:]) + for i := 0; i < 3*4; i++ { + buf.WriteByte(0x00) } - if c.ThinProvReturnsZeros { - lowLBA[0] &= 0x40 + rd := m.RawData + for len(rd)%4 != 0 { + rd = append(rd, 0) } + buf.Write(rd) + return buf.Bytes() +} + +func (m *ISCSICommand) noopInBytes() []byte { + buf := &bytes.Buffer{} + + buf.WriteByte(byte(OpNoopIn)) + var b byte + b |= 0x80 + // byte 1 + buf.WriteByte(b) + + b = 0 + buf.WriteByte(b) + buf.WriteByte(b) + buf.WriteByte(b) + buf.Write(util.MarshalUint64(uint64(len(m.RawData)))[5:]) // 5-8 + // Skip through to byte 12 + for i := 0; i < 2*4; i++ { + buf.WriteByte(0x00) + } + buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:]) + for i := 0; i < 4; i++ { + buf.WriteByte(0xff) + } + buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:]) + for i := 0; i < 3*4; i++ { + buf.WriteByte(0x00) + } + rd := m.RawData + for len(rd)%4 != 0 { + rd = append(rd, 0) + } + buf.Write(rd) return buf.Bytes() } diff --git a/pkg/port/iscsit/conn.go b/pkg/port/iscsit/conn.go new file mode 100644 index 0000000..c02aa32 --- /dev/null +++ b/pkg/port/iscsit/conn.go @@ -0,0 +1,125 @@ +/* +Copyright 2016 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 iscsit + +import ( + "net" + + "github.com/gostor/gotgt/pkg/api" +) + +const ( + CONN_STATE_FREE = 0 + CONN_STATE_SECURITY = 1 + CONN_STATE_SECURITY_AUTH = 2 + CONN_STATE_SECURITY_DONE = 3 + CONN_STATE_SECURITY_LOGIN = 4 + CONN_STATE_SECURITY_FULL = 5 + CONN_STATE_LOGIN = 6 + CONN_STATE_LOGIN_FULL = 7 + CONN_STATE_FULL = 8 + CONN_STATE_KERNEL = 9 + CONN_STATE_CLOSE = 10 + CONN_STATE_EXIT = 11 + CONN_STATE_SCSI = 12 + CONN_STATE_INIT = 13 + CONN_STATE_START = 14 + CONN_STATE_READY = 15 +) + +var ( + DATAIN byte = 0x01 + DATAOUT byte = 0x10 +) + +type iscsiConnection struct { + state int + authState int + session *ISCSISession + sessionType int + sessionParam []ISCSISessionParam + tid int + cid uint16 + rxIOState int + txIOState int + refcount int + conn net.Conn + initiator string + initiatorAlias string + + rxBuffer []byte + txBuffer []byte + req *ISCSICommand + resp *ISCSICommand + + // StatSN - the status sequence number on this connection + statSN uint32 + // ExpStatSN - the expected status sequence number on this connection + expStatSN uint32 + // CmdSN - the command sequence number at the target + cmdSN uint32 + // ExpCmdSN - the next expected command sequence number at the target + expCmdSN uint32 + // MaxCmdSN - the maximum CmdSN acceptable at the target from this initiator + maxCmdSN uint32 + + rxTask *iscsiTask + txTask *iscsiTask + + authMethod AuthMethod +} + +type taskState int + +const ( + taskPending taskState = 1 + taskSCSI taskState = 2 +) + +type iscsiTask struct { + tag uint32 + conn *iscsiConnection + cmd *ISCSICommand + scmd *api.SCSICommand + state taskState +} + +func (c *iscsiConnection) init() { + c.state = CONN_STATE_FREE + c.refcount = 1 + c.sessionParam = []ISCSISessionParam{} + for _, param := range sessionKeys { + c.sessionParam = append(c.sessionParam, ISCSISessionParam{Value: param.def}) + } +} + +func (c *iscsiConnection) readData(size int) ([]byte, int, error) { + var buf = make([]byte, size) + length, err := c.conn.Read(buf) + if err != nil { + return nil, -1, err + } + return buf, length, nil +} + +func (c *iscsiConnection) write(resp []byte) (int, error) { + return c.conn.Write(resp) +} + +func (c *iscsiConnection) close() { + c.conn.Close() +} diff --git a/pkg/port/iscsit/iscsid.go b/pkg/port/iscsit/iscsid.go new file mode 100644 index 0000000..4917738 --- /dev/null +++ b/pkg/port/iscsit/iscsid.go @@ -0,0 +1,592 @@ +/* +Copyright 2016 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 iscsit + +import ( + "bytes" + "fmt" + "net" + "os" + + "github.com/golang/glog" + "github.com/gostor/gotgt/pkg/api" + "github.com/gostor/gotgt/pkg/port" + "github.com/gostor/gotgt/pkg/scsi" + "github.com/gostor/gotgt/pkg/util" +) + +type ISCSITargetService struct { + SCSI *scsi.SCSITargetService + Name string + Targets map[string]*ISCSITarget +} + +func init() { + port.RegisterTargetService("iscsi", NewISCSITargetService) +} + +func NewISCSITargetService(base *scsi.SCSITargetService) (port.SCSITargetService, error) { + return &ISCSITargetService{ + Name: "iscsi", + Targets: map[string]*ISCSITarget{}, + SCSI: base, + }, nil +} + +func (s *ISCSITargetService) NewTarget(target string) (port.SCSITargetDriver, error) { + if _, ok := s.Targets[target]; ok { + return nil, fmt.Errorf("target name has been existed") + } + stgt, err := s.SCSI.NewSCSITarget(len(s.Targets), "iscsi", target) + if err != nil { + return nil, err + } + tgt := newISCSITarget(stgt) + s.Targets[target] = tgt + + return tgt, nil +} + +func (s *ISCSITargetService) Run() error { + l, err := net.Listen("tcp", ":3260") + if err != nil { + glog.Error(err) + os.Exit(1) + } + defer l.Close() + + for { + glog.Info("Listening ...") + conn, err := l.Accept() + if err != nil { + glog.Error(err) + } + glog.Info("Accepting ...") + iscsiConn := &iscsiConnection{conn: conn} + iscsiConn.init() + iscsiConn.rxIOState = IOSTATE_RX_BHS + + glog.Infof("connection is connected from %s...\n", conn.RemoteAddr().String()) + // start a new thread to do with this command + go s.handler(DATAIN, iscsiConn) + } + return nil +} + +func (s *ISCSITargetService) handler(events byte, conn *iscsiConnection) { + + if events&DATAIN != 0 { + glog.Infof("rx handler processing...") + go s.rxHandler(conn) + } + if conn.state != CONN_STATE_CLOSE && events&DATAOUT != 0 { + glog.Infof("tx handler processing...") + s.txHandler(conn) + } + if conn.state == CONN_STATE_CLOSE { + glog.Infof("iscsi connection[%d] closed", conn.cid) + conn.close() + } +} + +func (s *ISCSITargetService) rxHandler(conn *iscsiConnection) { + var ( + hdigest uint = 0 + ddigest uint = 0 + final bool = false + cmd *ISCSICommand + ) + if conn.state == CONN_STATE_SCSI { + hdigest = conn.sessionParam[ISCSI_PARAM_HDRDGST_EN].Value & DIGEST_CRC32C + ddigest = conn.sessionParam[ISCSI_PARAM_DATADGST_EN].Value & DIGEST_CRC32C + } + for { + switch conn.rxIOState { + case IOSTATE_RX_BHS: + glog.Infof("rx handler: IOSTATE_RX_BHS") + buf, length, err := conn.readData(BHS_SIZE) + if err != nil { + glog.Error(err) + return + } + if length == 0 { + conn.state = CONN_STATE_CLOSE + return + } + conn.rxBuffer = buf + cmd, err = parseHeader(buf) + if err != nil { + glog.Error(err) + conn.state = CONN_STATE_CLOSE + return + } + conn.req = cmd + if length == BHS_SIZE && cmd.DataLen != 0 { + conn.rxIOState = IOSTATE_RX_INIT_AHS + break + } + glog.Infof("got command: \n%s", cmd.String()) + glog.Infof("got buffer: %v", buf) + final = true + case IOSTATE_RX_INIT_AHS: + conn.rxIOState = IOSTATE_RX_DATA + break + if hdigest != 0 { + conn.rxIOState = IOSTATE_RX_INIT_HDIGEST + } + case IOSTATE_RX_DATA: + if ddigest != 0 { + conn.rxIOState = IOSTATE_RX_INIT_DDIGEST + } + if cmd == nil { + return + } + dl := ((cmd.DataLen + DataPadding - 1) / DataPadding) * DataPadding + buf, length, err := conn.readData(dl) + if err != nil { + glog.Error(err) + return + } + if length != dl { + conn.state = CONN_STATE_CLOSE + return + } + cmd.RawData = buf[:cmd.DataLen] + conn.rxBuffer = append(conn.rxBuffer, buf...) + final = true + glog.Infof("got command: \n%s", cmd.String()) + default: + glog.Errorf("error %d %d\n", conn.state, conn.rxIOState) + return + } + + if final { + break + } + } + + if conn.state == CONN_STATE_SCSI { + s.scsiCommandHandler(conn) + } else { + conn.txIOState = IOSTATE_TX_BHS + conn.resp = &ISCSICommand{} + switch conn.req.OpCode { + case OpLoginReq: + glog.Infof("OpLoginReq") + if err := s.iscsiExecLogin(conn); err != nil { + glog.Error(err) + conn.state = CONN_STATE_CLOSE + } + case OpLogoutReq: + glog.Infof("OpLogoutReq") + if err := iscsiExecLogout(conn); err != nil { + conn.state = CONN_STATE_CLOSE + } + case OpTextReq: + glog.Infof("OpTextReq") + if err := s.iscsiExecText(conn); err != nil { + conn.state = CONN_STATE_CLOSE + } + default: + iscsiExecReject(conn) + } + glog.Infof("%#v", conn.resp.String()) + s.handler(DATAOUT, conn) + } +} + +func (s *ISCSITargetService) iscsiExecLogin(conn *iscsiConnection) error { + var ( + target *ISCSITarget + cmd = conn.req + ) + conn.resp = &ISCSICommand{ + OpCode: OpLoginResp, + Transit: true, + CSG: cmd.CSG, + NSG: FullFeaturePhase, + StatSN: cmd.ExpStatSN, + TaskTag: cmd.TaskTag, + ExpCmdSN: cmd.CmdSN, + MaxCmdSN: cmd.CmdSN, + RawData: util.MarshalKVText([]util.KeyValue{ + {"HeaderDigest", "None"}, + {"DataDigest", "None"}, + }), + } + pairs := util.ParseKVText(cmd.RawData) + if initiatorName, ok := pairs["InitiatorName"]; ok { + conn.initiator = initiatorName + } + if alias, ok := pairs["InitiatorAlias"]; ok { + conn.initiatorAlias = alias + } + targetName := pairs["TargetName"] + if sessType, ok := pairs["SessionType"]; ok { + if sessType == "Normal" { + conn.sessionType = SESSION_NORMAL + } else { + conn.sessionType = SESSION_DISCOVERY + } + } + if conn.sessionType == SESSION_DISCOVERY { + conn.tid = 0xffff + } else { + for _, t := range s.Targets { + if t.SCSITarget.Name == targetName { + target = t + break + } + } + if target == nil { + conn.state = CONN_STATE_EXIT + return fmt.Errorf("No target found with name(%s)", targetName) + } + conn.tid = target.TID + } + switch conn.state { + case CONN_STATE_FREE: + conn.state = CONN_STATE_SECURITY + case CONN_STATE_SECURITY: + } + + conn.state = CONN_STATE_LOGIN_FULL + conn.expCmdSN = cmd.CmdSN + conn.statSN += 1 + + switch conn.sessionType { + case SESSION_NORMAL: + if conn.session == nil { + // create a new session + sess, err := s.NewISCSISession(conn) + if err != nil { + glog.Error(err) + return err + } + conn.session = sess + } + case SESSION_DISCOVERY: + + } + + return nil +} + +func iscsiExecLogout(conn *iscsiConnection) error { + cmd := conn.req + conn.resp = &ISCSICommand{ + OpCode: OpLogoutResp, + StatSN: cmd.ExpStatSN, + TaskTag: cmd.TaskTag, + } + if conn.session == nil { + conn.resp.ExpCmdSN = cmd.CmdSN + conn.resp.MaxCmdSN = cmd.CmdSN + } else { + conn.resp.ExpCmdSN = conn.session.ExpCmdSN + conn.resp.MaxCmdSN = conn.session.ExpCmdSN + 10 + } + return nil +} + +func (s *ISCSITargetService) iscsiExecText(conn *iscsiConnection) error { + var result = []util.KeyValue{} + cmd := conn.req + keys := util.ParseKVText(cmd.RawData) + if st, ok := keys["SendTargets"]; ok { + if st == "All" { + list, err := s.SCSI.GetTargetList() + if err != nil { + return err + } + for _, t := range list { + result = append(result, util.KeyValue{"TargetName", t.Name}) + result = append(result, util.KeyValue{"TargetAddress", "127.0.0.1:3260,1"}) + } + } + } + + conn.resp = &ISCSICommand{ + OpCode: OpTextResp, + Final: true, + NSG: FullFeaturePhase, + StatSN: cmd.ExpStatSN, + TaskTag: cmd.TaskTag, + ExpCmdSN: cmd.CmdSN, + MaxCmdSN: cmd.CmdSN, + } + conn.resp.RawData = util.MarshalKVText(result) + return nil +} + +func iscsiExecNoopOut(conn *iscsiConnection) error { + cmd := conn.req + conn.resp = &ISCSICommand{ + OpCode: OpNoopIn, + Final: true, + NSG: FullFeaturePhase, + StatSN: cmd.ExpStatSN, + TaskTag: cmd.TaskTag, + ExpCmdSN: cmd.CmdSN + 1, + MaxCmdSN: cmd.CmdSN + 10, + } + return nil +} + +func iscsiExecReject(conn *iscsiConnection) error { + conn.resp = &ISCSICommand{ + OpCode: OpReject, + } + return nil +} + +func (s *ISCSITargetService) txHandler(conn *iscsiConnection) { + var ( + hdigest uint = 0 + ddigest uint = 0 + final bool = false + ) + if conn.state == CONN_STATE_SCSI { + hdigest = conn.sessionParam[ISCSI_PARAM_HDRDGST_EN].Value & DIGEST_CRC32C + ddigest = conn.sessionParam[ISCSI_PARAM_DATADGST_EN].Value & DIGEST_CRC32C + } + if conn.state == CONN_STATE_SCSI && conn.txTask == nil { + err := s.scsiCommandHandler(conn) + if err != nil { + glog.Error(err) + return + } + } + for { + switch conn.txIOState { + case IOSTATE_TX_BHS: + glog.V(2).Infof("ready to write response") + glog.V(2).Infof("%s", conn.resp.String()) + glog.V(2).Infof("length of RawData is %d", len(conn.resp.RawData)) + glog.V(2).Infof("length of resp is %d", len(conn.resp.Bytes())) + if l, err := conn.write(conn.resp.Bytes()); err != nil { + glog.Error(err) + return + } else { + conn.txIOState = IOSTATE_TX_INIT_AHS + glog.V(2).Infof("success to write %d length", l) + } + case IOSTATE_TX_INIT_AHS: + if hdigest != 0 { + conn.txIOState = IOSTATE_TX_INIT_HDIGEST + } else { + conn.txIOState = IOSTATE_TX_INIT_DATA + } + if conn.txIOState != IOSTATE_TX_AHS { + final = true + } + case IOSTATE_TX_AHS: + case IOSTATE_TX_INIT_DATA: + final = true + case IOSTATE_TX_DATA: + if ddigest != 0 { + conn.txIOState = IOSTATE_TX_INIT_DDIGEST + } + default: + glog.Errorf("error %d %d\n", conn.state, conn.txIOState) + return + } + + if final { + break + } + } + + switch conn.state { + case CONN_STATE_CLOSE, CONN_STATE_EXIT: + conn.state = CONN_STATE_CLOSE + case CONN_STATE_SECURITY_LOGIN: + conn.state = CONN_STATE_LOGIN + glog.Infof("CONN_STATE_LOGIN") + case CONN_STATE_SECURITY_FULL, CONN_STATE_LOGIN_FULL: + if conn.sessionType == SESSION_NORMAL { + conn.state = CONN_STATE_KERNEL + glog.Infof("CONN_STATE_KERNEL") + conn.state = CONN_STATE_SCSI + glog.Infof("CONN_STATE_SCSI") + } else { + conn.state = CONN_STATE_FULL + glog.Infof("CONN_STATE_FULL") + } + conn.rxIOState = IOSTATE_RX_BHS + s.handler(DATAIN, conn) + case CONN_STATE_SCSI: + conn.txTask = nil + default: + conn.rxIOState = IOSTATE_RX_BHS + s.handler(DATAIN, conn) + } + glog.Infof("%d", conn.state) +} + +func (s *ISCSITargetService) scsiCommandHandler(conn *iscsiConnection) (err error) { + req := conn.req + switch req.OpCode { + case OpSCSICmd: + glog.Infof("SCSI Command processing...") + scmd := &api.SCSICommand{} + task := &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: scmd} + conn.rxTask = task + if err = s.iscsiTaskQueueHandler(task); err != nil { + return + } else { + conn.rxTask = nil + conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: &api.SCSICommand{}} + conn.txIOState = IOSTATE_TX_BHS + conn.statSN += 1 + resp := &ISCSICommand{ + Immediate: true, + Final: true, + StatSN: req.ExpStatSN, + TaskTag: req.TaskTag, + ExpCmdSN: conn.session.ExpCmdSN, + MaxCmdSN: conn.session.ExpCmdSN + 10, + Status: scmd.Result, + SCSIResponse: 0x00, + HasStatus: true, + } + switch scmd.Direction { + case api.SCSIDataRead: + resp.OpCode = OpSCSIIn + if scmd.InSDBBuffer.Buffer != nil { + buf := scmd.InSDBBuffer.Buffer.Bytes() + resp.RawData = buf + } else { + resp.RawData = []byte{} + } + case api.SCSIDataWrite: + resp.OpCode = OpSCSIResp + if scmd.InSDBBuffer.Buffer != nil { + buf := scmd.InSDBBuffer.Buffer.Bytes() + resp.RawData = buf + } else { + resp.RawData = []byte{} + } + case api.SCSIDataBidirection: + case api.SCSIDataNone: + resp.OpCode = OpSCSIResp + } + conn.resp = resp + } + case OpSCSIOut: + case OpNoopOut: + conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag} + conn.txIOState = IOSTATE_TX_BHS + iscsiExecNoopOut(conn) + case OpLogoutReq: + conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag} + conn.txIOState = IOSTATE_TX_BHS + iscsiExecLogout(conn) + case OpTextReq, OpSNACKReq: + err = fmt.Errorf("Cannot handle yet %s", opCodeMap[conn.req.OpCode]) + glog.Error(err) + return + default: + err = fmt.Errorf("Unknown op %s", opCodeMap[conn.req.OpCode]) + glog.Error(err) + return + } + conn.rxIOState = IOSTATE_RX_BHS + s.handler(DATAIN|DATAOUT, conn) + return nil +} + +func (s *ISCSITargetService) iscsiTaskQueueHandler(task *iscsiTask) error { + conn := task.conn + sess := conn.session + cmd := task.cmd + if cmd.Immediate { + return s.iscsiExecTask(task) + } + cmdsn := cmd.CmdSN + glog.V(2).Infof("CmdSN of command is %d, ExpCmdSN of session is %d", cmdsn, sess.ExpCmdSN) + if cmdsn == sess.ExpCmdSN { + retry: + cmdsn += 1 + sess.ExpCmdSN = cmdsn + glog.Infof("session's ExpCmdSN is %d", cmdsn) + + glog.Infof("process task(%d)", task.cmd.CmdSN) + if err := s.iscsiExecTask(task); err != nil { + glog.Error(err) + } + if len(sess.PendingTasks) == 0 { + return nil + } + task = sess.PendingTasks.Pop().(*iscsiTask) + cmd = task.cmd + if cmd.CmdSN != cmdsn { + sess.PendingTasks.Push(task) + return nil + } + task.state = taskSCSI + goto retry + } else { + if cmd.CmdSN < sess.ExpCmdSN { + err := fmt.Errorf("unexpected cmd serial number: (%d, %d)", cmd.CmdSN, sess.ExpCmdSN) + glog.Error(err) + return err + } + glog.Infof("add task(%d) into task queue", task.cmd.CmdSN) + // add this connection into queue and set this task as pending task + task.state = taskPending + sess.PendingTasks.Push(task) + } + + return nil +} + +func (s *ISCSITargetService) iscsiExecTask(task *iscsiTask) error { + cmd := task.cmd + switch cmd.OpCode { + case OpSCSICmd: + if cmd.Read { + if cmd.Write { + task.scmd.Direction = api.SCSIDataBidirection + } else { + task.scmd.Direction = api.SCSIDataRead + } + } else { + if cmd.Write { + task.scmd.Direction = api.SCSIDataWrite + } + } + task.scmd.CommandITNID = task.conn.session.Tsih + task.scmd.SCB = bytes.NewBuffer(cmd.CDB) + task.scmd.SCBLength = len(cmd.CDB) + task.scmd.Lun = cmd.LUN + task.scmd.Tag = uint64(cmd.TaskTag) + task.state = taskSCSI + task.scmd.OutSDBBuffer.Buffer = bytes.NewBuffer(cmd.RawData) + // add scsi target process queue + err := s.SCSI.AddCommandQueue(task.conn.session.Target.SCSITarget.TID, task.scmd) + if err != nil { + task.state = 0 + } + return err + case OpLogoutReq: + + case OpNoopOut: + // just do it in iscsi layer + } + return nil +} diff --git a/pkg/port/iscsit/iscsit.go b/pkg/port/iscsit/iscsit.go index f0d240f..9698710 100644 --- a/pkg/port/iscsit/iscsit.go +++ b/pkg/port/iscsit/iscsit.go @@ -1,5 +1,5 @@ /* -Copyright 2015 The GoStor Authors All rights reserved. +Copyright 2016 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. @@ -17,11 +17,38 @@ limitations under the License. // iSCSI Target Driver package iscsit -import ( - "github.com/gostor/gotgt/pkg/api" - "github.com/gostor/gotgt/pkg/util" +import "github.com/gostor/gotgt/pkg/api" + +const ( + IOSTATE_FREE = iota + + IOSTATE_RX_BHS + IOSTATE_RX_INIT_AHS + IOSTATE_RX_AHS + IOSTATE_RX_INIT_HDIGEST + IOSTATE_RX_HDIGEST + IOSTATE_RX_CHECK_HDIGEST + IOSTATE_RX_INIT_DATA + IOSTATE_RX_DATA + IOSTATE_RX_INIT_DDIGEST + IOSTATE_RX_DDIGEST + IOSTATE_RX_CHECK_DDIGEST + IOSTATE_RX_END + + IOSTATE_TX_BHS + IOSTATE_TX_INIT_AHS + IOSTATE_TX_AHS + IOSTATE_TX_INIT_HDIGEST + IOSTATE_TX_HDIGEST + IOSTATE_TX_INIT_DATA + IOSTATE_TX_DATA + IOSTATE_TX_INIT_DDIGEST + IOSTATE_TX_DDIGEST + IOSTATE_TX_END ) +var ISCSI_OPCODE_MASK = 0x3F + type ISCSIDiscoveryMethod string var ( @@ -38,7 +65,7 @@ type ISCSIRedirectInfo struct { } type ISCSITarget struct { - *api.SCSITarget + api.SCSITarget api.SCSITargetDriverCommon Sessions []*ISCSISession @@ -51,9 +78,9 @@ type ISCSITarget struct { NopCount int } -func NewISCSITarget(target *api.SCSITarget) *ISCSITarget { +func newISCSITarget(target *api.SCSITarget) *ISCSITarget { return &ISCSITarget{ - SCSITarget: target, + SCSITarget: *target, } } @@ -91,96 +118,3 @@ func (tgt *ISCSITarget) GetLu(lun uint8) (uint64, error) { func (tgt *ISCSITarget) CommandNotify(nid uint64, result int, cmd *api.SCSICommand) error { return nil } -func (tgt *ISCSITarget) ProcessCommand(buf []byte) ([]byte, error) { - b := make([]byte, 48) // TODO: sync.Pool - b = buf[0:48] - m, err := parseHeader(b) - if err != nil { - return nil, err - } - m.RawHeader = b - if m.DataLen > 0 { - m.RawData = buf[48:m.DataLen] - } - resp := &ISCSICommand{} - switch m.OpCode { - case OpLoginReq: - resp = &ISCSICommand{ - OpCode: OpLoginResp, - Transit: true, - NSG: FullFeaturePhase, - StatSN: m.ExpStatSN, - TaskTag: m.TaskTag, - ExpCmdSN: m.CmdSN, - MaxCmdSN: m.CmdSN, - RawData: util.MarshalKVText(map[string]string{ - "HeaderDigest": "None", - "DataDigest": "None", - }), - } - break - case OpLogoutReq: - resp = &ISCSICommand{ - OpCode: OpLogoutResp, - StatSN: m.ExpStatSN, - TaskTag: m.TaskTag, - ExpCmdSN: m.CmdSN, - MaxCmdSN: m.CmdSN, - } - case OpSCSICmd: - resp = &ISCSICommand{ - OpCode: OpSCSIResp, - Final: true, - StatSN: m.ExpStatSN, - TaskTag: m.TaskTag, - ExpCmdSN: m.CmdSN + 1, - MaxCmdSN: m.CmdSN + 10, - } - switch api.SCSICommandType(m.CDB[0]) { - case api.TEST_UNIT_READY: - // test unit ready - resp.Status = api.SAM_STAT_GOOD - resp.SCSIResponse = 0x01 - break - case api.READ_CAPACITY: - resp.OpCode = OpSCSIIn - resp.HasStatus = true - var data []byte - data = append(data, MarshalUint64(uint64(0))[4:]...) - data = append(data, MarshalUint64(uint64(0))[4:]...) - resp.RawData = data - break - case api.SERVICE_ACTION_IN: - resp.OpCode = OpSCSIIn - resp.HasStatus = true - sa := m.CDB[1] & 0x1f - switch sa { - case 0x10: - c := &Capacity{} - resp.RawData = c.bytes() - } - break - case api.INQUIRY: - resp.OpCode = OpSCSIIn - resp.HasStatus = true - alloc := int(ParseUint(m.CDB[3:5])) - inq := &InquiryData{ - Vendor: [8]byte{'1', '1', 'c', 'a', 'n', 's'}, - Product: [16]byte{'c', 'o', 'f', 'f', 'e', 'e'}, - RevisionLevel: [4]byte{'1', '.', '0'}, - SerialNumber: 52, - } - - if len(inq.bytes()) >= alloc { - resp.RawData = inq.bytes()[:alloc] - } else { - resp.RawData = inq.bytes() - } - break - default: - break - } - } - b1 := resp.Bytes() - return b1, nil -} diff --git a/pkg/port/iscsit/login.go b/pkg/port/iscsit/login.go index 3dec0d8..cad1237 100644 --- a/pkg/port/iscsit/login.go +++ b/pkg/port/iscsit/login.go @@ -1,6 +1,10 @@ package iscsit -import "bytes" +import ( + "bytes" + + "github.com/gostor/gotgt/pkg/util" +) func (m *ISCSICommand) loginRespBytes() []byte { // rfc7143 11.13 @@ -20,20 +24,20 @@ func (m *ISCSICommand) loginRespBytes() []byte { buf.WriteByte(b) b = 0 - buf.WriteByte(b) // version-max - buf.WriteByte(b) // version-active - buf.WriteByte(b) // ahsLen - buf.Write(MarshalUint64(uint64(len(m.RawData)))[5:]) // data segment length, no padding - buf.Write(MarshalUint64(m.ISID)[2:]) - buf.Write(MarshalUint64(uint64(m.TSIH))[6:]) - buf.Write(MarshalUint64(uint64(m.TaskTag))[4:]) + buf.WriteByte(b) // version-max + buf.WriteByte(b) // version-active + buf.WriteByte(b) // ahsLen + buf.Write(util.MarshalUint64(uint64(len(m.RawData)))[5:]) // data segment length, no padding + buf.Write(util.MarshalUint64(m.ISID)[2:]) + buf.Write(util.MarshalUint64(uint64(m.TSIH))[6:]) + buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:]) buf.WriteByte(b) buf.WriteByte(b) buf.WriteByte(b) buf.WriteByte(b) // "reserved" - buf.Write(MarshalUint64(uint64(m.StatSN))[4:]) - buf.Write(MarshalUint64(uint64(m.ExpCmdSN))[4:]) - buf.Write(MarshalUint64(uint64(m.MaxCmdSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:]) buf.WriteByte(byte(m.StatusClass)) buf.WriteByte(byte(m.StatusDetail)) buf.WriteByte(b) diff --git a/pkg/port/iscsit/logout.go b/pkg/port/iscsit/logout.go index 3ce91f9..4f41d9a 100644 --- a/pkg/port/iscsit/logout.go +++ b/pkg/port/iscsit/logout.go @@ -1,6 +1,10 @@ package iscsit -import "bytes" +import ( + "bytes" + + "github.com/gostor/gotgt/pkg/util" +) func (m *ISCSICommand) logoutRespBytes() []byte { buf := &bytes.Buffer{} @@ -11,13 +15,13 @@ func (m *ISCSICommand) logoutRespBytes() []byte { for i := 4; i < 16; i++ { buf.WriteByte(0x00) } - buf.Write(MarshalUint64(uint64(m.TaskTag))[4:]) + buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:]) for i := 20; i < 24; i++ { buf.WriteByte(0x00) } - buf.Write(MarshalUint64(uint64(m.StatSN))[4:]) - buf.Write(MarshalUint64(uint64(m.ExpCmdSN))[4:]) - buf.Write(MarshalUint64(uint64(m.MaxCmdSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:]) for i := 36; i < 48; i++ { buf.WriteByte(0x00) } diff --git a/pkg/port/iscsit/portal.go b/pkg/port/iscsit/portal.go index 2d8033e..679164a 100644 --- a/pkg/port/iscsit/portal.go +++ b/pkg/port/iscsit/portal.go @@ -1,5 +1,5 @@ /* -Copyright 2015 The GoStor Authors All rights reserved. +Copyright 2016 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. diff --git a/pkg/port/iscsit/session.go b/pkg/port/iscsit/session.go index b043bbb..8dcf891 100644 --- a/pkg/port/iscsit/session.go +++ b/pkg/port/iscsit/session.go @@ -1,5 +1,5 @@ /* -Copyright 2015 The GoStor Authors All rights reserved. +Copyright 2016 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. @@ -16,13 +16,133 @@ limitations under the License. package iscsit -import "crypto/rand" +import ( + "fmt" + "math/rand" + "time" +) + +var ( + SESSION_NORMAL int = 0 + SESSION_DISCOVERY int = 1 +) + +var DIGEST_CRC32C uint = 1 << 1 +var DIGEST_NONE uint = 1 << 0 +var DIGEST_ALL uint = DIGEST_NONE | DIGEST_CRC32C +var BHS_SIZE = 48 + +const ( + MAX_QUEUE_CMD_MIN = 1 + MAX_QUEUE_CMD_DEF = 128 + MAX_QUEUE_CMD_MAX = 512 +) + +const ( + ISCSI_PARAM_MAX_RECV_DLENGTH = iota + ISCSI_PARAM_HDRDGST_EN + ISCSI_PARAM_DATADGST_EN + ISCSI_PARAM_INITIAL_R2T_EN + ISCSI_PARAM_MAX_R2T + ISCSI_PARAM_IMM_DATA_EN + ISCSI_PARAM_FIRST_BURST + ISCSI_PARAM_MAX_BURST + ISCSI_PARAM_PDU_INORDER_EN + ISCSI_PARAM_DATASEQ_INORDER_EN + ISCSI_PARAM_ERL + ISCSI_PARAM_IFMARKER_EN + ISCSI_PARAM_OFMARKER_EN + ISCSI_PARAM_DEFAULTTIME2WAIT + ISCSI_PARAM_DEFAULTTIME2RETAIN + ISCSI_PARAM_OFMARKINT + ISCSI_PARAM_IFMARKINT + ISCSI_PARAM_MAXCONNECTIONS + /* iSCSI Extensions for RDMA (RFC5046) */ + ISCSI_PARAM_RDMA_EXTENSIONS + ISCSI_PARAM_TARGET_RDSL + ISCSI_PARAM_INITIATOR_RDSL + ISCSI_PARAM_MAX_OUTST_PDU + /* "local" parmas, never sent to the initiator */ + ISCSI_PARAM_FIRST_LOCAL + ISCSI_PARAM_MAX_XMIT_DLENGTH = ISCSI_PARAM_FIRST_LOCAL + ISCSI_PARAM_MAX_QUEUE_CMD + /* must always be last */ + ISCSI_PARAM_MAX +) type ISCSISessionParam struct { State int Value uint } +/* + * The defaults here are according to the spec and must not be changed, + * otherwise the initiator may make the wrong assumption. If you want + * to change a value, edit the value in iscsi_target_create. + * + * The param MaxXmitDataSegmentLength doesn't really exist. It's a way + * to remember the RDSL of the initiator, which defaults to 8k if he has + * not told us otherwise. + */ +type iscsiSessionKeys struct { + name string + def uint + min uint + max uint +} + +var sessionKeys []iscsiSessionKeys = []iscsiSessionKeys{ + // ISCSI_PARAM_MAX_RECV_DLENGTH + {"MaxRecvDataSegmentLength", 8192, 512, 16777215}, + // ISCSI_PARAM_HDRDGST_EN + {"HeaderDigest", DIGEST_NONE, DIGEST_NONE, DIGEST_ALL}, + // ISCSI_PARAM_DATADGST_EN + {"DataDigest", DIGEST_NONE, DIGEST_NONE, DIGEST_ALL}, + // ISCSI_PARAM_INITIAL_R2T_EN + {"InitialR2T", 1, 0, 1}, + // ISCSI_PARAM_MAX_R2T + {"MaxOutstandingR2T", 1, 1, 65535}, + // ISCSI_PARAM_IMM_DATA_EN + {"ImmediateData", 1, 0, 1}, + // ISCSI_PARAM_FIRST_BURST + {"FirstBurstLength", 65536, 512, 16777215}, + // ISCSI_PARAM_MAX_BURST + {"MaxBurstLength", 262144, 512, 16777215}, + // ISCSI_PARAM_PDU_INORDER_EN + {"DataPDUInOrder", 1, 0, 1}, + // ISCSI_PARAM_DATASEQ_INORDER_EN + {"DataSequenceInOrder", 1, 0, 1}, + // ISCSI_PARAM_ERL + {"ErrorRecoveryLevel", 0, 0, 2}, + // ISCSI_PARAM_IFMARKER_EN + {"IFMarker", 0, 0, 1}, + // ISCSI_PARAM_OFMARKER_EN + {"OFMarker", 0, 0, 1}, + // ISCSI_PARAM_DEFAULTTIME2WAIT + {"DefaultTime2Wait", 2, 0, 3600}, + // ISCSI_PARAM_DEFAULTTIME2RETAIN + {"DefaultTime2Retain", 20, 0, 3600}, + // ISCSI_PARAM_OFMARKINT + {"OFMarkInt", 2048, 1, 65535}, + // ISCSI_PARAM_IFMARKINT + {"IFMarkInt", 2048, 1, 65535}, + // ISCSI_PARAM_MAXCONNECTIONS + {"MaxConnections", 1, 1, 65535}, + // ISCSI_PARAM_RDMA_EXTENSIONS + {"RDMAExtensions", 0, 0, 1}, + // ISCSI_PARAM_TARGET_RDSL + {"TargetRecvDataSegmentLength", 8192, 512, 16777215}, + // ISCSI_PARAM_INITIATOR_RDSL + {"InitiatorRecvDataSegmentLength", 8192, 512, 16777215}, + // ISCSI_PARAM_MAX_OUTST_PDU + {"MaxOutstandingUnexpectedPDUs", 0, 2, 4294967295}, + // "local" parmas, never sent to the initiator + // ISCSI_PARAM_MAX_XMIT_DLENGTH + {"MaxXmitDataSegmentLength", 8192, 512, 16777215}, + // ISCSI_PARAM_MAX_QUEUE_CMD + {"MaxQueueCmd", MAX_QUEUE_CMD_DEF, MAX_QUEUE_CMD_MIN, MAX_QUEUE_CMD_MAX}, +} + // Session is an iSCSI session. type ISCSISession struct { Refcount int @@ -30,58 +150,112 @@ type ISCSISession struct { InitiatorAlias string Target *ISCSITarget Isid uint64 - Tsih uint16 + Tsih uint64 + + ExpCmdSN uint32 // only one connection per session - Connections []*ISCSIConnection - Commands []*ISCSICommand - PendingCommands []*ISCSICommand - ExpectionCommandSN uint32 - MaxQueueCommand uint32 - SessionParam []ISCSISessionParam - Info string - Rdma int + Connections []*iscsiConnection + Commands []*ISCSICommand + PendingTasks taskQueue + MaxQueueCommand uint32 + SessionParam []ISCSISessionParam + Info string + Rdma int } -type ISCSIHeader struct { +type taskQueue []*iscsiTask + +func (tq taskQueue) Len() int { return len(tq) } + +func (tq taskQueue) Less(i, j int) bool { + // We want Pop to give us the highest, not lowest, priority so we use greater than here. + return tq[i].cmd.CmdSN > tq[j].cmd.CmdSN } -type ISCSIPdu struct { - Bhs ISCSIHeader - AhsSize uint - DataSize uint +func (tq taskQueue) Swap(i, j int) { + tq[i], tq[j] = tq[j], tq[i] } -type ISCSIConnection struct { - State int - RxIostate int - TxIostate int - Refcount int +func (tq *taskQueue) Push(x interface{}) { + item := x.(*iscsiTask) + *tq = append(*tq, item) +} - Session *ISCSISession +func (tq *taskQueue) Pop() interface{} { + old := *tq + n := len(old) + item := old[n-1] + *tq = old[0 : n-1] + return item +} - TID int - CID int - Auth AuthMethod - StatSN uint32 - ExpectionStatSN uint32 - CommandSN uint32 - ExpectionCommandSN uint32 - MaxCommandSN uint32 - Request ISCSIPdu - Response ISCSIPdu +// The BHS is 48 bytes long. The Opcode and DataSegmentLength fields +// appear in all iSCSI PDUs. In addition, when used, the Initiator Task +// Tag and Logical Unit Number always appear in the same location in the +// header. +type iscsiHeader struct { + opcode uint8 + flags uint8 // Final bit + rsvd2 [2]uint8 + hlength uint8 // AHSs total length + dlength [3]uint8 // Data length + lun [8]uint8 + itt uint8 // Initiator Task Tag + ttt uint8 // Target Task Tag + statsn uint8 + expStatSN uint8 + maxStatSN uint8 + other [12]uint8 +} + +type iscsiPdu struct { + bhs iscsiHeader + ahsSize uint + dataSize uint } // New creates a new session. -func NewISCSISession() (*ISCSISession, error) { - var tsih uint16 - b := make([]byte, 2) - if _, err := rand.Read(b); err != nil { - return nil, err - } - tsih += uint16(b[0]) << 8 - tsih += uint16(b[1]) +func (s *ISCSITargetService) NewISCSISession(conn *iscsiConnection) (*ISCSISession, error) { + var ( + target *ISCSITarget + tsih uint64 + ) - return &ISCSISession{ - Tsih: tsih, - }, nil + for _, t := range s.Targets { + if t.TID == conn.tid { + target = t + break + } + } + if target == nil { + return nil, fmt.Errorf("No target found with tid(%d)", conn.tid) + } + + for { + rand.Seed(int64(time.Now().UTC().Nanosecond())) + tsih = uint64(rand.Uint32()) + for _, s := range target.Sessions { + if s.Tsih == tsih { + tsih = 0 + break + } + } + if tsih != 0 { + break + } + } + + sess := &ISCSISession{ + Tsih: tsih, + Initiator: conn.initiator, + InitiatorAlias: conn.initiatorAlias, + Target: target, + Connections: []*iscsiConnection{conn}, + SessionParam: conn.sessionParam, + MaxQueueCommand: uint32(conn.sessionParam[ISCSI_PARAM_MAX_QUEUE_CMD].Value), + Rdma: 0, + ExpCmdSN: conn.expCmdSN, + } + conn.session = sess + return sess, nil } diff --git a/pkg/port/service.go b/pkg/port/service.go new file mode 100644 index 0000000..04dc6a0 --- /dev/null +++ b/pkg/port/service.go @@ -0,0 +1,47 @@ +/* +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 port + +import ( + "fmt" + + "github.com/gostor/gotgt/pkg/scsi" +) + +type SCSITargetService interface { + Run() error + NewTarget(string) (SCSITargetDriver, error) +} + +type TargetServiceFunc func(*scsi.SCSITargetService) (SCSITargetService, error) + +var registeredPlugins = map[string](TargetServiceFunc){} + +func RegisterTargetService(name string, f TargetServiceFunc) { + registeredPlugins[name] = f +} + +func NewTargetService(name string, s *scsi.SCSITargetService) (SCSITargetService, error) { + if name == "" { + return nil, nil + } + f, ok := registeredPlugins[name] + if !ok { + return nil, fmt.Errorf("SCSI target driver %s is not found.", name) + } + return f(s) +} diff --git a/pkg/scsi/backingstore.go b/pkg/scsi/backingstore.go index 4e585bd..a06ab68 100644 --- a/pkg/scsi/backingstore.go +++ b/pkg/scsi/backingstore.go @@ -1,5 +1,5 @@ /* -Copyright 2015 The GoStor Authors All rights reserved. +Copyright 2016 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. @@ -18,6 +18,7 @@ package scsi import ( "fmt" + "os" "github.com/gostor/gotgt/pkg/api" ) @@ -28,15 +29,7 @@ type BaseBackingStore struct { OflagsSupported int } -type BackingStore interface { - Open(dev *api.SCSILu, path string, fd *int, size *uint64) error - Close(dev *api.SCSILu) error - Init(dev *api.SCSILu, Opts string) error - Exit(dev *api.SCSILu) error - CommandSubmit(cmd *api.SCSICommand) error -} - -type BackingStoreFunc func() (BackingStore, error) +type BackingStoreFunc func() (api.BackingStore, error) var registeredPlugins = map[string](BackingStoreFunc){} @@ -44,7 +37,7 @@ func RegisterBackingStore(name string, f BackingStoreFunc) { registeredPlugins[name] = f } -func NewBackingStore(name string) (BackingStore, error) { +func NewBackingStore(name string) (api.BackingStore, error) { if name == "" { return nil, nil } @@ -59,8 +52,8 @@ type fakeBackingStore struct { BaseBackingStore } -func (fake *fakeBackingStore) Open(dev *api.SCSILu, path string, fd *int, size *uint64) error { - return nil +func (fake *fakeBackingStore) Open(dev *api.SCSILu, path string) (*os.File, error) { + return nil, nil } func (fake *fakeBackingStore) Close(dev *api.SCSILu) error { diff --git a/pkg/scsi/backingstore/common.go b/pkg/scsi/backingstore/common.go index 411793e..34efc2f 100644 --- a/pkg/scsi/backingstore/common.go +++ b/pkg/scsi/backingstore/common.go @@ -1,5 +1,5 @@ /* -Copyright 2015 The GoStor Authors All rights reserved. +Copyright 2016 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. @@ -16,40 +16,188 @@ limitations under the License. package backingstore -import "github.com/gostor/gotgt/pkg/scsi" +import ( + "bytes" + "fmt" + "os" + + "github.com/golang/glog" + "github.com/gostor/gotgt/pkg/api" + "github.com/gostor/gotgt/pkg/scsi" + "github.com/gostor/gotgt/pkg/util" +) func init() { scsi.RegisterBackingStore("file", new) } type FileBackingStore struct { - BaseBackingStore + scsi.BaseBackingStore } -func new() (scsi.BackingStore, error) { - return NullBackingStore{ - Name: "file", - DataSize: 0, - OflagsSupported: 0, +func new() (api.BackingStore, error) { + return &FileBackingStore{ + BaseBackingStore: scsi.BaseBackingStore{ + Name: "file", + DataSize: 0, + OflagsSupported: 0, + }, }, nil } -func (bs *FileBackingStore) Open(dev *SCSILu, path string, fd *int, size *uint64) error { +func (bs *FileBackingStore) Open(dev *api.SCSILu, path string) (*os.File, error) { + f, err := os.OpenFile(path, os.O_RDWR, os.ModePerm) + if err != nil { + return nil, err + } + return f, nil +} + +func (bs *FileBackingStore) Close(dev *api.SCSILu) error { + return dev.File.Close() +} + +func (bs *FileBackingStore) Init(dev *api.SCSILu, Opts string) error { return nil } -func (bs *FileBackingStore) Close(dev *SCSILu) error { +func (bs *FileBackingStore) Exit(dev *api.SCSILu) error { return nil } -func (bs *FileBackingStore) Init(dev *SCSILu, Opts string) error { - return nil -} +func (bs *FileBackingStore) CommandSubmit(cmd *api.SCSICommand) (err error) { + var ( + scb = cmd.SCB.Bytes() + offset = cmd.Offset + opcode = api.SCSICommandType(scb[0]) + lu = cmd.Device + key = scsi.ILLEGAL_REQUEST + asc = scsi.ASC_INVALID_FIELD_IN_CDB + wbuf []byte = []byte{} + rbuf []byte = []byte{} + length int + doVerify bool = false + doWrite bool = false + ) + switch opcode { + case api.ORWRITE_16: + tmpbuf := []byte{} + length, err = lu.File.ReadAt(tmpbuf, int64(offset)) + if length != len(tmpbuf) { + key = scsi.MEDIUM_ERROR + asc = scsi.ASC_READ_ERROR + break + } + cmd.InSDBBuffer.Buffer = bytes.NewBuffer(tmpbuf) -func (bs *FileBackingStore) Exit(dev *SCSILu) error { - return nil -} + wbuf = cmd.OutSDBBuffer.Buffer.Bytes() + doWrite = true + goto write + case api.COMPARE_AND_WRITE: + // TODO + doWrite = true + goto write + case api.SYNCHRONIZE_CACHE, api.SYNCHRONIZE_CACHE_16: + break + case api.WRITE_VERIFY, api.WRITE_VERIFY_12, api.WRITE_VERIFY_16: + doVerify = true + case api.WRITE_6, api.WRITE_10, api.WRITE_12, api.WRITE_16: + wbuf = cmd.OutSDBBuffer.Buffer.Bytes() + doWrite = true + goto write + case api.WRITE_SAME, api.WRITE_SAME_16: + // TODO + break + case api.READ_6, api.READ_10, api.READ_12, api.READ_16: + length, err = lu.File.ReadAt(rbuf, int64(offset)) + if err != nil { + key = scsi.MEDIUM_ERROR + asc = scsi.ASC_READ_ERROR + break + } + for i := 0; i < int(cmd.TL)-length; i++ { + rbuf = append(rbuf, 0) + } + + if (opcode != api.READ_6) && (scb[1]&0x10 != 0) { + util.Fadvise(lu.File, int64(offset), int64(length), util.POSIX_FADV_NOREUSE) + } + cmd.InSDBBuffer.Buffer = bytes.NewBuffer(rbuf) + case api.PRE_FETCH_10, api.PRE_FETCH_16: + err = util.Fadvise(lu.File, int64(offset), int64(cmd.TL), util.POSIX_FADV_WILLNEED) + if err != nil { + key = scsi.MEDIUM_ERROR + asc = scsi.ASC_READ_ERROR + } + case api.VERIFY_10, api.VERIFY_12, api.VERIFY_16: + doVerify = true + goto verify + case api.UNMAP: + // TODO + default: + break + } +write: + if doWrite { + // hack: wbuf = []byte("hello world!") + length, err = lu.File.WriteAt(wbuf, int64(offset)) + if err != nil || length != len(wbuf) { + glog.Error(err) + key = scsi.MEDIUM_ERROR + asc = scsi.ASC_READ_ERROR + goto sense + } + glog.V(2).Infof("write data at %d for length %d", offset, length) + var pg *api.ModePage + for _, p := range lu.ModePages { + if p.Pcode == 0x08 && p.SubPcode == 0 { + pg = &p + break + } + } + if pg == nil { + key = scsi.ILLEGAL_REQUEST + asc = scsi.ASC_INVALID_FIELD_IN_CDB + goto sense + } + if ((opcode != api.WRITE_6) && (scb[1]&0x8 != 0)) || (pg.Data[0]&0x04 == 0) { + if err = util.Fdatasync(lu.File); err != nil { + key = scsi.MEDIUM_ERROR + asc = scsi.ASC_READ_ERROR + goto sense + } + } + + if (opcode != api.WRITE_6) && (scb[1]&0x10 != 0) { + util.Fadvise(lu.File, int64(offset), int64(length), util.POSIX_FADV_NOREUSE) + } + } +verify: + if doVerify { + length, err = lu.File.ReadAt(rbuf, int64(offset)) + if length != len(rbuf) { + key = scsi.MEDIUM_ERROR + asc = scsi.ASC_READ_ERROR + goto sense + } + if !bytes.Equal(cmd.OutSDBBuffer.Buffer.Bytes(), rbuf) { + err = fmt.Errorf("verify fail between out buffer and read buffer") + key = scsi.MISCOMPARE + asc = scsi.ASC_MISCOMPARE_DURING_VERIFY_OPERATION + goto sense + } + if scb[1]&0x10 != 0 { + util.Fadvise(lu.File, int64(offset), int64(length), util.POSIX_FADV_WILLNEED) + } + } + glog.Infof("io done %s", string(scb)) +sense: + if err != nil { + glog.Error(err) + return err + } + _ = key + _ = asc -func (bs *FileBackingStore) CommandSubmit(cmd *SCSICommand) error { return nil } diff --git a/pkg/scsi/backingstore/null.go b/pkg/scsi/backingstore/null.go index afb7837..ae5ad8b 100644 --- a/pkg/scsi/backingstore/null.go +++ b/pkg/scsi/backingstore/null.go @@ -17,45 +17,47 @@ limitations under the License. package backingstore import ( - "github.com/gostor/gotgt/pkg/scsi" + "os" - "github.com/golang/glog" + "github.com/gostor/gotgt/pkg/api" + "github.com/gostor/gotgt/pkg/scsi" ) func init() { - scsi.RegisterBackingStore("null", new) + scsi.RegisterBackingStore("null", newNull) } type NullBackingStore struct { - BaseBackingStore + scsi.BaseBackingStore } -func new() (scsi.BackingStore, error) { - return NullBackingStore{ - Name: "null", - DataSize: 0, - OflagsSupported: 0, +func newNull() (api.BackingStore, error) { + return &NullBackingStore{ + BaseBackingStore: scsi.BaseBackingStore{ + Name: "null", + DataSize: 0, + OflagsSupported: 0, + }, }, nil } -func (bs *NullBackingStore) Open(dev *SCSILu, path string, fd *int, size *uint64) error { - glog.V(1).Infof("NULL backing store open, size: %d", size) +func (bs *NullBackingStore) Open(dev *api.SCSILu, path string) (*os.File, error) { + return nil, nil +} + +func (bs *NullBackingStore) Close(dev *api.SCSILu) error { return nil } -func (bs *NullBackingStore) Close(dev *SCSILu) error { +func (bs *NullBackingStore) Init(dev *api.SCSILu, Opts string) error { return nil } -func (bs *NullBackingStore) Init(dev *SCSILu, Opts string) error { +func (bs *NullBackingStore) Exit(dev *api.SCSILu) error { return nil } -func (bs *NullBackingStore) Exit(dev *SCSILu) error { - return nil -} - -func (bs *NullBackingStore) CommandSubmit(cmd *SCSICommand) error { - cmd.Result = SAM_STAT_GOOD +func (bs *NullBackingStore) CommandSubmit(cmd *api.SCSICommand) error { + cmd.Result = api.SAM_STAT_GOOD return nil } diff --git a/pkg/scsi/lun.go b/pkg/scsi/lun.go index 2856a32..1a2849e 100644 --- a/pkg/scsi/lun.go +++ b/pkg/scsi/lun.go @@ -16,19 +16,51 @@ limitations under the License. package scsi -import "github.com/gostor/gotgt/pkg/api" +import ( + "os" -type SCSILuOps struct { - *api.SCSILu + "github.com/gostor/gotgt/pkg/api" +) - DeviceProtocol SCSIDeviceProtocol - Storage *BackingStore - Target *api.SCSITarget - Attrs api.SCSILuPhyAttribute +func NewSCSILu(lun uint64, target *api.SCSITarget) (*api.SCSILu, error) { + sbc := NewSBCDevice() + backing, err := NewBackingStore("file") + if err != nil { + return nil, err + } + var lu = &api.SCSILu{ + Lun: lun, + Target: target, + PerformCommand: luPerformCommand, + DeviceProtocol: sbc, + Storage: backing, + BlockShift: 0, + Size: 1024 * 1024 * 1024, + } + // hack this + if _, err = os.Stat("/tmp/data.txt"); err != nil && os.IsExist(err) { + os.Create("/tmp/data.txt") + } + f, err := backing.Open(lu, "/tmp/data.txt") + if err != nil { + return nil, err + } + lu.File = f + lu.DeviceProtocol.InitLu(lu) + lu.Attrs.Online = true + return lu, nil +} - // function handler for command performing and finishing - PerformCommand CommandFunc - FinishCommand func(*api.SCSITarget, *api.SCSICommand) +func luPerformCommand(tid int, cmd *api.SCSICommand) api.SAMStat { + op := int(cmd.SCB.Bytes()[0]) + fn := cmd.Device.DeviceProtocol.PerformCommand(op) + if fn != nil { + fnop := fn.(SCSIDeviceOperation) + // host := cmd.ITNexus.Host + host := 0 + return fnop.CommandPerformFunc(host, cmd) + } + return api.SAMStatGood } func luPreventRemoval(lu *api.SCSILu) bool { diff --git a/pkg/scsi/sbc.go b/pkg/scsi/sbc.go index f9caa98..30a193c 100644 --- a/pkg/scsi/sbc.go +++ b/pkg/scsi/sbc.go @@ -18,9 +18,13 @@ limitations under the License. package scsi import ( + "bytes" "encoding/binary" + "fmt" + "github.com/golang/glog" "github.com/gostor/gotgt/pkg/api" + "github.com/gostor/gotgt/pkg/util" ) const ( @@ -36,30 +40,75 @@ type SBCSCSIDeviceProtocol struct { BaseSCSIDeviceProtocol } -func (sbc *SBCSCSIDeviceProtocol) InitLu(lu *api.SCSILu) error { - return nil +func (sbc SBCSCSIDeviceProtocol) PerformCommand(opcode int) interface{} { + return sbc.SCSIDeviceOps[opcode] } -func (sbc *SBCSCSIDeviceProtocol) ExitLu(lu *api.SCSILu) error { - return nil -} -func (sbc *SBCSCSIDeviceProtocol) ConfigLu(lu *api.SCSILu) error { - return nil -} -func (sbc *SBCSCSIDeviceProtocol) OnlineLu(lu *api.SCSILu) error { - return nil -} -func (sbc *SBCSCSIDeviceProtocol) OfflineLu(lu *api.SCSILu) error { + +func (sbc SBCSCSIDeviceProtocol) InitLu(lu *api.SCSILu) error { + var tgt = lu.Target + // init LU's phy attribute + lu.Attrs.DeviceType = api.TYPE_DISK + lu.Attrs.Qualifier = false + lu.Attrs.Thinprovisioning = false + lu.Attrs.Removable = false + lu.Attrs.Readonly = false + lu.Attrs.SWP = false + lu.Attrs.SenseFormat = false + lu.Attrs.VendorID = "GOSTOR" + lu.Attrs.SCSIID = fmt.Sprintf("GOSTOR %x%d", tgt.TID, lu.Lun) + lu.Attrs.SCSISN = fmt.Sprintf("beaf%d%d", tgt.TID, lu.Lun) + lu.Attrs.ProductID = "VIRTUAL-DISK" + lu.Attrs.VersionDesction = []uint16{ + 0x04C0, // SBC-3 no version claimed + 0x0960, // iSCSI + 0x0300, // SPC-3 + } + if lu.BlockShift == 0 { + lu.BlockShift = api.DefaultBlockShift + } + pages := []api.ModePage{} + // Vendor uniq - However most apps seem to call for mode page 0 + pages = append(pages, api.ModePage{0, 0, []byte{}}) + // Disconnect page + pages = append(pages, api.ModePage{2, 0, []byte{0x80, 0x80, 0, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}) + // Caching Page + pages = append(pages, api.ModePage{8, 0, []byte{0x14, 0, 0xff, 0xff, 0, 0, 0xff, 0xff, 0xff, 0xff, 0x80, 0x14, 0, 0, 0, 0, 0, 0}}) + + // Control page + pages = append(pages, api.ModePage{0x0a, 0, []byte{2, 0x10, 0, 0, 0, 0, 0, 0, 2, 0}}) + + // Control Extensions mode page: TCMOS:1 + pages = append(pages, api.ModePage{0x0a, 1, []byte{0x04, 0x00, 0x00}}) + // Informational Exceptions Control page + pages = append(pages, api.ModePage{0x1c, 0, []byte{8, 0, 0, 0, 0, 0, 0, 0, 0, 0}}) + lu.ModePages = pages return nil } -func NewSBCDevice() SBCSCSIDeviceProtocol { +func (sbc SBCSCSIDeviceProtocol) ConfigLu(lu *api.SCSILu) error { + return nil +} + +func (sbc SBCSCSIDeviceProtocol) OnlineLu(lu *api.SCSILu) error { + return nil +} + +func (sbc SBCSCSIDeviceProtocol) OfflineLu(lu *api.SCSILu) error { + return nil +} + +func (sbc SBCSCSIDeviceProtocol) ExitLu(lu *api.SCSILu) error { + return nil +} + +func NewSBCDevice() api.SCSIDeviceProtocol { var sbc = SBCSCSIDeviceProtocol{ BaseSCSIDeviceProtocol{ Type: api.TYPE_DISK, - SCSIDeviceOps: make([]SCSIDeviceOperation, 256), + SCSIDeviceOps: []SCSIDeviceOperation{}, }, } - for i := 0; i <= 256; i++ { + for i := 0; i < 256; i++ { sbc.SCSIDeviceOps = append(sbc.SCSIDeviceOps, NewSCSIDeviceOperation(SPCIllegalOp, nil, 0)) } sbc.SCSIDeviceOps[api.TEST_UNIT_READY] = NewSCSIDeviceOperation(SPCTestUnit, nil, 0) @@ -103,9 +152,10 @@ func NewSBCDevice() SBCSCSIDeviceProtocol { sbc.SCSIDeviceOps[api.PRE_FETCH_16] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN) sbc.SCSIDeviceOps[api.SYNCHRONIZE_CACHE_16] = NewSCSIDeviceOperation(SBCSyncCache, nil, PR_EA_FA|PR_EA_FN|PR_WE_FA|PR_WE_FN) sbc.SCSIDeviceOps[api.WRITE_SAME_16] = NewSCSIDeviceOperation(SBCReadWrite, nil, 0) - sbc.SCSIDeviceOps[api.SERVICE_ACTION_IN] = NewSCSIDeviceOperation(SPCServiceAction, nil, 0) + sbc.SCSIDeviceOps[api.SERVICE_ACTION_IN] = NewSCSIDeviceOperation(SBCServiceAction, nil, 0) sbc.SCSIDeviceOps[api.REPORT_LUNS] = NewSCSIDeviceOperation(SPCReportLuns, nil, 0) + sbc.SCSIDeviceOps[api.MAINT_PROTOCOL_IN] = NewSCSIDeviceOperation(SPCServiceAction, nil, 0) sbc.SCSIDeviceOps[api.EXCHANGE_MEDIUM] = NewSCSIDeviceOperation(SPCServiceAction, nil, 0) sbc.SCSIDeviceOps[api.READ_12] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN) sbc.SCSIDeviceOps[api.WRITE_12] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_WE_FA|PR_EA_FA|PR_WE_FA|PR_WE_FN) @@ -132,15 +182,18 @@ func SBCModeSense(host int, cmd *api.SCSICommand) api.SAMStat { deviceSpecific |= 0x80 } - data := cmd.InSDBBuffer.Buffer - data.Next(2) + buf := cmd.InSDBBuffer.Buffer + data := []byte{0x00, 0x00, 0x00, 0x00} + if buf != nil { + data = buf.Bytes() + } if cmd.SCB.Bytes()[0] == 0x1a { - data.WriteByte(deviceSpecific) + data[2] = deviceSpecific } else { - data.Next(1) - data.WriteByte(deviceSpecific) + data[3] = deviceSpecific } + cmd.InSDBBuffer.Buffer = bytes.NewBuffer(data) return api.SAMStatGood } @@ -196,7 +249,135 @@ func SBCUnmap(host int, cmd *api.SCSICommand) api.SAMStat { } func SBCReadWrite(host int, cmd *api.SCSICommand) api.SAMStat { - return api.SAMStatGood + var ( + key = ILLEGAL_REQUEST + asc = ASC_INVALID_FIELD_IN_CDB + dev = cmd.Device + scb = cmd.SCB.Bytes() + opcode = api.SCSICommandType(scb[0]) + lba uint64 + tl uint32 + err error + ) + if dev.Attrs.Removable && !dev.Attrs.Online { + key = NOT_READY + asc = ASC_MEDIUM_NOT_PRESENT + glog.Warningf("sense") + goto sense + } + + switch opcode { + case api.READ_10, api.READ_12, api.READ_16, api.WRITE_10, api.WRITE_12, api.WRITE_16, api.ORWRITE_16, + api.WRITE_VERIFY, api.WRITE_VERIFY_12, api.WRITE_VERIFY_16, api.COMPARE_AND_WRITE: + // We only support protection information type 0 + /* + if scb[1]&0xe0 != 0 { + key = ILLEGAL_REQUEST + asc = ASC_INVALID_FIELD_IN_CDB + glog.Warningf("sense") + goto sense + } + */ + if cmd.OutSDBBuffer.Buffer == nil { + cmd.OutSDBBuffer.Buffer = &bytes.Buffer{} + } + case api.WRITE_SAME, api.WRITE_SAME_16: + // We dont support resource-provisioning so ANCHOR bit == 1 is an error. + if scb[1]&0x10 != 0 { + key = ILLEGAL_REQUEST + asc = ASC_INVALID_FIELD_IN_CDB + goto sense + } + // We only support unmap for thin provisioned LUNS + if (scb[1]&0x08 != 0) && !dev.Attrs.Thinprovisioning { + key = ILLEGAL_REQUEST + asc = ASC_INVALID_FIELD_IN_CDB + goto sense + } + // We only support protection information type 0 + if scb[1]&0xe0 != 0 { + key = ILLEGAL_REQUEST + asc = ASC_INVALID_FIELD_IN_CDB + goto sense + } + // LBDATA and PBDATA can not both be set + if (scb[1] & 0x06) == 0x06 { + key = ILLEGAL_REQUEST + asc = ASC_INVALID_FIELD_IN_CDB + goto sense + } + } + + if dev.Attrs.Readonly || dev.Attrs.SWP { + switch opcode { + case api.WRITE_6, api.WRITE_10, api.WRITE_12, api.WRITE_16, api.ORWRITE_16, + api.WRITE_VERIFY, api.WRITE_VERIFY_12, api.WRITE_VERIFY_16, api.WRITE_SAME, api.WRITE_SAME_16, + api.PRE_FETCH_10, api.PRE_FETCH_16, api.COMPARE_AND_WRITE: + key = DATA_PROTECT + asc = ASC_WRITE_PROTECT + glog.Warningf("sense") + goto sense + } + } + + lba = getSCSIReadWriteOffset(scb) + tl = getSCSIReadWriteCount(scb) + + // Verify that we are not doing i/o beyond the end-of-lun + if tl != 0 { + if lba+uint64(tl) < lba || lba+uint64(tl) > dev.Size>>dev.BlockShift { + key = ILLEGAL_REQUEST + asc = ASC_LBA_OUT_OF_RANGE + glog.Warningf("sense: lba: %d, tl: %d, size: %d", lba, tl, dev.Size>>dev.BlockShift) + goto sense + } + } else { + if lba >= dev.Size>>dev.BlockShift { + key = ILLEGAL_REQUEST + asc = ASC_LBA_OUT_OF_RANGE + glog.Warningf("sense") + goto sense + } + } + + cmd.Offset = lba << dev.BlockShift + cmd.TL = tl << dev.BlockShift + + // Handle residuals + switch opcode { + case api.READ_6, api.READ_10, api.READ_12, api.READ_16: + /* + if (cmd->tl != scsi_get_in_length(cmd)) + scsi_set_in_resid_by_actual(cmd, cmd->tl); + */ + case api.WRITE_6, api.WRITE_10, api.WRITE_12, api.WRITE_16, api.WRITE_VERIFY, api.WRITE_VERIFY_12, api.WRITE_VERIFY_16: + /* + if (cmd->tl != scsi_get_out_length(cmd)) { + scsi_set_out_resid_by_actual(cmd, cmd->tl); + + /* We need to clamp the size of the in-buffer + * so that we dont try to write > cmd->tl in the + * backend store. + * + if (cmd->tl < scsi_get_out_length(cmd)) { + scsi_set_out_length(cmd, cmd->tl); + } + } + */ + } + + err = dev.Storage.CommandSubmit(cmd) + if err != nil { + glog.Error(err) + key = HARDWARE_ERROR + asc = ASC_INTERNAL_TGT_FAILURE + } else { + return api.SAMStatGood + } + +sense: + BuildSenseData(cmd, key, asc) + return api.SAMStatCheckCondition } func SBCReserve(host int, cmd *api.SCSICommand) api.SAMStat { @@ -224,7 +405,7 @@ func SBCReadCapacity(host int, cmd *api.SCSICommand) api.SAMStat { scb = cmd.SCB.Bytes() key = ILLEGAL_REQUEST asc = ASC_LUN_NOT_SUPPORTED - data = cmd.InSDBBuffer.Buffer + data = &bytes.Buffer{} bshift = cmd.Device.BlockShift size = cmd.Device.Size >> bshift ) @@ -240,9 +421,11 @@ func SBCReadCapacity(host int, cmd *api.SCSICommand) api.SAMStat { goto sense } - if cmd.InSDBBuffer.Length < 8 { - goto overflow - } + /* + if cmd.InSDBBuffer.Length < 8 { + goto overflow + } + */ // data[0] = (size >> 32) ? __cpu_to_be32(0xffffffff) : __cpu_to_be32(size - 1); if size>>32 != 0 { @@ -253,8 +436,9 @@ func SBCReadCapacity(host int, cmd *api.SCSICommand) api.SAMStat { // data[1] = __cpu_to_be32(1U << bshift); binary.Write(data, binary.BigEndian, uint32(1<> 6 + subpcode = scb[3] + blkDesctionLen = 0 + key = ILLEGAL_REQUEST + asc = ASC_INVALID_FIELD_IN_CDB + ) + if dbd == 0 { + blkDesctionLen = 8 + } + if pctrl == 3 { + asc = ASC_SAVING_PARMS_UNSUP + goto sense + } + _ = dbd + _ = pcode + _ = subpcode + _ = mode6 + _ = blkDesctionLen return api.SAMStatGood +sense: + BuildSenseData(cmd, key, asc) + return api.SAMStatCheckCondition } func SPCSendDiagnostics(host int, cmd *api.SCSICommand) api.SAMStat { @@ -263,10 +324,93 @@ sense: return api.SAMStatCheckCondition } +func getSCSICmdSize(opcode api.SCSICommandType) byte { + var scsi_command_size = []byte{6, 10, 10, 12, 16, 12, 10, 10} + + return scsi_command_size[(byte(opcode)>>5)&7] +} + +func reportOpcodesAll(cmd *api.SCSICommand, rctd int) error { + var ( + data = []byte{0x00, 0x00, 0x00, 0x00} + ) + for _, i := range []api.SCSICommandType{api.TEST_UNIT_READY, api.WRITE_6, api.INQUIRY, api.READ_CAPACITY, api.WRITE_10, api.WRITE_16, api.REPORT_LUNS, api.WRITE_12} { + data = append(data, byte(i)) + // reserved + data = append(data, 0x00) + // service action + data = append(data, 0x00) + data = append(data, 0x00) + // reserved + data = append(data, 0x00) + // flags : no service action, possibly timeout desc + if rctd != 0 { + data = append(data, 0x02) + } else { + data = append(data, 0x00) + } + // cdb length + length := getSCSICmdSize(i) + data = append(data, (length>>8)&0xff) + data = append(data, length&0xff) + // timeout descriptor + if rctd != 0 { + // length == 0x0a + data[1] = 0x0a + for n := 0; n < 12; n++ { + data = append(data, 0x00) + } + } + } + buf := util.MarshalUint32(uint32(len(data) - 4)) + buf = append(buf, data[4:]...) + cmd.InSDBBuffer.Buffer = bytes.NewBuffer(buf) + return nil +} + +func reportOpcodeOne(cmd *api.SCSICommand, rctd int, opcode byte, rsa uint16, serviceAction bool) error { + return nil +} + // This is useful for the various commands using the SERVICE ACTION format. func SPCServiceAction(host int, cmd *api.SCSICommand) api.SAMStat { // TODO + scb := cmd.SCB.Bytes() + reporting_options := scb[2] & 0x07 + opcode := scb[3] + rctd := int(scb[2] & 0x80) + rsa := util.GetUnalignedUint16(scb[4:]) + switch reporting_options { + case 0x00: /* report all */ + glog.V(3).Infof("Service Action: report all") + err := reportOpcodesAll(cmd, rctd) + if err != nil { + glog.Error(err) + goto sense + } + case 0x01: /* report one no service action*/ + glog.V(3).Infof("Service Action: report one no service action") + err := reportOpcodeOne(cmd, rctd, opcode, rsa, false) + if err != nil { + glog.Error(err) + goto sense + } + case 0x02: /* report one service action */ + glog.V(3).Infof("Service Action: report one service action") + err := reportOpcodeOne(cmd, rctd, opcode, rsa, true) + if err != nil { + glog.Error(err) + goto sense + } + default: + goto sense + } return api.SAMStatGood + +sense: + cmd.InSDBBuffer.Resid = 0 + BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB) + return api.SAMStatCheckCondition } func SPCPRReadKeys(host int, cmd *api.SCSICommand) api.SAMStat { @@ -360,6 +504,7 @@ func SPCRequestSense(host int, cmd *api.SCSICommand) api.SAMStat { var ( allocationLength uint32 actualLength uint32 + data = &bytes.Buffer{} ) allocationLength = util.GetUnalignedUint32(cmd.SCB.Bytes()[4:8]) @@ -372,8 +517,11 @@ func SPCRequestSense(host int, cmd *api.SCSICommand) api.SAMStat { } else { actualLength = allocationLength } - binary.Write(cmd.InSDBBuffer.Buffer, binary.BigEndian, cmd.SenseBuffer.Bytes()[0:actualLength]) + if cmd.SenseBuffer != nil { + data.Write(cmd.SenseBuffer.Bytes()[0:actualLength]) + } cmd.InSDBBuffer.Resid = int32(actualLength) + cmd.InSDBBuffer.Buffer = data // reset sense buffer in cmnd cmd.SenseBuffer = &bytes.Buffer{} diff --git a/pkg/scsi/target.go b/pkg/scsi/target.go index a7a46f8..4dda242 100644 --- a/pkg/scsi/target.go +++ b/pkg/scsi/target.go @@ -21,18 +21,28 @@ import ( "github.com/golang/glog" "github.com/gostor/gotgt/pkg/api" - "github.com/gostor/gotgt/pkg/port" ) -func NewTarget(tid int, driverName, name string) (*api.SCSITarget, error) { +func (s *SCSITargetService) NewSCSITarget(tid int, driverName, name string) (*api.SCSITarget, error) { // verify the target ID // verify the target's Name // verify the low level driver - var target = &api.SCSITarget{Name: name, TID: tid} - var tgt = port.NewTargetDriver(driverName, target) - target.SCSITargetDriver = tgt + var target = &api.SCSITarget{ + Name: name, + TID: tid, + Devices: []*api.SCSILu{}, + } + lun, err := NewSCSILu(0, target) + if err != nil { + glog.Errorf("fail to create LU: %v", err) + return nil, err + } + s.mutex.Lock() + target.Devices = append(target.Devices, lun) + s.Targets = append(s.Targets, target) + s.mutex.Unlock() return target, nil } @@ -40,7 +50,7 @@ func deviceReserve(cmd *api.SCSICommand) error { var lu *api.SCSILu for _, dev := range cmd.Target.Devices { if dev.Lun == cmd.Device.Lun { - lu = &dev + lu = dev break } } diff --git a/pkg/util/util.go b/pkg/util/util.go index 33b5bce..78ebe24 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -1,5 +1,5 @@ /* -Copyright 2015 The GoStor Authors All rights reserved. +Copyright 2016 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. @@ -16,7 +16,16 @@ limitations under the License. package util -import "encoding/binary" +import ( + "encoding/binary" + "os" + "syscall" +) + +type KeyValue struct { + Key string + Value string +} func GetUnalignedUint16(u8 []uint8) uint16 { return binary.BigEndian.Uint16(u8) @@ -52,13 +61,49 @@ func ParseKVText(txt []byte) map[string]string { return m } -func MarshalKVText(kv map[string]string) []byte { +func MarshalKVText(kv []KeyValue) []byte { var data []byte - for k, v := range kv { - data = append(data, []byte(k)...) + for _, v := range kv { + data = append(data, []byte(v.Key)...) data = append(data, '=') - data = append(data, []byte(v)...) + data = append(data, []byte(v.Value)...) data = append(data, 0) } return data } + +func MarshalUint32(i uint32) []byte { + var data []byte + for j := 0; j < 4; j++ { + b := byte(i >> uint(4*(3-j)) & 0xff) + data = append(data, b) + } + return data +} + +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 +} + +const ( + POSIX_FADV_NORMAL = iota + POSIX_FADV_RANDOM + POSIX_FADV_SEQUENTIAL + POSIX_FADV_WILLNEED + POSIX_FADV_DONTNEED + POSIX_FADV_NOREUSE +) + +func Fadvise(file *os.File, off, length int64, advice uint32) error { + // syscall.SYS_FADVISE64 = 221 + _, _, err := syscall.Syscall6(221, file.Fd(), uintptr(off), uintptr(length), uintptr(advice), 0, 0) + if err != 0 { + return err + } + return nil +} diff --git a/pkg/util/util_darwin.go b/pkg/util/util_darwin.go new file mode 100644 index 0000000..2463f41 --- /dev/null +++ b/pkg/util/util_darwin.go @@ -0,0 +1,27 @@ +// +build darwin +/* +Copyright 2016 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 util + +import ( + "os" + "syscall" +) + +func Fdatasync(file *os.File) error { + return syscall.Fsync(int(file.Fd())) +} diff --git a/pkg/util/util_linux.go b/pkg/util/util_linux.go new file mode 100644 index 0000000..0d46f3f --- /dev/null +++ b/pkg/util/util_linux.go @@ -0,0 +1,27 @@ +// +build linux +/* +Copyright 2016 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 util + +import ( + "os" + "syscall" +) + +func Fdatasync(file *os.File) error { + return syscall.Fdatasync(int(file.Fd())) +}