Skip to content

Commit

Permalink
Add files
Browse files Browse the repository at this point in the history
  • Loading branch information
k-sone committed Jan 26, 2019
1 parent cb02ecf commit 7191022
Show file tree
Hide file tree
Showing 18 changed files with 2,967 additions and 1 deletion.
57 changes: 56 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,56 @@
# ipmigo
ipmigo
======

**Work In Progress**

ipmigo is a golang implementation for IPMI client.

Supported Version
-----------------

* IPMI v2.0(lanplus)

Examples
--------

```go
package main

import (
"fmt"

"github.com/k-sone/ipmigo"
)

func main() {
c, err := ipmigo.NewClient(ipmigo.Arguments{
Version: ipmigo.V2_0,
Address: "192.168.1.1:623",
Username: "myuser",
Password: "mypass",
CipherSuiteID: 3,
})
if err != nil {
fmt.Println(err)
return
}

if err := c.Open(); err != nil {
fmt.Println(err)
return
}
defer c.Close()

cmd := &ipmigo.GetPOHCounterCommand{}
if err := c.Execute(cmd); err != nil {
fmt.Println(err)
return
}
fmt.Println("Power On Hours", cmd.PowerOnHours())
}
```

License
-------

MIT
170 changes: 170 additions & 0 deletions asf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package ipmigo

import (
"encoding/binary"
"encoding/hex"
"fmt"
"net"
"time"
)

const (
asfHeaderSize = 8
pongBodySize = 16
asfIANA = 0x000011be
)

type asfType uint8

const (
asfTypePing = 0x80
asfTypePong = 0x40
)

func (t asfType) String() string {
switch t {
case asfTypePing:
return "Ping"
case asfTypePong:
return "Pong"
default:
return fmt.Sprintf("Unknown(%d)", t)
}
}

type asfHeader struct {
IANA uint32
Type asfType
Tag uint8
Reserved uint8
Length uint8
}

func (a *asfHeader) Marshal() ([]byte, error) {
buf := make([]byte, asfHeaderSize)
binary.BigEndian.PutUint32(buf, a.IANA)
buf[4] = byte(a.Type)
buf[5] = a.Tag
buf[6] = a.Reserved
buf[7] = a.Length
return buf, nil
}

func (a *asfHeader) Unmarshal(buf []byte) ([]byte, error) {
if len(buf) < asfHeaderSize {
return nil, &MessageError{
Message: fmt.Sprintf("Invalid ASF header size : %d", len(buf)),
Detail: hex.EncodeToString(buf),
}
}

a.IANA = binary.BigEndian.Uint32(buf)
a.Type = asfType(buf[4])
a.Tag = buf[5]
a.Reserved = buf[6]
a.Length = buf[7]

return buf[8:], nil
}

func (a *asfHeader) String() string {
return fmt.Sprintf(
`{"IANA":%d,"Type":"%s","Tag":%d,"Reserved":%d,"Length":%d}`,
a.IANA, a.Type, a.Tag, a.Reserved, a.Length)
}

// RMCP/ASF Ping Message (Section 13.2.3)
type pingMessage struct {
RMCPHeader *rmcpHeader
ASFHeader *asfHeader
}

func (p *pingMessage) Marshal() ([]byte, error) {
buf1, err := p.RMCPHeader.Marshal()
if err != nil {
return nil, err
}
buf2, err := p.ASFHeader.Marshal()
if err != nil {
return nil, err
}
return append(buf1, buf2...), nil
}

func (p *pingMessage) String() string {
return fmt.Sprintf(`{"RMCPHeader":%s,"ASFHeader":%s}`, p.RMCPHeader, p.ASFHeader)
}

func newPingMessage() *pingMessage {
return &pingMessage{
RMCPHeader: &rmcpHeader{
Version: rmcpVersion1,
Sequence: rmcpNoAckSeq,
Class: rmcpClassASF,
},
ASFHeader: &asfHeader{
IANA: asfIANA,
Type: asfTypePing,
},
}
}

// RMCP/ASF Pong Message (Section 13.2.4)
type pongMessage struct {
rmcpHeader *rmcpHeader
asfHeader *asfHeader
IANA uint32
OEM uint32
SupEntities uint8
SupInteract uint8
Reserved [6]byte
}

func (p *pongMessage) SupportedIPMI() bool {
return p.SupEntities&0x80 != 0
}

func (p *pongMessage) Unmarshal(buf []byte) ([]byte, error) {
if len(buf) < pongBodySize {
return nil, &MessageError{
Message: fmt.Sprintf("Invalid Pong body size : %d", len(buf)),
Detail: hex.EncodeToString(buf),
}
}

p.IANA = binary.BigEndian.Uint32(buf)
p.OEM = binary.BigEndian.Uint32(buf[4:])
p.SupEntities = buf[8]
p.SupInteract = buf[9]
copy(p.Reserved[:], buf[10:])

return buf[pongBodySize:], nil
}

