Skip to content

Commit 463ac61

Browse files
committed
Basic IPv6 support
Still missing: manual connection, QR-Code support needs to be changed to accomodate both IP versions (preferrably without breaking existing support
1 parent b3ff6c3 commit 463ac61

File tree

9 files changed

+364
-218
lines changed

9 files changed

+364
-218
lines changed

src/auth.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,13 @@ def get_server_creds(self):
6969

7070
def get_cached_cert(self, hostname, ip_info):
7171
try:
72-
return self.remote_certs["%s.%s" % (hostname, ip_info.ip4_address)]
72+
return self.remote_certs["%s.%s" % (hostname, ip_info)]
7373
except KeyError:
7474
return None
7575

7676
def process_remote_cert(self, hostname, ip_info, server_data):
7777
if server_data is None:
78-
return False
78+
return util.CertProcessingResult.FAILURE
7979
decoded = base64.decodebytes(server_data)
8080

8181
hasher = hashlib.sha256()
@@ -89,11 +89,20 @@ def process_remote_cert(self, hostname, ip_info, server_data):
8989
logging.debug("Decryption failed for remote '%s': %s" % (hostname, str(e)))
9090
cert = None
9191

92+
res = util.CertProcessingResult.FAILURE
9293
if cert:
93-
self.remote_certs["%s.%s" % (hostname, ip_info.ip4_address)] = cert
94-
return True
95-
else:
96-
return False
94+
key = "%s.%s" % (hostname, ip_info)
95+
val = self.remote_certs.get(key)
96+
97+
if val is None:
98+
res = util.CertProcessingResult.CERT_INSERTED
99+
elif val == cert:
100+
res = util.CertProcessingResult.CERT_UP_TO_DATE
101+
return res
102+
else:
103+
res = util.CertProcessingResult.CERT_UPDATED
104+
self.remote_certs[key] = cert
105+
return res
97106

98107
def get_encoded_local_cert(self):
99108
hasher = hashlib.sha256()
@@ -133,6 +142,8 @@ def _make_key_cert_pair(self):
133142

134143
if self.ip_info.ip4_address is not None:
135144
alt_names.append(x509.IPAddress(ipaddress.IPv4Address(self.ip_info.ip4_address)))
145+
if self.ip_info.ip6_address is not None:
146+
alt_names.append(x509.IPAddress(ipaddress.IPv6Address(self.ip_info.ip6_address)))
136147

137148
builder = builder.add_extension(x509.SubjectAlternativeName(alt_names), critical=True)
138149

src/networkmonitor.py

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -102,65 +102,89 @@ def get_valid_interface_infos(self):
102102

103103
try:
104104
ip4 = iface[netifaces.AF_INET][0]
105+
except KeyError:
106+
ip4 = None
107+
try:
108+
ip6 = iface[netifaces.AF_INET6][0]
109+
except KeyError:
110+
ip6 = None
105111

106-
try:
107-
ip6 = iface[netifaces.AF_INET6][0]
108-
except KeyError:
109-
ip6 = None
110-
112+
if ip4 is not None or ip6 is not None:
111113
info = util.InterfaceInfo(ip4, ip6, iname)
112114
valid.append(info)
113-
except KeyError:
114-
continue
115115

116116
return valid
117117

118118
def get_default_interface_info(self):
119-
ip = self.get_default_ip()
119+
ip4 = self.get_default_ip4()
120+
ip6 = self.get_default_ip6()
120121
fallback_info = None
121122

122123
for info in self.get_valid_interface_infos():
123124
if fallback_info is None:
124125
fallback_info = info
125126
try:
126-
if ip == info.ip4["addr"]:
127+
if ip4 == info.ip4["addr"]:
128+
return info
129+
except:
130+
pass
131+
try:
132+
if ip6 == info.ip6["addr"]:
127133
return info
128134
except:
129135
pass
130136

131137
return fallback_info
132138

133-
def get_default_ip(self):
134-
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
135-
try:
136-
s.connect(("8.8.8.8", 80))
137-
except OSError as e:
138-
# print("Unable to retrieve IP address: %s" % str(e))
139-
return "0.0.0.0"
139+
def get_default_ip(self, ip_version):
140+
with socket.socket(ip_version, socket.SOCK_DGRAM) as s:
141+
if ip_version == socket.AF_INET:
142+
try:
143+
s.connect(("8.8.8.8", 80))
144+
except OSError as e:
145+
# print("Unable to retrieve IP address: %s" % str(e))
146+
return "0.0.0.0"
147+
else:
148+
try:
149+
s.connect(("2001:4860:4860::8888", 80))
150+
except OSError as e:
151+
# print("Unable to retrieve IP address: %s" % str(e))
152+
return "[::]"
140153
ans = s.getsockname()[0]
141154
return ans
142155

156+
def get_default_ip4(self):
157+
return self.get_default_ip(socket.AF_INET)
158+
159+
def get_default_ip6(self):
160+
return self.get_default_ip(socket.AF_INET6)
161+
143162
def emit_state_changed(self):
144163
logging.debug("Network state changed: online = %s" % str(self.online))
145164
self.emit("state-changed", self.online)
146165

147166
# TODO: Do this with libnm
148167
def same_subnet(self, other_ip_info):
149-
iface = ipaddress.IPv4Interface("%s/%s" % (self.current_ip_info.ip4_address,
150-
self.current_ip_info.ip4["netmask"]))
168+
if self.current_ip_info.ip4_address is not None and other_ip_info.ip4_address is not None:
169+
iface = ipaddress.IPv4Interface("%s/%s" % (self.current_ip_info.ip4_address,
170+
self.current_ip_info.ip4["netmask"]))
151171

152-
my_net = iface.network
172+
my_net = iface.network
153173

154-
if my_net is None:
155-
# We're more likely to have failed here than to have found something on a different subnet.
156-
return True
174+
if my_net is None:
175+
# We're more likely to have failed here than to have found something on a different subnet.
176+
return True
157177

158-
if my_net.netmask.exploded == "255.255.255.255":
159-
logging.warning("Discovery: netmask is 255.255.255.255 - are you on a vpn?")
160-
return False
178+
if my_net.netmask.exploded == "255.255.255.255":
179+
logging.warning("Discovery: netmask is 255.255.255.255 - are you on a vpn?")
180+
return False
161181

162-
for addr in list(my_net.hosts()):
163-
if other_ip_info.ip4_address == addr.exploded:
164-
return True
182+
for addr in list(my_net.hosts()):
183+
if other_ip_info.ip4_address == addr.exploded:
184+
return True
165185

186+
return False
187+
if self.current_ip_info.ip6_address is not None and other_ip_info.ip6_address is not None:
188+
return True # TODO: Verify that this is actually true
189+
logging.debug("No IP address found: %s" % (self))
166190
return False

src/remote.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import gettext
55
import threading
66
import logging
7+
import socket
78

89
from gi.repository import GObject, GLib
910

@@ -79,6 +80,8 @@ def __init__(self, ident, hostname, display_hostname, ip_info, port, local_ident
7980

8081
self.has_zc_presence = False # This is currently unused.
8182

83+
self.last_register = 0
84+
8285
def start_remote_thread(self):
8386
# func = lambda: return
8487

@@ -104,7 +107,7 @@ def remote_thread_v1(self):
104107

105108
def run_secure_loop():
106109
logging.debug("Remote: Starting a new connection loop for %s (%s:%d)"
107-
% (self.display_hostname, self.ip_info.ip4_address, self.port))
110+
% (self.display_hostname, self.ip_info, self.port))
108111

109112
cert = auth.get_singleton().get_cached_cert(self.hostname, self.ip_info)
110113
creds = grpc.ssl_channel_credentials(cert)
@@ -121,7 +124,7 @@ def run_secure_loop():
121124

122125
if not self.ping_timer.is_set():
123126
logging.debug("Remote: Unable to establish secure connection with %s (%s:%d). Trying again in %ds"
124-
% (self.display_hostname, self.ip_info.ip4_address, self.port, CHANNEL_RETRY_WAIT_TIME))
127+
% (self.display_hostname, self.ip_info, self.port, CHANNEL_RETRY_WAIT_TIME))
125128
self.ping_timer.wait(CHANNEL_RETRY_WAIT_TIME)
126129
return True # run_secure_loop()
127130

