Changing DNS settings using PyObjC
Asked Answered
E

1

18

I'm trying to change the DNS servers on my Mac (10.10.4) using PyObjC (3.0.4).

Everything seems to work: I get an authentication dialog prompting that my program is trying to change network settings, and the commit/apply commands return True, which would indicate they were successful.

However, the system settings aren't actually changed: they remain the same as before. Any idea why they don't "stick"?

The code (standalone, should work if you have a recent version of PyObjC installed):

#!/usr/bin/env python

import  objc
from    SystemConfiguration import *

# Open dynamic store and get primary interface
store = SCDynamicStoreCreate(None, 'MyApp', None, None)
primaryif = SCDynamicStoreCopyValue(store, 'State:/Network/Global/IPv4')['PrimaryInterface']
if primaryif:
    print "Using %s as primary interface" % primaryif
else:
    raise "Can't find primary interface"

# Load SecurityInterface framework to provide SFAuthorization
objc.initFrameworkWrapper(
    frameworkName       = "SecurityInterface",
    frameworkIdentifier = "com.apple.securityinterface",
    frameworkPath       = objc.pathForFramework("/System/Library/Frameworks/SecurityInterface.framework"),
    globals             = globals()
)

# Access system preferences
preferences = SCPreferencesCreateWithAuthorization(None, 'MyApp', None, SFAuthorization.authorization().authorizationRef())

# Lock preferences
SCPreferencesLock(preferences, True)

# Get list of network services
networkSet = SCNetworkSetCopyCurrent(preferences)
networkSetServices = SCNetworkSetCopyServices(networkSet)

# Find the network service that belongs to the primary interface
for networkService in networkSetServices:
    interface = SCNetworkServiceGetInterface(networkService)
    if primaryif != SCNetworkInterfaceGetBSDName(interface):
        continue

    # Load currently configured DNS servers
    networkProtocol = SCNetworkServiceCopyProtocol(networkService, kSCNetworkProtocolTypeDNS)
    DNSDict = SCNetworkProtocolGetConfiguration(networkProtocol) or {}

    # Set new DNS servers
    DNSDict[kSCPropNetDNSServerAddresses] = [ '192.168.23.12', '8.8.4.4' ]
    SCNetworkProtocolSetConfiguration(networkService, DNSDict)

    # Unlock, commit and apply preferences
    print "UL", SCPreferencesUnlock(preferences)
    print "CO", SCPreferencesCommitChanges(preferences)
    print "AP", SCPreferencesApplyChanges(preferences)

EDIT: most of the above code is based on this page, which also suggests "touching" the dynamic store to make the settings stick (the code to do this is commented out right at the end). However, it doesn't seem to do anything.

EDIT #2: by disassembling /usr/sbin/networksetup I'm getting the idea that I need a set of specific rights (system.services.systemconfiguration.network) before any changes are accepted.

