Code to get local IP on all platforms

Dear users and developers,

I have found an old thread regarding this theme:
https://listarc.cal.bham.ac.uk/lists/sc-users-2015/msg46783.html

Based on this thread, I constructed two codes which detect the local IP address as follows:

(
var winCMD, macCMD, linuxCMD;

winCMD = {var ipv4; ipv4 = "ipconfig | findstr /C:IPv4".unixCmdGetStdOut; ipv4.[39..ipv4.size-1]};
// works under Windows 11.

macCMD = { |device| ("ipconfig getifaddr" + device).unixCmdGetStdOut};
// works under macOS 10.14 and 12.2.1.

linuxCMD = { "hostname -I | awk '{print $1}'".unixCmdGetStdOut};
// works under Ubuntu 20.04.3.

~ip = Platform.case(
	\osx,       { var ethernet = macCMD.("en1"); (ethernet=="").if {macCMD.("en0") } { ethernet } },
	\linux,     { linuxCMD.() },
	\windows,   { winCMD.() }
)
)
(
// works under macOS 12.2.1, Ubuntu 20.04.3 and Windows 11.
// Under Windows, Python3 should be installed using "Microsoft Store" to automatically register the Python path.
~ip = {
var pythonStatements = [
"import socket",
"s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)",
"s.connect(('8.8.8.8', 80))",
"print(s.getsockname()[0])"
];
var cmd = "python3 -c \"" ++ pythonStatements.join("; ") ++ "\"";
var ipaddr = cmd.unixCmdGetStdOut.stripWhiteSpace;
ipaddr
}.()
)

On my end, the first code block works as commented in the code without problems. I hope it could generally be applicable, but I am unsure.

Regarding the second code, however, SC-IDE does not respond after evaluating that code under Windows. Therefore, I cannot quit the interpreter, and I should quit the SC-IDE and then restart it. Thus, the second code is not usable under Windows. Why does this problem happen solely under Windows?

Best regards,

Just a few comments here:

  • depending on python to get one’s IP is a rather heavy dependency. Sure, python is most likely found in most Linux distros, but it’s not guaranteed; python2 comes standard with macOS, but python3 needs to be installed separately; on Windows it’s not provided by default and even if installed one would need to make sure it’s included in %PATH% env variable; depending on most standard system commands is preferable
  • IMO the command to check for one’s IP should always return an array (excluding 127.0.0.1) - it’s not uncommon to have more than one interface (e.g. wireless and ethernet) and it’s not always clear which one you’d want to know about. Also, they should probably be listed in the order they are set in the OS
    • in this spirit I believe that there should be a separate command to get all addresses, and a separate one to get it for a specific interface
  • NetLib has an implementation for Linux and macOS (NetAddr.myIP), though I think it might not always work on macOS IIRC
    • it lists a single address, even if multiple interfaces are available
2 Likes

Thanks for your kind comments. I reconstructed the code. The code below now outputs an array consisting of local IPs (or an array with one local IP if there is only one) except “127.0.0.1”.

It successfully detects multiple IPv4 addresses as well as one IPv4 address under macOS 12.2.1 and Windows 11. However, under Ubuntu 20.04.3, which ran over Parallels Desktop, I strangely could not assign IPv4 to the second network card. Thus, I confirmed only the output of an array with one IP, but I hope it also could output multiple IPs.

// The following strings are to avoid listing network bridges which seem to be created by Parallels Desktop:
// | grep -Fv "inet 10."  

(
~ip = { 
	var winCMD, macCMD, linCMD, ips;

	winCMD = {var ipInfo, ipv4s; ipInfo = "ipconfig | findstr /C:IPv4".unixCmdGetStdOut.replace("   IPv4 Address. . . . . . . . . . . : ", "")};
	macCMD = { ("ifconfig | grep -Fv" + "inet 10.".quote + "| grep" + "inet ".quote + "| grep -Fv 127.0.0.1 | awk '{print $2}'").unixCmdGetStdOut };
	linCMD = { ("ifconfig | grep" + "inet ".quote + "| grep -Fv 127.0.0.1 | awk '{print $2}'").unixCmdGetStdOut };
	
	ips = Platform.case(
		\osx,       { macCMD.() },
		\linux,     { linCMD.() },
		\windows,   { winCMD.() }
	);

	ips = ips.split(Char.nl);
	ips.removeAt(ips.size-1);
	ips
}.()
)
4 Likes

