commit 3d97c863d1f31651d55edeb9f0f9ef8ad9efde9c Author: ptrcnull Date: Wed Nov 10 09:06:53 2021 +0100 feat: Initial comimt diff --git a/README.md b/README.md new file mode 100644 index 0000000..91c2156 --- /dev/null +++ b/README.md @@ -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 \ No newline at end of file diff --git a/cmd/govmtoolsd/main.go b/cmd/govmtoolsd/main.go new file mode 100644 index 0000000..f046fee --- /dev/null +++ b/cmd/govmtoolsd/main.go @@ -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 {} +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ee9ee11 --- /dev/null +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..799ea51 --- /dev/null +++ b/go.sum @@ -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= diff --git a/network.go b/network.go new file mode 100644 index 0000000..665a4de --- /dev/null +++ b/network.go @@ -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) + +} diff --git a/network_xdr.go b/network_xdr.go new file mode 100644 index 0000000..a3e4355 --- /dev/null +++ b/network_xdr.go @@ -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" +} diff --git a/proto.go b/proto.go new file mode 100644 index 0000000..34fdd6e --- /dev/null +++ b/proto.go @@ -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 +} diff --git a/rpc.go b/rpc.go new file mode 100644 index 0000000..1ae59a0 --- /dev/null +++ b/rpc.go @@ -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 +} diff --git a/socket.go b/socket.go new file mode 100644 index 0000000..ab12434 --- /dev/null +++ b/socket.go @@ -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 +} diff --git a/version.go b/version.go new file mode 100644 index 0000000..6f7354c --- /dev/null +++ b/version.go @@ -0,0 +1,6 @@ +package govmtools + +const Name = "open-vm-tools" +const Version = "11.2.5" +const VersionNumber = "11333" +const BuildNumber = "17337674"