Skip to content

Commit

Permalink
Add guest tools framework
Browse files Browse the repository at this point in the history
Initial version to support vmware-tools "lite" in pure Go.

Towards:

Issue vmware#742
Issue vmware#407
Issue vmware#406
  • Loading branch information
dougm committed Jun 29, 2016
1 parent 3c38685 commit 361a966
Show file tree
Hide file tree
Showing 11 changed files with 1,260 additions and 0 deletions.
48 changes: 48 additions & 0 deletions cmd/toolbox/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2016 VMware, Inc. 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 main

import (
"fmt"
"log"
"os"

"github.com/vmware/vic/pkg/vsphere/toolbox"
)

func main() {
in := toolbox.NewBackdoorChannelIn()
out := toolbox.NewBackdoorChannelOut()

service := toolbox.NewService(in, out)

vix := toolbox.RegisterVixRelayedCommandHandler(service)

// Trigger a command start, for example:
// govc guest.start -vm vm-name kill SIGHUP
vix.ProcessStartCommand = func(r *toolbox.VixMsgStartProgramRequest) (int, error) {
fmt.Fprintf(os.Stderr, "guest-command: %s %s\n", r.ProgramPath, r.Arguments)
return -1, nil
}

err := service.Start()
if err != nil {
log.Fatal(err)
}

defer service.Stop()

service.Wait()
}
71 changes: 71 additions & 0 deletions pkg/vsphere/toolbox/backdoor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2016 VMware, Inc. 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 toolbox

import (
"errors"

"github.com/vmware/vmw-guestinfo/message"
"github.com/vmware/vmw-guestinfo/vmcheck"
)

const (
rpciProtocol uint32 = 0x49435052
tcloProtocol uint32 = 0x4f4c4354
)

var (
ErrNotVirtualWorld = errors.New("not in a virtual world")
)

type backdoorChannel struct {
protocol uint32

*message.Channel
}

func (b *backdoorChannel) Start() error {
if !vmcheck.IsVirtualWorld() {
return ErrNotVirtualWorld
}

channel, err := message.NewChannel(b.protocol)
if err != nil {
return err
}

b.Channel = channel

return nil
}

func (b *backdoorChannel) Stop() error {
err := b.Channel.Close()
return err
}

// NewBackdoorChannelOut creates a Channel for use with the RPCI protocol
func NewBackdoorChannelOut() Channel {
return &backdoorChannel{
protocol: rpciProtocol,
}
}

// NewBackdoorChannelOut creates a Channel for use with the TCLO protocol
func NewBackdoorChannelIn() Channel {
return &backdoorChannel{
protocol: tcloProtocol,
}
}
17 changes: 17 additions & 0 deletions pkg/vsphere/toolbox/backdoor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2016 VMware, Inc. 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 toolbox

var _ Channel = new(backdoorChannel)
23 changes: 23 additions & 0 deletions pkg/vsphere/toolbox/channel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2016 VMware, Inc. 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 toolbox

// Channel abstracts the guest<->vmx RPC transport
type Channel interface {
Start() error
Stop() error
Send([]byte) error
Receive() ([]byte, error)
}
201 changes: 201 additions & 0 deletions pkg/vsphere/toolbox/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// Copyright 2016 VMware, Inc. 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 toolbox

import (
"bytes"
"fmt"
"log"
"net"
"os"
"sync"
"time"
)

// Service receives and dispatches incoming RPC requests from the vmx
type Service struct {
name string
in Channel
out Channel
handlers map[string]Handler
stop chan struct{}
wg *sync.WaitGroup

Interval time.Duration
PrimaryIP func() string
}

// NewService initializes a Service instance
func NewService(rpcIn Channel, rpcOut Channel) *Service {
s := &Service{
name: "toolbox", // Same name used by vmtoolsd
in: NewTraceChannel(rpcIn),
out: NewTraceChannel(rpcOut),
handlers: make(map[string]Handler),
wg: new(sync.WaitGroup),
stop: make(chan struct{}, 1),

Interval: time.Second,
PrimaryIP: DefaultIP,
}

s.RegisterHandler("reset", s.Reset)
s.RegisterHandler("ping", s.Ping)
s.RegisterHandler("Set_Option", s.SetOption)

return s
}

// Start initializes the RPC channels and starts a goroutine to listen for incoming RPC requests
func (s *Service) Start() error {
err := s.in.Start()
if err != nil {
return err
}

err = s.out.Start()
if err != nil {
return err
}

ticker := time.NewTicker(s.Interval)

s.wg.Add(1)
go func() {
defer s.wg.Done()

for {
select {
case <-ticker.C:
_ = s.in.Send(nil) // POKE

request, _ := s.in.Receive()

if len(request) > 0 {
response := s.Dispatch(request)

_ = s.in.Send(response)
}
case <-s.stop:
ticker.Stop()
return
}
}
}()

return nil
}

// Stop cancels the RPC listener routine created via Start
func (s *Service) Stop() {
s.stop <- struct{}{}

_ = s.in.Stop()
_ = s.out.Stop()
}

// Wait blocks until Start returns
func (s *Service) Wait() {
s.wg.Wait()
}

// Handler is given the raw argument portion of an RPC request and returns a response
type Handler func([]byte) ([]byte, error)

// RegisterHandler for the given RPC name
func (s *Service) RegisterHandler(name string, handler Handler) {
s.handlers[name] = handler
}

// Dispatch an incoming RPC request to a Handler
func (s *Service) Dispatch(request []byte) []byte {
msg := bytes.SplitN(request, []byte{' '}, 2)
name := msg[0]

// Trim NULL byte terminator
if len(name) > 0 && name[len(name)-1] == 0 {
name = name[:len(name)-1]
}

handler, ok := s.handlers[string(name)]

if !ok {
log.Printf("unknown command: '%s'\n", name)
return []byte("Unknown Command")
}

var args []byte
if len(msg) == 2 {
args = msg[1]
}

response, err := handler(args)
if err == nil {
response = append([]byte("OK "), response...)
} else {
log.Printf("error calling %s: %s\n", name, err)
response = append([]byte("ERR "), response...)
}

return response
}

// Reset is the default Handler for reset requests
func (s *Service) Reset([]byte) ([]byte, error) {
return []byte("ATR " + s.name), nil
}

// Ping is the default Handler for ping requests
func (s *Service) Ping([]byte) ([]byte, error) {
return nil, nil
}

// SetOption is the default Handler for Set_Option requests
func (s *Service) SetOption(args []byte) ([]byte, error) {
opts := bytes.SplitN(args, []byte{' '}, 2)
if Trace {
fmt.Fprintf(os.Stderr, "set option %s=%s\n", string(opts[0]), string(opts[1]))
}

switch string(opts[0]) {
case "broadcastIP": // TODO: const-ify
if opts[1][0] == 1 {
ip := s.PrimaryIP()
msg := fmt.Sprintf("info-set guestinfo.ip %s", ip)
return nil, s.out.Send([]byte(msg))
}
default:
// TODO: handle other options...
}

return nil, nil
}

// DefaultIP is used by default when responding to a Set_Option broadcastIP request
// It can be overridden with the Service.PrimaryIP field
func DefaultIP() string {
addrs, err := net.InterfaceAddrs()
if err == nil {
for _, addr := range addrs {
if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() {
if ip.IP.To4() != nil {
return ip.IP.String()
}
}
}
}

return ""
}
Loading

0 comments on commit 361a966

Please sign in to comment.