Thank you very much for this piece of code! I also need this for a project.

Sorry for bumping this, but I got a few remarks to add :

First, I think this should be useful if integrated in SCLang directly, to facilitate remote communication ? Unfortunately, I don’t know how to use git :confused: . But if the addition makes sense, and someone knows how to create a pull request, I’d be happy to help on the integration.

Second thing concerns the linux implementation. On my Ubuntu 22.04 LTS, ifconfig isn’t installed by default. It’s not difficult to install, but I’m working on a software for non-engineers people, and I’d rather have it working ‘out-of-the-box’, with no third party install. In my case, I just want the software to display local IP as text.

I found this command, ip address, which, I think, was installed by default :

ip address | grep "inet " | awk '{print $2}'

But I don’t know if it’s the case for the whole Linux ecosystem.

Also, it outputs the ‘subaddress’ (don’t know how it’s called) : 192.168.1.37 /24.
Should this be removed from the IP ?

As a reference, here’s my ‘updated’ version of the code :

~getLocalIPs = {
	var winCMD, macCMD, linCMD, ips;

	winCMD = {
		var ipInfo, ipv4s;
		ipInfo = "ipconfig | findstr /C:IPv4"
		.unixCmdGetStdOut
		.replace("   IPv4 Address. . . . . . . . . . . : ", "");
	};

	macCMD = {
		("ifconfig | grep -Fv" + "inet 10."
			.quote + "| grep" + "inet "
			.quote + "| grep -Fv 127.0.0.1 | awk '{print $2}'"
		).unixCmdGetStdOut;
	};
	
	linCMD = {
		("ip address | grep \"inet \" | grep -Fv 127.0.0.1 | awk '{print $2}'")
		.unixCmdGetStdOut;
	};
	
	ips = Platform.case(
		\osx,       { macCMD.() },
		\linux,     { linCMD.() },
		\windows,   { winCMD.() }
	);

	ips = ips.split(Char.nl);
	ips.removeAt(ips.size-1);
	ips
};
2 Likes

I think it will always be a system that something like this won’t work. You can try to have more than one option, check the system, or have a backup command if the first fail. For example, try ip first and falls back to ifconfig if it’s not in the system etc (yes, it can happen in Linux). If you have to use just shell pipes, one alternative is using an || (or) like (ip -o -4 addr show | awk -F'[ /]+' '/inet / {print $4}' || ifconfig | awk -F'[ :]+' '/inet addr:/ {print $4}') 2>/dev/null (or something like this, it’s just an example, not tested), the 2>/dev/null in the end will make the failure silent

I also agree that using Python isn’t a good idea. Sure, Python “WON” the programming languages war ))) … but it’s far from a safe bet.

1 Like

I am glad to read that my code is helpful to you.

I am not sure what you mean exactly, but I assume you mean that sclang should provide a class and some methods to get local addresses.

I am not one of the developers, but it does not seem appropriate because it depends on terminal commands which change with OS distribution:

Whenever I have tried to do this kind of work, I have often changed the way via the terminal commands or python code. What I uploaded last year is the most convincing and stable way at that time (no, in all my experiences in the last decades. I just tried a lot of possibilities and chose one). I cannot remember if ifconfig was included in the Ubuntu release I tried then… I am sorry if forgot to describe the installation of ifconfig.

The number 24 is the port number. sclang’s default port number is 57120, and scserver’s default port number is 57110. So we do not need it when using SuperCollider, even when sending and receiving OSC messages between software programs across machines. So I would say it should be removed in most cases. I have slightly modified your updated code to remove the port number as follows:

(
~getLocalIPs = {
	var winCMD, macCMD, linCMD, ips;

	winCMD = {
		"ipconfig | findstr /C:IPv4"
		.unixCmdGetStdOut
		.replace("   IPv4 Address. . . . . . . . . . . : ", "");
	};

	macCMD = {
		("ifconfig | grep -Fv" + "inet 10."
			.quote + "| grep" + "inet "
			.quote + "| grep -Fv 127.0.0.1 | awk '{print $2}'"
		).unixCmdGetStdOut;
	};
	
	linCMD = {
		("ip address | grep \"inet \" | grep -Fv 127.0.0.1 | awk '{print $2}' | cut -d '/' -f1")
		.unixCmdGetStdOut;
	};
	
	ips = Platform.case(
		\osx,       { macCMD.() },
		\linux,     { linCMD.() },
		\windows,   { winCMD.() }
	);

	ips = ips.split(Char.nl);
	ips.removeAt(ips.size-1);
	ips
}
)