@@ -134,13 +137,13 @@ def run_secure_loop():
134137

135138
if self.busy:
136139
logging.debug("Remote Ping: Skipping keepalive ping to %s (%s:%d) (busy)"
137-
% (self.display_hostname, self.ip_info.ip4_address, self.port))
140+
% (self.display_hostname, self.ip_info, self.port))
138141
self.busy = False
139142
else:
140143
try:
141144
# t = GLib.get_monotonic_time()
142145
logging.debug("Remote Ping: to %s (%s:%d)"
143-
% (self.display_hostname, self.ip_info.ip4_address, self.port))
146+
% (self.display_hostname, self.ip_info, self.port))
144147
self.stub.Ping(warp_pb2.LookupName(id=self.local_ident,
145148
readable_name=util.get_hostname()),
146149
timeout=5)
@@ -150,7 +153,7 @@ def run_secure_loop():
150153
self.set_remote_status(RemoteStatus.AWAITING_DUPLEX)
151154
if self.check_duplex_connection():
152155
logging.debug("Remote: Connected to %s (%s:%d)"
153-
% (self.display_hostname, self.ip_info.ip4_address, self.port))
156+
% (self.display_hostname, self.ip_info, self.port))
154157

155158
self.set_remote_status(RemoteStatus.ONLINE)
156159

