別記事の続きです。
ここではポートフォワードの設定や仮想メディアの情報取得について書いておきます。完成形のサンプルは最後に掲載します。
もくじ:
1. ポートフォワードの設定
IPアドレスの数が限られている環境ではNATを使って、ホストのアドレスを各VMで共用すると便利です。サーバ用途など外側からのアクセスが必要な場合は、ポートフォワードで特定のポートに接続させる構成が可能です。というか、私がVirtualBoxを使う理由はこのポートフォワードが比較的簡単に構成できるためです。
VirtualBoxではいわゆるNAT(NAPT, IPマスカレード)が構成できるネットワークは2種類あります。つまり、VM間通信用のセグメントがない”NAT”(VM作成時のデフォルト)と、専用のセグメントを仮想的に作成しVM間がローカルなアドレスで通信できる”NATNetwork”の2種類です。
複数のVMで同じセグメントを共有する必要がない場合、例えばpingは疎通させなくてもいい、とか固定のアドレスを持つ必要がないなど、であれば前者の”NAT”で十分です。
私も特にVM間通信が不要な使い方ばかりなので、”NATNetwork”は使わず、専ら”NAT”を使っています。一般的な用語としてのNATではなく、VirtualBoxの仮想NICの構成種別としての”NAT”です。
外からのアクセスを待ち受けるには、ポートフォワードを設定し、ゲスト側とホスト側のポートを関連付けます。
APIでは、”NAT”向けのポートフォワードをredirect
、”NATNetwork”向けのポートフォワードをportforward
と名称を分けているようです。redirectは各VMに対して、portforwardはホスト単位に設定可能なNATネットワーク(仮想セグメント)と呼ばれる仮想ネットワーク毎に設定されます。いずれもiptablesのような高度なパケットフィルタではなく、単にゲストとホストのListenポートの組に対してTCP/UDPのフォワードを定義できるといった基本的なアドレス変換機能になっています。
今回はredirect(NAT, GUIでは下記の画面)のみ扱いますが、メソッドの使い方はportforward(NATNetwork)でも似た感じになっているようなので、参考になると思われます。
なお、ホスト側でファイヤウォールを利用している場合(大抵は利用していると思いますが)には当該ポートの受信許可も設定する必要があります。
1.1 一覧の取得
redirectはINATEngine.redirects
から参照できます。
IMachine.getNetworkAdapter()
に仮想アダプタの番号を指定すると、INetwrokAdapter
のプロパティからINATEngine
を参照できます。
また、”NAT”の仮想アダプタを期待しているので、種類をチェックしています。
1 2 3 4 5 6 7 8 | vm_ = self.vbox.findMachine(vm_name) nic_ = vm_.getNetworkAdapter(nic_num) # check if "NAT" type adapter if nic_.attachmentType != self.cst.NetworkAttachmentType_NAT: raise Exception("unexpected attachment type") # get defined redirects redirects_ = [str(x) for x in nic_.NATEngine.redirects] |
リダイレクトの各ルール毎の設定を表す文字列の配列となっており、フォーマットはこんな感じになっています。
1 | redirects_ = [u'Rule 1,1,,6000,,22', u'Rule 2,1,,6001,,3389'] |
仮想アダプタはデフォルトの設定を使っていると0番のものになると思います。まあPCで使っているレベルなら複数使うケース自体そうないと思いますので0番を設定できれば十分かも知れません。
仮想アダプタの最大数はホスト側のハードウェア設定に依存していますがおそらく8くらいの環境が多いと思います。GUIからは4つまで参照でき、以降の設定は設定ファイル経由で設定できます。
1.2 リダイレクトの追加
種類に”NAT”を指定したアダプタの場合、ゲスト側(NAT内側)のアドレスは自動的に割り振られる(10.0.2.15)ので、今回は指定せず自動とします。
リダイレクトを追加するには、INATEngine.addRedirect()
を使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # change with exclusive lock session_ = self.manager.getSessionObject(self.vbox) self.vm.lockMachine(session_, self.cst.LockType_Write) if config["protocol"] == "TCP": proto_ = self.cst.NATProtocol_TCP else: proto_ = self.cst.NATProtocol_UDP # claim mutable machine object for editing vm_ = session_.machine natengine_ = vm_.getNetworkAdapter(nic_num).NATEngine # add redirect without specifying host/guest IP address natengine_.addRedirect(config["name"], proto_, "", config["host_port"], "", config["guest_port"]) # commit change and dispose session vm_.saveSettings() self.manager.closeMachineSession(session_) |
また、設定変更のために対象のロックIMachine.lockMachine()
、設定保存IMachine.saveSettings()
を呼んだうえで明示的にセッションを終了closeMachineSession()
します。
1.3 リダイレクトの削除
リダイレクトを削除するには名前を指定してINATEngine.removeRedirect()
を呼びます。ロック取得から設定変更の流れは追加の場合と同じです。
1 2 3 4 5 6 7 8 9 10 11 | # lock machine for changing config session_ = self.manager.getSessionObject(self.vbox) self.vm.lockMachine(session_, self.cst.LockType_Write) vm_ = session_.machine # remove redirect with specifying config name vm_.getNetworkAdapter(nic_num).NATEngine.removeRedirect(config_name) # commit change and dispose session vm_.saveSettings() self.manager.closeMachineSession(session_) |
2. 仮想メディアの取得
vbox APIでは、仮想ハードディスクや仮想DVDドライブはメディアmediumとして定義されています。
単に設定を調べる場合のほか、可変仮想HDDを縮小する際など、IDを取得できると便利です。
VMごとに接続されているメディアをIMachine.mediumAttachments
から取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | vm_ = self.vbox.findMachine(vm_name) for attach_ in vm_.mediumAttachments: print("attachemnt - port: {}, controller: {}".format( attach_.port, attach_.controller)) buf_ = attach_.medium # skip if no medium connected if str(buf_) == "" or buf_ is None: print(" - none connected") continue print(" - medium id: {} (type: {}, state: {})".format( buf_.id, buf_.deviceType, buf_.state)) ... |
取得した結果はこんな感じになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | >>> from vbox_client import vboxClient >>> _client = vboxClient() >>> _client.inspect_attached_medium("vm1") attachment - port: 1, controller: IDE - medium id: 7fd35...25ad (type: DVD , state: Created) - medium location: C:\...\image.iso attachment - port: 0, controller: SATA - medium id: 2aa8d...f1f3 (type: HardDisk , state: LockedWrite) - medium location: C:\...\vm1-1.vdi attachment - port: 0, controller: SCSI - medium id: d21c7...3365 (type: HardDisk , state: Created) - medium location: C:\...\vm1-2.vdi attachment - port: 1, controller: SCSI - medium id: 1ccad...ab6d (type: HardDisk , state: Created) - medium location: C:\...\vm1-3.vdi |
3. サンプル
前回分と合わせてサンプルは下記のようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | from pprint import pprint from vboxapi import VirtualBoxManager class vboxClient: def __init__(self, config=None): if config is None: # work on local host self.manager = VirtualBoxManager(None, None) else: # work with remote host (requires SOAP client) url_ = "http://{}:{}".format(config["host"], config["port"]) self.manager = VirtualBoxManager("WEBSERVICE", {"url": url_, "user": config["user"], "password": config["password"]}) # instanciate virtualbox interface self.vbox = self.manager.getVirtualBox() self.vm = None self.cst = self.manager.constants self.__timeout_msec = 10000 def list_machines(self): # here enumerates interested properties attrs = ["name", "id", "state", "autostartEnabled", "CPUCount", "memorySize", "settingsFilePath", "logFolder", "chipsetType", "sessionName", "sessionState", "sessionPID"] for vm_ in self.manager.getArray(self.vbox, 'machines'): buf_ = {} for k_ in attrs: buf_[k_] = str(vm_.__getattr__(k_)) # just print on console pprint(buf_) def start_vm(self, vm_name): # this may raise exception if specified vm name does not exist vm_ = self.vbox.findMachine(vm_name) # invoke lauching VM process session_ = self.manager.getSessionObject(self.vbox) prog_ = vm_.launchVMProcess(session_, "headless", "") # wait and terminate current session prog_.waitForCompletion(self.__timeout_msec) self.manager.closeMachineSession(session_) def stop_vm(self, vm_name, force=False): vm_ = self.vbox.findMachine(vm_name) session_ = self.manager.getSessionObject(self.vbox) # expecting this lock will be freed after power down vm_.lockMachine(session_, self.cst.LockType_Shared) if force: # explicitly stop VM process prog_ = session_.console.powerDown() prog_.waitForCompletion(self.__timeout_msec) else: # try ACPI shutdown session_.console.powerButton() def inspect_redirects(self, vm_name): sysprops = self.vbox.systemProperties vm_ = self.vbox.findMachine(vm_name) adapter_count = sysprops.getMaxNetworkAdapters(vm_.chipsetType) print("NIC count: {} (chipset:{})".format(str(adapter_count), str(vm_.chipsetType))) for i in range(adapter_count): nic_ = vm_.getNetworkAdapter(i) print("+ slot: {}, enabled: {}, type:{}".format(i, nic_.enabled, nic_.adapterType)) if nic_.enabled: print(" - attachment type: {}".format(nic_.attachmentType)) print(" - MAC address: {}".format(nic_.MACAddress)) print(" - NAT network: {}".format(nic_.NATNetwork)) print(" - redirects: {}".format(nic_.NATEngine.redirects)) def __validate_redirects(self, vm_name, config_name, nic_num): # check attachment type vm_ = self.vbox.findMachine(vm_name) nic_ = vm_.getNetworkAdapter(nic_num) if nic_.attachmentType != self.cst.NetworkAttachmentType_NAT: raise Exception("unexpected attachment type") # check if already exists redirects_ = [str(x) for x in nic_.NATEngine.redirects] self.vm = vm_ return config_name in [y.split(",")[0] for y in redirects_] def add_redirect(self, vm_name, config, nic_num=0): # preflight check pprint(config) if self.__validate_redirects(vm_name, config["name"], nic_num): raise Exception("the same name configuration is already exists") # change with exclusive lock session_ = self.manager.getSessionObject(self.vbox) self.vm.lockMachine(session_, self.cst.LockType_Write) if config["protocol"] == "TCP": proto_ = self.cst.NATProtocol_TCP else: proto_ = self.cst.NATProtocol_UDP # claim mutable machine object for editing vm_ = session_.machine natengine_ = vm_.getNetworkAdapter(nic_num).NATEngine # add redirect without specifying host/guest IP address natengine_.addRedirect(config["name"], proto_, "", config["host_port"], "", config["guest_port"]) # commit change and dispose session vm_.saveSettings() self.manager.closeMachineSession(session_) def remove_redirect(self, vm_name, config_name, nic_num=0): # preflight check pprint(config_name) if not self.__validate_redirects(vm_name, config_name, nic_num): raise Exception("specified configuration is not found") session_ = self.manager.getSessionObject(self.vbox) self.vm.lockMachine(session_, self.cst.LockType_Write) vm_ = session_.machine vm_.getNetworkAdapter(nic_num).NATEngine.removeRedirect(config_name) vm_.saveSettings() self.manager.closeMachineSession(session_) def inspect_attached_medium(self, vm_name): vm_ = self.vbox.findMachine(vm_name) for attach_ in vm_.mediumAttachments: print("attachemnt - port: {}, controller: {}".format( attach_.port, attach_.controller)) buf_ = attach_.medium # skip if no medium connected if str(buf_) == "" or buf_ is None: print(" - none connected") continue print(" - medium id: {} (type: {}, state: {})".format( buf_.id, buf_.deviceType, buf_.state)) print(" - medium location: {}".format(buf_.location)) |
おわり。