~getLocalIPs.()

I can test this code on Windows 11 Pro (ARM 64, and Intel), MacOS 10.14.6 (Intel), 12.7 (M1), 13.5 (Intel), Ubuntu 22.04 ARM64. So, I do not know this code is applicable for all Linux.

If it is applicable to most Linux, I can add this function as a localIPs method to the NetAddr class, then make a PR on github and you can approve it for the next step, but there are more steps and it is unclear whether this would be included or not. The reason for this is simply as @smoge pointed out:

(
You may think I am too pessimistic, but I recently tried to implement the musicXML feature and the scientific pitch notation to sclang, but it seems impossible, so I switched to developing my own Quark…
)

Would you still try to implement .localIPs on NetAddr together? It would be appriciateful if other users on this forum who are involved as developers could also give their opinion.

Indeed that’s what I meant :slight_smile: .


The context

A friend of mine asked me to be able to control my SC software live using her sequencer, Orca. I noticed it was able to send OSC messages, so I wrote a wrapper using OSCFunc :

var doSmthFunc = OSCFunc(
    { |msg, time, addr, recvPort|
		~mySoftware.doSmth(); },
	"/doSmth");

I was amazed by the simplicity of this. In short, this means that any ‘computer’ musician in my band can take control or interact with my own ‘computer instrument’. And that’s not restricted to server commands, that can be any subtly-overcomplicated musical function I wrote.

Sidenotes :

  • this also work with MIDI, see MIDIFunc.
  • This comes with network delay.

So SCLang provides an elegant way to convert any piece of code into a remote controlled algorithm. So I naively though ‘Ok, SCLang has the hard part implemented, all I have to do is to find both my local network IP and the port associated to SCLang, and give it to my friend’. So I started looking up the documentation, but couldn’t find any method to fetch the IP. That’s how I ended on this forum thread.


Is this relevant ?

Such method isn’t implemented for the reasons mentioned above. IP manipulation is OS dependant.

But how it sounds in my brain is : “I, SCLang, have everything you need to provide remote control, except the ability to provide the correct adress.”. Sounds a bit weird (also, again, it’s legit).

I’ve seen a lot of people struggling setting up multi-client when using the server, because they didn’t really know what’s that ‘public IP’ thing they were asked for. But they all eventually succeeded, and they weren’t using SCLang, but ‘simpler’ languages like Sonic-Pi or FoxDot/Troop. Those people were artists rather than software engineers (pictural simplification ofc), and I think that most of SCLang users don’t have any problem with OS manipulation, shell commands, etc.

Adding a method to provide IP inside SCLang doesn’t adress this issue for languages who only communicate with the server.

Is this relevant : I’d say yes, but I don’t feel I have the ability to tell. This is mostly ‘quality of life’ as far as I can tell.


Is it secure?

I have no idea what is secure and what is not.

But I can see that such a method would kinda transform SCLang into a big red button with a mark on top of it : “I can give you my IP, an open port and a way to send me commands, and I’m likely to be operating in real-time.”.

Best thing I can say about this.


How to implement

Only if it’s actually relevant.

I think the method goes into the Platform class, because it’s a method related to an OS operation.
The method can only get the IPs of the machine SCLang is running on, if a NetAddr is created to communicate with an other machine, it must already contain the other machine IP, right ?

I see two uses for this :

  • get the IP to send OSC Msgs to SCLang (as in my previous example)
  • get the IP of the ‘server’ if it is local, to send it OSC Msgs directly (aka remote control)

So I think the main method should be inside Platform, and there would be an other method inside the Server class, which calls the one inside of Platform, because they essentially do the same thing ?

Then the method itself would be the one we already discussed previously, with proper documentation saying that since it’s OS dependant, it might fail because the proper underlying call might not be implemented for this specific OS ?

I did two PRs, one for the class library, one for the help document. It was my fault for doing two PRs. I did not think about the help document when I did the first PR.
You can see my PRs on the following webpages

