diff --git a/core/lib/Drupal/Core/Path/PathValidator.php b/core/lib/Drupal/Core/Path/PathValidator.php
index e6ebf5d..6f5a28d 100644
--- a/core/lib/Drupal/Core/Path/PathValidator.php
+++ b/core/lib/Drupal/Core/Path/PathValidator.php
@@ -81,7 +81,28 @@ public function isValid($path) {
   /**
    * {@inheritdoc}
    */
+  public function isValidWithoutAccessCheck($path) {
+    return (bool) $this->getUrlIfValidWithoutAccessCheck($path);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getUrlIfValid($path) {
+    return $this->getUrl($path, TRUE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getUrlIfValidWithoutAccessCheck($path) {
+    return $this->getUrl($path, FALSE);
+  }
+
+  /**
+   * Helper for getUrlIfValid() and getUrlIfValidWithoutAccessCheck().
+   */
+  protected function getUrl($path, $access_check) {
     $parsed_url = UrlHelper::parse($path);
 
     $options = [];
@@ -104,7 +125,7 @@ public function getUrlIfValid($path) {
 
     $path = ltrim($path, '/');
     $request = Request::create('/' . $path);
-    $attributes = $this->getPathAttributes($path, $request);
+    $attributes = $this->getPathAttributes($path, $request, $access_check);
 
     if (!$attributes) {
       return FALSE;
@@ -123,12 +144,15 @@ public function getUrlIfValid($path) {
    *   The path to check.
    * @param \Symfony\Component\HttpFoundation\Request $request
    *   A request object with the given path.
+   * @param bool $access_check
+   *   If FALSE then skip access check and check only whether the path is
+   *   valid.
    *
    * @return array|bool
    *   An array of request attributes of FALSE if an exception was thrown.
    */
-  protected function getPathAttributes($path, Request $request) {
-    if ($this->account->hasPermission('link to any page')) {
+  protected function getPathAttributes($path, Request $request, $access_check) {
+    if (!$access_check || $this->account->hasPermission('link to any page')) {
       $router = $this->accessUnawareRouter;
     }
     else {
diff --git a/core/lib/Drupal/Core/Path/PathValidatorInterface.php b/core/lib/Drupal/Core/Path/PathValidatorInterface.php
index e0bcf04..311b644 100644
--- a/core/lib/Drupal/Core/Path/PathValidatorInterface.php
+++ b/core/lib/Drupal/Core/Path/PathValidatorInterface.php
@@ -24,6 +24,19 @@
   public function getUrlIfValid($path);
 
   /**
+   * Returns an URL object, if the path is valid.
+   *
+   * Unlike getUrlIfValid(), access check is not performed.
+   *
+   * @param string $path
+   *   The path to check.
+   *
+   * @return \Drupal\Core\Url|false
+   *   The url object, or FALSE if the path is not valid.
+   */
+  public function getUrlIfValidWithoutAccessCheck($path);
+
+  /**
    * Checks if the URL path is valid and accessible by the current user.
    *
    * @param string $path
@@ -34,4 +47,17 @@ public function getUrlIfValid($path);
    */
   public function isValid($path);
 
+  /**
+   * Checks if the URL path is valid.
+   *
+   * Unlike isValid(), access check is not performed.
+   *
+   * @param string $path
+   *   The path to check.
+   *
+   * @return bool
+   *   TRUE if the path is valid.
+   */
+  public function isValidWithoutAccessCheck($path);
+
 }
diff --git a/core/modules/migrate/src/Plugin/migrate/process/Route.php b/core/modules/migrate/src/Plugin/migrate/process/Route.php
new file mode 100644
index 0000000..1e76def
--- /dev/null
+++ b/core/modules/migrate/src/Plugin/migrate/process/Route.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal\Plugin\migrate\process\d6\Route.
+ */
+
+namespace Drupal\migrate\Plugin\migrate\process;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\migrate\Entity\MigrationInterface;
+use Drupal\Core\Path\PathValidatorInterface;
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate\ProcessPluginBase;
+use Drupal\migrate\Row;
+
+/**
+ * @MigrateProcessPlugin(
+ *   id = "route"
+ * )
+ */
+class Route extends ProcessPluginBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * @var \Drupal\Core\Path\PathValidatorInterface
+   */
+  protected $pathValidator;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, PathValidatorInterface $pathValidator) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->migration = $migration;
+    $this->pathValidator = $pathValidator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $migration,
+      $container->get('path.validator')
+    );
+  }
+  /**
+   * {@inheritdoc}
+   *
+   * Set the destination route information based on the source link_path.
+   */
+  public function transform($value, MigrateExecutable $migrate_executable, Row $row, $destination_property) {
+    list($link_path, $options) = $value;
+    $extracted = $this->pathValidator->getUrlIfValidWithoutAccessCheck($link_path);
+    $route = array();
+
+    if ($extracted) {
+      if ($extracted->isExternal()) {
+        $route['route_name'] = null;
+        $route['route_parameters'] = array();
+        $route['options'] = $options;
+        $route['url'] = $extracted->getUri();
+      }
+      else {
+        $route['route_name'] = $extracted->getRouteName();
+        $route['route_parameters'] = $extracted->getRouteParameters();
+        $route['options'] = $extracted->getOptions();
+
+        if (isset($options['query'])) {
+          // If the querystring is stored as a string (as in D6), convert it
+          // into an array.
+          if (is_string($options['query'])) {
+            parse_str($options['query'], $old_query);
+          }
+          else {
+            $old_query = $options['query'];
+          }
+          $options['query'] = $route['options']['query'] + $old_query;
+          unset($route['options']['query']);
+        }
+        $route['options'] = $route['options'] + $options;
+        $route['url'] = null;
+      }
+    }
+
+    return $route;
+  }
+
+}
+
diff --git a/core/modules/migrate_drupal/config/install/migrate.migration.d6_menu_links.yml b/core/modules/migrate_drupal/config/install/migrate.migration.d6_menu_links.yml
new file mode 100644
index 0000000..1cafbe9
--- /dev/null
+++ b/core/modules/migrate_drupal/config/install/migrate.migration.d6_menu_links.yml
@@ -0,0 +1,50 @@
+id: d6_menu_links
+label: Drupal 6 menu links
+migration_groups:
+  - Drupal 6
+source:
+  plugin: d6_menu_link
+  constants:
+    bundle: menu_link_content
+process:
+  id: mlid
+  bundle: 'constants/bundle'
+  title: link_title
+  description:
+    plugin: extract
+    source:
+      - options
+    index:
+      - 0
+      - attributes
+      - title
+  menu_name:
+    plugin: migration
+    migration: d6_menu
+    source: menu_name
+  route:
+    plugin: route
+    source:
+      - link_path
+      - options
+  route_name: @route/route_name
+  route_parameters: @route/route_parameters
+  url: @route/url
+  options: @route/options
+  external: external
+  weight: weight
+  expanded: expanded
+  enabled: enabled
+  parent:
+    -
+      plugin: skip_process_on_empty
+      source: plid
+    -
+      plugin: migration
+      migration: d6_menu_links
+  changed: updated
+destination:
+  plugin: entity:menu_link_content
+migration_dependencies:
+  required:
+    - d6_menu
diff --git a/core/modules/migrate_drupal/config/schema/migrate_drupal.source.schema.yml b/core/modules/migrate_drupal/config/schema/migrate_drupal.source.schema.yml
index fe65db2..e28173f 100644
--- a/core/modules/migrate_drupal/config/schema/migrate_drupal.source.schema.yml
+++ b/core/modules/migrate_drupal/config/schema/migrate_drupal.source.schema.yml
@@ -64,6 +64,14 @@ migrate.source.d6_comment_entity_form_display_subject:
       type: migrate_entity_constant
       label: 'Constants'
 
+migrate.source.d6_menu_link:
+  type: migrate_source_sql
+  label: 'Drupal 6 menu link'
+  mapping:
+    constants:
+      type: migrate_entity_constant
+      label: 'Constants'
+
 migrate.source.d6_box:
   type: migrate_source_sql
   label: 'Drupal 6 box'
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/MenuLink.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/MenuLink.php
new file mode 100644
index 0000000..4711477
--- /dev/null
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/MenuLink.php
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal\Plugin\migrate\source\d6\MenuLink.
+ */
+
+namespace Drupal\migrate_drupal\Plugin\migrate\source\d6;
+
+use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
+use Drupal\migrate\Row;
+
+/**
+ * Drupal 6 menu link source from database.
+ *
+ * @MigrateSource(
+ *   id = "d6_menu_link",
+ * )
+ */
+class MenuLink extends DrupalSqlBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    $query = $this->select('menu_links', 'ml')
+      ->fields('ml', array(
+        'menu_name',
+        'mlid',
+        'plid',
+        'link_path',
+        'router_path',
+        'link_title',
+        'options',
+        'module',
+        'hidden',
+        'external',
+        'has_children',
+        'expanded',
+        'weight',
+        'depth',
+        'customized',
+        'p1',
+        'p2',
+        'p3',
+        'p4',
+        'p5',
+        'p6',
+        'p7',
+        'p8',
+        'p9',
+        'updated'
+      ))
+      ->condition('module', 'menu')
+      ->condition('customized', 1);
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    return array(
+      'menu_name' => t("The menu name. All links with the same menu name (such as 'navigation') are part of the same menu."),
+      'mlid' => t('The menu link ID (mlid) is the integer primary key.'),
+      'plid' => t('The parent link ID (plid) is the mlid of the link above in the hierarchy, or zero if the link is at the top level in its menu.'),
+      'link_path' => t('The Drupal path or external path this link points to.'),
+      'router_path' => t('For links corresponding to a Drupal path (external = 0), this connects the link to a {menu_router}.path for joins.'),
+      'link_title' => t('The text displayed for the link, which may be modified by a title callback stored in {menu_router}.'),
+      'options' => t('A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.'),
+      'module' => t('The name of the module that generated this link.'),
+      'hidden' => t('A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link)'),
+      'external' => t('A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).'),
+      'has_children' => t('Flag indicating whether any links have this link as a parent (1 = children exist, 0 = no children).'),
+      'expanded' => t('Flag for whether this link should be rendered as expanded in menus - expanded links always have their child links displayed, instead of only when the link is in the active trail (1 = expanded, 0 = not expanded)'),
+      'weight' => t('Link weight among links in the same menu at the same depth.'),
+      'depth' => t('The depth relative to the top level. A link with plid == 0 will have depth == 1.'),
+      'customized' => t('A flag to indicate that the user has manually created or edited the link (1 = customized, 0 = not customized).'),
+      'p1' => t('The first mlid in the materialized path. If N = depth, then pN must equal the mlid. If depth > 1 then p(N-1) must equal the plid. All pX where X > depth must equal zero. The columns p1 .. p9 are also called the parents.'),
+      'p2' => t('The second mlid in the materialized path. See p1.'),
+      'p3' => t('The third mlid in the materialized path. See p1.'),
+      'p4' => t('The fourth mlid in the materialized path. See p1.'),
+      'p5' => t('The fifth mlid in the materialized path. See p1.'),
+      'p6' => t('The sixth mlid in the materialized path. See p1.'),
+      'p7' => t('The seventh mlid in the materialized path. See p1.'),
+      'p8' => t('The eighth mlid in the materialized path. See p1.'),
+      'p9' => t('The ninth mlid in the materialized path. See p1.'),
+      'updated' => t('Flag that indicates that this link was generated during the update from Drupal 5.'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareRow(Row $row) {
+    $row->setSourceProperty('options', unserialize($row->getSourceProperty('options')));
+    $row->setSourceProperty('enabled', !$row->getSourceProperty('hidden'));
+
+    return parent::prepareRow($row);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    $ids['mlid']['type'] = 'integer';
+    return $ids;
+  }
+
+}
diff --git a/core/modules/migrate_drupal/src/Tests/Dump/Drupal6MenuLink.php b/core/modules/migrate_drupal/src/Tests/Dump/Drupal6MenuLink.php
new file mode 100644
index 0000000..57bcdb4
--- /dev/null
+++ b/core/modules/migrate_drupal/src/Tests/Dump/Drupal6MenuLink.php
@@ -0,0 +1,289 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal\Tests\Dump\Drupal6MenuLink.
+ */
+
+namespace Drupal\migrate_drupal\Tests\Dump;
+
+/**
+ * Database dump for testing the menu link migration.
+ */
+class Drupal6MenuLink extends Drupal6DumpBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load() {
+    $this->createTable('menu_links', array(
+      'description' => 'Contains the individual links within a menu.',
+      'fields' => array(
+        'menu_name' => array(
+          'description' => "The menu name. All links with the same menu name (such as 'navigation') are part of the same menu.",
+          'type' => 'varchar',
+          'length' => 32,
+          'not null' => TRUE,
+          'default' => ''),
+        'mlid' => array(
+          'description' => 'The menu link ID (mlid) is the integer primary key.',
+          'type' => 'serial',
+          'unsigned' => TRUE,
+          'not null' => TRUE),
+        'plid' => array(
+          'description' => 'The parent link ID (plid) is the mlid of the link above in the hierarchy, or zero if the link is at the top level in its menu.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0),
+        'link_path' => array(
+          'description' => 'The Drupal path or external path this link points to.',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+          'default' => ''),
+        'router_path' => array(
+          'description' => 'For links corresponding to a Drupal path (external = 0), this connects the link to a {menu_router}.path for joins.',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+          'default' => ''),
+        'link_title' => array(
+          'description' => 'The text displayed for the link, which may be modified by a title callback stored in {menu_router}.',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+          'default' => ''),
+        'options' => array(
+          'description' => 'A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.',
+          'type' => 'text',
+          'not null' => FALSE),
+        'module' => array(
+          'description' => 'The name of the module that generated this link.',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+          'default' => 'system'),
+        'hidden' => array(
+          'description' => 'A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link)',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+          'size' => 'small'),
+        'external' => array(
+          'description' => 'A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+          'size' => 'small'),
+        'has_children' => array(
+          'description' => 'Flag indicating whether any links have this link as a parent (1 = children exist, 0 = no children).',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+          'size' => 'small'),
+        'expanded' => array(
+          'description' => 'Flag for whether this link should be rendered as expanded in menus - expanded links always have their child links displayed, instead of only when the link is in the active trail (1 = expanded, 0 = not expanded)',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+          'size' => 'small'),
+        'weight' => array(
+          'description' => 'Link weight among links in the same menu at the same depth.',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0),
+        'depth' => array(
+          'description' => 'The depth relative to the top level. A link with plid == 0 will have depth == 1.',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+          'size' => 'small'),
+        'customized' => array(
+          'description' => 'A flag to indicate that the user has manually created or edited the link (1 = customized, 0 = not customized).',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+          'size' => 'small'),
+        'p1' => array(
+          'description' => 'The first mlid in the materialized path. If N = depth, then pN must equal the mlid. If depth > 1 then p(N-1) must equal the plid. All pX where X > depth must equal zero. The columns p1 .. p9 are also called the parents.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0),
+        'p2' => array(
+          'description' => 'The second mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0),
+        'p3' => array(
+          'description' => 'The third mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0),
+        'p4' => array(
+          'description' => 'The fourth mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0),
+        'p5' => array(
+          'description' => 'The fifth mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0),
+        'p6' => array(
+          'description' => 'The sixth mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0),
+        'p7' => array(
+          'description' => 'The seventh mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0),
+        'p8' => array(
+          'description' => 'The eighth mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0),
+        'p9' => array(
+          'description' => 'The ninth mlid in the materialized path. See p1.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0),
+        'updated' => array(
+          'description' => 'Flag that indicates that this link was generated during the update from Drupal 5.',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+          'size' => 'small'),
+      ),
+      'indexes' => array(
+        'path_menu' => array(array('link_path', 128), 'menu_name'),
+        'menu_plid_expand_child' => array(
+          'menu_name', 'plid', 'expanded', 'has_children'),
+        'menu_parents' => array(
+          'menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
+        'router_path' => array(array('router_path', 128)),
+      ),
+      'primary key' => array('mlid'),
+    ));
+    $this->database->insert('menu_links')->fields(array(
+      'menu_name',
+      'mlid',
+      'plid',
+      'link_path',
+      'router_path',
+      'link_title',
+      'options',
+      'module',
+      'hidden',
+      'external',
+      'has_children',
+      'expanded',
+      'weight',
+      'depth',
+      'customized',
+      'p1',
+      'p2',
+      'p3',
+      'p4',
+      'p5',
+      'p6',
+      'p7',
+      'p8',
+      'p9',
+      'updated',
+    ))
+    ->values(array(
+      'menu_name' => 'secondary-links',
+      'mlid' => 138,
+      'plid' => 0,
+      'link_path' => 'user/login',
+      'router_path' => 'user/login',
+      'link_title' => 'Test 1',
+      'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:16:"Test menu link 1";}}',
+      'module' => 'menu',
+      'hidden' => 0,
+      'external' => 0,
+      'has_children' => 1,
+      'expanded' => 0,
+      'weight' => 15,
+      'depth' => 1,
+      'customized' => 1,
+      'p1' => '138',
+      'p2' => '0',
+      'p3' => '0',
+      'p4' => '0',
+      'p5' => '0',
+      'p6' => '0',
+      'p7' => '0',
+      'p8' => '0',
+      'p9' => '0',
+      'updated' => '0',
+    ))
+    ->values(array(
+      'menu_name' => 'secondary-links',
+      'mlid' => 139,
+      'plid' => 138,
+      'link_path' => 'admin',
+      'router_path' => 'admin',
+      'link_title' => 'Test 2',
+      'options' => 'a:2:{s:5:"query";s:7:"foo=bar";s:10:"attributes";a:1:{s:5:"title";s:16:"Test menu link 2";}}',
+      'module' => 'menu',
+      'hidden' => 0,
+      'external' => 0,
+      'has_children' => 0,
+      'expanded' => 1,
+      'weight' => 12,
+      'depth' => 2,
+      'customized' => 1,
+      'p1' => '138',
+      'p2' => '139',
+      'p3' => '0',
+      'p4' => '0',
+      'p5' => '0',
+      'p6' => '0',
+      'p7' => '0',
+      'p8' => '0',
+      'p9' => '0',
+      'updated' => '0',
+    ))
+    ->values(array(
+      'menu_name' => 'secondary-links',
+      'mlid' => 140,
+      'plid' => 0,
+      'link_path' => 'http://drupal.org',
+      'router_path' => '',
+      'link_title' => 'Drupal.org',
+      'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:0:"";}}',
+      'module' => 'menu',
+      'hidden' => 0,
+      'external' => 1,
+      'has_children' => 0,
+      'expanded' => 0,
+      'weight' => 0,
+      'depth' => 1,
+      'customized' => 1,
+      'p1' => '0',
+      'p2' => '0',
+      'p3' => '0',
+      'p4' => '0',
+      'p5' => '0',
+      'p6' => '0',
+      'p7' => '0',
+      'p8' => '0',
+      'p9' => '0',
+      'updated' => '0',
+    ))
+    ->execute();
+  }
+}
diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6Test.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6Test.php
index e8e3445..4dd02c4 100644
--- a/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6Test.php
+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6Test.php
@@ -87,6 +87,7 @@ class MigrateDrupal6Test extends MigrateFullDrupalTestBase {
     'd6_locale_settings',
     'd6_menu_settings',
     'd6_menu',
+    'd6_menu_links',
     'd6_node_revision',
     'd6_node',
     'd6_node_settings',
@@ -176,6 +177,7 @@ protected function getDumps() {
       $tests_path . '/Drupal6ForumSettings.php',
       $tests_path . '/Drupal6LocaleSettings.php',
       $tests_path . '/Drupal6Menu.php',
+      $tests_path . '/Drupal6MenuLink.php',
       $tests_path . '/Drupal6MenuSettings.php',
       $tests_path . '/Drupal6NodeBodyInstance.php',
       $tests_path . '/Drupal6Node.php',
@@ -253,6 +255,7 @@ protected function getTestClassesList() {
       __NAMESPACE__ . '\MigrateForumConfigsTest',
       __NAMESPACE__ . '\MigrateLocaleConfigsTest',
       __NAMESPACE__ . '\MigrateMenuConfigsTest',
+      __NAMESPACE__ . '\MigrateMenuLinkTest',
       __NAMESPACE__ . '\MigrateMenuTest',
       __NAMESPACE__ . '\MigrateNodeConfigsTest',
       __NAMESPACE__ . '\MigrateNodeRevisionTest',
diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateMenuLinkTest.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateMenuLinkTest.php
new file mode 100644
index 0000000..495f487
--- /dev/null
+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateMenuLinkTest.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal\Tests\d6\MigrateMenuLinkTest
+ */
+
+namespace Drupal\migrate_drupal\Tests\d6;
+
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate_drupal\Tests\MigrateDrupalTestBase;
+
+/**
+ * Menu link migration.
+ *
+ * @group migrate_drupal
+ */
+class MigrateMenuLinkTest extends MigrateDrupalTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('menu_ui');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $menu = entity_create('menu', array('id' => 'secondary-links'));
+    $menu->enforceIsNew(TRUE);
+    $menu->save();
+
+    $this->prepareMigrations(array(
+      'd6_menu' => array(
+        array(array('secondary-links'), array('secondary-links')),
+      ),
+    ));
+
+    $migration = entity_load('migration', 'd6_menu_links');
+    $dumps = array(
+      $this->getDumpDirectory() . '/Drupal6MenuLink.php',
+    );
+    $this->prepare($migration, $dumps);
+    $executable = new MigrateExecutable($migration, $this);
+    $executable->import();
+  }
+
+  public function testMenuLinks() {
+    $menu_link = entity_load('menu_link_content', 138);
+    $this->assertIdentical($menu_link->getTitle(), 'Test 1');
+    $this->assertIdentical($menu_link->getMenuName(), 'secondary-links');
+    $this->assertIdentical($menu_link->getDescription(), 'Test menu link 1');
+    $this->assertIdentical($menu_link->getURL(), null);
+    $this->assertIdentical($menu_link->isEnabled(), TRUE);
+    $this->assertIdentical($menu_link->isExpanded(), FALSE);
+    $this->assertIdentical(serialize($menu_link->getOptions()), 'a:2:{s:5:"query";a:0:{}s:10:"attributes";a:1:{s:5:"title";s:16:"Test menu link 1";}}');
+    $this->assertIdentical($menu_link->getRouteName(), 'user.login');
+    $this->assertIdentical($menu_link->getRouteParameters(), array());
+    $this->assertIdentical($menu_link->getWeight(), 15);
+
+    $menu_link = entity_load('menu_link_content', 139);
+    $this->assertIdentical($menu_link->getTitle(), 'Test 2');
+    $this->assertIdentical($menu_link->getMenuName(), 'secondary-links');
+    $this->assertIdentical($menu_link->getDescription(), 'Test menu link 2');
+    $this->assertIdentical($menu_link->getURL(), null);
+    $this->assertIdentical($menu_link->isEnabled(), TRUE);
+    $this->assertIdentical($menu_link->isExpanded(), TRUE);
+    $this->assertIdentical(serialize($menu_link->getOptions()), 'a:2:{s:5:"query";a:1:{s:3:"foo";s:3:"bar";}s:10:"attributes";a:1:{s:5:"title";s:16:"Test menu link 2";}}');
+    $this->assertIdentical($menu_link->getRouteName(), 'system.admin');
+    $this->assertIdentical($menu_link->getRouteParameters(), array());
+    $this->assertIdentical($menu_link->getWeight(), 12);
+
+    $menu_link = entity_load('menu_link_content', 140);
+    $this->assertIdentical($menu_link->getTitle(), 'Drupal.org');
+    $this->assertIdentical($menu_link->getMenuName(), 'secondary-links');
+    $this->assertIdentical($menu_link->getDescription(), '');
+    $this->assertIdentical($menu_link->getURL(), 'http://drupal.org');
+    $this->assertIdentical($menu_link->isEnabled(), TRUE);
+    $this->assertIdentical($menu_link->isExpanded(), FALSE);
+    $this->assertIdentical(serialize($menu_link->getOptions()), 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:0:"";}}');
+    $this->assertIdentical($menu_link->getRouteName(), NULL);
+    $this->assertIdentical($menu_link->getRouteParameters(), array());
+    $this->assertIdentical($menu_link->getWeight(), 0);
+  }
+
+}
diff --git a/core/modules/migrate_drupal/tests/src/Unit/source/d6/MenuLinkSourceTest.php b/core/modules/migrate_drupal/tests/src/Unit/source/d6/MenuLinkSourceTest.php
new file mode 100644
index 0000000..3b12f3c
--- /dev/null
+++ b/core/modules/migrate_drupal/tests/src/Unit/source/d6/MenuLinkSourceTest.php
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\migrate_drupal\Unit\source\d6\MenuLinkSourceTest.
+ */
+
+namespace Drupal\Tests\migrate_drupal\Unit\source\d6;
+
+use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
+
+/**
+ * Tests D6 menu link source plugin.
+ *
+ * @group migrate_drupal
+ */
+class MenuLinkSourceTest extends MigrateSqlSourceTestCase {
+
+  // The plugin system is not working during unit testing so the source plugin
+  // class needs to be manually specified.
+  const PLUGIN_CLASS = 'Drupal\migrate_drupal\Plugin\migrate\source\d6\MenuLink';
+
+  // The fake Migration configuration entity.
+  protected $migrationConfiguration = array(
+    // The ID of the entity, can be any string.
+    'id' => 'mlid',
+    // Leave it empty for now.
+    'idlist' => array(),
+    // This needs to be the identifier of the actual key: cid for comment, nid
+    // for node and so on.
+    'source' => array(
+      'plugin' => 'drupal6_menu_link',
+    ),
+    'sourceIds' => array(
+      'mlid' => array(
+        // This is where the field schema would go but for now we need to
+        // specify the table alias for the key. Most likely this will be the
+        // same as BASE_ALIAS.
+        'alias' => 'ml',
+      ),
+    ),
+    'destinationIds' => array(
+      'mlid' => array(
+        // This is where the field schema would go.
+      ),
+    ),
+  );
+
+  protected $expectedResults = array(
+    array(
+      'menu_name' => 'menu-test-menu',
+      'mlid' => 138,
+      'plid' => 0,
+      'link_path' => 'admin',
+      'router_path' => 'admin',
+      'link_title' => 'Test 1',
+      'options' => array('attributes' => array('title' => 'Test menu link 1')),
+      'module' => 'menu',
+      'hidden' => 0,
+      'external' => 0,
+      'has_children' => 1,
+      'expanded' => 0,
+      'weight' => 15,
+      'depth' => 1,
+      'customized' => 1,
+      'p1' => '138',
+      'p2' => '0',
+      'p3' => '0',
+      'p4' => '0',
+      'p5' => '0',
+      'p6' => '0',
+      'p7' => '0',
+      'p8' => '0',
+      'p9' => '0',
+      'updated' => '0',
+    ),
+    array(
+      'menu_name' => 'menu-test-menu',
+      'mlid' => 139,
+      'plid' => 138,
+      'link_path' => 'admin/modules',
+      'router_path' => 'admin/modules',
+      'link_title' => 'Test 2',
+      'options' => array('attributes' => array('title' => 'Test menu link 2')),
+      'module' => 'menu',
+      'hidden' => 0,
+      'external' => 0,
+      'has_children' => 0,
+      'expanded' => 0,
+      'weight' => 12,
+      'depth' => 2,
+      'customized' => 1,
+      'p1' => '138',
+      'p2' => '139',
+      'p3' => '0',
+      'p4' => '0',
+      'p5' => '0',
+      'p6' => '0',
+      'p7' => '0',
+      'p8' => '0',
+      'p9' => '0',
+      'updated' => '0',
+    ),
+  );
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    // This array stores the database.
+    foreach ($this->expectedResults as $k => $row) {
+      $this->databaseContents['menu_links'][$k] = $row;
+      $this->databaseContents['menu_links'][$k]['options'] = serialize($this->databaseContents['menu_links'][$k]['options']);
+    }
+    parent::setUp();
+  }
+
+}
+
+namespace Drupal\Tests\migrate_drupal\Unit\source\d6;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\migrate_drupal\Plugin\migrate\source\d6\MenuLink;
+
+class TestMenuLink extends MenuLink {
+  public function setDatabase(Connection $database) {
+    $this->database = $database;
+  }
+  public function setModuleHandler(ModuleHandlerInterface $module_handler) {
+    $this->moduleHandler = $module_handler;
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Path/PathValidatorTest.php b/core/tests/Drupal/Tests/Core/Path/PathValidatorTest.php
index 0722680..30eb1a8 100644
--- a/core/tests/Drupal/Tests/Core/Path/PathValidatorTest.php
+++ b/core/tests/Drupal/Tests/Core/Path/PathValidatorTest.php
@@ -329,5 +329,52 @@ public function testGetUrlIfValidWithFrontPageAndQueryAndFragments() {
     $this->assertEquals('berg', $url->getOptions()['fragment']);
   }
 
+  /**
+   * Tests the isValidWithoutAccessCheck() method.
+   *
+   * @covers ::isValidWithoutAccessCheck
+   */
+  public function testIsValidWithoutAccessCheck() {
+    $this->account->expects($this->never())
+      ->method('hasPermission')
+      ->with('link to any page');
+    $this->accessAwareRouter->expects($this->never())
+      ->method('match');
+    $this->accessUnawareRouter->expects($this->once())
+      ->method('match')
+      ->with('/test-path')
+      ->willReturn([RouteObjectInterface::ROUTE_NAME => 'test_route', '_raw_variables' => new ParameterBag(['key' => 'value'])]);
+    $this->pathProcessor->expects($this->once())
+      ->method('processInbound')
+      ->willReturnArgument(0);
+    $this->assertTrue($this->pathValidator->isValidWithoutAccessCheck('test-path'));
+  }
+
+  /**
+   * Tests the getUrlIfValidWithoutAccessCheck() method.
+   *
+   * @covers ::getUrlIfValidWithoutAccessCheck
+   */
+  public function testGetUrlIfValidWithoutAccessCheck() {
+    $this->account->expects($this->never())
+      ->method('hasPermission')
+      ->with('link to any page');
+    $this->accessAwareRouter->expects($this->never())
+      ->method('match');
+    $this->accessUnawareRouter->expects($this->once())
+      ->method('match')
+      ->with('/test-path')
+      ->willReturn([RouteObjectInterface::ROUTE_NAME => 'test_route', '_raw_variables' => new ParameterBag(['key' => 'value'])]);
+    $this->pathProcessor->expects($this->once())
+      ->method('processInbound')
+      ->willReturnArgument(0);
+
+    $url = $this->pathValidator->getUrlIfValidWithoutAccessCheck('test-path');
+    $this->assertInstanceOf('Drupal\Core\Url', $url);
+
+    $this->assertEquals('test_route', $url->getRouteName());
+    $this->assertEquals(['key' => 'value'], $url->getRouteParameters());
+  }
+
 }
 
