Getting metadata (identity) about an SMB volume's server

I like to find a way to identify network volumes, and whether they're run by certain servers, e.g. specifically whether they're on a Synology NAS.

Reason is that Synology, while apparently supporting the Spotlight-over-SMB API, comes with a lot of bugs, requiring me to work around them when searching on those volumes with the macOS Spotlight API.

I could, of course, ask the user to "configure" each mounted volume in my software, but I'd rather do this automagically, if possible, as it's less prone to user mistakes.

So, my question is: Is there a way to learn a bit more about the server of a mounted network volume? E.g., if I could learn its IP address, I could try to connect to it via http protocol and then maybe get a useful response that identifies it as being from Synology.

Or, alternatively, can I tell which SMB volumes are served by a Mac, so that I can at least assume that those handle Spotlight calls correctly, while I assume anything else is buggy (so far, AFAIK, Synology is the only other SMB server that supports Spotlight search).

I've tried to find some data in the IORegistry, but that doesn't seem to store anything about network vols. The statfs function doesn't seem to give me anything for that either, nor do the various fcntl calls as far as I could tell.

I also checked with the DA apis, e.g.:

DASessionRef daSession = DASessionCreate (NULL);
CFURLRef furl = CFURLCreateWithFileSystemPath(NULL, CFSTR("/Volumes/TheNAS"), kCFURLPOSIXPathStyle, true);
DADiskRef daDisk = DADiskCreateFromVolumePath (NULL, daSession, furl);
if (daDisk) {
  CFDictionaryRef daInfo = DADiskCopyDescription (daDisk);
  NSLog(@"%@", daInfo);
}

However, this only prints basic information:

DAVolumeKind = smbfs;
DAVolumeMountable = 1;
DAVolumeName = TheNAS;
DAVolumeNetwork = 1;
DAVolumePath = "file:///Volumes/TheNAS/";

Where, then, does Finder's "Get Info" get the smb path from, for example?

Accepted Reply

This is on the Mac, right? If so, NetFSCopyURLForRemountingVolume returns the URL used to mount the volume. You can get the host part of that and away you go.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Thanks, Quinn. You don't know any other means to get information about the server, then? I wonder if the Spotlight SMB API may pass more information from server to client, for instance. Maybe that info is accessible somehow.

Add a Comment

Replies

This is on the Mac, right? If so, NetFSCopyURLForRemountingVolume returns the URL used to mount the volume. You can get the host part of that and away you go.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Thanks, Quinn. You don't know any other means to get information about the server, then? I wonder if the Spotlight SMB API may pass more information from server to client, for instance. Maybe that info is accessible somehow.

Add a Comment

Alright, I figured out a way to identify the server by contacting it via https and then checking its certificate. That should be pretty reliable.

Below the code I use, though I ran into one complication: The mount URL I get for my QNAP NAS is: "QNAS._smb._tcp.local", but that is not a valid host name I can use in a NSURLRequest!

I need to transform that into the actual host name, which is "QNAS.local". Since the former is a Bonjour related name, and when I browse the Bonjour registry with BonJeff, I can find the mapping, this seems to be an overly complicated method. I wonder what the proper way is to get the basic host name from such a service name. I've googled for a while but could not find anything about it. For now, I simply remove all components from the host name that start with an underscore, but I'm not sure if that's a safe method.

#import <NetFS/NetFS.h>
#import <Security/Security.h>
#import "AppDelegate.h"

@interface AppDelegate () <NSURLSessionDelegate>
@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
	CFURLRef furl = CFURLCreateWithFileSystemPath(NULL, CFSTR("/Volumes/TheNAS"), kCFURLPOSIXPathStyle, true);
	NSURL *url = CFBridgingRelease(NetFSCopyURLForRemountingVolume (furl));
	NSArray *parts = [[url.host componentsSeparatedByString:@"."] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString* _Nullable part, id _Nullable bindings) {
		return ![part hasPrefix:@"_"];
	}]];
	NSString *addr = [parts componentsJoinedByString:@"."];
	NSLog(@"host: %@ -> %@", url.host, addr);

	NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
	[request setURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://%@", addr]]];
	NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
	[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
		NSLog(@"Done.");
		[NSApp terminate:self];
	}] resume];
}

-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler
{
	SecTrustRef trustRef = [[challenge protectionSpace] serverTrust];
	SecCertificateRef certRef = SecTrustGetCertificateAtIndex(trustRef, 0);
	CFStringRef name = nil;
	SecCertificateCopyCommonName(certRef, &name);
	NSLog(@"name: %@", name);

	// reject the challenge because we have all we wanted
	completionHandler (NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}

@end

The mount URL I get for my QNAP NAS is: QNAS._smb._tcp.local

Oh, wow, that’s the name of an SRV record. I wasn’t expecting that.

I wonder what the proper way is to get the basic host name from such a service name.

I recommend that you use the DNS-SD API for this. To get started, look at the SRVResolver sample code.

IMPORTANT That sample predates DNSServiceSetDispatchQueue, which greatly simplifies this whole business. I recommend that you combine the SRV concepts it shows with some code that uses DNSServiceSetDispatchQueue, such as DNSSDObjects.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

@eskimo, With the code from SRVResolver I can indeed resolve QNAS._smb._tcp.local. Problem is: If the host name is a plain one, like syno.local, then I get neither an error nor a callback. And using a Timeout for a local-only NS resolve seems wrong to me.

How should I best handle this? Is there a way to tell when I need to use the SRV record resolver?

Is there a way to tell when I need to use the SRV record resolver?

Not as such. However, it’s easy to distinguish the two cases by looking for the ._smb._tcp. pattern. This can’t appear in an IP address or a standard DNS host name (_ is not a valid character for DNS host name).

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

@eskimo,

There's a new problem I just ran into:

If the host is a Windows server, the domain name of the mounted volume resolve to a name that I cannot use - neither directly nor with the proposed SRVResolver.

For instance, I set up a Windows server with the netbios(?) name "win-serv", and a volume named "WinServer". When I Get Info on the mounted volume, it shows as:

smb://win-serv/WinServer

When I retrieve the domain name, e.g. with statfs, the name I get is just "win-serv", but that can't be used as an IP address.

I found that I can resolve this name with the shell cmd smbutil lookup win-serv, though.

So my questions are:

  1. Is there an API for doing what smbutil lookup does?
  2. How do I tell that I need to use this method? I guess I could just always fall back to it when I cannot reach the server via its given name, but is there a clearer indicator that this is a WINS(?) and not a usual DNS name?

I have a related question on StackOverflow but that hasn't gotten me anywhere, either. This entire WINS/NetBIOS thing is a mystery on macOS, it seems.

1. Is there an API for doing what smbutil lookup does?

Not that I’m aware of.

2. How do I tell that I need to use this method?

I can’t think of any good options for that.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • I guess I'm gonna start porting a few samba tools to macOS :)

Add a Comment