I think these PRs can remain as not approved because only one version of Linux is tested, and as mentioned above, this feature is fragile due to system dependencies. One more thing: the developers want to reduce the functionality and split the class libraries into core and others. The feature I just PR’d does not seem to be a core feature, I suppose.

For comparison’s sake: In Open Stage Control, starting the server prints a QR code to the console representing the Open Stage server’s IP and port (as a URL). Then, in Firefox on my tablet, I can tap on the location bar and then there’s a button to scan for an address… and done. Scan address… really doesn’t get any easier than that.

In context of this thread, what I take from that is: 1/ Open Stage Control seems able to get the IP address in a cross-platform way (so this at least is technically possible – see below though) and 2/ there’s a way to provide the address to users without requiring them to type digits.

(“Technically possible”: This thread identified that a command-line approach may not be reliable on every system. Perhaps a better approach would be a primitive that calls the OS directly? Nobody likes writing primitives, but if this is likely to be successful in 99.9% of systems where the command-line way would be ok in 95% of systems, I’d favor putting in the extra effort.)

Open Stage is open source – could steal from there.

hjh

1 Like

Perhaps a better approach would be a primitive that calls the OS directly?

Yes! This can be done by (temporarily) “connecting” a UDP socket to an arbitrary IP endpoint and then obtaining the local IP endpoint with the getsockname function (getsockname(2) - Linux manual page).

This is exactly what oscpack does in its UDPSocket::LocalEndpointFor method:

Implementation:

FWIW, I am successfully using the same trick in my AOO library (Files ¡ develop ¡ cm / aoo ¡ GitLab) to get the local IPv4 and global IPv6 address, so I can confirm that it works indeed :slight_smile:

1 Like

Cool ! Thanks for sharing this.

I was about to ask this, but you somehow already answered. Can I still get your confirmation ?

If I understood correctly (unlikely), the SC’s Interpreter is a Virtual Machine that parses SCLang instructions and transforms them into Primitives calls (are we talking about bytecodes here ?) ? This allows SCLang to be completely independent from the OS, because OS dependant stuff is relegated to the Interpreter itself right ?

So in term of ‘code philosophy’, anything related to the OS should be handled by a Primitive ? But for some cases, since it’s a collaborative project and stuff, some exceptions might be tolerated, although not encouraged ?

Sorry, that’s a lot of questions, but this will help me be more autonomous contributing.

Sclang code is compiled into byte codes (which are different from primitives). Byte codes are things like:

  1. Push integer 1 onto the stack.
  2. Push integer 2 onto the stack.
  3. Send special arithmetic message ‘+’.
  4. Send message ‘postln’.

(Exercise: What’s the sclang code that would produce [more or less] these byte codes?)

Primitives are special method definitions where the main body is written in C++. They’re marked in the class library by underscores: Integer’s + method begins with _Integer_Add and this dispatches to the C++ implementation first.

The class library can’t actually do anything by itself. Every execution path must eventually get down to primitive(s) to do the real work. In that sense, the class library organizes the use of primitives into more convenient macros.

hjh

For examples, the following methods of NetAddr?

	*langPort {
		_GetLangPort
		^this.primitiveFailed;
	}

	*matchLangIP {|ipstring|
		_MatchLangIP
		^this.primitiveFailed;
	}

I tried to find out where the definitions of _GetLangPort and _MatchLangIP are, but could not find them in the SuperCollider installation on my end.

Primitives are located inside supercollider/lang/LangPrimSource/

(but there are exceptions, QT primitives are inside QTCollider’s folder for example)

Those particular primitives are inside OSCData.cpp :

1 Like

Thanks for your help!
I have no experience with this, but it seems to be doable by copying the implementation part into OSCData.cpp and writing similar code as *langPort into the NetAddr.sc file, if this cpp file is applicable on all platforms? It is not what I can do, but what I should just learn.

@Dindoleon
Your wish has come true thanks to @Spacechild1!
I am also very happy with this implementation because I can get the same functionality using sclang’s method without my function.

Without @Dindoleon’s post I would not have thought to do PR with this functionality! Thanks again!

Your wish has come true thanks to @Spacechild1!

Let’s only celebrate when the PR (Add `localIP`, `localEndPoint` and `localIPs` class methods to NetAddr by Spacechild1 · Pull Request #6153 · supercollider/supercollider · GitHub) gets actually merged :wink:

3 Likes