Index: src/wp-includes/class-wp-network.php
===================================================================
--- src/wp-includes/class-wp-network.php	(revision 41324)
+++ src/wp-includes/class-wp-network.php	(working copy)
@@ -148,9 +148,9 @@
 			case 'id':
 				return (int) $this->id;
 			case 'blog_id':
-				return $this->blog_id;
+				return $this->get_main_site_id();
 			case 'site_id':
-				return (int) $this->blog_id;
+				return (int) $this->get_main_site_id();
 		}
 
 		return null;
@@ -202,6 +202,24 @@
 	}
 
 	/**
+	 * Returns the main site ID for the network.
+	 *
+	 * Internal method used by the magic getter for the 'blog_id' and
+	 * 'site_id' properties.
+	 *
+	 * @since 4.9.0
+	 *
+	 * @return string|int Main site ID as numeric string or integer.
+	 */
+	private function get_main_site_id() {
+		if ( empty( $this->blog_id ) ) {
+			$this->blog_id = get_main_site_id( $this->id );
+		}
+
+		return $this->blog_id;
+	}
+
+	/**
 	 * Set the site name assigned to the network if one has not been populated.
 	 *
 	 * @since 4.4.0
Index: src/wp-includes/functions.php
===================================================================
--- src/wp-includes/functions.php	(revision 41324)
+++ src/wp-includes/functions.php	(working copy)
@@ -4392,19 +4392,94 @@
  * Determine whether a site is the main site of the current network.
  *
  * @since 3.0.0
+ * @since 4.9.0 The $network_id parameter has been added.
  *
- * @param int $site_id Optional. Site ID to test. Defaults to current site.
+ * @param int $site_id    Optional. Site ID to test. Defaults to current site.
+ * @param int $network_id Optional. Network ID of the network to check for.
+ *                        Defaults to current network.
  * @return bool True if $site_id is the main site of the network, or if not
  *              running Multisite.
  */
-function is_main_site( $site_id = null ) {
-	if ( ! is_multisite() )
+function is_main_site( $site_id = null, $network_id = null ) {
+	if ( ! is_multisite() ) {
 		return true;
+	}
 
-	if ( ! $site_id )
+	if ( ! $site_id ) {
 		$site_id = get_current_blog_id();
+	}
 
-	return (int) $site_id === (int) get_network()->site_id;
+	$site_id = (int) $site_id;
+
+	return (int) $site_id === (int) get_network( $network_id )->site_id;
+}
+
+/**
+ * Gets the main site ID.
+ *
+ * @since 4.9.0
+ *
+ * @param int $network_id Optional. The ID of the network for which to get the main site.
+ *                        Defaults to the current network.
+ * @return int The ID of the main site.
+ */
+function get_main_site_id( $network_id = null ) {
+	if ( ! is_multisite() ) {
+		return 1;
+	}
+
+	/**
+	 * Filters the main site ID.
+	 *
+	 * Returning anything other than null will effectively short-circuit the function, returning
+	 * the result parsed as an integer immediately.
+	 *
+	 * @since 4.9.0
+	 *
+	 * @param int|null $main_site_id If anything other than null is returned, it is interpreted as the main site ID.
+	 * @param int $network_id The ID of the network for which the main site was detected.
+	 */
+	$main_site_id = apply_filters( 'pre_get_main_site_id', null, $network_id );
+	if ( null !== $main_site_id ) {
+		return (int) $main_site_id;
+	}
+
+	$network = get_network( $network_id );
+	if ( ! $network ) {
+		return 0;
+	}
+
+	if ( ( defined( 'DOMAIN_CURRENT_SITE' ) && defined( 'PATH_CURRENT_SITE' ) && $network->domain === DOMAIN_CURRENT_SITE && $network->path === PATH_CURRENT_SITE )
+	     || ( defined( 'SITE_ID_CURRENT_SITE' ) && $network->id == SITE_ID_CURRENT_SITE ) ) {
+		if ( defined( 'BLOG_ID_CURRENT_SITE' ) ) {
+			return BLOG_ID_CURRENT_SITE;
+		} elseif ( defined( 'BLOGID_CURRENT_SITE' ) ) { // deprecated.
+			return BLOGID_CURRENT_SITE;
+		}
+	}
+
+	$site = get_site();
+	if ( $site->domain === $network->domain && $site->path === $network->path ) {
+		$main_site_id = (int) $site->id;
+	} else {
+		$main_site_id = wp_cache_get( 'network:' . $network->id . ':main_site', 'site-options' );
+		if ( false === $main_site_id ) {
+			$_sites       = get_sites( array(
+				'fields'     => 'ids',
+				'number'     => 1,
+				'domain'     => $network->domain,
+				'path'       => $network->path,
+				'network_id' => $network->id,
+			) );
+			$main_site_id = ! empty( $_sites ) ? array_shift( $_sites ) : 0;
+
+			wp_cache_add( 'network:' . $network->id . ':main_site', $main_site_id, 'site-options' );
+		}
+	}
+
+	$main_site_id = (int) $main_site_id;
+
+	return $main_site_id;
 }
 
 /**
Index: src/wp-includes/ms-load.php
===================================================================
--- src/wp-includes/ms-load.php	(revision 41324)
+++ src/wp-includes/ms-load.php	(working copy)
@@ -135,7 +135,7 @@
 
 /**
  * Retrieves the closest matching site object by its domain and path.
- * 
+ *
  * This will not necessarily return an exact match for a domain and path. Instead, it
  * breaks the domain and path into pieces that are then used to match the closest
  * possibility from a query.
@@ -424,13 +424,7 @@
 
 	// Figure out the current network's main site.
 	if ( empty( $current_site->blog_id ) ) {
-		if ( $current_blog->domain === $current_site->domain && $current_blog->path === $current_site->path ) {
-			$current_site->blog_id = $current_blog->blog_id;
-		} elseif ( ! $current_site->blog_id = wp_cache_get( 'network:' . $current_site->id . ':main_site', 'site-options' ) ) {
-			$current_site->blog_id = $wpdb->get_var( $wpdb->prepare( "SELECT blog_id FROM $wpdb->blogs WHERE domain = %s AND path = %s",
-				$current_site->domain, $current_site->path ) );
-			wp_cache_add( 'network:' . $current_site->id . ':main_site', $current_site->blog_id, 'site-options' );
-		}
+		$current_site->blog_id = get_main_site_id( $current_site->id );
 	}
 
 	return true;
Index: tests/phpunit/tests/multisite/site.php
===================================================================
--- tests/phpunit/tests/multisite/site.php	(revision 41324)
+++ tests/phpunit/tests/multisite/site.php	(working copy)
@@ -741,6 +741,27 @@
 		restore_current_blog();
 	}
 
+	/**
+	 * @ticket 29684
+	 */
+	function test_is_main_site_different_network() {
+		$args = array(
+			'domain' => 'wordpress.org',
+			'path'   => '/',
+		);
+
+		$network_id = self::factory()->network->create( $args );
+
+		$args['site_id'] = $network_id;
+
+		$site_id = self::factory()->blog->create( $args );
+		$other_site_id = self::factory()->blog->create( array_merge( $args, array(
+			'path' => '/foo/',
+		) ) );
+
+		$this->assertTrue( is_main_site( $site_id, $network_id ) );
+	}
+
 	function test_switch_upload_dir() {
 		$this->assertTrue( is_main_site() );
 
