-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
2,967 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.