├── Changelog ├── LICENSE ├── commands.go ├── README.md ├── fsfreeze.go ├── main.go ├── fs.go ├── network.go ├── sysinfo.go └── docs └── commands.md /Changelog: -------------------------------------------------------------------------------- 1 | version 0.6: 2 | 3 | - Code refactoring and few improvements. 4 | 5 | version 0.5: 6 | 7 | - Added functions to manage routing table. 8 | 9 | version 0.4: 10 | 11 | - Added functions to Up/Down a network interface. 12 | 13 | version 0.3: 14 | 15 | - Added functions to freeze/thaw filesystems. 16 | 17 | version 0.2: 18 | 19 | - An error description now returns as a base64-encoded string. 20 | - Added an extended code status for error response. 21 | - Added function to get a list of default gateways for IPv4/IPv6 families. 22 | 23 | version 0.1: 24 | 25 | - Initial release. 26 | - Functions for working with guest's files: reading/writing files, setting mode/uid/gid, creating directories, listing directories etc. 27 | - Functions for working with network settings: adding/removing IP-adresses, getting summary information. 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Sergey Zhuravlev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /commands.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | var Commands = map[string]func(chan<- *Response, *json.RawMessage, string){ 8 | "sysinfo": GetSystemInfo, 9 | "get-netifaces": GetNetIfaces, 10 | "get-route-list": GetRouteList, 11 | "route-add": RouteAdd, 12 | "route-del": RouteDel, 13 | "ipaddr-add": IpAddrAdd, 14 | "ipaddr-del": IpAddrDel, 15 | "linux-ipaddr-add": IpAddrAdd, // Deprecated since ver. 0.4 16 | "linux-ipaddr-del": IpAddrDel, // Deprecated since ver. 0.4 17 | "net-iface-up": NetIfaceUp, 18 | "net-iface-down": NetIfaceDown, 19 | "file-open": FileOpen, 20 | "file-close": FileClose, 21 | "file-read": FileRead, 22 | "file-write": FileWrite, 23 | "get-file-md5sum": GetFileMd5sum, 24 | "directory-create": DirectoryCreate, 25 | "directory-list": DirectoryList, 26 | "file-chmod": FileChmod, 27 | "file-chown": FileChown, 28 | "file-stat": FileStat, 29 | "fs-freeze": FsFreeze, 30 | "fs-unfreeze": FsUnFreeze, 31 | "get-freeze-status": GetFreezeStatus, 32 | } 33 | 34 | type Request struct { 35 | Command string `json:"execute"` 36 | RawArgs *json.RawMessage `json:"arguments"` 37 | Tag string `json:"tag"` 38 | } 39 | 40 | type Response struct { 41 | Value interface{} 42 | Tag string 43 | Err error 44 | } 45 | 46 | func GetCommandList(cResp chan<- *Response, tag string) { 47 | CmdList := make([]string, 0, 3+len(Commands)) 48 | 49 | for _, item := range []string{"get-commands", "agent-shutdown", "ping"} { 50 | CmdList = append(CmdList, item) 51 | } 52 | 53 | for cmdName, _ := range Commands { 54 | CmdList = append(CmdList, cmdName) 55 | } 56 | 57 | cResp <- &Response{&CmdList, tag, nil} 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Phoenix guest agent 2 | ------------------- 3 | [![Build Status](https://drone.io/github.com/0xef53/phoenix-guest-agent/status.png)](https://drone.io/github.com/0xef53/phoenix-guest-agent/latest) 4 | 5 | Phoenix is a guest-side agent for qemu-kvm virtual machines. It provides communication with the 6 | guest system using virtio-serial port, and allows to perform some commands in the guest system from 7 | the master. 8 | 9 | 10 | ### Supported functions 11 | 12 | - working with guest's files and directories: reading/writing files, setting mode/uid/gid, creating directories, listing directories etc. 13 | - querying and setting network parameters: adding/removing IP-adresses, getting summary information. 14 | - freezing/thawing guest filesystems. 15 | - querying summary information about the guest: uptime, load average, utsname, logged in users, ram/swap usage, block devices stat, etc. 16 | 17 | 18 | ### How to use 19 | 20 | Launch a qemu-kvm process with additional options for the character device driver required to 21 | communicate with the guest agent: 22 | 23 | qemu-system-x86_64 \ 24 | -chardev socket,id=ga0,path=/var/run/guestagent.sock,server,nowait \ 25 | -device virtio-serial-pci \ 26 | -device virtserialport,chardev=ga0,name=org.guest-agent.0 27 | 28 | On the guest system, launch the guest agent like this: 29 | 30 | phoenix-ga -p /dev/virtio-ports/org.guest-agent.0 31 | 32 | Now, we can talk to guest agent from the master server: 33 | 34 | master# socat - UNIX-CONNECT:/var/run/guestagent.sock 35 | { "execute": "get-commands", "tag": "abc" } 36 | { "return": ["get-commands", "agent-shutdown", "ping", "get-netifaces", "linux-ipaddr-add", "linux-ipaddr-del", "file-open", "file-close", "file-read", "file-write", "get-file-md5sum"], "tag": "abc" } 37 | 38 | { "execute": "ping", "tag": "def" } 39 | { "return": "0.1", "tag": "def" } 40 | 41 | Communication with the guest agent occurs at QMP-like protocol. The success response contains 42 | the field "return" with the results of command execution. The error response contains the field 43 | "error" with a base64-encoded description. For details see the [commands documentation](docs/commands.md). 44 | 45 | Since version 0.4 the field "error" also contains an extended code status, which can take next values: 46 | 47 | - an unsigned number (`errno`) describing an error condition 48 | - and `-1` in case if an extended code is not defined 49 | 50 | E.g: 51 | 52 | { "error": { "bufb64": "anVzdCBhIHRlc3QgZXJyb3I=", "code": -1 }, "tag": "8be" } 53 | 54 | 55 | ### Resource consumption 56 | 57 | Measurements were performed using the cpuacct cgroups controller and pmap. 58 | 59 | In the idling agent consumed about 17 seconds of CPU time per day and about 2.5 Mb RSS. 60 | 61 | ### Getting binary 62 | 63 | Latest version is available [here](https://drone.io/github.com/0xef53/phoenix-guest-agent/files/phoenix-ga) 64 | 65 | ### Installing from source 66 | 67 | mkdir phoenix-ga && cd phoenix-ga 68 | export GOPATH=$(pwd); go get -v -tags netgo -ldflags '-s -w' github.com/0xef53/phoenix-guest-agent/... 69 | mv bin/phoenix-guest-agent bin/phoenix-ga 70 | 71 | ### Supported OS 72 | 73 | GNU/Linux 74 | 75 | ### License 76 | 77 | MIT 78 | -------------------------------------------------------------------------------- /fsfreeze.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | "syscall" 10 | ) 11 | 12 | // from linux/fs.h 13 | const FIFREEZE = 0xC0045877 14 | const FITHAW = 0xC0045878 15 | 16 | var FROZEN bool = false 17 | 18 | type MEntry struct { 19 | FSSpec string 20 | FSFile string 21 | FSType string 22 | } 23 | 24 | func GetMountPoints() ([]MEntry, error) { 25 | f, err := os.Open("/proc/self/mounts") 26 | if err != nil { 27 | return nil, err 28 | } 29 | defer f.Close() 30 | 31 | m := make([]MEntry, 0, 10) 32 | 33 | isExist := func(fsspec string) bool { 34 | for _, v := range m { 35 | if v.FSSpec == fsspec { 36 | return true 37 | } 38 | } 39 | return false 40 | } 41 | 42 | scanner := bufio.NewScanner(f) 43 | 44 | for scanner.Scan() { 45 | fields := strings.Fields(scanner.Text()) 46 | if len(fields) < 2 || isExist(fields[0]) || fields[0][0] != '/' || 47 | fields[2] == "smbfs" || fields[2] == "cifs" { 48 | continue 49 | } 50 | 51 | // Ignoring the loop devices 52 | if strings.HasPrefix(fields[0], "/dev/loop") { 53 | continue 54 | } 55 | // Ignoring the dm- devices 56 | st, err := os.Lstat(fields[0]) 57 | if err != nil { 58 | return nil, err 59 | } 60 | if st.Mode()&os.ModeSymlink != 0 { 61 | if s, err := os.Readlink(fields[0]); err != nil { 62 | return nil, err 63 | } else { 64 | fields[0] = filepath.Base(s) 65 | } 66 | } 67 | if strings.HasPrefix(fields[0], "dm-") { 68 | continue 69 | } 70 | 71 | m = append(m, MEntry{fields[0], fields[1], fields[2]}) 72 | } 73 | if err = scanner.Err(); err != nil { 74 | return nil, err 75 | } 76 | 77 | return m, nil 78 | } 79 | 80 | func ioctl(fd uintptr, request, argp uintptr) (err error) { 81 | _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, request, argp) 82 | if errno != 0 { 83 | err = errno 84 | } 85 | 86 | return os.NewSyscallError("ioctl", err) 87 | } 88 | 89 | func GetFreezeStatus(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 90 | cResp <- &Response{FROZEN, tag, nil} 91 | } 92 | 93 | func FsFreeze(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 94 | m, err := GetMountPoints() 95 | if err != nil { 96 | cResp <- &Response{nil, tag, err} 97 | return 98 | } 99 | 100 | FROZEN = true 101 | 102 | for _, mp := range m { 103 | fs, err := os.Open(mp.FSFile) 104 | if err != nil { 105 | cResp <- &Response{nil, tag, err} 106 | return 107 | } 108 | 109 | if err := ioctl(fs.Fd(), FIFREEZE, 0); err != nil && 110 | err.(*os.SyscallError).Err.(syscall.Errno) != syscall.EOPNOTSUPP && 111 | err.(*os.SyscallError).Err.(syscall.Errno) != syscall.EBUSY { 112 | cResp <- &Response{nil, tag, err} 113 | return 114 | } 115 | 116 | fs.Close() 117 | } 118 | 119 | cResp <- &Response{true, tag, nil} 120 | } 121 | 122 | func FsUnFreeze(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 123 | m, err := GetMountPoints() 124 | if err != nil { 125 | cResp <- &Response{nil, tag, err} 126 | return 127 | } 128 | 129 | for _, mp := range m { 130 | fs, err := os.Open(mp.FSFile) 131 | if err != nil { 132 | cResp <- &Response{nil, tag, err} 133 | return 134 | } 135 | 136 | if err := ioctl(fs.Fd(), FITHAW, 0); err != nil && err.(*os.SyscallError).Err.(syscall.Errno) != syscall.EINVAL { 137 | cResp <- &Response{nil, tag, err} 138 | return 139 | } 140 | 141 | fs.Close() 142 | } 143 | 144 | FROZEN = false 145 | 146 | cResp <- &Response{true, tag, nil} 147 | } 148 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/base64" 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "io" 10 | "log" 11 | "os" 12 | "sync" 13 | "syscall" 14 | "time" 15 | ) 16 | 17 | const ( 18 | VERSION = "0.7.2" 19 | LOGFILE = "/var/log/phoenix.log" 20 | ) 21 | 22 | var ( 23 | PORTPATH string = "/dev/virtio-ports/org.guest-agent.0" 24 | DEBUG bool = false 25 | SHOWVER bool = false 26 | ) 27 | 28 | func debug(v ...interface{}) { 29 | if DEBUG { 30 | log.Println(v...) 31 | } 32 | } 33 | 34 | type Port struct { 35 | sync.Mutex 36 | f *os.File 37 | fd uintptr 38 | } 39 | 40 | func OpenPort(dev string) (*Port, error) { 41 | f, err := os.OpenFile(dev, syscall.O_RDWR|syscall.O_ASYNC|syscall.O_NDELAY, 0666) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | fd := f.Fd() 47 | 48 | if err := syscall.SetNonblock(int(fd), false); err != nil { 49 | return nil, err 50 | } 51 | 52 | return &Port{f: f, fd: fd}, nil 53 | } 54 | 55 | func (p *Port) SendError(err error, tag string) (int, error) { 56 | code := -1 57 | 58 | switch err.(type) { 59 | case *os.PathError: 60 | code = int(err.(*os.PathError).Err.(syscall.Errno)) 61 | } 62 | 63 | res := fmt.Sprintf( 64 | `{"error": {"bufb64": "%s", "code": %d}, "tag": "%s"}`+"\n", 65 | base64.StdEncoding.EncodeToString([]byte(err.Error())), 66 | code, 67 | tag) 68 | 69 | p.Lock() 70 | defer p.Unlock() 71 | 72 | return p.f.Write([]byte(res)) 73 | } 74 | 75 | func (p *Port) SendResponse(resp interface{}, tag string) (int, error) { 76 | res := struct { 77 | Return interface{} `json:"return"` 78 | Tag string `json:"tag"` 79 | }{ 80 | resp, 81 | tag, 82 | } 83 | 84 | resB, err := json.Marshal(&res) 85 | if err != nil { 86 | log.Fatalln("Failed to marshal response object:", err) 87 | } 88 | 89 | p.Lock() 90 | defer p.Unlock() 91 | 92 | return p.f.Write(append(resB, '\x0a')) 93 | } 94 | 95 | func (p *Port) Close() error { 96 | return p.f.Close() 97 | } 98 | 99 | func listenPort(cReq chan<- []byte, epollFd int, reader *bufio.Reader) { 100 | events := make([]syscall.EpollEvent, 32) 101 | 102 | var buf []byte 103 | var err error 104 | 105 | for { 106 | if _, err := syscall.EpollWait(epollFd, events, -1); err != nil { 107 | log.Fatalln("Error receiving epoll events:", err) 108 | } 109 | 110 | buf, err = reader.ReadBytes('\x0a') 111 | switch err { 112 | case nil: 113 | case io.EOF: 114 | time.Sleep(time.Second * 1) 115 | continue 116 | default: 117 | log.Fatalln(err) 118 | } 119 | 120 | break 121 | } 122 | 123 | cReq <- buf 124 | } 125 | 126 | func init() { 127 | flag.StringVar(&PORTPATH, "p", PORTPATH, "device path") 128 | flag.BoolVar(&DEBUG, "debug", DEBUG, "enable debug mode") 129 | flag.BoolVar(&SHOWVER, "v", SHOWVER, "print version information and quit") 130 | } 131 | 132 | func main() { 133 | flag.Parse() 134 | 135 | if SHOWVER { 136 | fmt.Println("Version:", VERSION) 137 | return 138 | } 139 | 140 | debug("Phoenix Guest Agent started [pid=" + fmt.Sprintf("%d", os.Getpid()) + "]") 141 | 142 | port, err := OpenPort(PORTPATH) 143 | if err != nil { 144 | log.Fatalln("Failed to open character device:", err) 145 | } 146 | defer port.Close() 147 | 148 | epollFd, err := syscall.EpollCreate1(0) 149 | if err != nil { 150 | log.Fatalln("Error creating epoll:", err) 151 | } 152 | defer syscall.Close(epollFd) 153 | 154 | ctlEvent := syscall.EpollEvent{Events: syscall.EPOLLIN, Fd: int32(port.fd)} 155 | if err := syscall.EpollCtl(epollFd, syscall.EPOLL_CTL_ADD, int(port.fd), &ctlEvent); err != nil { 156 | log.Fatalln("Error registering epoll event:", err) 157 | } 158 | 159 | reader := bufio.NewReader(port.f) 160 | 161 | cReq := make(chan []byte) 162 | cResp := make(chan *Response, 1) 163 | 164 | lock := false 165 | 166 | for { 167 | if !lock { 168 | lock = true 169 | go listenPort(cReq, epollFd, reader) 170 | } 171 | 172 | select { 173 | case jsonReq := <-cReq: 174 | lock = false 175 | 176 | req := &Request{} 177 | 178 | if err := json.Unmarshal(jsonReq, &req); err != nil { 179 | debug("JSON parse error:", err) 180 | port.SendError(fmt.Errorf("JSON parse error: %s", err), "") 181 | continue 182 | } 183 | 184 | switch req.Command { 185 | case "ping": 186 | port.SendResponse(VERSION, req.Tag) 187 | continue 188 | case "agent-shutdown": 189 | debug("Shutdown command received from client") 190 | return 191 | case "get-commands": 192 | go GetCommandList(cResp, req.Tag) 193 | continue 194 | } 195 | 196 | if FROZEN && req.Command != "get-freeze-status" && req.Command != "fs-unfreeze" { 197 | debug("All filesystems are frozen. Cannot execute:", req.Command) 198 | port.SendError(fmt.Errorf("All filesystems are frozen. Cannot execute: %s", req.Command), req.Tag) 199 | continue 200 | } 201 | 202 | if _, ok := Commands[req.Command]; !ok { 203 | debug("Unknown command:", req.Command) 204 | port.SendError(fmt.Errorf("Unknown command: %s", req.Command), req.Tag) 205 | continue 206 | } 207 | 208 | debug("Processing command:", req.Command+", tag =", req.Tag) 209 | go Commands[req.Command](cResp, req.RawArgs, req.Tag) 210 | case resp := <-cResp: 211 | if resp.Err != nil { 212 | port.SendError(resp.Err, resp.Tag) 213 | } else { 214 | port.SendResponse(resp.Value, resp.Tag) 215 | } 216 | } // end of select 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /fs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "os" 10 | "sync" 11 | "syscall" 12 | ) 13 | 14 | type FD struct { 15 | sync.RWMutex 16 | next int 17 | h map[int]*os.File 18 | } 19 | 20 | func NewFD() *FD { 21 | return &FD{next: 1, h: make(map[int]*os.File)} 22 | } 23 | 24 | func (fd *FD) Add(f *os.File) { 25 | fd.Lock() 26 | defer fd.Unlock() 27 | 28 | fd.h[fd.next] = f 29 | fd.next += 1 30 | } 31 | 32 | func (fd *FD) Get(id int) (f *os.File, err error) { 33 | fd.RLock() 34 | defer fd.RUnlock() 35 | 36 | f, ok := fd.h[id] 37 | if !ok { 38 | return nil, fmt.Errorf("Incorrect handle id") 39 | } 40 | 41 | return f, nil 42 | } 43 | 44 | func (fd *FD) Del(id int) { 45 | fd.Lock() 46 | defer fd.Unlock() 47 | 48 | if fd.h[id] != nil { 49 | fd.h[id].Close() 50 | delete(fd.h, id) 51 | } 52 | } 53 | 54 | var FDStore = NewFD() 55 | 56 | func FileOpen(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 57 | var f *os.File 58 | var err error 59 | 60 | args := &struct { 61 | Path string `json:"path"` 62 | Mode string `json:"mode"` 63 | Perm os.FileMode `json:"perm"` 64 | Force bool `json:"force"` 65 | }{ 66 | Perm: 0644, 67 | Force: false, 68 | } 69 | 70 | json.Unmarshal(*rawArgs, &args) 71 | 72 | switch args.Mode { 73 | case "w": 74 | f, err = os.OpenFile(args.Path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, args.Perm) 75 | if err != nil && args.Force && err.(*os.PathError).Err.(syscall.Errno) == syscall.ETXTBSY { 76 | if err = os.Remove(args.Path); err == nil { 77 | f, err = os.OpenFile(args.Path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, args.Perm) 78 | } 79 | } 80 | default: 81 | f, err = os.OpenFile(args.Path, os.O_RDONLY, 0) 82 | } 83 | 84 | if err != nil { 85 | cResp <- &Response{nil, tag, err} 86 | return 87 | } 88 | 89 | FDStore.Add(f) 90 | 91 | cResp <- &Response{(FDStore.next - 1), tag, nil} 92 | } 93 | 94 | func FileRead(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 95 | args := &struct { 96 | Id int `json:"handle_id"` 97 | }{} 98 | 99 | json.Unmarshal(*rawArgs, &args) 100 | 101 | f, err := FDStore.Get(args.Id) 102 | if err != nil { 103 | cResp <- &Response{nil, tag, err} 104 | return 105 | } 106 | 107 | buf := make([]byte, 4096) 108 | eof := false 109 | 110 | n, err := f.Read(buf) 111 | switch err { 112 | case nil: 113 | case io.EOF: 114 | f.Close() 115 | FDStore.Del(args.Id) 116 | eof = true 117 | default: 118 | cResp <- &Response{nil, tag, err} 119 | return 120 | } 121 | 122 | res := &struct { 123 | Buf []byte `json:"bufb64"` 124 | Eof bool `json:"eof"` 125 | }{ 126 | []byte(buf[:n]), 127 | eof, 128 | } 129 | 130 | cResp <- &Response{res, tag, nil} 131 | } 132 | 133 | func FileWrite(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 134 | args := &struct { 135 | Id int `json:"handle_id"` 136 | Buf []byte `json:"bufb64"` 137 | Eof bool `json:"eof"` 138 | }{} 139 | 140 | json.Unmarshal(*rawArgs, &args) 141 | 142 | f, err := FDStore.Get(args.Id) 143 | if err != nil { 144 | cResp <- &Response{nil, tag, err} 145 | return 146 | } 147 | 148 | if args.Eof { 149 | FDStore.Del(args.Id) 150 | cResp <- &Response{true, tag, nil} 151 | return 152 | } 153 | 154 | if _, err := f.Write(args.Buf); err != nil { 155 | cResp <- &Response{nil, tag, err} 156 | return 157 | } 158 | 159 | cResp <- &Response{true, tag, nil} 160 | } 161 | 162 | func FileClose(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 163 | args := &struct { 164 | Id int `json:"handle_id"` 165 | }{} 166 | 167 | json.Unmarshal(*rawArgs, &args) 168 | 169 | FDStore.Del(args.Id) 170 | 171 | cResp <- &Response{true, tag, nil} 172 | } 173 | 174 | func DirectoryCreate(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 175 | args := &struct { 176 | Path string `json:"path"` 177 | Perm os.FileMode `json:"perm"` 178 | }{ 179 | Perm: 0755, 180 | } 181 | 182 | json.Unmarshal(*rawArgs, &args) 183 | 184 | if err := os.MkdirAll(args.Path, args.Perm); err != nil { 185 | cResp <- &Response{nil, tag, err} 186 | return 187 | } 188 | 189 | cResp <- &Response{true, tag, nil} 190 | } 191 | 192 | func FileChmod(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 193 | args := &struct { 194 | Path string `json:"path"` 195 | Perm os.FileMode `json:"perm"` 196 | }{} 197 | 198 | json.Unmarshal(*rawArgs, &args) 199 | 200 | if err := os.Chmod(args.Path, args.Perm); err != nil { 201 | cResp <- &Response{nil, tag, err} 202 | return 203 | } 204 | 205 | cResp <- &Response{true, tag, nil} 206 | } 207 | 208 | func FileChown(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 209 | args := &struct { 210 | Path string `json:"path"` 211 | Uid int `json:"uid"` 212 | Gid int `json:"gid"` 213 | }{} 214 | 215 | json.Unmarshal(*rawArgs, &args) 216 | 217 | if err := os.Chown(args.Path, args.Uid, args.Gid); err != nil { 218 | cResp <- &Response{nil, tag, err} 219 | return 220 | } 221 | 222 | cResp <- &Response{true, tag, nil} 223 | } 224 | 225 | type FStat struct { 226 | Name string `json:"name"` 227 | IsDir bool `json:"isdir"` 228 | Stat interface{} `json:"stat"` 229 | } 230 | 231 | func FileStat(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 232 | args := &struct { 233 | Path string `json:"path"` 234 | }{} 235 | 236 | json.Unmarshal(*rawArgs, &args) 237 | 238 | file, err := os.Lstat(args.Path) 239 | if err != nil { 240 | cResp <- &Response{nil, tag, err} 241 | return 242 | } 243 | 244 | cResp <- &Response{&FStat{file.Name(), file.IsDir(), file.Sys()}, tag, nil} 245 | } 246 | 247 | func DirectoryList(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 248 | args := &struct { 249 | Path string `json:"path"` 250 | N int `json:"n"` 251 | }{ 252 | N: -1, 253 | } 254 | 255 | json.Unmarshal(*rawArgs, &args) 256 | 257 | dir, err := os.Open(args.Path) 258 | if err != nil { 259 | cResp <- &Response{nil, tag, err} 260 | return 261 | } 262 | 263 | files, err := dir.Readdir(args.N) 264 | if err != nil { 265 | cResp <- &Response{nil, tag, err} 266 | return 267 | } 268 | 269 | flist := make([]*FStat, 0, len(files)) 270 | 271 | for _, file := range files { 272 | flist = append(flist, &FStat{file.Name(), file.IsDir(), file.Sys()}) 273 | } 274 | 275 | cResp <- &Response{&flist, tag, nil} 276 | } 277 | 278 | func GetFileMd5sum(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 279 | args := &struct { 280 | Path string `json:"path"` 281 | }{} 282 | 283 | json.Unmarshal(*rawArgs, &args) 284 | 285 | f, err := os.Open(args.Path) 286 | if err != nil { 287 | cResp <- &Response{nil, tag, err} 288 | return 289 | } 290 | defer f.Close() 291 | 292 | hash := md5.New() 293 | 294 | if _, err := io.Copy(hash, f); err != nil { 295 | cResp <- &Response{nil, tag, err} 296 | return 297 | } 298 | 299 | cResp <- &Response{hex.EncodeToString(hash.Sum(nil)), tag, nil} 300 | } 301 | -------------------------------------------------------------------------------- /network.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "net" 6 | "os" 7 | 8 | "github.com/vishvananda/netlink" 9 | ) 10 | 11 | type NetIf struct { 12 | Index int `json:"index"` 13 | Name string `json:"name"` 14 | Hwaddr string `json:"hwaddr"` 15 | Flags string `json:"flags"` 16 | Ips []string `json:"ips"` 17 | } 18 | 19 | func GetNetIfaces(cResp chan<- *Response, args *json.RawMessage, tag string) { 20 | ifaces, err := net.Interfaces() 21 | if err != nil { 22 | cResp <- &Response{nil, tag, err} 23 | return 24 | } 25 | 26 | iflist := make([]*NetIf, 0, len(ifaces)) 27 | 28 | for _, netif := range ifaces { 29 | addrs, err := netif.Addrs() 30 | if err != nil { 31 | cResp <- &Response{nil, tag, err} 32 | return 33 | } 34 | 35 | str_addrs := make([]string, 0, len(addrs)) 36 | for _, addr := range addrs { 37 | str_addrs = append(str_addrs, addr.String()) 38 | } 39 | 40 | iflist = append(iflist, &NetIf{ 41 | netif.Index, 42 | netif.Name, 43 | netif.HardwareAddr.String(), 44 | netif.Flags.String(), 45 | str_addrs, 46 | }) 47 | } 48 | 49 | cResp <- &Response{&iflist, tag, nil} 50 | } 51 | 52 | func NewRTNetlinkError(err error) error { 53 | return os.NewSyscallError("rtnetlink", err) 54 | } 55 | 56 | func IpAddrAdd(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 57 | args := &struct { 58 | Ifname string `json:"ifname"` 59 | IpCidr string `json:"ip"` 60 | }{} 61 | 62 | json.Unmarshal(*rawArgs, &args) 63 | 64 | iface, err := netlink.LinkByName(args.Ifname) 65 | if err != nil { 66 | cResp <- &Response{nil, tag, NewRTNetlinkError(err)} 67 | return 68 | } 69 | 70 | ip, err := netlink.ParseAddr(args.IpCidr) 71 | if err != nil { 72 | cResp <- &Response{nil, tag, NewRTNetlinkError(err)} 73 | return 74 | } 75 | 76 | if err := netlink.AddrAdd(iface, ip); err != nil { 77 | cResp <- &Response{nil, tag, NewRTNetlinkError(err)} 78 | return 79 | } 80 | 81 | cResp <- &Response{true, tag, nil} 82 | } 83 | 84 | func IpAddrDel(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 85 | args := &struct { 86 | Ifname string `json:"ifname"` 87 | IpCidr string `json:"ip"` 88 | }{} 89 | 90 | json.Unmarshal(*rawArgs, &args) 91 | 92 | iface, err := netlink.LinkByName(args.Ifname) 93 | if err != nil { 94 | cResp <- &Response{nil, tag, NewRTNetlinkError(err)} 95 | return 96 | } 97 | 98 | ip, err := netlink.ParseAddr(args.IpCidr) 99 | if err != nil { 100 | cResp <- &Response{nil, tag, NewRTNetlinkError(err)} 101 | return 102 | } 103 | 104 | if err := netlink.AddrDel(iface, ip); err != nil { 105 | cResp <- &Response{nil, tag, NewRTNetlinkError(err)} 106 | return 107 | } 108 | 109 | cResp <- &Response{true, tag, nil} 110 | } 111 | 112 | type IPNet net.IPNet 113 | 114 | func (n IPNet) MarshalJSON() ([]byte, error) { 115 | t := struct { 116 | IP net.IP `json:"ip"` 117 | Mask net.IP `json:"mask"` 118 | }{ 119 | IP: n.IP, 120 | Mask: net.IP(n.Mask), 121 | } 122 | 123 | return json.Marshal(t) 124 | } 125 | 126 | type Route struct { 127 | Ifname string `json:"ifname"` 128 | Scope netlink.Scope `json:"scope"` 129 | Dst *IPNet `json:"dst"` 130 | Src net.IP `json:"src"` 131 | Gw net.IP `json:"gateway"` 132 | } 133 | 134 | func GetRouteList(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 135 | args := &struct { 136 | Family int `json:"family"` 137 | }{} 138 | 139 | json.Unmarshal(*rawArgs, &args) 140 | 141 | var family int 142 | 143 | switch args.Family { 144 | case netlink.FAMILY_ALL, netlink.FAMILY_V4, netlink.FAMILY_V6: 145 | family = args.Family 146 | } 147 | 148 | rlist, err := netlink.RouteList(nil, family) 149 | if err != nil { 150 | cResp <- &Response{nil, tag, NewRTNetlinkError(err)} 151 | return 152 | } 153 | 154 | rlist2 := make([]Route, 0, len(rlist)) 155 | 156 | for _, r := range rlist { 157 | link, _ := net.InterfaceByIndex(r.LinkIndex) 158 | var n IPNet 159 | if r.Dst != nil { 160 | n = IPNet(*r.Dst) 161 | } 162 | r2 := Route{ 163 | Ifname: link.Name, 164 | Scope: r.Scope, 165 | Dst: &n, 166 | Src: r.Src, 167 | Gw: r.Gw, 168 | } 169 | rlist2 = append(rlist2, r2) 170 | } 171 | 172 | cResp <- &Response{rlist2, tag, nil} 173 | } 174 | 175 | func NewNetlinkRoute(ifname, dst, src, gw string, scope netlink.Scope) (*netlink.Route, error) { 176 | link, err := netlink.LinkByName(ifname) 177 | if err != nil { 178 | return nil, err 179 | } 180 | 181 | dstNet, err := netlink.ParseIPNet(dst) 182 | if err != nil { 183 | return nil, err 184 | } 185 | 186 | r := netlink.Route{ 187 | LinkIndex: link.Attrs().Index, 188 | Dst: dstNet, 189 | Src: net.ParseIP(src), 190 | Gw: net.ParseIP(gw), 191 | Scope: scope, 192 | } 193 | 194 | return &r, nil 195 | } 196 | 197 | func RouteAdd(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 198 | args := &struct { 199 | Ifname string `json:"ifname"` 200 | Dst string `json:"dst"` 201 | Src string `json:"src"` 202 | Gw string `json:"gateway"` 203 | Scope netlink.Scope `json:"scope"` 204 | }{} 205 | 206 | json.Unmarshal(*rawArgs, &args) 207 | 208 | route, err := NewNetlinkRoute(args.Ifname, args.Dst, args.Src, args.Gw, args.Scope) 209 | if err != nil { 210 | cResp <- &Response{nil, tag, NewRTNetlinkError(err)} 211 | return 212 | } 213 | 214 | if err := netlink.RouteAdd(route); err != nil { 215 | cResp <- &Response{nil, tag, NewRTNetlinkError(err)} 216 | return 217 | } 218 | 219 | cResp <- &Response{true, tag, nil} 220 | } 221 | 222 | func RouteDel(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 223 | args := &struct { 224 | Ifname string `json:"ifname"` 225 | Dst string `json:"dst"` 226 | Src string `json:"src"` 227 | Gw string `json:"gateway"` 228 | Scope netlink.Scope `json:"scope"` 229 | }{} 230 | 231 | json.Unmarshal(*rawArgs, &args) 232 | 233 | route, err := NewNetlinkRoute(args.Ifname, args.Dst, args.Src, args.Gw, args.Scope) 234 | if err != nil { 235 | cResp <- &Response{nil, tag, NewRTNetlinkError(err)} 236 | return 237 | } 238 | 239 | if err := netlink.RouteDel(route); err != nil { 240 | cResp <- &Response{nil, tag, NewRTNetlinkError(err)} 241 | return 242 | } 243 | 244 | cResp <- &Response{true, tag, nil} 245 | } 246 | 247 | func NetIfaceUp(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 248 | args := &struct { 249 | Ifname string `json:"ifname"` 250 | }{} 251 | 252 | json.Unmarshal(*rawArgs, &args) 253 | 254 | iface := &netlink.Device{netlink.LinkAttrs{Name: args.Ifname}} 255 | if err := netlink.LinkSetUp(iface); err != nil { 256 | cResp <- &Response{nil, tag, NewRTNetlinkError(err)} 257 | return 258 | } 259 | 260 | cResp <- &Response{true, tag, nil} 261 | } 262 | 263 | func NetIfaceDown(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 264 | args := &struct { 265 | Ifname string `json:"ifname"` 266 | }{} 267 | 268 | json.Unmarshal(*rawArgs, &args) 269 | 270 | iface := &netlink.Device{netlink.LinkAttrs{Name: args.Ifname}} 271 | if err := netlink.LinkSetDown(iface); err != nil { 272 | cResp <- &Response{nil, tag, NewRTNetlinkError(err)} 273 | return 274 | } 275 | 276 | cResp <- &Response{true, tag, nil} 277 | } 278 | -------------------------------------------------------------------------------- /sysinfo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/binary" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | "os/exec" 13 | "path/filepath" 14 | "strconv" 15 | "strings" 16 | "syscall" 17 | "time" 18 | ) 19 | 20 | type SysInfo struct { 21 | Uname *Utsname `json:"uname"` // utsname struct 22 | LongBit int `json:"long_bit"` // getconf LONG_BIT 23 | Uptime time.Duration `json:"uptime"` // time since boot 24 | Loadavg LoadAverage `json:"loadavg"` // 1, 5, and 15 minute load averages 25 | Mem MemStat `json:"ram"` // memory stat (total/free/buffers/cached) in kB 26 | Swap SwapStat `json:"swap"` // swap stat (total/free) in kB 27 | Users []LoggedUser `json:"users"` // logged-in users from /var/run/utmp 28 | Disks []BlockDev `json:"disks"` // some disks stats 29 | } 30 | 31 | type Utsname struct { 32 | Sysname string `json:"sysname"` 33 | Nodename string `json:"nodename"` 34 | Release string `json:"release"` 35 | Version string `json:"version"` 36 | Machine string `json:"machine"` 37 | Domainname string `json:"domain"` 38 | } 39 | 40 | type LoadAverage struct { 41 | One float64 `json:"1m"` 42 | Five float64 `json:"5m"` 43 | Fifteen float64 `json:"15m"` 44 | } 45 | 46 | type MemStat struct { 47 | Total uint64 `json:"total"` 48 | Free uint64 `json:"free"` 49 | Buffers uint64 `json:"buffers"` 50 | Cached uint64 `json:"cached"` 51 | FreeTotal uint64 `json:"free_total"` 52 | } 53 | 54 | type SwapStat struct { 55 | Total uint64 `json:"total"` 56 | Free uint64 `json:"free"` 57 | } 58 | 59 | type LoggedUser struct { 60 | Name string `json:"name"` 61 | Device string `json:"device"` 62 | Host string `json:"host"` 63 | LoginTime int64 `json:"login_time"` 64 | } 65 | 66 | type BlockDev struct { 67 | Path string `json:"name"` 68 | IsMounted bool `json:"is_mounted"` 69 | Mountpoint string `json:"mountpoint"` 70 | SizeTotal int64 `json:"size_total"` // in kB 71 | SizeUsed int64 `json:"size_used"` // in kB 72 | SizeAvail int64 `json:"size_avail"` // in kB 73 | } 74 | 75 | // Values for Utmp.Type field 76 | type Utype int16 77 | 78 | // Type for ut_exit, below 79 | const ( 80 | Empty Utype = iota // record does not contain valid info (formerly known as UT_UNKNOWN on Linux) 81 | RunLevel = iota // change in system run-level (see init(8)) 82 | BootTime = iota // time of system boot (in ut_tv) 83 | NewTime = iota // time after system clock change (in ut_tv) 84 | OldTime = iota // time before system clock change (in ut_tv) 85 | InitProcess = iota // process spawned by init(8) 86 | LoginProcess = iota // session leader process for user login 87 | UserProcess = iota // normal process 88 | DeadProcess = iota // terminated process 89 | Accounting = iota // not implemented 90 | 91 | LineSize = 32 92 | NameSize = 32 93 | HostSize = 256 94 | ) 95 | 96 | type ExitStatus struct { 97 | Termination int16 `json:"termination"` // process termination status 98 | Exit int16 `json:"exit"` // process exit status 99 | } 100 | 101 | type TimeVal struct { 102 | Sec int32 `json:"seconds"` 103 | Usec int32 `json:"microseconds"` 104 | } 105 | 106 | // http://man7.org/linux/man-pages/man5/utmp.5.html 107 | type Utmp struct { 108 | Type Utype // type of record 109 | _ int16 // padding because Go doesn't 4-byte align 110 | Pid int32 // PID of login process 111 | Device [LineSize]byte // device name of tty - "/dev/" 112 | Id [4]byte // terminal name suffix or inittab(5) ID 113 | User [NameSize]byte // username 114 | Host [HostSize]byte // hostname for remote login or kernel version for run-level messages 115 | Exit ExitStatus // exit status of a process marked as DeadProcess; not used by Linux init(1) 116 | Session int32 // session ID (getsid(2)), used for windowing 117 | Time TimeVal // time entry was made 118 | Addr [4]int32 // internet address of remote host; IPv4 address uses just Addr[0] 119 | Unused [20]byte // reserved for future use 120 | } 121 | 122 | func GetSystemInfo(cResp chan<- *Response, rawArgs *json.RawMessage, tag string) { 123 | st := &syscall.Sysinfo_t{} 124 | 125 | if err := syscall.Sysinfo(st); err != nil { 126 | cResp <- &Response{nil, tag, err} 127 | return 128 | } 129 | 130 | sinfo := &SysInfo{} 131 | sinfo.Uptime = time.Duration(st.Uptime) 132 | 133 | // float64(1< 0 { 317 | switch { 318 | case bytes.HasPrefix(s.Bytes(), []byte(`MemTotal:`)): 319 | _, err = fmt.Sscanf(s.Text(), "MemTotal:%d", &mi.Total) 320 | case bytes.HasPrefix(s.Bytes(), []byte(`MemFree:`)): 321 | _, err = fmt.Sscanf(s.Text(), "MemFree:%d", &mi.Free) 322 | case bytes.HasPrefix(s.Bytes(), []byte(`Buffers:`)): 323 | _, err = fmt.Sscanf(s.Text(), "Buffers:%d", &mi.Buffers) 324 | case bytes.HasPrefix(s.Bytes(), []byte(`Cached:`)): 325 | _, err = fmt.Sscanf(s.Text(), "Cached:%d", &mi.Cached) 326 | default: 327 | continue 328 | } 329 | 330 | if err != nil { 331 | return err 332 | } 333 | 334 | n-- 335 | } 336 | if err = s.Err(); err != nil { 337 | return err 338 | } 339 | 340 | mi.FreeTotal = mi.Free + mi.Buffers + mi.Cached 341 | 342 | return nil 343 | } 344 | 345 | func parseMounts() (map[string]string, error) { 346 | f, err := os.Open("/proc/self/mounts") 347 | if err != nil { 348 | return nil, err 349 | } 350 | defer f.Close() 351 | 352 | mounts := make(map[string]string) 353 | 354 | scanner := bufio.NewScanner(f) 355 | for scanner.Scan() { 356 | fields := strings.Fields(scanner.Text()) 357 | if len(fields) < 2 { 358 | continue 359 | } 360 | mounts[fields[0]] = fields[1] 361 | } 362 | if err = scanner.Err(); err != nil { 363 | return nil, err 364 | } 365 | 366 | return mounts, nil 367 | } 368 | -------------------------------------------------------------------------------- /docs/commands.md: -------------------------------------------------------------------------------- 1 | General 2 | ------- 3 | 4 | - All requests to the Agent should be terminated with CRLF 5 | - Request may contain a string field "tag" to uniquely identify a response from Agent 6 | - For more details, see the QMP-specification 7 | 8 | Commands 9 | -------- 10 | 11 | ### ping 12 | 13 | **Returns:** the version of the guest agent 14 | 15 | **Example:** 16 | 17 | -> { "execute": "ping" } 18 | <- { "return": "0.4" } 19 | 20 | 21 | ### get-commands 22 | 23 | **Returns:** list of available commands 24 | 25 | **Example:** 26 | 27 | -> { "execute": "get-commands" } 28 | <- { "return": ["agent-shutdown", "ping", "get-netifaces"] } 29 | 30 | 31 | ### agent-shutdown 32 | 33 | Shutdown the guest agent. If the agent started by the init process, it will be automatically launched. Therefore in this case the command reloads agent. 34 | 35 | **Returns:** true on success 36 | 37 | **Example:** 38 | 39 | -> { "execute": "agent-shutdown" } 40 | <- { "return": true } 41 | 42 | 43 | ### sysinfo 44 | 45 | **Returns:** summary information about the guest system: uptime, load average, utsname, logged in users, ram/swap usage, block devices stat, etc. 46 | 47 | **Example:** 48 | 49 | -> { "execute": "sysinfo" } 50 | <- { 51 | "long_bit": 64, 52 | "disks": [ 53 | { 54 | "size_used": 621900, 55 | "name": "/dev/vda", 56 | "size_total": 5131008, 57 | "size_avail": 4230592, 58 | "is_mounted": true, 59 | "mountpoint": "/" 60 | }, 61 | { 62 | "size_used": 42468, 63 | "name": "/dev/vdb", 64 | "size_total": 42468, 65 | "size_avail": 0, 66 | "is_mounted": true, 67 | "mountpoint": "/lib/modules" 68 | } 69 | ], 70 | "users": [ 71 | { 72 | "device": "pts/0", 73 | "host": "79.172.60.6", 74 | "name": "root", 75 | "login_time": 1478346362 76 | } 77 | ], 78 | "ram": { 79 | "buffer": 14928, 80 | "total": 1032644, 81 | "free": 838220 82 | }, 83 | "uptime": 12488, 84 | "uname": { 85 | "sysname": "Linux", 86 | "domain": "(none)", 87 | "nodename": "vm-61b27f65", 88 | "machine": "x86_64", 89 | "version": "#66-Ubuntu SMP Wed Oct 19 14:12:37 UTC 2016", 90 | "release": "4.4.0-45-generic" 91 | }, 92 | "loadavg": { 93 | "5m": 0.0087890625, 94 | "15m": 0, 95 | "1m": 0.0185546875 96 | }, 97 | "swap": { 98 | "total": 0, 99 | "free": 0 100 | } 101 | } 102 | 103 | 104 | ### file-open 105 | 106 | Open a file in the guest system. 107 | 108 | **Arguments:** 109 | 110 | - `path` -- full path to the file in the guest system 111 | - `mode` -- "r" or "w". Default is "r" 112 | - `perm` -- an access mode in dec of the file. Default is 420 (644 in oct) 113 | 114 | **Returns:** a file descriptor 115 | 116 | **Example:** 117 | 118 | -> { "execute": "file-open", arguments: { "path": "/path/to/file" } } 119 | <- { "return": 9 } 120 | 121 | 122 | ### file-read 123 | 124 | Read from the open file descriptor in the guest system. If EOF, the file will be automatically closed. 125 | 126 | **Arguments:** 127 | 128 | - `handle_id` -- a file descriptor 129 | 130 | **Returns:** 131 | 132 | - `bufb64` -- a base64-encoded string 133 | - `eof` -- true, if EOF 134 | 135 | **Example:** 136 | 137 | -> { "execute": "file-read", "arguments": { "handle_id": 9 } } 138 | <- { "return": { "bufb64": "Ny4yCg==", "eof": false } } 139 | 140 | 141 | ### file-write 142 | 143 | Write to the open file descriptor in the guest system. If EOF, the file will be automatically closed. 144 | 145 | **Arguments:** 146 | 147 | - `handle_id` -- a file descriptor 148 | - `bufb64` -- a base64-encoded string representing data to be written 149 | - `eof` -- true, if this is the last chunk of data 150 | 151 | **Returns:** true on success 152 | 153 | **Example:** 154 | 155 | -> { "execute": "file-write", "arguments": { "bufb64": "Ny4wCg==", "handle_id": 9, "eof": false } } 156 | <- { "return": true } 157 | 158 | 159 | ### file-close 160 | 161 | Close an open file descriptor in the guest system. 162 | 163 | **Arguments:** 164 | 165 | - `handle_id` -- a file descriptor 166 | 167 | **Returns:** true on success 168 | 169 | **Example:** 170 | 171 | -> { "execute": "file-close", "arguments': { "handle_id": 9 } } 172 | <- { "return": true } 173 | 174 | 175 | ### get-file-md5sum 176 | 177 | **Arguments:** 178 | 179 | - `path` -- full path to the file in the guest system 180 | 181 | **Returns:** a md5 hash of the file 182 | 183 | **Example:** 184 | 185 | -> { "execute": "get-file-md5sum", "arguments": { "path": "/path/to/file" } } 186 | <- { "return": "a81dbdee5126e79a7099b890c5a36ebe" } 187 | 188 | 189 | ### file-chown 190 | 191 | Change the file uid and gid in the guest system. 192 | 193 | **Arguments:** 194 | 195 | - `path` -- full path to the file in the guest system 196 | - `uid` -- a numeric uid of the file. Default is 0 197 | - `gid` -- a numeric gid of the file. Default is 0 198 | 199 | **Returns:** true on success 200 | 201 | **Example:** 202 | 203 | -> { "execute": "file-chown", "arguments": { "path": "/path/to/file", "uid": 0, "gid": 0 } } 204 | <- { "return": true } 205 | 206 | 207 | ### file-chmod 208 | 209 | Change the file mode bits in the guest system. 210 | 211 | **Arguments:**  212 | 213 | - `path` -- full path to the file in the guest system 214 | - `perm` -- an access mode in dec of the file which to be set 215 | 216 | **Returns:** true on success 217 | 218 | **Example:** 219 | 220 | -> { "execute": "file-chmod", "arguments": { "path": "/path/to/file", "perm": 493 } } 221 | <- { "return": true } 222 | 223 | ### file-stat 224 | 225 | **Arguments:**  226 | 227 | - `path` -- full path to the file in the guest system 228 | 229 | **Returns:** a stat structure of the file 230 | 231 | **Example:** 232 | 233 | -> { "execute": "file-stat", "arguments": { "path": "/path/to/file" } } 234 | <- { "return": { "name": "file", "isdir": false, "stat": {} } } 235 | 236 | 237 | ### directory-create 238 | 239 | **Arguments:**  240 | 241 | - `path` -- full path to the new directory in the guest system 242 | - `perm` -- an access mode in dec of the directory which to be set. Default is 493 (755 in oct) 243 | 244 | **Returns:** true on success 245 | 246 | **Example:** 247 | 248 | -> { "execute": "directory-create", "arguments": { "path": "/path/to/file" } } 249 | <- { "return": true } 250 | 251 | 252 | ### directory-list 253 | 254 | **Arguments:**  255 | 256 | - `path` -- full path to the directory in the guest system 257 | - `n` -- the number of elements in the returned list (if <= 0, then returns all files in the directory). Default is 0. 258 | 259 | **Returns:** a list of the file stat structures in this directory 260 | 261 | **Example:** 262 | 263 | -> { "execute": "directory-list", "arguments": { "path": "/path/to/directory" } } 264 | <- { "return": [] } 265 | 266 | 267 | ### fs-freeze 268 | 269 | Sync and freeze all freezable local guest file systems. 270 | 271 | Since version 0.6 this command ignores file systems located on loop and dm devices. It's a necessary measure to prevent blockages due to multiple mounts of the same devices (e.g., bind mounts). 272 | 273 | **Returns:** true on success 274 | 275 | **Example:** 276 | 277 | -> { "execute": "fs-freeze" } 278 | <- { "return": true } 279 | 280 | 281 | ### fs-unfreeze 282 | 283 | Unfreeze all frozen guest file systems. 284 | 285 | **Returns:** true on success 286 | 287 | **Example:** 288 | 289 | -> { "execute": "fs-unfreeze" } 290 | <- { "return": true } 291 | 292 | 293 | ### get-freeze-status 294 | 295 | **Returns:** true if file systems are frozen or false otherwise 296 | 297 | **Example:** 298 | 299 | -> { "execute": "get-freeze-status" } 300 | <- { "return": false } 301 | 302 | 303 | ### get-netifaces 304 | 305 | **Returns:** list of network parameters: IP-adresses, MAC-adresses etc. 306 | 307 | **Example:** 308 | 309 | -> { "execute": "get-netifaces" } 310 | <- { "return": [ 311 | { "hwaddr": "", 312 | "index": 1, 313 | "flags": "up|loopback", 314 | "name": "lo", 315 | "ips": ["127.0.0.1/8", "::1/128"] 316 | }, 317 | { "hwaddr": "00:16:3e:02:92:07", 318 | "index": 2, 319 | "flags": "up|broadcast|multicast", 320 | "name": "eth0", 321 | "ips': ["193.107.236.127/32", "fe80::216:3eff:fe02:9207/64"'] 322 | } ] 323 | } 324 | 325 | 326 | ### ipaddr-add 327 | 328 | Add the IP-address to the network interface in the guest system. 329 | 330 | The old name `linux-ipaddr-add` is also available, but is deprecated. 331 | 332 | **Arguments:** 333 | 334 | - `ip` -- an IP-address in CIDR format 335 | - `ifname` -- a network interface name 336 | 337 | **Returns:** true on success 338 | 339 | **Example:** 340 | 341 | -> { "execute": "linux-ipaddr-add", "arguments": { "ip": "192.168.55.77/32", "ifname": "eth0" } } 342 | <- { "return": true } 343 | 344 | 345 | ### ipaddr-del 346 | 347 | Remove the IP-address from the network interface in the guest system. 348 | 349 | The old name `linux-ipaddr-del` is also available, but is deprecated. 350 | 351 | **Arguments:** 352 | 353 | - `ip` -- an IP-address in CIDR format 354 | - `ifname` -- a network interface name 355 | 356 | **Returns:** true on success 357 | 358 | **Example:** 359 | 360 | -> { "execute": "linux-ipaddr-del", "arguments": { "ip": "192.168.55.77/32", "ifname": "eth0" } } 361 | <- { "return": true } 362 | 363 | 364 | ### net-iface-up 365 | 366 | Bring up the specified network interface in the guest system. 367 | 368 | **Arguments:** 369 | 370 | - `ifname` -- a network interface name 371 | 372 | **Returns:** true on success 373 | 374 | **Example:** 375 | 376 | -> { "execute": "net-iface-up", "arguments": { "ifname": "eth1" } } 377 | <- { "return": true } 378 | 379 | 380 | ### net-iface-down 381 | 382 | Bring down the specified network interface in the guest system. 383 | 384 | **Arguments:** 385 | 386 | - `ifname` -- a network interface name 387 | 388 | **Returns:** true on success 389 | 390 | **Example:** 391 | 392 | -> { "execute": "net-iface-down", "arguments": { "ifname": "eth1" } } 393 | <- { "return": true } 394 | 395 | 396 | ### get-route-list 397 | 398 | **Arguments:** 399 | 400 | - `family` -- an integer representation of family type from linux/socket.h: AF_UNSPEC, AF_INET or AF_INET6. Default is AF_UNSPEC 401 | 402 | **Returns:** a list of routing table entries 403 | 404 | **Example:** 405 | 406 | -> { "execute": "get-route-list", "arguments": { "family": 2 } } 407 | <- { "return": [ 408 | { "ifname": "eth0", 409 | "scope": 0, 410 | "dst": { "ip": "","mask": "" }, 411 | "src": "", 412 | "gateway": "10.11.11.11" 413 | }, 414 | { "ifname": "eth0", 415 | "scope": 253, 416 | "dst": { "ip": "10.11.11.11", "mask": "255.255.255.255" }, 417 | "src": "", 418 | "gateway": "" 419 | } ] 420 | } 421 | 422 | This output is equivalent to: 423 | 424 | $ ip route show 425 | default via 10.11.11.11 dev eth0 426 | 10.11.11.11 dev eth0 scope link 427 | 428 | 429 | ### route-add 430 | 431 | Add a new entry to the routing table in the guest system. 432 | 433 | **Arguments:** 434 | 435 | - `ifname` -- a name of output interface 436 | - `dst` -- a destination prefix of the route 437 | - `src` -- a source address to prefer when sending to the destinations covered by the route prefix 438 | - `gateway` -- an address of the nexthop router 439 | - `scope` -- an address scope. Default is 0 (`RT_SCOPE_UNIVERSE`, see `man 7 rtnetlink`) 440 | 441 | **Returns:** true on success 442 | 443 | **Example:** 444 | 445 | -> { "execute": "route-add", "arguments": { "ifname": "eth1", "dst": "8.8.8.8/32", "src": "", "gateway": "10.0.0.254" } } 446 | <- { "return": true } 447 | 448 | This command is identical to `ip route add 8.8.8.8/32 via 10.0.0.254` 449 | 450 | 451 | ### route-del 452 | 453 | Remove an entry from the routing table in the guest system. 454 | 455 | **Arguments:** 456 | 457 | - `ifname` -- a name of output interface 458 | - `dst` -- a destination prefix of the route 459 | - `src` -- a source address to prefer when sending to the destinations covered by the route prefix 460 | - `gateway` -- an address of the nexthop router 461 | - `scope` -- an address scope. Default is 0 (`RT_SCOPE_UNIVERSE`, see `man 7 rtnetlink`) 462 | 463 | **Returns:** true on success 464 | 465 | **Example:** 466 | 467 | -> { "execute": "route-del", "arguments": { "ifname": "eth0", "dst": "172.16.1.0/22", "src": "", "gateway": "10.0.0.254" } } 468 | <- { "return": true } 469 | 470 | This command is identical to `ip route del 172.16.1.0/22 via 10.0.0.254` 471 | --------------------------------------------------------------------------------