-
Notifications
You must be signed in to change notification settings - Fork 248
RFD 189: Console Access - vmadmd console proxy #1159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 8 commits
fd44461
b4a61eb
f6b4547
c09edc8
dda998b
44a60a8
abfe095
44e65b8
5e564d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -63,6 +63,7 @@ var REPORTED_STATES = ['running', 'stopped']; | |||||
| var VMADMD_PORT = 8080; | ||||||
| var VMADMD_AUTOBOOT_FILE = '/tmp/.autoboot_vmadmd'; | ||||||
|
|
||||||
| var CONSOLE = {}; | ||||||
| var PROV_WAIT = {}; | ||||||
| var SDC = {}; | ||||||
| var SPICE = {}; | ||||||
|
|
@@ -411,6 +412,186 @@ function reloadRemoteDisplay(vmobj) | |||||
| spawnRemoteDisplay(vmobj); | ||||||
| } | ||||||
|
|
||||||
| // | ||||||
| // spawnConsoleProxy() | ||||||
| // | ||||||
| // Creates a TCP proxy for console access (serial console for KVM, zone console | ||||||
| // for other brands). | ||||||
| // | ||||||
| // For KVM: Proxies to unix socket at <zonepath>/root/tmp/vm.console | ||||||
| // For other brands: Proxies to zone console device at /dev/zcons/<zonename>/zoneconsole | ||||||
| // | ||||||
| // vmobj must have: | ||||||
| // | ||||||
| // brand | ||||||
| // state | ||||||
| // uuid | ||||||
| // zonename | ||||||
| // zonepath | ||||||
| // zone_state | ||||||
| // | ||||||
| function spawnConsoleProxy(vmobj) | ||||||
| { | ||||||
| var addr; | ||||||
| var consolePath; | ||||||
| var port = 0; // Let the OS assign an ephemeral port | ||||||
| var server; | ||||||
| var zonepath = vmobj.zonepath; | ||||||
|
|
||||||
| if (!vmobj.zonepath) { | ||||||
| zonepath = '/zones/' + vmobj.uuid; | ||||||
| } | ||||||
|
|
||||||
| if (vmobj.state !== 'running' && vmobj.zone_state !== 'running') { | ||||||
| log.debug('skipping console setup for non-running VM ' + vmobj.uuid); | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| // Determine console path based on brand | ||||||
| if (vmobj.brand === 'kvm') { | ||||||
| // KVM uses unix socket for serial console | ||||||
| consolePath = path.join(zonepath, '/root/tmp/vm.console'); | ||||||
| } else { | ||||||
| // Bhyve, Joyent, LX, etc. use zoneadmd console socket | ||||||
| consolePath = '/var/run/zones/' + vmobj.zonename + '.console_sock'; | ||||||
| } | ||||||
|
|
||||||
| // Create TCP server that proxies to console socket | ||||||
| server = net.createServer(function (c) { | ||||||
| var consoleSocket = new net.Socket(); | ||||||
| var remote_address = ''; | ||||||
| var isKvm = (vmobj.brand === 'kvm'); | ||||||
| var handshakeDone = false; | ||||||
| var handshakeTimer = null; | ||||||
| var cleanedUp = false; | ||||||
|
|
||||||
| remote_address = '[' + c.remoteAddress + ']:' + c.remotePort; | ||||||
|
|
||||||
| // Cleanup function ensures all resources are properly released | ||||||
| function cleanup() { | ||||||
| if (cleanedUp) { | ||||||
| return; | ||||||
| } | ||||||
| cleanedUp = true; | ||||||
|
|
||||||
| if (handshakeTimer) { | ||||||
| clearTimeout(handshakeTimer); | ||||||
| handshakeTimer = null; | ||||||
| } | ||||||
|
|
||||||
| if (consoleSocket && !consoleSocket.destroyed) { | ||||||
| consoleSocket.destroy(); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| c.on('close', function (had_error) { | ||||||
| log.info('console connection ended from ' + remote_address + | ||||||
| ' for VM ' + vmobj.uuid); | ||||||
| cleanup(); | ||||||
| }); | ||||||
|
|
||||||
| consoleSocket.on('error', function (err) { | ||||||
| log.warn('console socket error for VM ' + vmobj.uuid + | ||||||
| ': ' + err.message); | ||||||
| cleanup(); | ||||||
| c.end(); | ||||||
| }); | ||||||
|
|
||||||
| c.on('error', function (err) { | ||||||
| log.warn('console net socket error for VM ' + vmobj.uuid + | ||||||
| ': ' + err.message); | ||||||
| cleanup(); | ||||||
| }); | ||||||
|
|
||||||
| // For non-KVM brands, perform zoneadmd console handshake | ||||||
| if (!isKvm) { | ||||||
| // Set timeout for handshake completion | ||||||
| handshakeTimer = setTimeout(function() { | ||||||
| if (!handshakeDone) { | ||||||
| log.error('console handshake timeout for VM ' + vmobj.uuid); | ||||||
| cleanup(); | ||||||
| c.end(); | ||||||
| } | ||||||
| }, 5000); | ||||||
|
|
||||||
| consoleSocket.once('connect', function () { | ||||||
| // Send zlogin-C handshake: IDENT <locale> <flags>\n | ||||||
| consoleSocket.write('IDENT C 0\n'); | ||||||
|
|
||||||
| // Wait for OK response before starting data flow | ||||||
| consoleSocket.once('data', function (data) { | ||||||
| clearTimeout(handshakeTimer); | ||||||
| handshakeTimer = null; | ||||||
|
|
||||||
| if (data.toString().indexOf('OK') === 0) { | ||||||
|
||||||
| if (data.toString().indexOf('OK') === 0) { | |
| if (data.toString().trim() === 'OK') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A JS person should reality-check this.
Outdated
Copilot
AI
Oct 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic number 100 for substring length should be defined as a named constant to make the truncation limit explicit and configurable.
| ': ' + data.toString().substring(0, 100)); | |
| ': ' + data.toString().substring(0, CONSOLE_HANDSHAKE_LOG_TRUNCATE_LEN)); |
Copilot
AI
Oct 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CONSOLE object is being populated inside the async callback, but the function returns immediately without waiting for the server to start listening. This creates a race condition where other code trying to access CONSOLE[vmobj.uuid] might find it undefined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A JS person should reality-check this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When this moves to non-draft we'll need >= two reviewers for JS reality checking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and other always-on calls to spawnRemoteDisplay() have me curious about an overarching question. Will ask it in the overarching comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic number 5000 for handshake timeout should be defined as a named constant to make the timeout configurable and self-documenting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, copilot is making its case well so far.