feat: Initial comimt
This commit is contained in:
commit
3d97c863d1
10 changed files with 841 additions and 0 deletions
25
README.md
Normal file
25
README.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
# govmtools
|
||||
|
||||
> Reimplementation of [open-vm-tools](https://github.com/vmware/open-vm-tools) in pure Go
|
||||
|
||||
##
|
||||
|
||||
|
||||
## DataMap
|
||||
|
||||
### Field types
|
||||
- empty = 0
|
||||
- int64 = 1
|
||||
- string = 2
|
||||
- int64list = 3
|
||||
- stringlist = 4
|
||||
- max = 5
|
||||
|
||||
### Fields
|
||||
- type = 1
|
||||
- payload = 2
|
||||
- fast_close = 3
|
||||
|
||||
### Types
|
||||
- data = 1
|
||||
- ping = 2
|
38
cmd/govmtoolsd/main.go
Normal file
38
cmd/govmtoolsd/main.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ptrcnull/govmtools"
|
||||
)
|
||||
|
||||
func main() {
|
||||
sock, err := govmtools.NewSocket()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("connected! %#v\n", sock)
|
||||
err = sock.ReportVersionData()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sock.MustSendCommand("vmx.capability.unified_loop toolbox\x00")
|
||||
sock.MustSendCommand("log toolbox: Version: 11.2.5.26209 (build-17337674)\x00")
|
||||
sock.MustSendCommand("tools.capability.statechange ")
|
||||
sock.MustSendCommand("tools.capability.softpowerop_retry ")
|
||||
sock.MustSendCommand("tools.capability.guest_conf_directory /etc/vmware-tools\x00")
|
||||
sock.MustSendCommand("tools.set.versiontype 11333 4\x00")
|
||||
sock.MustSendCommand("info-set guestinfo.ip 10.99.0.6\x00")
|
||||
guestInfoNetwork, err := govmtools.GetGuestInfoNetwork()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sock.SetGuestInfo(govmtools.GuestInfoDnsName, "openldap")
|
||||
sock.SetGuestInfo(govmtools.GuestInfoUptime, "26642699")
|
||||
sock.SetGuestInfo(govmtools.GuestInfoIpAddressV3, guestInfoNetwork)
|
||||
sock.SetGuestInfo(govmtools.GuestInfoBuildNumber, "build-" + govmtools.BuildNumber)
|
||||
sock.MustSendCommand("info-set guestinfo.appInfo \x00")
|
||||
|
||||
select {}
|
||||
}
|
12
go.mod
Normal file
12
go.mod
Normal file
|
@ -0,0 +1,12 @@
|
|||
module github.com/ptrcnull/govmtools
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/ptrcnull/vsock v0.0.0-20211110040213-bace62f83228
|
||||
github.com/stellar/go-xdr v0.0.0-20211103144802-8017fc4bdfee
|
||||
github.com/vishvananda/netlink v1.1.0
|
||||
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3
|
||||
)
|
||||
|
||||
require github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
|
20
go.sum
Normal file
20
go.sum
Normal file
|
@ -0,0 +1,20 @@
|
|||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/ptrcnull/vsock v0.0.0-20211110040213-bace62f83228 h1:ApLO3fuH29Nslm8s0G9tVe8UVG61Yg9ob4J80RxNX5U=
|
||||
github.com/ptrcnull/vsock v0.0.0-20211110040213-bace62f83228/go.mod h1:1bEx6UYqCMOhYlho1+7LgWEJroZcFMSpAt7iesq/uiQ=
|
||||
github.com/stellar/go-xdr v0.0.0-20211103144802-8017fc4bdfee h1:fbVs0xmXpBvVS4GBeiRmAE3Le70ofAqFMch1GTiq/e8=
|
||||
github.com/stellar/go-xdr v0.0.0-20211103144802-8017fc4bdfee/go.mod h1:yoxyU/M8nl9LKeWIoBrbDPQ7Cy+4jxRcWcOayZ4BMps=
|
||||
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w=
|
||||
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
288
network.go
Normal file
288
network.go
Normal file
|
@ -0,0 +1,288 @@
|
|||
package govmtools
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
xdr "github.com/stellar/go-xdr/xdr3"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
func GetGuestNicProto() (*GuestNicProto, error) {
|
||||
res := NicInfoV3{
|
||||
Nics: []GuestNicV3{},
|
||||
Routes: []InetCidrRouteEntry{},
|
||||
DnsConfigInfo: []DnsConfigInfo{},
|
||||
}
|
||||
|
||||
ifaces, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get ifaces: %w", err)
|
||||
}
|
||||
|
||||
ignoredPrefixes := []string{"veth", "docker", "virbr", "lo", "br-"}
|
||||
for _, iface := range ifaces {
|
||||
attrs := iface.Attrs()
|
||||
|
||||
skip := false
|
||||
for _, prefix := range ignoredPrefixes {
|
||||
if strings.HasPrefix(attrs.Name, prefix) {
|
||||
skip = true
|
||||
}
|
||||
}
|
||||
if skip || attrs.HardwareAddr.String() == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
nic := GuestNicV3{
|
||||
MacAddress: attrs.HardwareAddr.String(),
|
||||
Ips: []IpAddressEntry{},
|
||||
}
|
||||
|
||||
addrs, err := netlink.AddrList(iface, netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
fmt.Println("error getting addresses:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(addrs) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
// ugly
|
||||
ip := addr.IP
|
||||
var address InetAddress
|
||||
var addrType InetAddressType
|
||||
if ip.To4() != nil {
|
||||
address = InetAddress(ip.To4())
|
||||
addrType = IatIpv4
|
||||
} else {
|
||||
address = InetAddress(ip.To16())
|
||||
addrType = IatIpv6
|
||||
}
|
||||
prefixLength, _ := addr.Mask.Size()
|
||||
|
||||
entry := IpAddressEntry{
|
||||
IpAddressAddr: TypedIpAddress{
|
||||
IpAddressAddrType: addrType,
|
||||
IpAddressAddr: address,
|
||||
},
|
||||
IpAddressPrefixLength: InetAddressPrefixLength(prefixLength),
|
||||
IpAddressOrigin: nil,
|
||||
IpAddressStatus: nil,
|
||||
}
|
||||
nic.Ips = append(nic.Ips, entry)
|
||||
}
|
||||
|
||||
routes, err := netlink.RouteList(iface, netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
fmt.Println("error getting routes:", err)
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
var addr TypedIpAddress
|
||||
if route.Dst.IP.To4() != nil {
|
||||
addr = TypedIpAddress{
|
||||
IpAddressAddrType: IatIpv4,
|
||||
IpAddressAddr: InetAddress(route.Dst.IP.To4()),
|
||||
}
|
||||
} else {
|
||||
addr = TypedIpAddress{
|
||||
IpAddressAddrType: IatIpv6,
|
||||
IpAddressAddr: InetAddress(route.Dst.IP.To16()),
|
||||
}
|
||||
}
|
||||
routePrefix, _ := route.Dst.Mask.Size()
|
||||
r := InetCidrRouteEntry{
|
||||
InetCidrRouteDest: addr,
|
||||
InetCidrRoutePfxLen: InetAddressPrefixLength(routePrefix),
|
||||
InetCidrRouteNextHop: route.Gw.,
|
||||
InetCidrRouteIfIndex: 0,
|
||||
InetCidrRouteType: 0,
|
||||
InetCidrRouteMetric: 0,
|
||||
}
|
||||
}
|
||||
|
||||
res.Nics = append(res.Nics, nic)
|
||||
}
|
||||
|
||||
return &GuestNicProto{
|
||||
Type: ProtoTypeV3,
|
||||
NicInfoV3: &NicInfoV3Wrapper{
|
||||
NicInfoV3: []NicInfoV3{},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetGuestInfoNetwork() (string, error) {
|
||||
data, err := GetGuestNicProto()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_, err = xdr.Marshal(buf, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func DoStuff() {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
xdr.Marshal(buf, &GuestNicProto{
|
||||
Type: ProtoTypeV3,
|
||||
NicInfoV3: &NicInfoV3Wrapper{
|
||||
NicInfoV3: []NicInfoV3{
|
||||
{
|
||||
Nics: []GuestNicV3{
|
||||
{
|
||||
MacAddress: "00:50:56:9f:41:dd",
|
||||
Ips: []IpAddressEntry{
|
||||
{
|
||||
IpAddressAddr: TypedIpAddress{
|
||||
IpAddressAddrType: IatIpv4,
|
||||
IpAddressAddr: []byte{10, 99, 0, 4},
|
||||
},
|
||||
IpAddressPrefixLength: 16,
|
||||
IpAddressOrigin: nil,
|
||||
IpAddressStatus: []IpAddressStatus{
|
||||
IasPreferred,
|
||||
},
|
||||
},
|
||||
{
|
||||
IpAddressAddr: TypedIpAddress{
|
||||
IpAddressAddrType: IatIpv4,
|
||||
IpAddressAddr: []byte{10, 99, 1, 51},
|
||||
},
|
||||
IpAddressPrefixLength: 16,
|
||||
IpAddressOrigin: nil,
|
||||
IpAddressStatus: []IpAddressStatus{
|
||||
IasPreferred,
|
||||
},
|
||||
},
|
||||
{
|
||||
IpAddressAddr: TypedIpAddress{
|
||||
IpAddressAddrType: IatIpv6,
|
||||
IpAddressAddr: []byte{
|
||||
0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x02, 0x50, 0x56, 0xff,
|
||||
0xfe, 0x9f, 0x41, 0xdd,
|
||||
},
|
||||
},
|
||||
IpAddressPrefixLength: 64,
|
||||
IpAddressOrigin: nil,
|
||||
IpAddressStatus: []IpAddressStatus{
|
||||
IasUnknown,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Routes: []InetCidrRouteEntry{
|
||||
{
|
||||
InetCidrRouteDest: TypedIpAddress{
|
||||
IpAddressAddrType: IatIpv4,
|
||||
IpAddressAddr: []byte{0, 0, 0, 0},
|
||||
},
|
||||
InetCidrRoutePfxLen: 0,
|
||||
InetCidrRouteNextHop: []TypedIpAddress{
|
||||
{
|
||||
IpAddressAddrType: IatIpv4,
|
||||
IpAddressAddr: []byte{10, 99, 0, 1},
|
||||
},
|
||||
},
|
||||
InetCidrRouteIfIndex: 0,
|
||||
InetCidrRouteType: 0,
|
||||
InetCidrRouteMetric: 0,
|
||||
},
|
||||
{
|
||||
InetCidrRouteDest: TypedIpAddress{
|
||||
IpAddressAddrType: IatIpv4,
|
||||
IpAddressAddr: []byte{10, 99, 0, 0},
|
||||
},
|
||||
InetCidrRoutePfxLen: 16,
|
||||
InetCidrRouteNextHop: nil,
|
||||
InetCidrRouteIfIndex: 0,
|
||||
InetCidrRouteType: 0,
|
||||
InetCidrRouteMetric: 0,
|
||||
},
|
||||
{
|
||||
InetCidrRouteDest: TypedIpAddress{
|
||||
IpAddressAddrType: IatIpv6,
|
||||
IpAddressAddr: []byte{
|
||||
0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
},
|
||||
InetCidrRoutePfxLen: 64,
|
||||
InetCidrRouteNextHop: nil,
|
||||
InetCidrRouteIfIndex: 0,
|
||||
InetCidrRouteType: 0,
|
||||
InetCidrRouteMetric: 0x100,
|
||||
},
|
||||
{
|
||||
InetCidrRouteDest: TypedIpAddress{
|
||||
IpAddressAddrType: IatIpv6,
|
||||
IpAddressAddr: []byte{
|
||||
0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x02, 0x50, 0x56, 0xff,
|
||||
0xfe, 0x9f, 0x41, 0xdd,
|
||||
},
|
||||
},
|
||||
InetCidrRoutePfxLen: 128,
|
||||
InetCidrRouteNextHop: nil,
|
||||
InetCidrRouteIfIndex: 0,
|
||||
InetCidrRouteType: 0,
|
||||
InetCidrRouteMetric: 0,
|
||||
},
|
||||
{
|
||||
InetCidrRouteDest: TypedIpAddress{
|
||||
IpAddressAddrType: IatIpv6,
|
||||
IpAddressAddr: []byte{
|
||||
0xff, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
},
|
||||
InetCidrRoutePfxLen: 8,
|
||||
InetCidrRouteNextHop: nil,
|
||||
InetCidrRouteIfIndex: 0,
|
||||
InetCidrRouteType: 0,
|
||||
InetCidrRouteMetric: 256,
|
||||
},
|
||||
},
|
||||
DnsConfigInfo: []DnsConfigInfo{
|
||||
{
|
||||
HostName: []string{"imagepacker"},
|
||||
DomainName: []string{"t2hack.internal"},
|
||||
ServerList: []TypedIpAddress{
|
||||
{
|
||||
IpAddressAddrType: IatIpv4,
|
||||
IpAddressAddr: []byte{10, 99, 0, 8},
|
||||
},
|
||||
{
|
||||
IpAddressAddrType: IatIpv4,
|
||||
IpAddressAddr: []byte{10, 99, 0, 8},
|
||||
},
|
||||
},
|
||||
SearchSuffixes: []string{"t2hack.internal"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
fmt.Println(hex.Dump(buf.Bytes()))
|
||||
ioutil.WriteFile("/home/patrycja/newdata", buf.Bytes(), 0755)
|
||||
|
||||
}
|
140
network_xdr.go
Normal file
140
network_xdr.go
Normal file
|
@ -0,0 +1,140 @@
|
|||
package govmtools
|
||||
|
||||
import (
|
||||
xdr "github.com/stellar/go-xdr/xdr3"
|
||||
)
|
||||
|
||||
type InetAddressType int
|
||||
|
||||
const (
|
||||
IatUnknown InetAddressType = iota
|
||||
IatIpv4
|
||||
IatIpv6
|
||||
IatIpv4Z
|
||||
IatIpv6Z
|
||||
IatDns
|
||||
)
|
||||
|
||||
type IpAddressOrigin int
|
||||
|
||||
const (
|
||||
IaoOther IpAddressOrigin = 1
|
||||
IaoManual IpAddressOrigin = 2
|
||||
IaoDhcp IpAddressOrigin = 4
|
||||
IaoLinkLayer IpAddressOrigin = 5
|
||||
IaoRandom IpAddressOrigin = 6
|
||||
)
|
||||
|
||||
type IpAddressStatus int
|
||||
const (
|
||||
IasPreferred = iota + 1
|
||||
IasDeprecated
|
||||
IasInvalid
|
||||
IasInaccessible
|
||||
IasUnknown
|
||||
IasTentative
|
||||
IasDuplicate
|
||||
IasOptimistic
|
||||
)
|
||||
|
||||
type InetAddress []byte
|
||||
|
||||
type TypedIpAddress struct {
|
||||
IpAddressAddrType InetAddressType
|
||||
IpAddressAddr InetAddress
|
||||
}
|
||||
|
||||
type InetAddressPrefixLength uint
|
||||
|
||||
type IpAddressEntry struct {
|
||||
IpAddressAddr TypedIpAddress
|
||||
IpAddressPrefixLength InetAddressPrefixLength
|
||||
IpAddressOrigin []IpAddressOrigin
|
||||
IpAddressStatus []IpAddressStatus
|
||||
}
|
||||
|
||||
type DnsConfigInfo struct {
|
||||
HostName []string
|
||||
DomainName []string
|
||||
ServerList []TypedIpAddress
|
||||
SearchSuffixes []string
|
||||
}
|
||||
|
||||
type WinsConfigInfo struct {
|
||||
Primary TypedIpAddress
|
||||
Secondary TypedIpAddress
|
||||
}
|
||||
|
||||
type DhcpConfigInfo struct {
|
||||
Enabled bool
|
||||
DhcpSettings []string
|
||||
}
|
||||
|
||||
type GuestNicV3 struct {
|
||||
MacAddress string
|
||||
Ips []IpAddressEntry
|
||||
DnsConfigInfo []DnsConfigInfo
|
||||
WinsConfigInfo []WinsConfigInfo
|
||||
DhcpConfigInfoV4 []DhcpConfigInfo
|
||||
DhcpConfigInfoV6 []DhcpConfigInfo
|
||||
}
|
||||
|
||||
type InetCidrRouteType int
|
||||
|
||||
const (
|
||||
IcrtOther InetCidrRouteType = iota + 1
|
||||
IcrtReject
|
||||
IcrtLocal
|
||||
IcrtRemote
|
||||
)
|
||||
|
||||
type InetCidrRouteEntry struct {
|
||||
InetCidrRouteDest TypedIpAddress
|
||||
InetCidrRoutePfxLen InetAddressPrefixLength
|
||||
InetCidrRouteNextHop []TypedIpAddress
|
||||
InetCidrRouteIfIndex uint32
|
||||
InetCidrRouteType InetCidrRouteType
|
||||
InetCidrRouteMetric uint32
|
||||
}
|
||||
|
||||
type NicInfoV3Wrapper struct {
|
||||
NicInfoV3 []NicInfoV3
|
||||
}
|
||||
|
||||
type NicInfoV3 struct {
|
||||
Nics []GuestNicV3
|
||||
Routes []InetCidrRouteEntry
|
||||
DnsConfigInfo []DnsConfigInfo
|
||||
WinsConfigInfo []WinsConfigInfo
|
||||
DhcpConfigInfoV4 []DhcpConfigInfo
|
||||
DhcpConfigInfoV6 []DhcpConfigInfo
|
||||
}
|
||||
|
||||
type GuestNicProtoType int
|
||||
|
||||
const (
|
||||
ProtoTypeV3 = 3
|
||||
)
|
||||
|
||||
func (GuestNicProtoType) ValidEnum(i int32) bool {
|
||||
return i == ProtoTypeV3
|
||||
}
|
||||
|
||||
type GuestNicProto struct {
|
||||
Type GuestNicProtoType
|
||||
NicInfoV3 *NicInfoV3Wrapper
|
||||
}
|
||||
|
||||
var _ xdr.Union = (*GuestNicProto)(nil)
|
||||
|
||||
func (g GuestNicProto) ArmForSwitch(i int32) (string, bool) {
|
||||
if i != 3 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return "NicInfoV3", true
|
||||
}
|
||||
|
||||
func (g GuestNicProto) SwitchFieldName() string {
|
||||
return "Type"
|
||||
}
|
126
proto.go
Normal file
126
proto.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
package govmtools
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Field struct {
|
||||
Type FieldType
|
||||
ID FieldID
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (s *Socket) WritePacket(pkt []Field) error {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
for _, field := range pkt {
|
||||
err := binary.Write(buf, order, field.Type)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write field type: %w", err)
|
||||
}
|
||||
|
||||
if field.Type == FieldEmpty {
|
||||
continue
|
||||
}
|
||||
|
||||
err = binary.Write(buf, order, field.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write field ID: %w", err)
|
||||
}
|
||||
|
||||
switch field.Type {
|
||||
case FieldInt64:
|
||||
err = binary.Write(buf, order, field.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write int64: %w", err)
|
||||
}
|
||||
case FieldString:
|
||||
val := field.Value.(string)
|
||||
err = binary.Write(buf, order, int32(len(val)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("write string len: %w", err)
|
||||
}
|
||||
|
||||
_, err = buf.WriteString(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write string: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res := make([]byte, buf.Len()+4)
|
||||
order.PutUint32(res, uint32(buf.Len()))
|
||||
copy(res[4:], buf.Bytes())
|
||||
|
||||
_, err := s.conn.Write(res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Socket) ReadPacket() ([]Field, error) {
|
||||
length := make([]byte, 4)
|
||||
_, err := s.conn.Read(length)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read len: %w", err)
|
||||
}
|
||||
|
||||
|
||||
response := make([]byte, order.Uint32(length))
|
||||
_, err = s.conn.Read(response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read: %w", err)
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(response)
|
||||
var fields []Field
|
||||
|
||||
for buf.Len() > 0 {
|
||||
field := Field{}
|
||||
|
||||
err = binary.Read(buf, order, &field.Type)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse field type: %w", err)
|
||||
}
|
||||
|
||||
if field.Type == FieldEmpty {
|
||||
continue
|
||||
}
|
||||
|
||||
err = binary.Read(buf, order, &field.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse field ID: %w", err)
|
||||
}
|
||||
|
||||
switch field.Type {
|
||||
case FieldInt64:
|
||||
var val int64
|
||||
err = binary.Read(buf, order, val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse int64: %w", err)
|
||||
}
|
||||
field.Value = val
|
||||
case FieldString:
|
||||
var length uint32
|
||||
err = binary.Read(buf, order, &length)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse string length: %w", err)
|
||||
}
|
||||
|
||||
byt := make([]byte, length)
|
||||
_, err := buf.Read(byt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse string: %w", err)
|
||||
}
|
||||
field.Value = string(byt)
|
||||
}
|
||||
|
||||
fields = append(fields, field)
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
49
rpc.go
Normal file
49
rpc.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package govmtools
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var order = binary.BigEndian
|
||||
|
||||
func (s *Socket) RpcSend(data []byte) ([]byte, error) {
|
||||
// TODO error handling
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
binary.Write(buf, order, FieldInt64)
|
||||
binary.Write(buf, order, FieldIDType)
|
||||
binary.Write(buf, order, PacketTypeData)
|
||||
|
||||
binary.Write(buf, order, FieldEmpty)
|
||||
|
||||
binary.Write(buf, order, FieldString)
|
||||
binary.Write(buf, order, FieldIDPayload)
|
||||
binary.Write(buf, order, uint32(len(data)))
|
||||
|
||||
buf.Write(data)
|
||||
|
||||
res := make([]byte, buf.Len()+4)
|
||||
order.PutUint32(res, uint32(buf.Len()))
|
||||
copy(res[4:], buf.Bytes())
|
||||
|
||||
_, err := s.conn.Write(res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("write: %w", err)
|
||||
}
|
||||
|
||||
responseLength := make([]byte, 4)
|
||||
_, err = s.conn.Read(responseLength)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read len: %w", err)
|
||||
}
|
||||
|
||||
response := make([]byte, order.Uint32(responseLength))
|
||||
_, err = s.conn.Read(response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read: %w", err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
137
socket.go
Normal file
137
socket.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
package govmtools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ptrcnull/vsock"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type PacketType uint32
|
||||
const (
|
||||
PacketTypeData PacketType = iota + 1
|
||||
PacketTypePing
|
||||
)
|
||||
|
||||
type FieldID uint32
|
||||
const (
|
||||
FieldIDType FieldID = iota + 1
|
||||
FieldIDPayload
|
||||
FieldIDFastClose
|
||||
)
|
||||
|
||||
type FieldType uint32
|
||||
const (
|
||||
FieldEmpty FieldType = iota
|
||||
FieldInt64
|
||||
FieldString
|
||||
FieldInt64List
|
||||
FieldStringList
|
||||
FieldMax
|
||||
)
|
||||
|
||||
type GuestInfoType uint32
|
||||
const (
|
||||
GuestInfoError GuestInfoType = iota
|
||||
GuestInfoDnsName
|
||||
GuestInfoIpAddress
|
||||
GuestInfoDiskFreeSpace
|
||||
GuestInfoBuildNumber
|
||||
GuestInfoOsNameFull
|
||||
GuestInfoOsName
|
||||
GuestInfoUptime
|
||||
GuestInfoMemory
|
||||
GuestInfoIpAddressV2
|
||||
GuestInfoIpAddressV3
|
||||
GuestInfoOsDetailed
|
||||
GuestInfoMax
|
||||
)
|
||||
|
||||
const GuestConnectPort = 976
|
||||
const ContextID = unix.VMADDR_CID_HYPERVISOR
|
||||
|
||||
type Socket struct {
|
||||
conn *vsock.Conn
|
||||
}
|
||||
|
||||
func NewSocket() (*Socket, error) {
|
||||
conn, err := vsock.Dial(ContextID, GuestConnectPort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connect: %w", err)
|
||||
}
|
||||
|
||||
return &Socket{
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Socket) SendCommand(cmd string) error {
|
||||
err := s.WritePacket([]Field{
|
||||
{
|
||||
Type: FieldInt64,
|
||||
ID: FieldIDType,
|
||||
Value: PacketTypeData,
|
||||
},
|
||||
{
|
||||
Type: FieldEmpty,
|
||||
},
|
||||
{
|
||||
Type: FieldString,
|
||||
ID: FieldIDPayload,
|
||||
Value: cmd,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("write packet: %w", err)
|
||||
}
|
||||
|
||||
res, err := s.ReadPacket()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read packet: %w", err)
|
||||
}
|
||||
if len(res) != 1 {
|
||||
return fmt.Errorf("unexpected response length: %d", len(res))
|
||||
}
|
||||
field := res[0]
|
||||
if field.Type != FieldString {
|
||||
return fmt.Errorf("unexpected response type: %d", field.Type)
|
||||
}
|
||||
if field.ID != FieldIDPayload {
|
||||
return fmt.Errorf("unexpected response id: %d", field.ID)
|
||||
}
|
||||
|
||||
response := field.Value.(string)
|
||||
if response != "1 " {
|
||||
return fmt.Errorf("unexpected response: %s", response)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Socket) SetGuestInfo(messageType GuestInfoType, message string) {
|
||||
s.MustSendCommand(fmt.Sprintf("SetGuestInfo %d %s", messageType, message))
|
||||
}
|
||||
|
||||
func (s *Socket) MustSendCommand(cmd string) {
|
||||
err := s.SendCommand(cmd)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Socket) ReportVersionData() error {
|
||||
data := []string{
|
||||
"description " + Name + " " + Version + " build " + BuildNumber,
|
||||
"versionString " + Version,
|
||||
"versionNumber " + VersionNumber,
|
||||
"buildNumber " + BuildNumber,
|
||||
}
|
||||
|
||||
for _, value := range data {
|
||||
err := s.SendCommand("info-set guestinfo.vmtools." + value + "\x00")
|
||||
if err != nil {
|
||||
return fmt.Errorf("send: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
6
version.go
Normal file
6
version.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package govmtools
|
||||
|
||||
const Name = "open-vm-tools"
|
||||
const Version = "11.2.5"
|
||||
const VersionNumber = "11333"
|
||||
const BuildNumber = "17337674"
|
Loading…
Reference in a new issue