func (p *pongMessage) String() string {
return fmt.Sprintf(
`{"RMCPHeader":%s,"ASFHeader":%s,"IANA":%d,"OEM":%d,`+
`"SupEntities":%d,"SupInteract":%d,"Reserved":"%s"}`,
p.rmcpHeader, p.asfHeader, p.IANA, p.OEM, p.SupEntities, p.SupInteract,
hex.EncodeToString(p.Reserved[:]))
}

func ping(conn net.Conn, timeout time.Duration) error {
res, _, err := sendMessage(conn, newPingMessage(), timeout)
if err != nil {
return err
}

pong, ok := res.(*pongMessage)
if !ok {
return &MessageError{
Message: "Received an unexpected message (Ping)",
Detail: res.String(),
}
}
if !pong.SupportedIPMI() {
return ErrNotSupportedIPMI
}

return nil
}
141 changes: 141 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package ipmigo

import (
"fmt"
"time"
)

type Version int

const (
V1_5 Version = iota + 1
V2_0
)

// Channel Privilege Levels. (Section 6.8)
type PrivilegeLevel uint8

const (
PrivilegeCallback PrivilegeLevel = iota + 1
PrivilegeUser
PrivilegeOperator
PrivilegeAdministrator
)

func (p PrivilegeLevel) String() string {
switch p {
case PrivilegeCallback:
return "CALLBACK"
case PrivilegeUser:
return "USER"
case PrivilegeOperator:
return "OPERATOR"
case PrivilegeAdministrator:
return "ADMINISTRATOR"
default:
return fmt.Sprintf("Unknown(%d)", p)
}
}

// An argument for creating an IPMI Client
type Arguments struct {
Version Version // IPMI version to use
Network string // See net.Dial parameter (The default is `udp`)
Address string // See net.Dial parameter
Timeout time.Duration // Each connect/read-write timeout (The default is 5sec)
Retries uint // Number of retries (The default is `0`)
Username string // Remote server username
Password string // Remote server password
PrivilegeLevel PrivilegeLevel // Session privilege level (The default is `Administrator`)
CipherSuiteID uint // ID of cipher suite, See Table 22-20 (The default is `0` which no auth and no encrypt)
}

func (a *Arguments) setDefault() {
if a.Version == 0 {
a.Version = V2_0
}
if a.Network == "" {
a.Network = "udp"
}
if a.Timeout == 0 {
a.Timeout = 5 * time.Second
}
if a.PrivilegeLevel == 0 {
a.PrivilegeLevel = PrivilegeAdministrator
}
}

func (a *Arguments) validate() error {
switch a.Version {
case V2_0:
if len(a.Password) > passwordMaxLengthV2_0 {
return &ArgumentError{
Value: a.Password,
Message: "Password is too long",
}
}
if a.CipherSuiteID < 0 || a.CipherSuiteID > uint(len(cipherSuiteIDs)-1) {
return &ArgumentError{
Value: a.CipherSuiteID,
Message: "Invalid Cipher Suite ID",
}
}
if a.CipherSuiteID > 3 {
return &ArgumentError{
Value: a.CipherSuiteID,
Message: "Unsupported Cipher Suite ID in ipmigo",
}
}
case V1_5:
// TODO Support v1.5 ?
fallthrough
default:
return &ArgumentError{
Value: a.Version,
Message: "Unsupported IPMI version",
}
}

if a.PrivilegeLevel < 0 || a.PrivilegeLevel > PrivilegeAdministrator {
return &ArgumentError{
Value: a.PrivilegeLevel,
Message: "Invalid Privilege Level",
}
}

if len(a.Username) > userNameMaxLength {
return &ArgumentError{
Value: a.Username,
Message: "Username is too long",
}
}

return nil
}

// IPMI Client
type Client struct {
session session
}

func (c *Client) Ping() error { return c.session.Ping() }
func (c *Client) Open() error { return c.session.Open() }
func (c *Client) Close() error { return c.session.Close() }
func (c *Client) Execute(cmd Command) error { return c.session.Execute(cmd) }

// Create an IPMI Client
func NewClient(args Arguments) (*Client, error) {
if err := args.validate(); err != nil {
return nil, err
}
args.setDefault()

var s session
switch args.Version {
case V1_5:
s = newSessionV1_5(&args)
case V2_0:
s = newSessionV2_0(&args)
}
return &Client{session: s}, nil
}
Loading

0 comments on commit 7191022

Please sign in to comment.