[dds] Support IPv6 for DAP with --ipv6 flag

Change-Id: I07c66b79e832f200bf5f27c20930e1ba6a2ebda7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/205261
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
Danny Tuppeny 2021-07-02 00:04:52 +00:00 committed by commit-bot@chromium.org
parent 8e4e4c56aa
commit 8f01499793
10 changed files with 90 additions and 58 deletions

View file

@ -105,6 +105,9 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
/// yet been made.
DartDevelopmentService? _dds;
/// Whether to use IPv6 for DAP/Debugger services.
final bool ipv6;
/// Whether to enable DDS for launched applications.
final bool enableDds;
@ -121,6 +124,7 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
DartDebugAdapter(
ByteStreamServerChannel channel, {
this.ipv6 = false,
this.enableDds = true,
this.enableAuthCodes = true,
this.logger,
@ -194,8 +198,8 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
logger?.call('Starting a DDS instance for $uri');
final dds = await DartDevelopmentService.startDartDevelopmentService(
uri,
// TODO(dantup): Allow this to be disabled?
enableAuthCodes: true,
enableAuthCodes: enableAuthCodes,
ipv6: ipv6,
);
_dds = dds;
uri = dds.wsUri!;

View file

@ -43,11 +43,13 @@ class DartCliDebugAdapter extends DartDebugAdapter<DartLaunchRequestArguments> {
DartCliDebugAdapter(
ByteStreamServerChannel channel, {
bool ipv6 = false,
bool enableDds = true,
bool enableAuthCodes = true,
Logger? logger,
}) : super(
channel,
ipv6: ipv6,
enableDds: enableDds,
enableAuthCodes: enableAuthCodes,
logger: logger,
@ -109,7 +111,7 @@ class DartCliDebugAdapter extends DartDebugAdapter<DartLaunchRequestArguments> {
final vmServiceInfoFile = _vmServiceInfoFile;
final vmArgs = <String>[
if (debug) ...[
'--enable-vm-service=${args.vmServicePort ?? 0}',
'--enable-vm-service=${args.vmServicePort ?? 0}${ipv6 ? '/::1' : ''}',
'--pause_isolates_on_start=true',
if (!enableAuthCodes) '--disable-service-auth-codes'
],

View file

@ -16,6 +16,7 @@ import 'protocol_stream_transformers.dart';
/// A DAP server that binds to a port and runs in multi-session mode.
class DapServer {
final ServerSocket _socket;
final bool ipv6;
final bool enableDds;
final bool enableAuthCodes;
final Logger? logger;
@ -24,6 +25,7 @@ class DapServer {
DapServer._(
this._socket, {
this.ipv6 = false,
this.enableDds = true,
this.enableAuthCodes = true,
this.logger,
@ -55,6 +57,7 @@ class DapServer {
final channel = ByteStreamServerChannel(_input, _output, logger);
final adapter = DartCliDebugAdapter(
channel,
ipv6: ipv6,
enableDds: enableDds,
enableAuthCodes: enableAuthCodes,
logger: logger,
@ -70,15 +73,19 @@ class DapServer {
/// Starts a DAP Server listening on [host]:[port].
static Future<DapServer> create({
String host = 'localhost',
String? host,
int port = 0,
bool ipv6 = false,
bool enableDdds = true,
bool enableAuthCodes = true,
Logger? logger,
}) async {
final _socket = await ServerSocket.bind(host, port);
final hostFallback =
ipv6 ? InternetAddress.loopbackIPv6 : InternetAddress.loopbackIPv4;
final _socket = await ServerSocket.bind(host ?? hostFallback, port);
return DapServer._(
_socket,
ipv6: ipv6,
enableDds: enableDdds,
enableAuthCodes: enableAuthCodes,
logger: logger,

View file

@ -5,6 +5,7 @@
import 'package:test/test.dart';
import 'test_client.dart';
import 'test_scripts.dart';
import 'test_support.dart';
main() {
@ -12,11 +13,7 @@ main() {
group('debug mode breakpoints', () {
test('stops at a line breakpoint', () async {
final client = dap.client;
final testFile = dap.createTestFile(r'''
void main(List<String> args) async {
print('Hello!'); // BREAKPOINT
}
''');
final testFile = dap.createTestFile(simpleBreakpointProgram);
final breakpointLine = lineWith(testFile, '// BREAKPOINT');
await client.hitBreakpoint(testFile, breakpointLine);
@ -24,11 +21,7 @@ void main(List<String> args) async {
test('stops at a line breakpoint and can be resumed', () async {
final client = dap.client;
final testFile = dap.createTestFile(r'''
void main(List<String> args) async {
print('Hello!'); // BREAKPOINT
}
''');
final testFile = dap.createTestFile(simpleBreakpointProgram);
final breakpointLine = lineWith(testFile, '// BREAKPOINT');
// Hit the initial breakpoint.

View file

@ -6,6 +6,7 @@ import 'package:dds/src/dap/adapters/dart.dart';
import 'package:test/test.dart';
import 'test_client.dart';
import 'test_scripts.dart';
import 'test_support.dart';
main() {
@ -30,10 +31,7 @@ void main(List<String> args) {
test('evaluates expressions with complex results', () async {
final client = dap.client;
final testFile = await dap.createTestFile(r'''
void main(List<String> args) {
print('Hello!'); // BREAKPOINT
}''');
final testFile = await dap.createTestFile(simpleBreakpointProgram);
final breakpointLine = lineWith(testFile, '// BREAKPOINT');
final stop = await client.hitBreakpoint(testFile, breakpointLine);
@ -57,10 +55,7 @@ void main(List<String> args) {
'evaluates complex expressions expressions with evaluateToStringInDebugViews=true',
() async {
final client = dap.client;
final testFile = await dap.createTestFile(r'''
void main(List<String> args) {
print('Hello!'); // BREAKPOINT
}''');
final testFile = await dap.createTestFile(simpleBreakpointProgram);
final breakpointLine = lineWith(testFile, '// BREAKPOINT');
final stop = await client.hitBreakpoint(

View file

@ -6,6 +6,7 @@ import 'package:dds/src/dap/protocol_generated.dart';
import 'package:test/test.dart';
import 'test_client.dart';
import 'test_scripts.dart';
import 'test_support.dart';
main() {
@ -47,11 +48,7 @@ void main(List<String> args) async {
test('provides a list of threads', () async {
final client = dap.client;
final testFile = dap.createTestFile(r'''
void main(List<String> args) async {
print('Hello!'); // BREAKPOINT
}
''');
final testFile = dap.createTestFile(simpleBreakpointProgram);
final breakpointLine = lineWith(testFile, '// BREAKPOINT');
await client.hitBreakpoint(testFile, breakpointLine);
@ -63,11 +60,7 @@ void main(List<String> args) async {
test('runs with DDS', () async {
final client = dap.client;
final testFile = dap.createTestFile(r'''
void main(List<String> args) async {
print('Hello!'); // BREAKPOINT
}
''');
final testFile = dap.createTestFile(simpleBreakpointProgram);
final breakpointLine = lineWith(testFile, '// BREAKPOINT');
await client.hitBreakpoint(testFile, breakpointLine);
@ -77,9 +70,7 @@ void main(List<String> args) async {
}, timeout: Timeout.none);
test('runs with auth codes enabled', () async {
final testFile = dap.createTestFile(r'''
void main(List<String> args) {}
''');
final testFile = dap.createTestFile(emptyProgram);
final outputEvents = await dap.client.collectOutput(file: testFile);
expect(_hasAuthCode(outputEvents.first), isTrue);
@ -90,11 +81,7 @@ void main(List<String> args) {}
group('debug mode', () {
test('runs without DDS', () async {
final client = dap.client;
final testFile = dap.createTestFile(r'''
void main(List<String> args) async {
print('Hello!'); // BREAKPOINT
}
''');
final testFile = dap.createTestFile(simpleBreakpointProgram);
final breakpointLine = lineWith(testFile, '// BREAKPOINT');
await client.hitBreakpoint(testFile, breakpointLine);
@ -103,9 +90,7 @@ void main(List<String> args) async {
});
test('runs with auth tokens disabled', () async {
final testFile = dap.createTestFile(r'''
void main(List<String> args) {}
''');
final testFile = dap.createTestFile(emptyProgram);
final outputEvents = await dap.client.collectOutput(file: testFile);
expect(_hasAuthCode(outputEvents.first), isFalse);
@ -113,18 +98,39 @@ void main(List<String> args) {}
// These tests can be slow due to starting up the external server process.
}, timeout: Timeout.none);
}, additionalArgs: ['--no-dds', '--no-auth-codes']);
testDap((dap) async {
group('debug mode', () {
test('can run with ipv6', () async {
final testFile = dap.createTestFile(emptyProgram);
final outputEvents = await dap.client.collectOutput(file: testFile);
final vmServiceUri = _extractVmServiceUri(outputEvents.first);
// Check DAP server host.
expect(dap.server.host, equals('::1'));
// Check VM Service/DDS host.
expect(vmServiceUri.host, equals('::1'));
});
// These tests can be slow due to starting up the external server process.
}, timeout: Timeout.none);
}, additionalArgs: ['--ipv6']);
}
/// Extracts the VM Service URI from the "Connecting to ..." banner output by
/// the DAP server upon connection.
Uri _extractVmServiceUri(OutputEventBody vmConnectionBanner) {
// TODO(dantup): Change this to use the dart.debuggerUris custom event
// if implemented (whch VS Code also needs).
final vmServiceUriPattern = RegExp(r'Connecting to VM Service at ([^\s]+)\s');
final match = vmServiceUriPattern.firstMatch(vmConnectionBanner.output);
return Uri.parse(match!.group(1)!);
}
/// Checks for the presence of an auth token in a VM Service URI in the
/// "Connecting to VM Service" [OutputEvent].
bool _hasAuthCode(OutputEventBody vmConnection) {
// TODO(dantup): Change this to use the dart.debuggerUris custom event
// if implemented (whch VS Code also needs).
final vmServiceUriPattern = RegExp(r'Connecting to VM Service at ([^\s]+)\s');
final authCodePattern = RegExp(r'ws://127.0.0.1:\d+/[\w=]{5,15}/ws');
final vmServiceUri =
vmServiceUriPattern.firstMatch(vmConnection.output)!.group(1);
return vmServiceUri != null && authCodePattern.hasMatch(vmServiceUri);
bool _hasAuthCode(OutputEventBody vmConnectionBanner) {
final vmServiceUri = _extractVmServiceUri(vmConnectionBanner);
final authCodePattern = RegExp(r'^/[\w=]{5,15}/ws');
return authCodePattern.hasMatch(vmServiceUri.path);
}

View file

@ -305,8 +305,8 @@ class DapTestClient {
/// Creates a [DapTestClient] that connects the server listening on
/// [host]:[port].
static Future<DapTestClient> connect(
String host,
int port, {
String host = 'localhost',
bool captureVmServiceTraffic = false,
Logger? logger,
}) async {

View file

@ -0,0 +1,17 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// A simple empty Dart script that should run with no output and no errors.
const emptyProgram = '''
void main(List<String> args) {}
''';
/// A simple Dart script that should run with no errors and contains a comment
/// marker '// BREAKPOINT' for use in tests that require stopping at a breakpoint
/// but require no other context.
const simpleBreakpointProgram = r'''
void main(List<String> args) async {
print('Hello!'); // BREAKPOINT
}
''';

View file

@ -110,7 +110,7 @@ class DapTestSession {
var attempt = 1;
while (attempt++ <= 100) {
try {
return await DapTestClient.connect(server.port);
return await DapTestClient.connect(server.host, server.port);
} catch (e) {
await Future.delayed(const Duration(milliseconds: 200));
}

View file

@ -25,6 +25,7 @@ Future<void> main(List<String> arguments) async {
class DapCommand extends Command {
static const argHost = 'host';
static const argPort = 'port';
static const argIpv6 = 'ipv6';
static const argDds = 'dds';
static const argAuthCodes = 'auth-codes';
static const argVerbose = 'verbose';
@ -39,8 +40,9 @@ class DapCommand extends Command {
argParser
..addOption(
argHost,
defaultsTo: 'localhost',
help: 'The hostname/IP to bind the server to',
help: 'The hostname/IP to bind the server to. If not supplied, will'
' use the appropriate loopback address depending on whether'
' --ipv6 is set',
)
..addOption(
argPort,
@ -48,6 +50,10 @@ class DapCommand extends Command {
defaultsTo: '0',
help: 'The port to bind the server to',
)
..addFlag(
argIpv6,
help: 'Whether to bind DAP/VM Service/DDS to IPv6 addresses',
)
..addFlag(
argDds,
defaultsTo: true,
@ -69,10 +75,12 @@ class DapCommand extends Command {
final args = argResults!;
final port = int.parse(args[argPort]);
final host = args[argHost];
final ipv6 = args[argIpv6] as bool;
final server = await DapServer.create(
host: host,
port: port,
ipv6: ipv6,
enableDdds: args[argDds],
enableAuthCodes: args[argAuthCodes],
logger: args[argVerbose] ? print : null,