Skip to content
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

WSL: Use dnsmasq #1302

Merged
merged 6 commits into from
Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion scripts/download/wsl.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import path from 'path';
import { download } from '../lib/download.mjs';

export default async function main() {
const v = '0.12.1';
const v = '0.14';

await download(
`https://github.com/rancher-sandbox/rancher-desktop-wsl-distro/releases/download/v${ v }/distro-${ v }.tar`,
Expand Down
14 changes: 14 additions & 0 deletions src/assets/scripts/wsl-data.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# This is the /etc/wsl.conf for use with rancher-desktop-data
# As we do not have an actual data distribution, this file is included as part
# of the application and written out at runtime.

[automount]
# Prevent processing /etc/fstab, since it doesn't exist.
mountFsTab = false
# Prevent running ldconfig, since that doesn't exist.
ldconfig = false
# Needed for compatibility with some `npm install` scenarios.
options = metadata

# We _do_ want to generate `/etc/hosts` here, so that it can be used by the main
# distribution.
2 changes: 2 additions & 0 deletions src/k8s-engine/progressTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ export default class ProgressTracker {

/**
* Register an action.
* @param description Descriptive text for the action, to be shown to the user.
* @param priority Only the action with the largest priority will be shown among concurrent actions.
* @returns A promise that will be resolved when the passed-in promise resolves.
*/
action<T>(description: string, priority: number, promise: Promise<T>): Promise<T>;
Expand Down
95 changes: 93 additions & 2 deletions src/k8s-engine/wsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import LOGROTATE_K3S_SCRIPT from '@/assets/scripts/logrotate-k3s';
import SERVICE_BUILDKITD_INIT from '@/assets/scripts/buildkit.initd';
import SERVICE_BUILDKITD_CONF from '@/assets/scripts/buildkit.confd';
import INSTALL_WSL_HELPERS_SCRIPT from '@/assets/scripts/install-wsl-helpers';
import SCRIPT_DATA_WSL_CONF from '@/assets/scripts/wsl-data.conf';
import mainEvents from '@/main/mainEvents';
import * as childProcess from '@/utils/childProcess';
import Logging from '@/utils/logging';
Expand Down Expand Up @@ -59,7 +60,7 @@ const DISTRO_BLACKLIST = [
];

/** The version of the WSL distro we expect. */
const DISTRO_VERSION = '0.12.1';
const DISTRO_VERSION = '0.14';

/**
* The list of directories that are in the data distribution (persisted across
Expand Down Expand Up @@ -441,12 +442,12 @@ export default class WSLBackend extends events.EventEmitter implements K8s.Kuber
try {
// Create a distro archive from the main distro.
// WSL seems to require a working /bin/sh for initialization.
const OVERRIDE_FILES = { 'etc/wsl.conf': SCRIPT_DATA_WSL_CONF };
const REQUIRED_FILES = [
'/bin/busybox', // Base tools
'/bin/mount', // Required for WSL startup
'/bin/sh', // WSL requires a working shell to initialize
'/lib', // Dependencies for busybox
'/etc/wsl.conf', // WSL configuration for minimal startup
'/etc/passwd', // So WSL can spawn programs as a user
];
const archivePath = path.join(workdir, 'distro.tar');
Expand All @@ -471,6 +472,20 @@ export default class WSLBackend extends events.EventEmitter implements K8s.Kuber

await this.execCommand('tar', '-cf', await this.wslify(archivePath),
'-C', '/', ...extraFiles, ...DISTRO_DATA_DIRS);

// The tar-stream package doesn't handle appends well (needs to
// stream to a temporary file), and busybox tar doesn't support
// append either. Luckily Windows ships with a bsdtar that
// supports it, though it only supports short options.
for (const [relPath, contents] of Object.entries(OVERRIDE_FILES)) {
const absPath = path.join(workdir, 'tar', relPath);

await fs.promises.mkdir(path.dirname(absPath), { recursive: true });
await fs.promises.writeFile(absPath, contents);
}
await childProcess.spawnFile('tar.exe',
['-r', '-f', archivePath, '-C', path.join(workdir, 'tar'), ...Object.keys(OVERRIDE_FILES)]);
await this.execCommand('tar', '-tvf', await this.wslify(archivePath));
await this.execWSL('--import', DATA_INSTANCE_NAME, paths.wslDistroData, archivePath, '--version', '2');
} catch (ex) {
console.log(`Error registering data distribution: ${ ex }`);
Expand Down Expand Up @@ -512,6 +527,55 @@ export default class WSLBackend extends events.EventEmitter implements K8s.Kuber
}
}

/**
* Write out /etc/hosts in the main distribution, copying the bulk of the
* contents from the data distribution.
*/
protected async writeHostsFile() {
await this.progressTracker.action('Updating /etc/hosts', 50, async() => {
const contents = await fs.promises.readFile(`\\\\wsl$\\${ DATA_INSTANCE_NAME }\\etc\\hosts`);
const hosts = ['host.rancher-desktop.internal', 'host.docker.internal'];
const extra = [
'# BEGIN Rancher Desktop configuration.',
`${ this.hostIPAddress } ${ hosts.join(' ') }`,
'# END Rancher Desktop configuration.',
].map(l => `${ l }\n`).join('');

await fs.promises.writeFile(`\\\\wsl$\\${ INSTANCE_NAME }\\etc\\hosts`,
Buffer.concat([contents, Buffer.from(extra, 'utf-8')]));
});
}

/**
* Write configuration for dnsmasq / and /etc/resolv.conf; required before [runInit].
*/
protected async writeResolvConf() {
await this.progressTracker.action('Updating DNS configuration', 50,
// Tell dnsmasq to use the resolv.conf from the data distro as the
// upstream configuration.
Promise.all([
(async() => {
try {
const contents = await this.readFile(
'/run/resolvconf/resolv.conf', { distro: DATA_INSTANCE_NAME });

await this.writeFile('/etc/dnsmasq.d/data-resolv-conf', contents);
} catch (ex) {
console.error('Failed to copy existing resolv.conf');
throw ex;
}
})(),
this.writeFile(
'/etc/dnsmasq.d/rancher-desktop.conf',
Object.entries({
'resolv-file': '/etc/dnsmasq.d/data-resolv-conf',
'listen-address': await this.ipAddress,
}).map(([k, v]) => `${ k }=${ v }\n`).join('')),
this.writeFile('/etc/resolv.conf', `nameserver ${ await this.ipAddress }`),
this.writeConf('dnsmasq', { DNSMASQ_OPTS: '--user=dnsmasq --group=dnsmasq' }),
]));
}

/**
* Mount the data distribution over.
*/
Expand Down Expand Up @@ -655,6 +719,24 @@ export default class WSLBackend extends events.EventEmitter implements K8s.Kuber
}
}

/**
* Read the given file in a WSL distribution
* @param [filePath] the path of the file to read.
* @param [options] Optional configuratino for reading the file.
* @param [options.distro=INSTANCE_NAME] The distribution to read from.
* @param [options.encoding='utf-8'] The encoding to use for the result.
*/
protected async readFile(filePath: string, options?: Partial<{ distro: typeof INSTANCE_NAME | typeof DATA_INSTANCE_NAME, encoding : BufferEncoding}>) {
const distro = options?.distro ?? INSTANCE_NAME;
const encoding = options?.encoding ?? 'utf-8';
// Run wslpath here, to ensure that WSL generates any files we need.
const windowsPath = (await this.execWSL({ capture: true, encoding },
'--distribution', distro,
'/bin/wslpath', '-w', filePath)).trim();

return await fs.promises.readFile(windowsPath, options?.encoding ?? 'utf-8');
}

/**
* Write the given contents to a given file name in the RD WSL distribution.
* @param filePath The destination file path, in the WSL distribution.
Expand Down Expand Up @@ -851,6 +933,13 @@ export default class WSLBackend extends events.EventEmitter implements K8s.Kuber
})();
}

/** Get the IPv4 address of the WSL (VM) host interface, assuming it's already up. */
get hostIPAddress(): string | undefined {
const iface = os.networkInterfaces()['vEthernet (WSL)'];

return (iface ?? []).find(addr => addr.family === 'IPv4')?.address;
}

async getBackendInvalidReason(): Promise<K8s.KubernetesError | null> {
// Check if wsl.exe is available
try {
Expand Down Expand Up @@ -1022,6 +1111,8 @@ export default class WSLBackend extends events.EventEmitter implements K8s.Kuber
await this.upgradeDistroAsNeeded();
await this.ensureDistroRegistered();
await this.initDataDistribution();
await this.writeHostsFile();
await this.writeResolvConf();
})(),
this.progressTracker.action(
'Checking k3s images',
Expand Down