aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/fil/libre/repwifiapp/network
diff options
context:
space:
mode:
authorFil <fil.bergamo@riseup.net>2018-07-04 19:14:17 +0200
committerFil <fil.bergamo@riseup.net>2018-07-04 19:29:53 +0200
commit121a4bec96d2f9b842c6d393d2b9c6356a9a4405 (patch)
treeda1c557520d670def2f755492e3afd62ce891cf6 /app/src/fil/libre/repwifiapp/network
parenta6b34d6c75109b831976bc872a5a0a47a08c1664 (diff)
downloadRepWifiApp-121a4bec96d2f9b842c6d393d2b9c6356a9a4405.tar.gz
RepWifiApp-121a4bec96d2f9b842c6d393d2b9c6356a9a4405.tar.bz2
RepWifiApp-121a4bec96d2f9b842c6d393d2b9c6356a9a4405.zip
Add full integration with the Connectivity Frameworkv0.9-beta
Fixes Issue #1867 "Download app not working". Uses proxy classes to hook into the application framework, solving almost any problem with missing connectivity features, creating a connection that is fully managed by the framework itself. Introduces a background service that performs all backend operations. Cleans up code design, refining structure of classes and entities.
Diffstat (limited to 'app/src/fil/libre/repwifiapp/network')
-rw-r--r--app/src/fil/libre/repwifiapp/network/AccessPointInfo.java442
-rw-r--r--app/src/fil/libre/repwifiapp/network/ConnectionResult.java95
-rw-r--r--app/src/fil/libre/repwifiapp/network/ConnectionStatus.java298
-rw-r--r--app/src/fil/libre/repwifiapp/network/DhcpSettings.java242
-rw-r--r--app/src/fil/libre/repwifiapp/network/Engine.java168
-rw-r--r--app/src/fil/libre/repwifiapp/network/Engine6p0.java315
-rw-r--r--app/src/fil/libre/repwifiapp/network/IEngine.java37
-rw-r--r--app/src/fil/libre/repwifiapp/network/NetworkButton.java39
-rw-r--r--app/src/fil/libre/repwifiapp/network/NetworkManager.java379
-rw-r--r--app/src/fil/libre/repwifiapp/network/WpaCli.java252
-rw-r--r--app/src/fil/libre/repwifiapp/network/WpaSupplicant.java91
11 files changed, 2358 insertions, 0 deletions
diff --git a/app/src/fil/libre/repwifiapp/network/AccessPointInfo.java b/app/src/fil/libre/repwifiapp/network/AccessPointInfo.java
new file mode 100644
index 0000000..862a852
--- /dev/null
+++ b/app/src/fil/libre/repwifiapp/network/AccessPointInfo.java
@@ -0,0 +1,442 @@
+//
+// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net>
+//
+// This file is part of RepWifiApp.
+//
+// RepWifiApp is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// RepWifiApp is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>.
+//
+// ********************************************************************
+
+package fil.libre.repwifiapp.network;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import fil.libre.repwifiapp.Utils;
+import fil.libre.repwifiapp.helpers.Logger;
+import org.json.JSONException;
+import org.json.JSONObject;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class AccessPointInfo implements Parcelable {
+
+ public static final String DUMMY_VPN_PROFILE = "--------";
+
+ private static final int MAX_SSID_LENGTH = 32;
+ protected static final String SCAN_FILE_HDR = "bssid / frequency / signal level / flags / ssid";
+
+ private static final String JSONKEY_BSSID = "BSSID";
+ private static final String JSONKEY_SSID = "SSID";
+ private static final String JSONKEY_LASTUSED = "LastUsed";
+ private static final String JSONKEY_AUTH = "Auth";
+ private static final String JSONKEY_PSK = "PSK";
+ private static final String JSONKEY_VPN_PROFILE = "VpnProfile";
+ private static final String JSONKEY_DHCPSETS = "DhcpSettings";
+
+ private String _ssid;
+ private String _bssid;
+ private String _auth;
+ private String _level;
+ private String _freq;
+ private String _password;
+ private String _vpnProfileName = null;
+ private boolean _isHidden = false;
+ private long _lastTimeUsed;
+ private DhcpSettings _dhcpsets;
+
+ public AccessPointInfo(String ssid, String bssid, String authType, String level, String freq) {
+
+ this._ssid = ssid;
+ this._bssid = bssid;
+ this._auth = authType;
+ this._level = level;
+ this._freq = freq;
+
+ }
+
+ private AccessPointInfo(){
+ // for inner use;
+ }
+
+ public void setDhcpConfiguration(DhcpSettings sets) {
+ this._dhcpsets = sets;
+ }
+
+ public DhcpSettings getDhcpConfiguration() {
+
+ if (this._dhcpsets == null) {
+ return DhcpSettings.getDefault();
+ } else {
+ return this._dhcpsets;
+ }
+ }
+
+ public String getSsid() {
+ return this._ssid;
+ }
+
+ public String getSsid(int maxLength) {
+ String txt = getSsid();
+ if (maxLength > 4 && txt.length() > maxLength) {
+ txt = txt.substring(0, maxLength - 3) + "...";
+ }
+ return txt;
+ }
+
+ public void setHidden(boolean hidden) {
+ this._isHidden = hidden;
+ }
+
+ public boolean isHidden() {
+ return this._isHidden;
+ }
+
+ public String getBssid() {
+ return this._bssid;
+ }
+
+ public void setBssid(String bssid) {
+ this._bssid = bssid;
+ }
+
+ public String getVpnProfileName(){
+ if (_vpnProfileName == null){
+ return "";
+ } else {
+ return _vpnProfileName;
+ }
+ }
+
+ public void setVpnProfileName(String profileName){
+
+ if (profileName != null && profileName.equals(DUMMY_VPN_PROFILE)){
+ profileName = "";
+ }
+
+ _vpnProfileName = profileName;
+ }
+
+ public String getAuthType() {
+ if (_auth == null){
+ return "";
+ }
+ return this._auth;
+ }
+
+ public int getSignlalStrength() {
+ // return this._level;
+
+ if (this._level == null || this._level.isEmpty()) {
+ return 0;
+ }
+
+ int retval = 0;
+
+ try {
+ retval = Integer.parseInt(this._level);
+ } catch (NumberFormatException e) {
+ retval = 0;
+ }
+
+ return retval;
+
+ }
+
+ public String getFrequency() {
+ return this._freq;
+ }
+
+ public long getLastTimeUsed() {
+ return this._lastTimeUsed;
+ }
+
+ public void setLastTimeUsed(long timeStampInMillis) {
+ this._lastTimeUsed = timeStampInMillis;
+ }
+
+ public boolean isOlderThan(int days) {
+
+ if (this._lastTimeUsed == 0) {
+ return false;
+ }
+
+ long timeDiff = System.currentTimeMillis() - this._lastTimeUsed;
+ long spanMillis = Utils.daysToMilliseconds(days);
+
+ if (timeDiff > spanMillis) {
+ return true;
+ } else {
+ return false;
+ }
+
+ }
+
+ public String getPassword() {
+ if (_password == null){
+ return "";
+ }
+ return this._password;
+ }
+
+ public void setPassword(String password) {
+ this._password = password;
+ }
+
+ public boolean needsPassword() {
+
+ if ((this._auth == null) || (this._auth.equals(""))) {
+ // TODO
+ // check if default behavior should be with or without password,
+ // when no auth info is available.
+ return false;
+ }
+
+ if (this._auth.contains("WPA2") || this._auth.contains("WPA")) {
+ return true;
+ } else {
+ return false;
+ }
+
+ }
+
+ protected static AccessPointInfo parseLine(String line) {
+
+ try {
+
+ String[] params = line.split("\t");
+ if (params.length != 5) {
+ return null;
+ }
+
+ String bssid = params[0];
+ String freq = params[1];
+ String level = params[2];
+ String auth = params[3];
+ String ssid = params[4];
+
+ if (ssid.length() == 0 || ssid.length() > MAX_SSID_LENGTH) {
+ // invalid SSID.
+ return null;
+ }
+
+ AccessPointInfo info = new AccessPointInfo(ssid, bssid, auth, level, freq);
+ return info;
+
+ } catch (Exception e) {
+ Logger.logError("Error while parsing line: " + line, e);
+ return null;
+ }
+
+ }
+
+ public static AccessPointInfo[] parseScanResult(String scanResultContent) {
+
+ try {
+
+ if (scanResultContent == null) {
+ return null;
+ }
+
+ Logger.logDebug("AccesPointInfo trying to parse file scan content:\n"
+ + scanResultContent);
+
+ String[] lines = scanResultContent.split("\n");
+ List<AccessPointInfo> nets = new ArrayList<AccessPointInfo>();
+
+ if (lines == null) {
+ return null;
+ }
+
+ for (String l : lines) {
+ if (l.startsWith(SCAN_FILE_HDR)) {
+ // strip off the header
+ continue;
+ }
+
+ if (l.trim().equals("")) {
+ // empty line, skip.
+ continue;
+ }
+
+ // try to parse line into network info
+ AccessPointInfo info = AccessPointInfo.parseLine(l);
+ if (info == null) {
+ Logger.logError("Failed to parse line into AccessPointInfo: " + l);
+ continue;
+ }
+
+ nets.add(info);
+
+ }
+
+ sortInfosBySignalStrength(nets);
+
+ AccessPointInfo[] a = new AccessPointInfo[nets.size()];
+ a = nets.toArray(a);
+ return a;
+
+ } catch (Exception e) {
+ Logger.logError("Error while parsing scan results in class AccessPointInfo", e);
+ return null;
+ }
+
+ }
+
+ public static AccessPointInfo[] fromParcellableArray(Parcelable[] p){
+ if (p == null) {
+ return null;
+ }
+
+ AccessPointInfo[] infos = new AccessPointInfo[p.length];
+ for (int j = 0; j < infos.length; j++) {
+ infos[j] = (AccessPointInfo) p[j];
+ }
+
+ return infos;
+ }
+
+ public JSONObject toJson(){
+
+ try {
+
+ JSONObject j = new JSONObject();
+
+ j.put(JSONKEY_BSSID, getBssid());
+ j.put(JSONKEY_SSID, getSsid());
+ j.put(JSONKEY_PSK, getPassword());
+ j.put(JSONKEY_AUTH, getAuthType());
+ j.put(JSONKEY_LASTUSED, getLastTimeUsed());
+ j.put(JSONKEY_VPN_PROFILE, getVpnProfileName());
+
+ DhcpSettings sets = getDhcpConfiguration();
+ if (sets != null){
+ JSONObject dhcpj = sets.toJson();
+ if (dhcpj != null){
+ j.put(JSONKEY_DHCPSETS, dhcpj);
+ }
+
+ }
+
+ return j;
+
+ } catch (JSONException e) {
+ Logger.logError("Exception while converting AccessPointInfo to JSON.", e);
+ return null;
+ }
+
+ }
+
+ public static AccessPointInfo fromJsonObject(JSONObject json){
+
+ if (json == null || json.isNull(JSONKEY_BSSID) || json.isNull(JSONKEY_SSID)){
+ return null;
+ }
+
+ AccessPointInfo info = new AccessPointInfo();
+
+ try {
+ info._bssid = json.getString(JSONKEY_BSSID);
+ info._ssid = json.getString(JSONKEY_SSID);
+ info._auth = json.getString(JSONKEY_AUTH);
+ info._lastTimeUsed = json.getLong(JSONKEY_LASTUSED);
+
+ if (json.has(JSONKEY_PSK) && ! json.isNull(JSONKEY_PSK)){
+ info._password = json.getString(JSONKEY_PSK);
+ }
+
+ if ( json.has(JSONKEY_VPN_PROFILE) && ! json.isNull(JSONKEY_VPN_PROFILE)){
+ info._vpnProfileName = json.getString(JSONKEY_VPN_PROFILE);
+ }
+
+ if (json.has(JSONKEY_DHCPSETS) && ! json.isNull(JSONKEY_DHCPSETS)){
+ info._dhcpsets = DhcpSettings.fromJsonObject(json.getJSONObject(JSONKEY_DHCPSETS));
+ }
+
+ return info;
+
+ } catch (JSONException e) {
+ Logger.logError("Exception while parsing json object to AccessPointInfo", e);
+ return null;
+ }
+
+
+
+
+ }
+
+ private static void sortInfosBySignalStrength(List<AccessPointInfo> toSort) {
+
+ Collections.sort(toSort, new Comparator<AccessPointInfo>() {
+ public int compare(AccessPointInfo o1, AccessPointInfo o2) {
+ if (o1.getSignlalStrength() == o2.getSignlalStrength())
+ return 0;
+ return o1.getSignlalStrength() < o2.getSignlalStrength() ? -1 : 1;
+ }
+ });
+
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public AccessPointInfo(Parcel in){
+
+ this._ssid = in.readString();
+ this._bssid = in.readString();
+ this._auth = in.readString();
+ this._level = in.readString();
+ this._freq = in.readString();
+ this._password = in.readString();
+ this._vpnProfileName = in.readString();
+ this._isHidden = in.readInt() == 1 ? true : false;
+ this._lastTimeUsed = in.readLong();
+ this._dhcpsets = in.readParcelable(DhcpSettings.class.getClassLoader());
+
+ }
+
+ public static final Parcelable.Creator<AccessPointInfo> CREATOR = new Parcelable.Creator<AccessPointInfo>() {
+ public AccessPointInfo createFromParcel(Parcel in) {
+ return new AccessPointInfo(in);
+ }
+
+ @Override
+ public AccessPointInfo[] newArray(int size) {
+ return new AccessPointInfo[size];
+ }
+
+
+
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+
+ dest.writeString(_ssid);
+ dest.writeString(_bssid);
+ dest.writeString(_auth);
+ dest.writeString(_level);
+ dest.writeString(_freq);
+ dest.writeString(_password);
+ dest.writeString(_vpnProfileName);
+ dest.writeInt(_isHidden ? 1 : 0);
+ dest.writeLong(_lastTimeUsed);
+ dest.writeParcelable(_dhcpsets, flags);
+
+ }
+
+}
diff --git a/app/src/fil/libre/repwifiapp/network/ConnectionResult.java b/app/src/fil/libre/repwifiapp/network/ConnectionResult.java
new file mode 100644
index 0000000..32ae771
--- /dev/null
+++ b/app/src/fil/libre/repwifiapp/network/ConnectionResult.java
@@ -0,0 +1,95 @@
+//
+// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net>
+//
+// This file is part of RepWifiApp.
+//
+// RepWifiApp is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// RepWifiApp is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>.
+//
+// ********************************************************************
+
+package fil.libre.repwifiapp.network;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class ConnectionResult implements Parcelable {
+
+ public static final int CONN_OK = 0;
+ public static final int CONN_FAILED = 1;
+ public static final int CONN_TIMEOUT = 2;
+ // public static final int CONN_DNS_FAIL = 3; // Removed as dns is now set
+ // by the framework
+ public static final int CONN_GW_FAIL = 4;
+ public static final int CONN_ABORTED = 5;
+ public static final int CONN_NOTIFY_FAILED = 6;
+ public static final int NO_RESULT = -1;
+
+ private ConnectionStatus _status = null;
+ private int _result = NO_RESULT;
+
+ public ConnectionResult(int result) {
+ this._result = result;
+ this._status = null;
+ }
+
+ public ConnectionStatus getStatus() {
+ return _status;
+ }
+
+ public int getResult() {
+ return _result;
+ }
+
+ public void setStatus(ConnectionStatus status) {
+
+ // keep coherence between the result code and the status
+ if (status != null && status.isConnected() && this._result == CONN_OK) {
+ this._status = status;
+ } else if (this._result == CONN_OK) {
+ this._result = CONN_FAILED;
+ this._status = null;
+ } else {
+ this._status = null;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public ConnectionResult(Parcel in) {
+ this._result = in.readInt();
+ this._status = in.readParcelable(ConnectionStatus.class.getClassLoader());
+ }
+
+ public static final Parcelable.Creator<ConnectionResult> CREATOR = new Parcelable.Creator<ConnectionResult>() {
+ public ConnectionResult createFromParcel(Parcel in) {
+ return new ConnectionResult(in);
+ }
+
+ @Override
+ public ConnectionResult[] newArray(int size) {
+ return new ConnectionResult[size];
+ }
+
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(this._result);
+ dest.writeParcelable(this._status, flags);
+ }
+
+}
diff --git a/app/src/fil/libre/repwifiapp/network/ConnectionStatus.java b/app/src/fil/libre/repwifiapp/network/ConnectionStatus.java
new file mode 100644
index 0000000..5fac0a1
--- /dev/null
+++ b/app/src/fil/libre/repwifiapp/network/ConnectionStatus.java
@@ -0,0 +1,298 @@
+//
+// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net>
+//
+// This file is part of RepWifiApp.
+//
+// RepWifiApp is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// RepWifiApp is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>.
+//
+// ********************************************************************
+
+package fil.libre.repwifiapp.network;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import fil.libre.repwifiapp.Utils;
+import fil.libre.repwifiapp.helpers.Logger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+public class ConnectionStatus implements Parcelable {
+
+ public static final String STATUS_CONNECTED = "COMPLETED";
+ public static final String STATUS_INACTIVE = "INACTIVE";
+ public static final String STATUS_DISCONNECTED = "DISCONNECTED";
+ public static final String STATUS_UNDEFINED = "UNDEFINED";
+
+ public String wpaStatus = null;
+ public String SSID = null;
+ public String BSSID = null;
+ public String IP = null;
+ public String gateway = null;
+ public String subnetMask = null;
+ public String hwAddress = null;
+ public String broadcastAddress = null;
+
+ private static final String F_SEP = "=";
+ private static final String KeyStatus = "wpa_state";
+ private static final String KeySSID = "ssid";
+ private static final String KeyBSSID = "bssid";
+ private static final String KeyIP = "ip_address";
+
+ private static final String IFCONFIG_BCAST = "Bcast";
+ private static final String IFCONFIG_MASK = "Mask";
+ private static final String IFCONFIG_HWADDR = "HWaddr";
+ private static final String IFCONFIG_KVALSEP = ":";
+ private static final String IFCONFIG_FSEP = " ";
+
+ public ConnectionStatus() {
+ }
+
+ public static ConnectionStatus parseWpaCliOutput(String wpaCliOutput) {
+
+ if (wpaCliOutput == null) {
+ return null;
+ }
+
+ if (wpaCliOutput.trim().length() == 0) {
+ return null;
+ }
+
+ String[] lines = wpaCliOutput.split("\n");
+
+ ConnectionStatus s = new ConnectionStatus();
+ for (String line : lines) {
+
+ if (line.trim().equals("")) {
+ continue;
+ }
+
+ String[] fields = line.split(F_SEP);
+ if (fields.length < 2) {
+ continue;
+ }
+
+ String key = fields[0];
+ String val = fields[1];
+
+ if (key.equals(KeyBSSID)) {
+ s.BSSID = val;
+ } else if (key.equals(KeySSID)) {
+ s.SSID = val;
+ } else if (key.equals(KeyStatus)) {
+ s.wpaStatus = val;
+ } else if (key.equals(KeyIP)) {
+ s.IP = val;
+ }
+
+ }
+
+ return s;
+
+ }
+
+ public static ConnectionStatus getDummyDisconnected() {
+ ConnectionStatus s = new ConnectionStatus();
+ s.wpaStatus = STATUS_DISCONNECTED;
+ return s;
+ }
+
+ public boolean parseIfconfigOutput(String ifconfigOutput) {
+
+ if (ifconfigOutput == null) {
+ return false;
+ }
+
+ String[] lines = ifconfigOutput.split("\n");
+ if (lines.length < 2) {
+ return false;
+ }
+
+ String[] fields1 = lines[0].split(IFCONFIG_FSEP);
+ String[] fields2 = lines[1].split(IFCONFIG_FSEP);
+
+ for (String f : fields1) {
+ // first line uses a single blank space as key-val separator
+ String[] splt = f.split(" ");
+ if (splt.length == 2 && splt[0].equals(IFCONFIG_HWADDR)) {
+ this.hwAddress = splt[1];
+ }
+ }
+
+ for (String f : fields2) {
+ String[] splt = f.split(IFCONFIG_KVALSEP);
+ if (splt.length == 2) {
+
+ String key = splt[0];
+ String val = splt[1];
+
+ if (key.equals(IFCONFIG_MASK)) {
+
+ this.subnetMask = val.trim();
+
+ } else if (key.equals(IFCONFIG_BCAST)) {
+
+ this.broadcastAddress = val;
+
+ } else if (key.equals(IFCONFIG_HWADDR)) {
+
+ this.hwAddress = val;
+
+ }
+
+ }
+ }
+
+ return true;
+
+ }
+
+ public boolean isConnected() {
+
+ if (this.wpaStatus == null) {
+ return false;
+ }
+
+ if (this.wpaStatus.equals(STATUS_CONNECTED)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /***
+ * @return Returns a string representation of the current IP address and
+ * subnet mask (e.g. "192.168.1.123/24"); returns null if either the
+ * address or the mask is null.
+ */
+ public String addressAndMaskToString() {
+
+ if (this.IP == null || this.subnetMask == null) {
+ return null;
+ }
+
+ int mask = Utils.netmaskStringToInt(this.subnetMask);
+ return this.IP + "/" + mask;
+
+ }
+
+ public InetAddress getInetAddress() {
+ if (this.IP == null) {
+ return null;
+ }
+
+ try {
+ return InetAddress.getByName(this.IP);
+ } catch (UnknownHostException e) {
+ Logger.logError("Exception while parsing InetAddress from string", e);
+ return null;
+ }
+
+ }
+
+ public int getSubnetMaskInt() {
+ return Utils.netmaskStringToInt(this.subnetMask);
+ }
+
+ public InetAddress getGatewayInetAddress() {
+
+ if (this.gateway == null) {
+ return null;
+ }
+
+ try {
+ return InetAddress.getByName(this.gateway);
+ } catch (Exception e) {
+ Logger.logError("Exception while parsing gateway's InetAddress from string.", e);
+ return null;
+ }
+
+ }
+
+ public AccessPointInfo getNetworkDetails() {
+ AccessPointInfo i = new AccessPointInfo(SSID, BSSID, "", "", "");
+ return NetworkManager.getSavedNetwork(i);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public ConnectionStatus(Parcel in) {
+
+ this.SSID = in.readString();
+ this.BSSID = in.readString();
+ this.IP = in.readString();
+ this.subnetMask = in.readString();
+ this.gateway = in.readString();
+ this.hwAddress = in.readString();
+ this.broadcastAddress = in.readString();
+ this.wpaStatus = in.readString();
+
+ }
+
+ public static final Parcelable.Creator<ConnectionStatus> CREATOR = new Parcelable.Creator<ConnectionStatus>() {
+ public ConnectionStatus createFromParcel(Parcel in) {
+ return new ConnectionStatus(in);
+ }
+
+ public ConnectionStatus[] newArray(int size) {
+ return new ConnectionStatus[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+
+ dest.writeString(this.SSID);
+ dest.writeString(this.BSSID);
+ dest.writeString(this.IP);
+ dest.writeString(this.subnetMask);
+ dest.writeString(this.gateway);
+ dest.writeString(this.hwAddress);
+ dest.writeString(this.broadcastAddress);
+ dest.writeString(this.wpaStatus);
+
+ }
+
+ public boolean equals(ConnectionStatus status) {
+
+ if (status == null) {
+ return false;
+ }
+
+ if (status.isConnected() != this.isConnected()) {
+ return false;
+ }
+
+ return fieldEquals(this.IP, status.IP) && fieldEquals(this.BSSID, status.BSSID)
+ && fieldEquals(this.SSID, status.SSID)
+ && fieldEquals(this.subnetMask, status.subnetMask)
+ && fieldEquals(this.gateway, status.gateway)
+ && fieldEquals(this.hwAddress, status.hwAddress);
+
+ }
+
+ private boolean fieldEquals(String myField, String extField) {
+ return myField == null ? extField == null : myField.equals(extField);
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "WPAsts: %s; \nIP: %s; \nMask: %s; \nGway: %s; \nBcast: %s; \nHWaddr: %s ",
+ wpaStatus, IP, subnetMask, gateway, broadcastAddress, hwAddress);
+ }
+
+}
diff --git a/app/src/fil/libre/repwifiapp/network/DhcpSettings.java b/app/src/fil/libre/repwifiapp/network/DhcpSettings.java
new file mode 100644
index 0000000..65524d5
--- /dev/null
+++ b/app/src/fil/libre/repwifiapp/network/DhcpSettings.java
@@ -0,0 +1,242 @@
+//
+// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net>
+//
+// This file is part of RepWifiApp.
+//
+// RepWifiApp is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// RepWifiApp is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>.
+//
+// ********************************************************************
+
+package fil.libre.repwifiapp.network;
+
+import android.nfc.FormatException;
+import android.os.Parcel;
+import android.os.Parcelable;
+import fil.libre.repwifiapp.Utils;
+import fil.libre.repwifiapp.helpers.Logger;
+import org.apache.http.conn.util.InetAddressUtils;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class DhcpSettings implements Parcelable {
+
+ public boolean useDhcp;
+ private String _staticIP;
+ private int _mask;
+ private String _defGw;
+
+ private static final String JSONKEY_DHCP = "DHCP";
+ private static final String JSONKEY_STATIC_IP = "StaticIP";
+ private static final String JSONKEY_GW = "Gateway";
+
+ public DhcpSettings(boolean useDhcp, String staticIP, String subnetMask, String defaultGateway)
+ throws FormatException {
+ this(useDhcp, staticIP, Utils.netmaskStringToInt(subnetMask), defaultGateway);
+ }
+
+ public DhcpSettings(boolean useDhcp, String staticIP, int subnetMask, String defaultGatweay)
+ throws FormatException {
+
+ this.useDhcp = useDhcp;
+
+ if (!useDhcp) {
+
+ if (!validateParams(staticIP, defaultGatweay, subnetMask)) {
+ throw new FormatException("Invalid dhcp parameters!");
+ }
+
+ this._staticIP = staticIP;
+ this._mask = subnetMask;
+ this._defGw = defaultGatweay;
+
+ }
+
+ }
+
+ private DhcpSettings() {
+ // inner use
+ }
+
+ public static DhcpSettings parseSavedSettings(String staticIPwithMask, String defaultGateway) {
+
+ try {
+
+ String[] ipm = staticIPwithMask.split("/");
+ String ip = ipm[0];
+ int mask = Integer.parseInt(ipm[1]);
+
+ return new DhcpSettings(false, ip, mask, defaultGateway);
+
+ } catch (Exception e) {
+ Logger.logError("Exception while parsing DhcpSettings for saved network. Reverting to dhcp.",
+ e);
+ return null;
+ }
+
+ }
+
+ public static DhcpSettings getDefault() {
+ try {
+ return new DhcpSettings(true, null, 24, null);
+ } catch (FormatException e) {
+ // no format exception can happen.
+ return null;
+ }
+ }
+
+ private boolean validateParams(String ip, String gateway, int mask) {
+
+ if (isValidAddress(ip) && isValidAddress(gateway) && isValidMask(mask)) {
+ return true;
+ } else {
+ return false;
+ }
+
+ }
+
+ public static boolean isValidAddress(String ipAddress) {
+ return InetAddressUtils.isIPv4Address(ipAddress);
+ }
+
+ public static boolean isValidMaks(String mask) {
+ int m = Utils.netmaskStringToInt(mask);
+ return isValidMask(m);
+ }
+
+ public static boolean isValidMask(int mask) {
+ if (mask >= 8 && mask <= 32) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public String getStaticIP() {
+ if (_staticIP == null) {
+ return "";
+ }
+ return _staticIP;
+ }
+
+ public String getStaticIPwithMask() {
+ return getStaticIP() + "/" + String.valueOf(getSubnetMaskInt());
+ }
+
+ public int getSubnetMaskInt() {
+ return _mask;
+ }
+
+ public String getSubnetMaskString() {
+ String v = Utils.netmaskIntToString(_mask);
+ if (v == null) {
+ return "";
+ }
+ return v;
+ }
+
+ public String getDefaultGateway() {
+ if (_defGw == null) {
+ return "";
+ }
+ return _defGw;
+ }
+
+ public JSONObject toJson() {
+
+ JSONObject j = new JSONObject();
+
+ try {
+ j.put(JSONKEY_DHCP, useDhcp);
+ j.put(JSONKEY_GW, getDefaultGateway());
+ j.put(JSONKEY_STATIC_IP, getStaticIPwithMask());
+
+ return j;
+
+ } catch (JSONException e) {
+ Logger.logError("Exception while converting DhcpSettings to JSON.", e);
+ return null;
+ }
+
+ }
+
+ public static DhcpSettings fromJsonObject(JSONObject json) {
+
+ if (json == null) {
+ return null;
+ }
+
+ DhcpSettings sets = new DhcpSettings();
+
+ try {
+
+ sets.useDhcp = json.getBoolean(JSONKEY_DHCP);
+
+ if (json.has(JSONKEY_GW) && !json.isNull(JSONKEY_GW)) {
+ sets._defGw = json.getString(JSONKEY_GW);
+ }
+
+ if (json.has(JSONKEY_STATIC_IP) && !json.isNull(JSONKEY_STATIC_IP)) {
+
+ String[] splt = json.getString(JSONKEY_STATIC_IP).split("/");
+ sets._staticIP = splt[0];
+ sets._mask = Integer.parseInt(splt[1]);
+
+ }
+
+ return sets;
+
+ } catch (Exception e) {
+ Logger.logError("Exception while parsing json object to DhcpSettings", e);
+ return null;
+ }
+
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public DhcpSettings(Parcel in) {
+
+ this.useDhcp = in.readInt() == 1 ? true : false;
+ this._staticIP = in.readString();
+ this._mask = in.readInt();
+ this._defGw = in.readString();
+
+ }
+
+ public static final Parcelable.Creator<DhcpSettings> CREATOR = new Parcelable.Creator<DhcpSettings>() {
+ public DhcpSettings createFromParcel(Parcel in) {
+ return new DhcpSettings(in);
+ }
+
+ @Override
+ public DhcpSettings[] newArray(int size) {
+ return new DhcpSettings[size];
+ }
+
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+
+ dest.writeInt(useDhcp ? 1 : 0);
+ dest.writeString(_staticIP);
+ dest.writeInt(_mask);
+ dest.writeString(_defGw);
+
+ }
+
+}
diff --git a/app/src/fil/libre/repwifiapp/network/Engine.java b/app/src/fil/libre/repwifiapp/network/Engine.java
new file mode 100644
index 0000000..34de124
--- /dev/null
+++ b/app/src/fil/libre/repwifiapp/network/Engine.java
@@ -0,0 +1,168 @@
+//
+// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net>
+//
+// This file is part of RepWifiApp.
+//
+// RepWifiApp is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// RepWifiApp is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>.
+//
+// ********************************************************************
+
+package fil.libre.repwifiapp.network;
+
+import fil.libre.repwifiapp.Commons;
+import fil.libre.repwifiapp.helpers.Logger;
+import fil.libre.repwifiapp.helpers.RootCommand;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+
+public abstract class Engine implements IEngine {
+
+ public static final String PIDFILE_DHCPCD = "/data/misc/dhcp/dhcpcd-wlan0.pid";
+
+
+ @Override
+ public AccessPointInfo[] getAvailableNetworks() {
+
+ Logger.logDebug("getAvailableNetworks():");
+
+ if (!WpaSupplicant.start()) {
+ Logger.logError("Failed starting wpa_supplicant");
+ return null;
+ }
+
+ if (!WpaCli.scanNetworks()) {
+ Logger.logError("failed scanning networks");
+ return null;
+ }
+
+ String scanRes = WpaCli.getScanResults();
+
+ if (scanRes == null) {
+ Logger.logError("failed getting scan results");
+ return null;
+ }
+
+ AccessPointInfo[] a = AccessPointInfo.parseScanResult(scanRes);
+ if (a == null) {
+ Logger.logError("Unable to parse scan file into AccessPointInfo array");
+ return a;
+ }
+
+ Logger.logDebug("# of APs found: " + a.length);
+
+ return a;
+
+ }
+
+ public ConnectionStatus getConnectionStatus() {
+
+ ConnectionStatus s = WpaCli.getConnectionStatus();
+ if (s == null){
+ return null;
+ }
+
+ String ifcfg = getIfconfigString();
+ s.parseIfconfigOutput(ifcfg);
+ return s;
+ }
+
+ private String getIfconfigString(){
+
+ // needs root for accessing encapsulation and hardware address (tested).
+ RootCommand cmd = new RootCommand("ifconfig " + WpaSupplicant.INTERFACE_NAME);
+
+ try {
+ if (cmd.execute() != 0){
+ Logger.logError("FAILED to run ifconfig to obtain interface info.");
+ return null;
+ }
+ } catch (Exception e) {
+ Logger.logError("Exception while running ifconfig.", e);
+ return null;
+ }
+
+ return cmd.getOutput();
+
+ }
+
+ @Override
+ public abstract int connect(AccessPointInfo info);
+
+ @Override
+ public boolean disconnect() {
+
+ return WpaCli.disconnect();
+
+ }
+
+ public static boolean isInterfaceAvailable(String ifaceName) throws SocketException {
+
+ Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+ while (interfaces.hasMoreElements()) {
+ NetworkInterface nif = interfaces.nextElement();
+ String nm = nif.getName();
+ if (nm.equals(ifaceName)) {
+ return true;
+ }
+ }
+ return false;
+
+ }
+
+ public int runDhcpcd() throws Exception{
+ // needs root
+ // option -w avoids dhcpcd forking to background,
+ // in order to keep control over its exit code, and be able to wait for it.
+ // option -A avoids ARP IP checking, we use it to save some seconds in the connection process.
+ // option -t <timeout> sets time out for obtaining a lease.
+ String cmdText = "dhcpcd -w -A -t " + Commons.WAIT_FOR_DHCPCD + " " + WpaSupplicant.INTERFACE_NAME;
+ RootCommand su = new RootCommand(cmdText);
+ return su.execute();
+ }
+
+ public int runDhcpcd(DhcpSettings dhcpConfig) throws Exception{
+
+ if (dhcpConfig == null || dhcpConfig.useDhcp){
+ Logger.logDebug("running dhchpc without dhcp settings, reverting to dhcp");
+ return runDhcpcd();
+ }
+
+ Logger.logDebug("Running dhcpcd with custom ip settings");
+ String cmdMask = "dhcpcd -w -A -S ip_address=%s -S routers=%s -t %d %s";
+ String cmd = String.format(cmdMask,
+ dhcpConfig.getStaticIPwithMask(),
+ dhcpConfig.getDefaultGateway(),
+ Commons.WAIT_FOR_DHCPCD,
+ WpaSupplicant.INTERFACE_NAME);
+
+ RootCommand su = new RootCommand(cmd);
+ return su.execute();
+
+ }
+
+ public static boolean killDhcpcd(){
+ if (! RootCommand.executeRootCmd("killall -SIGINT dhcpcd")){
+ return false;
+ }
+ return RootCommand.executeRootCmd("rm " + PIDFILE_DHCPCD);
+ }
+
+ public boolean interfaceUp() {
+ // needs root (tested)
+ return RootCommand.executeRootCmd("ifconfig " + WpaSupplicant.INTERFACE_NAME + " up");
+ }
+
+
+}
diff --git a/app/src/fil/libre/repwifiapp/network/Engine6p0.java b/app/src/fil/libre/repwifiapp/network/Engine6p0.java
new file mode 100644
index 0000000..c6475e2
--- /dev/null
+++ b/app/src/fil/libre/repwifiapp/network/Engine6p0.java
@@ -0,0 +1,315 @@
+//
+// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net>
+//
+// This file is part of RepWifiApp.
+//
+// RepWifiApp is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// RepWifiApp is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>.
+//
+// ********************************************************************
+
+package fil.libre.repwifiapp.network;
+
+import fil.libre.repwifiapp.Commons;
+import fil.libre.repwifiapp.helpers.Logger;
+import fil.libre.repwifiapp.helpers.RootCommand;
+import fil.libre.repwifiapp.helpers.ShellCommand;
+import org.apache.http.conn.util.InetAddressUtils;
+
+public class Engine6p0 extends Engine {
+
+ private Object abortFlagSync = new Object();
+ private boolean abortConnectionSignaled = false;
+
+ @Override
+ public int connect(AccessPointInfo info) {
+
+ WpaSupplicant.kill();
+
+
+ if (info == null) {
+ Logger.logDebug("Engine's connect() received a null AccessPointInfo");
+ return ConnectionResult.CONN_FAILED;
+ }
+
+ if (!checkAbortSignal())
+ return ConnectionResult.CONN_ABORTED;
+
+ // clear any previously set network
+ if (!destroyNetwork()) {
+ Logger.logDebug("Unable to ndc destroy network");
+ return ConnectionResult.CONN_FAILED;
+ }
+
+ if (!checkAbortSignal())
+ return ConnectionResult.CONN_ABORTED;
+
+ // clear interface's ip
+ if (!clearAddrs()) {
+ Logger.logDebug("Unable to ndc clearaddrs");
+ return ConnectionResult.CONN_FAILED;
+ }
+
+ if (!checkAbortSignal())
+ return ConnectionResult.CONN_ABORTED;
+
+ // bring up interface
+ if (!interfaceUp()) {
+ Logger.logDebug("Unable to bring up interface.");
+ return ConnectionResult.CONN_FAILED;
+ }
+
+ if (!checkAbortSignal())
+ return ConnectionResult.CONN_ABORTED;
+
+ // launch wpa_supplicant specifying our custom configuration and the
+ // socket file
+ if (!WpaSupplicant.start()) {
+ Logger.logDebug("Unable to run wpa start");
+ return ConnectionResult.CONN_FAILED;
+ }
+
+ if (!checkAbortSignal())
+ return ConnectionResult.CONN_ABORTED;
+
+ // create new network and get network id
+ String netID = WpaCli.createNetworkGetId();
+ if (netID == null) {
+ Logger.logDebug("Unable to fetch network id");
+ return ConnectionResult.CONN_FAILED;
+ }
+
+ if (!checkAbortSignal())
+ return ConnectionResult.CONN_ABORTED;
+
+ // set network SSID
+ if (!WpaCli.setNetworkSSID(info.getSsid(), netID)) {
+ Logger.logDebug("Failed to set network ssid");
+ return ConnectionResult.CONN_FAILED;
+ }
+
+ if (!checkAbortSignal())
+ return ConnectionResult.CONN_ABORTED;
+
+ if (info.isHidden() && !WpaCli.setNetworkScanSSID(netID)) {
+ Logger.logDebug("Failed to set scan_ssid 1 for hidden network.");
+ return ConnectionResult.CONN_FAILED;
+ }
+
+ if (!checkAbortSignal())
+ return ConnectionResult.CONN_ABORTED;
+
+ // set password (if any)
+ if (!WpaCli.setNetworkPSK(info, netID)) {
+ Logger.logDebug("Failed to set network psk");
+ return ConnectionResult.CONN_FAILED;
+ }
+
+ if (!checkAbortSignal())
+ return ConnectionResult.CONN_ABORTED;
+
+ // select the network we just created
+ if (!WpaCli.selectNetwork(netID)) {
+ Logger.logDebug("Unable to wpa_cli select network");
+ return ConnectionResult.CONN_FAILED;
+ }
+
+ if (!checkAbortSignal())
+ return ConnectionResult.CONN_ABORTED;
+
+ // enable the newtork
+ if (!WpaCli.enableNetwork(netID)) {
+ Logger.logDebug("Unable to wpa_cli enable_newtork");
+ return ConnectionResult.CONN_FAILED;
+ }
+
+ if (!checkAbortSignal())
+ return ConnectionResult.CONN_ABORTED;
+
+ // kill previous dhchcd instances
+ if (!killDhcpcd()) {
+ Logger.logError("Unable to kill previous instances of dhcpcd");
+ }
+
+ if (!checkAbortSignal())
+ return ConnectionResult.CONN_ABORTED;
+
+ // get DHCP
+ Logger.logDebug("Attempt to run dhcpcd..");
+ try {
+
+ int retcode = runDhcpcd(info.getDhcpConfiguration());
+ if (retcode == 1) {
+ Logger.logDebug("Dhcpcd exited on timeout exceeded.");
+ return ConnectionResult.CONN_TIMEOUT;
+
+ } else if (retcode != 0) {
+ Logger.logDebug("Dhcpcd exited with unknown code: " + retcode);
+ return ConnectionResult.CONN_FAILED;
+
+ }
+ } catch (Exception e) {
+ Logger.logError("Exception while executing dhcpcd: ", e);
+ return ConnectionResult.CONN_FAILED;
+ }
+
+ if (!checkAbortSignal())
+ return ConnectionResult.CONN_ABORTED;
+
+ // try to fetch gateway
+ String gw = getGateWayTimeout(Commons.WAIT_FOR_GATEWAY);
+ if (gw == null || !InetAddressUtils.isIPv4Address(gw)) {
+ // failed to get gateway
+ Logger.logDebug("Failed to get gateway");
+ return ConnectionResult.CONN_GW_FAIL;
+ }
+
+ /*
+ * Calls to ndc to set gateway and dns were removed 2018-04-09
+ * Starting from 2018-04-20, RepWifi registers itself as a NetworkAgent.
+ * When we register, all the following steps are performed by
+ * ConnectivityService itself:
+ * - creating a new network
+ * - setting the gateway
+ * - setting DNS
+ * There is no reason to perform those actions on our side via the command line;
+ * it would conflict with ConnectivityService's calls to "ndc"
+ */
+
+ return ConnectionResult.CONN_OK;
+
+ }
+
+ public void abortConnection() {
+ synchronized (abortFlagSync) {
+ this.abortConnectionSignaled = true;
+ }
+ }
+
+ @Override
+ public ConnectionStatus getConnectionStatus() {
+ ConnectionStatus s = super.getConnectionStatus();
+ if (s == null) {
+ return null;
+ }
+
+ s.gateway = this.getGateway();
+
+ return s;
+ }
+
+ private boolean checkAbortSignal() {
+
+ synchronized (abortFlagSync) {
+ if (abortConnectionSignaled) {
+ abortConnectionSignaled = false;
+ Logger.logDebug("Engine received abort connection signal. Aborting connection...");
+ killDhcpcd();
+ disconnect();
+ return false;
+ } else {
+ return true;
+ }
+ }
+ }
+
+ private boolean destroyNetwork() {
+
+ // needs root (tested)
+ return RootCommand.executeRootCmd("ndc network destroy 1");
+ }
+
+ private String getGateWayTimeout(int timeoutMillis) {
+
+ String gw = getGateway();
+ if (gw != null && !gw.trim().isEmpty()) {
+ return gw;
+ }
+
+ Logger.logDebug("Gateway not available.. going into wait loop..");
+
+ // gateway not (yet) available
+ // waits for a maximum of timeoutMillis milliseconds
+ // to let the interface being registered.
+ int msWaited = 0;
+ while (msWaited < timeoutMillis) {
+
+ try {
+ Thread.sleep(100);
+ } catch (Exception e) {
+ return null;
+ }
+ msWaited += 100;
+
+ gw = getGateway();
+ if (gw != null && !gw.trim().isEmpty()) {
+ Logger.logDebug("Gateway found after wait loop!");
+ return gw;
+ }
+ }
+
+ // unable to get gateway
+ Logger.logError("Gateway not found after wait loop.");
+ return null;
+
+ }
+
+ private String getGateway() {
+
+ try {
+
+ // doesn't need root (tested)
+ ShellCommand cmd = new ShellCommand("ip route show dev " + WpaSupplicant.INTERFACE_NAME);
+ if (cmd.execute() != 0) {
+ Logger.logDebug("command failed show route");
+ return null;
+ }
+
+ // read command output
+ String out = cmd.getOutput();
+ if (out == null) {
+ return null;
+ }
+
+ String[] lines = out.split("\n");
+
+ for (String l : lines) {
+
+ if (l.contains("default via")) {
+
+ String[] f = l.split(" ");
+ if (f.length > 2) {
+
+ // found route's address:
+ return f[2];
+
+ }
+ }
+ }
+
+ return null;
+
+ } catch (Exception e) {
+ Logger.logError("Error while trying to fetch route", e);
+ return null;
+ }
+
+ }
+
+ private boolean clearAddrs() {
+ // needs root (tested)
+ return RootCommand.executeRootCmd("ndc interface clearaddrs "
+ + WpaSupplicant.INTERFACE_NAME);
+ }
+
+} \ No newline at end of file
diff --git a/app/src/fil/libre/repwifiapp/network/IEngine.java b/app/src/fil/libre/repwifiapp/network/IEngine.java
new file mode 100644
index 0000000..d598550
--- /dev/null
+++ b/app/src/fil/libre/repwifiapp/network/IEngine.java
@@ -0,0 +1,37 @@
+//
+// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net>
+//
+// This file is part of RepWifiApp.
+//
+// RepWifiApp is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// RepWifiApp is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>.
+//
+// ********************************************************************
+
+package fil.libre.repwifiapp.network;
+
+public interface IEngine {
+
+ public AccessPointInfo[] getAvailableNetworks();
+
+ public int connect(AccessPointInfo info);
+
+ public boolean disconnect();
+
+ public ConnectionStatus getConnectionStatus();
+
+ // public boolean isInterfaceAvailable(String ifaceName) throws SocketException;
+
+ public void abortConnection();
+
+}
diff --git a/app/src/fil/libre/repwifiapp/network/NetworkButton.java b/app/src/fil/libre/repwifiapp/network/NetworkButton.java
new file mode 100644
index 0000000..49c816c
--- /dev/null
+++ b/app/src/fil/libre/repwifiapp/network/NetworkButton.java
@@ -0,0 +1,39 @@
+//
+// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net>
+//
+// This file is part of RepWifiApp.
+//
+// RepWifiApp is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// RepWifiApp is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>.
+//
+// ********************************************************************
+
+package fil.libre.repwifiapp.network;
+
+import android.content.Context;
+import android.widget.Button;
+
+public class NetworkButton extends Button {
+
+ private String _bssid = "";
+
+ public NetworkButton(Context context, String networkBSSID) {
+ super(context);
+ this._bssid = networkBSSID;
+ }
+
+ public String getNetworkBSSID() {
+ return this._bssid;
+ }
+
+}
diff --git a/app/src/fil/libre/repwifiapp/network/NetworkManager.java b/app/src/fil/libre/repwifiapp/network/NetworkManager.java
new file mode 100644
index 0000000..4d5635d
--- /dev/null
+++ b/app/src/fil/libre/repwifiapp/network/NetworkManager.java
@@ -0,0 +1,379 @@
+//
+// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net>
+//
+// This file is part of RepWifiApp.
+//
+// RepWifiApp is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General public static License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// RepWifiApp is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General public static License for more details.
+//
+// You should have received a copy of the GNU General public static License
+// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>.
+//
+// ********************************************************************
+
+package fil.libre.repwifiapp.network;
+
+import fil.libre.repwifiapp.Utils;
+import fil.libre.repwifiapp.helpers.Logger;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import java.io.File;
+import java.util.ArrayList;
+
+public abstract class NetworkManager {
+
+ private static final String VERSION_NOTE = "RepWifiStorageVersion: 2.0";
+ private static final String F_SEP = "\t";
+ private static final int NET_MAX_AGE = 1095; // Expressed in days
+
+ private static String knownNetworksFile = null;
+
+ public static void init(String storageFilePath){
+ knownNetworksFile = storageFilePath;
+ }
+
+ private static AccessPointInfo getSavedInfo(AccessPointInfo i) {
+
+ if (i == null) {
+ return null;
+ }
+
+ String bssid = i.getBssid();
+ String ssid = i.getSsid();
+
+ if (bssid == null || ssid == null || bssid.trim().equals("") || ssid.trim().equals("")) {
+ return null;
+ }
+
+ AccessPointInfo ret = null;
+ AccessPointInfo[] list = getKnownNetworks();
+
+ if (list == null) {
+ return null;
+ }
+
+ for (AccessPointInfo toTest : list) {
+
+ // try to match both bssid and ssid.
+ // if bssid doesn't match, but ssid does,
+ // then the network is a candidate.
+ // if no bssid equality is found,
+ // then return the best match (only ssid), if any
+ if (toTest.getSsid().equals(ssid)) {
+
+ i.setPassword(toTest.getPassword());
+ i.setDhcpConfiguration(toTest.getDhcpConfiguration());
+ i.setVpnProfileName(toTest.getVpnProfileName());
+
+ if (toTest.getBssid().equals(bssid)) {
+ // complete match, return.
+ return i;
+
+ } else {
+ // probable match
+ ret = i;
+ }
+
+ }
+
+ }
+
+ return ret;
+
+ }
+
+ private static boolean saveOrRemove(AccessPointInfo info, boolean save) {
+
+ AccessPointInfo[] existingNets = getKnownNetworks();
+
+ ArrayList<AccessPointInfo> newlist = new ArrayList<AccessPointInfo>();
+
+ if (existingNets == null || existingNets.length == 0) {
+ // no existing storage yet, create it
+
+ if (save) {
+ // set timestamp
+ info.setLastTimeUsed(System.currentTimeMillis());
+ newlist.add(info);
+ AccessPointInfo[] newContents = new AccessPointInfo[newlist.size()];
+ newContents = newlist.toArray(newContents);
+
+ return saveList(newContents);
+
+ } else {
+ // nothing to do, return
+ return true;
+ }
+
+ }
+
+ if (save) {
+ // add the updated info to the storage
+ info.setLastTimeUsed(System.currentTimeMillis());
+ newlist.add(info);
+ }
+
+ for (AccessPointInfo old : existingNets) {
+
+ if (old == null) {
+ // error while loading from file. skip.
+ continue;
+ }
+
+ // keep network only if it's not older than the max age for a
+ // network
+ else if (old.isOlderThan(NET_MAX_AGE)) {
+ // skip it
+ continue;
+ }
+
+ else if (old.getBssid().equals(info.getBssid()) && old.getSsid().equals(info.getSsid())) {
+ // found previously saved entry for the same network we are
+ // managing
+ // skip it
+ continue;
+ }
+
+ else {
+ // old network info that can be kept in the storage
+ newlist.add(old);
+ }
+
+ }
+
+ AccessPointInfo[] newContents = new AccessPointInfo[newlist.size()];
+ newContents = newlist.toArray(newContents);
+
+ return saveList(newContents);
+
+ }
+
+ private static AccessPointInfo getFromStringOld(String savedString) {
+
+ if (savedString == null || savedString.trim().equals("")) {
+ return null;
+ }
+
+ String[] fields = savedString.split(F_SEP);
+
+ if (fields.length < 4) {
+ return null;
+ }
+
+ String bssid = fields[0];
+ String ssid = fields[1];
+ String pass = fields[2];
+ String lastUsed = fields[3];
+ String auth = null;
+ String ipWmask = null;
+ String gw = null;
+ boolean useDhcp = true;
+ String vpnProfile = null;
+
+ if (fields.length > 4) {
+ auth = fields[4];
+ }
+
+ if (fields.length > 6) {
+ ipWmask = fields[5];
+ gw = fields[6];
+ useDhcp = false;
+ }
+
+ if (fields.length > 7) {
+ vpnProfile = fields[7];
+ }
+
+ long lastusedmillis = 0;
+ try {
+ lastusedmillis = Long.parseLong(lastUsed);
+ } catch (NumberFormatException e) {
+ // invalid format
+ Logger.logError("Invalid time format in network manager \"" + lastUsed
+ + "\". Network BSSID: " + bssid, e);
+ }
+
+ if (bssid.trim().equals("") || ssid.trim().equals("") || pass.trim().equals("")) {
+ return null;
+ }
+
+ AccessPointInfo i = new AccessPointInfo(ssid, bssid, auth, null, null);
+ i.setPassword(pass);
+ i.setLastTimeUsed(lastusedmillis);
+ i.setVpnProfileName(vpnProfile);
+
+ if (!useDhcp) {
+ DhcpSettings s = DhcpSettings.parseSavedSettings(ipWmask, gw);
+ i.setDhcpConfiguration(s);
+ }
+
+ return i;
+
+ }
+
+ private static boolean saveList(AccessPointInfo[] list) {
+
+ try {
+
+ JSONArray jarr = new JSONArray();
+ for (AccessPointInfo i : list) {
+
+ JSONObject jo = i.toJson();
+ if (jo == null) {
+ return false;
+ }
+
+ jarr.put(jo);
+
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(VERSION_NOTE);
+ sb.append("\n");
+
+ sb.append(jarr.toString(2));
+
+ return Utils.writeFile(knownNetworksFile, sb.toString(), true);
+
+ } catch (Exception e) {
+ Logger.logError("Exception while saving AccessPointInfo array to JSON-formatted file.",
+ e);
+ return false;
+ }
+
+ }
+
+ public static boolean updateStorageVersion() {
+
+ try{
+ String[] lines = Utils.readFileLines(knownNetworksFile);
+
+ if (lines == null){
+ return false;
+ }
+
+ if (lines.length == 0) {
+ return true;
+ }
+
+ if (lines[0].trim().equals(VERSION_NOTE)) {
+ return true;
+
+ } else {
+
+ AccessPointInfo[] infos = getKnownNetworksOld();
+ if (infos == null || infos.length == 0) {
+ return true;
+ }
+ return saveList(infos);
+
+ }
+ }catch (Exception e){
+ Logger.logError("Exception while trying to update network storage version",e);
+ return false;
+ }
+
+ }
+
+ public static AccessPointInfo[] getKnownNetworks() {
+
+ try {
+
+ String fconts = Utils.readFile(knownNetworksFile);
+ if (fconts == null) {
+ return null;
+ }
+
+ if (!fconts.startsWith(VERSION_NOTE)) {
+ // wrong version, try to convert it
+ if (updateStorageVersion()) {
+ return getKnownNetworks();
+ } else {
+ return null;
+ }
+ }
+
+ JSONArray jarr = new JSONArray(fconts.replace(VERSION_NOTE, ""));
+ ArrayList<AccessPointInfo> list = new ArrayList<AccessPointInfo>();
+
+ int count = 0;
+ for (int i = 0; i < jarr.length(); i++) {
+ AccessPointInfo info = AccessPointInfo.fromJsonObject(jarr.getJSONObject(i));
+ if (info == null) {
+ continue;
+ }
+ count += 1;
+ list.add(info);
+ }
+
+ AccessPointInfo[] arr = new AccessPointInfo[count];
+ return list.toArray(arr);
+
+ } catch (Exception e) {
+ Logger.logError("Exception while parsing JSON content from storage file.", e);
+ return null;
+ }
+
+ }
+
+ public static AccessPointInfo[] getKnownNetworksOld() {
+
+ ArrayList<AccessPointInfo> list = new ArrayList<AccessPointInfo>();
+
+ File f = new File(knownNetworksFile);
+ if (!f.exists()) {
+ return null;
+ }
+
+ String[] lines = Utils.readFileLines(knownNetworksFile);
+ if (lines == null || lines.length == 0) {
+ return null;
+ }
+
+ for (String l : lines) {
+
+ AccessPointInfo info = getFromStringOld(l);
+ if (info != null) {
+ list.add(info);
+ }
+
+ }
+
+ AccessPointInfo[] ret = new AccessPointInfo[list.size()];
+ ret = list.toArray(ret);
+
+ return ret;
+
+ }
+
+ public static boolean isKnown(AccessPointInfo info) {
+
+ AccessPointInfo i = getSavedInfo(info);
+ if (i == null) {
+ return false;
+ } else {
+ return true;
+ }
+
+ }
+
+ public static boolean save(AccessPointInfo info) {
+ return saveOrRemove(info, true);
+ }
+
+ public static boolean remove(AccessPointInfo info) {
+ return saveOrRemove(info, false);
+ }
+
+ public static AccessPointInfo getSavedNetwork(AccessPointInfo i) {
+ return getSavedInfo(i);
+ }
+
+}
diff --git a/app/src/fil/libre/repwifiapp/network/WpaCli.java b/app/src/fil/libre/repwifiapp/network/WpaCli.java
new file mode 100644
index 0000000..10f6b7c
--- /dev/null
+++ b/app/src/fil/libre/repwifiapp/network/WpaCli.java
@@ -0,0 +1,252 @@
+//
+// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net>
+//
+// This file is part of RepWifiApp.
+//
+// RepWifiApp is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// RepWifiApp is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>.
+//
+// ********************************************************************
+
+
+package fil.libre.repwifiapp.network;
+
+import fil.libre.repwifiapp.helpers.Logger;
+import fil.libre.repwifiapp.helpers.RootCommand;
+
+public abstract class WpaCli {
+
+ private static final int SCAN_WAIT_INTERVAL = 4;
+ private static final String BASE_COMMAND = "wpa_cli -p" + WpaSupplicant.SOCKET_DIR + " -P"
+ + WpaSupplicant.PID_FILE + " -i" + WpaSupplicant.INTERFACE_NAME;
+
+ public static String createNetworkGetId() {
+
+ try {
+
+ RootCommand su = new RootCommand(BASE_COMMAND + " add_network");
+ if (su.execute() == 0) {
+ String out = su.getOutput();
+ if (out == null || out.trim().equals("")) {
+ return null;
+ } else {
+ return out.replace("\n", "");
+ }
+ } else {
+ return null;
+ }
+
+ } catch (Exception e) {
+ Logger.logError("Error while creating network", e);
+ return null;
+ }
+
+ }
+
+ public static boolean setNetworkSSID(String ssid, String networkID) {
+
+ try {
+
+ // needs root (wpa_cli)
+ return executeCmd(BASE_COMMAND + " set_network " + networkID + " ssid '\"" + ssid
+ + "\"'");
+
+ } catch (Exception e) {
+ Logger.logError("Error while setting network SSID", e);
+ return false;
+ }
+
+ }
+
+ public static boolean setNetworkPSK(AccessPointInfo info, String networkID) {
+
+ try {
+
+ // needs root (wpa_cli)
+
+ String cmdSetPass = null;
+ if (info.needsPassword()) {
+ cmdSetPass = BASE_COMMAND + " set_network " + networkID + " psk '\""
+ + info.getPassword() + "\"'";
+ } else {
+ cmdSetPass = BASE_COMMAND + " set_network " + networkID + " key_mgmt NONE";
+ }
+
+ return executeCmd(cmdSetPass);
+
+ } catch (Exception e) {
+ Logger.logError("Error while setting network PSK", e);
+ return false;
+ }
+
+ }
+
+ public static boolean setNetworkScanSSID(String networkID) {
+
+ try {
+
+ // needs root (wpa_cli)
+ return executeCmd(BASE_COMMAND + " set_network " + networkID + " scan_ssid 1");
+
+ } catch (Exception e) {
+ Logger.logError("Error while setting network SSID", e);
+ return false;
+ }
+ }
+
+ public static boolean selectNetwork(String networkID) {
+
+ try {
+
+ // needs root (wpa_cli)
+ return executeCmd(BASE_COMMAND + " select_network " + networkID);
+
+ } catch (Exception e) {
+ Logger.logError("Error while selecting network", e);
+ return false;
+ }
+
+ }
+
+ public static boolean enableNetwork(String networkID) {
+
+ try {
+
+ // needs root (wpa_cli)
+ return executeCmd(BASE_COMMAND + " enable_network " + networkID);
+
+ } catch (Exception e) {
+ Logger.logError("Error while enabling network", e);
+ return false;
+ }
+
+ }
+
+ public static boolean scanNetworks() {
+
+ // needs root (for wpa_supplicant and wpa_cli)
+ return RootCommand.executeRootCmd(BASE_COMMAND
+ + " scan; if [ $? -ne 0 ]; then exit 1; fi; sleep " + SCAN_WAIT_INTERVAL);
+
+ }
+
+ public static String getScanResults() {
+
+ try {
+
+ RootCommand su = new RootCommand(BASE_COMMAND
+ + " scan_results; if [ $? -ne 0 ]; then exit 1; fi ");
+ if (su.execute() == 0) {
+ String out = su.getOutput();
+ if (out == null || out.trim().equals("")) {
+ return null;
+ } else {
+ return out;
+ }
+ } else {
+ return null;
+ }
+
+ } catch (Exception e) {
+ Logger.logError("Error while executing wpa_cli status", e);
+ return null;
+ }
+
+ }
+
+ /***
+ * returns null if unable to determine connection status for any reason.
+ */
+ public static ConnectionStatus getConnectionStatus() {
+
+ Logger.logDebug("called getConnecitonStatus()");
+ if (!WpaSupplicant.isRunning()) {
+ // wpa_supplicant is not running.
+ // unable to determine status.
+ Logger.logDebug("wpa not running, cannot get connection status.");
+ return null;
+
+ }
+
+ try {
+
+ RootCommand su = new RootCommand(BASE_COMMAND + " status");
+ if (su.execute() == 0) {
+ String out = su.getOutput();
+ if (out == null || out.trim().equals("")) {
+ return null;
+ } else {
+ return ConnectionStatus.parseWpaCliOutput(out);
+ }
+ } else {
+ return null;
+ }
+
+ } catch (Exception e) {
+ Logger.logError("Error while executing wpa_cli status", e);
+ return null;
+ }
+
+ }
+
+ public static boolean disconnect() {
+
+ // needs root (for wpa_cli)
+
+ if (!WpaSupplicant.isRunning()) {
+ return true;
+ }
+
+ try {
+
+ return executeCmd(BASE_COMMAND + " disconnect");
+
+ } catch (Exception e) {
+ Logger.logError("Error while enabling network", e);
+ return false;
+ }
+ }
+
+ public static boolean terminateSupplicant() {
+
+ if (!WpaSupplicant.isRunning()){
+ return true;
+ }
+ try {
+
+ return executeCmd(BASE_COMMAND + " terminate");
+
+ } catch (Exception e) {
+ Logger.logError("Error while enabling network", e);
+ return false;
+ }
+
+ }
+
+ private static boolean executeCmd(String cmdTxt) throws Exception {
+
+ RootCommand su = new RootCommand(cmdTxt);
+ if (su.execute() == 0) {
+ String out = su.getOutput();
+ if (out != null && out.trim().replace("\n", "").equals("OK")) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ }
+
+}
diff --git a/app/src/fil/libre/repwifiapp/network/WpaSupplicant.java b/app/src/fil/libre/repwifiapp/network/WpaSupplicant.java
new file mode 100644
index 0000000..d8079b9
--- /dev/null
+++ b/app/src/fil/libre/repwifiapp/network/WpaSupplicant.java
@@ -0,0 +1,91 @@
+//
+// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net>
+//
+// This file is part of RepWifiApp.
+//
+// RepWifiApp is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// RepWifiApp is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>.
+//
+// ********************************************************************
+
+package fil.libre.repwifiapp.network;
+
+import fil.libre.repwifiapp.helpers.Logger;
+import fil.libre.repwifiapp.helpers.RootCommand;
+
+
+public abstract class WpaSupplicant {
+
+ public static final String INTERFACE_NAME = "wlan0";
+ public static final String WORKDIR = "/data/misc/wifi";
+ public static final String PID_FILE = WORKDIR + "/pidfile";
+ public static final String SOCKET_DIR = WORKDIR + "/sockets";
+ public static final String SOFTAP_FILE = WORKDIR + "/softap.conf";
+ public static final String P2P_CONF = WORKDIR + "/p2p_supplicant.conf";
+ public static final String WPA_CONF = WORKDIR + "/wpa_supplicant.conf";
+ public static final String ENTROPY_FILE = WORKDIR + "/entropy.bin";
+ public static final String OVERLAY_FILE = "/system/etc/wifi/wpa_supplicant_overlay.conf";
+
+ protected static final String BASE_COMMNAD = "wpa_supplicant -B -dd -i" + INTERFACE_NAME + " -C" + SOCKET_DIR + " -P" + PID_FILE
+ + " -I" + OVERLAY_FILE + " -e" + ENTROPY_FILE;
+
+ public static boolean start() {
+
+ Logger.logDebug("startWpaSupplicant():");
+ if (isRunning()){
+ return true;
+ }
+
+ // needs root (for wpa_supplicant)
+ if (RootCommand.executeRootCmd(BASE_COMMNAD)) {
+ return true;
+ } else {
+ Logger.logDebug("Failed to start wpa");
+ return false;
+ }
+
+ }
+
+ public static boolean kill(){
+ return RootCommand.executeRootCmd("killall -SIGINT wpa_supplicant");
+ }
+
+ public static boolean isRunning() {
+
+ boolean retval = false;
+
+ try {
+
+ RootCommand su = new RootCommand("pidof wpa_supplicant");
+ if (su.execute() == 0) {
+
+ if (su.getOutput().trim().equals("")) {
+ retval = false;
+ } else {
+ retval = true;
+ }
+
+ } else {
+ retval = false;
+ }
+
+ } catch (Exception e) {
+ Logger.logError("Exception during isWpaSupplicantRunning()", e);
+ retval = false;
+ }
+
+ return retval;
+
+ }
+
+}