Esmaria answered 18/8, 2015 at 13:17 Comment(24)
Have you tried running the script using sudo?Maltese
@l'L'l yeah, that doesn't change anything (literally) :-(Esmaria
I just tried the script and I get an error after it asks for authorization: DNSDict[kSCPropNetDNSServerAddresses] = [ '192.168.23.12', '8.8.4.4' ] TypeError: 'NoneType' object does not support item assignmentMaltese
@l'L'l it may be that you need to change the interface (en0 in my example). The example code is not performing error checking at every step.Esmaria
My PyObjC was ancient (which I think was the problem); I'll let you know what transpires after I update :)Maltese
@l'L'l I updated the example code so it tries to determine the primary interface. I also realized that DNSDict may be empty when you rely on DHCP to provide DNS server (I have them set manually) so I also fixed that. As for updating PyObjC, I would recommend using virtualenv. My experience with updating the system-provided PyObjC are not very good.Esmaria
I discovered a few things about what is (not) taking place. It looks like three .plist files in System Configuration never get updated (or even accessed) with the networksetup changes. I'm not exactly sure why that's happening (maybe Apple has changed something?), however, I did some digging around github and found an example that uses Python's os cmd to change the setup and it did indeed work. I realize it's a slightly different approach, although maybe it can be of some use perhaps.Maltese
The three files that I mentioned are: com.apple.networkextension.plist, preferences.plist, and resolv.conf. If you run the networksetup utility in terminal and set the dns there the values get set in those preferences.Maltese
@l'L'l using networksetup is the approach I'm actually using now (see this project). However, it's not ideal and I prefer setting the proxies (similar to DNS) programmatically. I'm almost certain that my call to SCPreferencesCreateWithAuthorization() is incomplete: it has to request particular rights, however, I also found that it may not actually be possible to request those using PyObjC (yet).Esmaria
Yeah I figured that's what your thinking was (using SystemConfiguration Framework directly with PyObjC) — it's much cleaner than splicing os commands into everything that needs to be read/written. Your code looks like it should work from what I could tell. The only thing I wasn't sure about was how you were using lock/unlock - in other examples I have seen they appear to use lock set on False, and immediately after that unlock is called. When I have a chance I'll take a better look at the script and see if there's anything that might come to mind.Maltese
@l'L'l thanks :-) I'm pretty sure that the issue I'm having is not related to the (un)locking. Some more background here (sorry for the SF link, that's where the mailinglist is hosted).Esmaria
No worries at all :) Also, Have you seen any working examples of the System Configuration Framework used via PyObjC where the preferences do stick? Just curious. I noticed these test scripts, so I'm wondering if they are functioning correctly.Maltese
@l'L'l I've based my code on examples that have supposedly worked at some point, but there have been changes in OS X that are now preventing it to work without getting the right permissions (for example, it used to be that you didn't have to authenticate using networksetup either, but that changed with Mavericks). I should probably make sure though that the Objective-C version of my code is working as expected, I'll spend some time on that.Esmaria
I think you're onto to something there; it's possible some those functions might have been patched not to work as they once did also because of a serious exploit that used SFAuthorization.Maltese
why not using dnspython?Barnett
@GermanRosales the primary goal is to change system network settings (DNS, but eventually also proxies, etc) :-)Esmaria
@robertklep: There may be authorization flags that need to be set; If you adjust your code to accompany them it might be worth a try in that regard.Maltese
@l'L'l besides the flags I need to set the rights that are required as well, and that's where things are falling apart at the moment. According to the developer of PyObjC, that's just not possible to do at the moment :-(Esmaria
@robertklep: The flags are important because this is where the rights are actually determined. Another thing you might do is create a helper tool to obtain authorization and apply the settings.Maltese
@l'L'l but settings the flags isn't a problem, that's just an integer. I could create a helper tool, but in that case I might as well use networksetup like I'm doing now :-)Esmaria
@robertklep: I'm curious about the PyObjC developers comment; did they elaborate on why it's not possible at the moment? just curious.Maltese
@l'L'l see this thread on the PyObjC mailing list: sourceforge.net/p/pyobjc/mailman/message/34386470 ("Ronald" is the PyObjC maintainer)Esmaria
Thanks for the link; It appears you'll be in a holding pattern until they support using a struct with an embedded pointer to a buffer apparently.Maltese
@l'L'l yeah, I'm glad this is just a hobby project ;-)Esmaria
C
2

Looks like there are issues with PyObjC that cause this to not work, however you may be able to find a way around it by using a different solution. If I were you, and my situation allowed it, I would just call the system command line tools to set the DNS servers.

According to OSXDaily, you can do this with:

networksetup -setdnsservers (Network Service) (DNS IP)

If you have cross platform requirements this is obviously less than desirable.

Camarena answered 10/11, 2016 at 22:20 Comment(1)
Thanks for the suggestion :D I already used networksetup (see this comment) and was looking for a pure PyObjC replacement. I never managed to get it working like that, but having to use networksetup is acceptable.Esmaria

© 2022 - 2024 — McMap. All rights reserved.