@@ -161,12 +164,12 @@ def run_secure_loop():
161164
duplex_fail_counter += 1
162165
if duplex_fail_counter > DUPLEX_MAX_FAILURES:
163166
logging.debug("Remote: CheckDuplexConnection to %s (%s:%d) failed too many times"
164-
% (self.display_hostname, self.ip_info.ip4_address, self.port))
167+
% (self.display_hostname, self.ip_info, self.port))
165168
self.ping_timer.wait(CHANNEL_RETRY_WAIT_TIME)
166169
return True
167170
except grpc.RpcError as e:
168171
logging.debug("Remote: Ping failed, shutting down %s (%s:%d)"
169-
% (self.display_hostname, self.ip_info.ip4_address, self.port))
172+
% (self.display_hostname, self.ip_info, self.port))
170173
break
171174

172175
self.ping_timer.wait(CONNECTED_PING_TIME if self.status == RemoteStatus.ONLINE else DUPLEX_WAIT_PING_TIME)
@@ -185,7 +188,7 @@ def run_secure_loop():
185188
continue
186189
except Exception as e:
187190
logging.critical("!! Major problem starting connection loop for %s (%s:%d): %s"
188-
% (self.display_hostname, self.ip_info.ip4_address, self.port, e))
191+
% (self.display_hostname, self.ip_info, self.port, e))
189192

190193
self.set_remote_status(RemoteStatus.OFFLINE)
191194
self.run_thread_alive = False
@@ -195,7 +198,9 @@ def remote_thread_v2(self):
195198

196199
self.emit_machine_info_changed() # Let's make sure the button doesn't have junk in it if we fail to connect.
197200

198-
logging.debug("Remote: Attempting to connect to %s (%s) - api version 2" % (self.display_hostname, self.ip_info.ip4_address))
201+
remote_ip, _, ip_version = self.ip_info.get_usable_ip()
202+
logging.debug("Remote: Attempting to connect to %s (%s) - api version 2" % (self.display_hostname, remote_ip))
203+
remote_ip = remote_ip if ip_version == socket.AF_INET else "[%s]" % (remote_ip,)
199204

200205
self.set_remote_status(RemoteStatus.INIT_CONNECTING)
201206

@@ -212,7 +217,7 @@ def run_secure_loop():
212217
('grpc.http2.min_ping_interval_without_data_ms', 5000)
213218
)
214219

215-
with grpc.secure_channel("%s:%d" % (self.ip_info.ip4_address, self.port), creds, options=opts) as channel:
220+
with grpc.secure_channel("%s:%d" % (remote_ip, self.port), creds, options=opts) as channel:
216221

217222
def channel_state_changed(state):
218223
if state != grpc.ChannelConnectivity.READY:
@@ -335,7 +340,7 @@ def rpc_call(self, func, *args, **kargs):
335340
except Exception as e:
336341
# exception concurrent.futures.thread.BrokenThreadPool is not available in bionic/python3 < 3.7
337342
logging.critical("!! RPC threadpool failure while submitting call to %s (%s:%d): %s"
338-
% (self.display_hostname, self.ip_info.ip4_address, self.port, e))
343+
% (self.display_hostname, self.ip_info, self.port, e))
339344

340345
# Not added to thread pool
341346
def check_duplex_connection(self):

0 commit comments

Comments
 (0)