/*
* Copyright 2015-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.util.environment;
import com.facebook.buck.log.Logger;
import com.facebook.buck.util.ListeningProcessExecutor;
import com.facebook.buck.util.ProcessExecutorParams;
import com.facebook.buck.util.SimpleProcessListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Mac OS X implementation for finding likely network states for diagnostic purposes. */
public class MacNetworkConfiguration {
private static final Logger LOG = Logger.get(MacNetworkConfiguration.class);
private static final long COMMAND_TIMEOUT_MS = 1000L;
// Utility class, do not instantiate.
private MacNetworkConfiguration() {}
/** Returns a string representing likely active network; eg 'Wired', 'WiFi:<ssid>'. */
public static Network getLikelyActiveNetwork() {
try {
for (String device : getDevicesByServiceOrder()) {
if (isDeviceActive(device)) {
Optional<String> ssid = getDeviceSSID(device);
if (ssid.isPresent()) {
return new Network(NetworkMedium.WIRELESS, ssid);
}
return new Network(NetworkMedium.WIRED);
}
}
return new Network(NetworkMedium.UNKNOWN);
} catch (InterruptedException e) {
return new Network(NetworkMedium.UNKNOWN);
}
}
static Pattern devicePattern = Pattern.compile("Device: ([^)]*)\\)");
/** Returns a list of the network devices in order of their service priorities. */
private static List<String> getDevicesByServiceOrder() throws InterruptedException {
/*
$ networksetup -listnetworkserviceorder
An asterisk (*) denotes that a network service is disabled.
(1) Display Ethernet (en6)
(Hardware Port: Display Ethernet, Device: en6)
(2) Wi-Fi
(Hardware Port: Wi-Fi, Device: en0)
*/
LOG.debug("Determine network service order and extract device names");
String serviceOrderOutput = runNetworkSetupCommand("listnetworkserviceorder");
Matcher matcher = devicePattern.matcher(serviceOrderOutput);
List<String> devices = new ArrayList<String>();
while (matcher.find()) {
devices.add(matcher.group(1));
}
return devices;
}
static Pattern activePattern = Pattern.compile("Active: (.*)$");
/** Indicates whether device is active (i.e. physically connected). */
private static Boolean isDeviceActive(String device) throws InterruptedException {
/*
$ networksetup -getMedia "en0"
Current: autoselect
Active: autoselect
$ networksetup -getMedia "en6"
Current: autoselect
Active: none
*/
LOG.debug("Determine active state of media for device");
String mediaOutput = runNetworkSetupCommand("getMedia", device);
Matcher matcher = activePattern.matcher(mediaOutput);
return (matcher.find() && !matcher.group(1).equals("none"));
}
static Pattern ssidPattern = Pattern.compile("Current Wi-Fi Network: (.*)$");
/** Gets the SSID of a device (sadly the most definitive way to determine wired vs wireless). */
private static Optional<String> getDeviceSSID(String device) throws InterruptedException {
/*
$ networksetup -getairportnetwork "en0"
Current Wi-Fi Network: lighthouse
-- or
$ networksetup -getairportnetwork "en0"
You are not associated with an AirPort network.
Wi-Fi power is currently off.
$ networksetup -getairportnetwork "en6"
en6 is not a Wi-Fi interface.
** Error: Error obtaining wireless information.
*/
LOG.debug("Determine WiFi SSID of device");
String mediaOutput = runNetworkSetupCommand("getairportnetwork", device);
Matcher matcher = ssidPattern.matcher(mediaOutput);
if (matcher.find()) {
return Optional.of(matcher.group(1));
}
return Optional.empty();
}
private static String runNetworkSetupCommand(String subCommand) throws InterruptedException {
return runNetworkSetupCommand(subCommand, "");
}
/** Naive `networksetup` invocation; returns non-empty string of stdout if all went well. */
private static String runNetworkSetupCommand(String subCommand, String argument)
throws InterruptedException {
ListeningProcessExecutor executor = new ListeningProcessExecutor();
SimpleProcessListener listener = new SimpleProcessListener();
ProcessExecutorParams params =
ProcessExecutorParams.builder()
.addCommand("networksetup")
.addCommand(String.format("-%s", subCommand))
.addCommand(argument)
.build();
ListeningProcessExecutor.LaunchedProcess process = null;
try {
process = executor.launchProcess(params, listener);
if (executor.waitForProcess(process, COMMAND_TIMEOUT_MS, TimeUnit.MILLISECONDS) != 0) {
return "";
}
return listener.getStdout();
} catch (IOException e) {
LOG.debug(e, "Exception while running networksetup command");
return "";
} finally {
if (process != null) {
executor.destroyProcess(process, /* force */ true);
}
}
}
}