Skip to content

Commit dbb7885

Browse files
authored
[feature] Allow estimating geographic location based on WHOIS data #1034
- Estimated geographic location logic, closes #1034. - Added notification and admin warning, closes #1035. - Set location estimated flag to ``False`` on manual update, closes #1036. - Added admin and API filters for estimated locations, closes #1028. --------- Signed-off-by: DragnEmperor <[email protected]>
1 parent 8e259c4 commit dbb7885

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2064
-197
lines changed

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ the OpenWISP architecture.
4949
user/openvpn.rst
5050
user/subnet-division-rules.rst
5151
user/whois.rst
52+
user/estimated-location.rst
5253
user/rest-api.rst
5354
user/settings.rst
5455

docs/user/estimated-location.rst

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
Estimated Location
2+
==================
3+
4+
.. important::
5+
6+
The **Estimated Location** feature is **disabled by default**.
7+
8+
Before enabling it, the :doc:`WHOIS Lookup feature <whois>` must be
9+
enabled. Then set
10+
:ref:`OPENWISP_CONTROLLER_ESTIMATED_LOCATION_ENABLED` to ``True``
11+
12+
.. contents:: **Table of contents**:
13+
:depth: 1
14+
:local:
15+
16+
Overview
17+
--------
18+
19+
The Estimated Location feature automatically creates or updates a device’s
20+
location based on latitude and longitude information retrieved from the
21+
WHOIS Lookup feature.
22+
23+
Trigger Conditions
24+
------------------
25+
26+
Estimated Location is triggered when:
27+
28+
- A **fresh WHOIS lookup** is performed for a device.
29+
- Or when a WHOIS record already exists for the device’s IP **and**:
30+
31+
- The device’s last IP address is **public**.
32+
- WHOIS lookup and Estimated Location is **enabled** for the device’s
33+
organization.
34+
35+
Behavior
36+
--------
37+
38+
The system will **attach the already existing matching location** of
39+
another device with same ip to the current device if:
40+
41+
- Only one device is found with that IP and it has a location.
42+
- The current device **has no location** or that location is
43+
**estimated**.
44+
45+
If there are multiple devices with location for the same IP, the system
46+
will **not attach any location** to the current device and a notification
47+
will be sent suggesting the user to manually assign/create a location for
48+
the device.
49+
50+
If there is **no matching location**, a new estimated location is created
51+
or the existing one is updated using coordinates from the WHOIS record,
52+
but only if the existing location is estimated.
53+
54+
If two devices share the same IP address and are assigned to the same
55+
location, and the last IP of one of the devices is updated, the system
56+
will create a new estimated location for that device.
57+
58+
Visibility of Estimated Status
59+
------------------------------
60+
61+
The estimated status of a location is visible on the location page if the
62+
feature is enabled for the organization. The location admin page also
63+
includes indicators for the estimated status.
64+
65+
- The name of the location will have suffix **(Estimated Location :
66+
<ip_address>)**.
67+
- A warning on top of the page.
68+
- **Is Estimated** field.
69+
70+
Changes to the ``coordinates`` and ``geometry`` of the estimated location
71+
will set the ``is_estimated`` field to ``False`` and remove the
72+
"(Estimated Location)" suffix with IP from the location name.
73+
74+
In REST API, the field will be visible in the :ref:`Device Location
75+
<device_location_estimated>`, :ref:`Location list
76+
<location_list_estimated>`, :ref:`Location Detail
77+
<location_detail_estimated>` and :ref:`Location list (GeoJson)
78+
<location_geojson_estimated>` if the feature is **enabled**. The field can
79+
also be used for filtering in the location list (including geojson)
80+
endpoints and in the :ref:`Device List <device_list_estimated_filters>`.

docs/user/rest-api.rst

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ If :doc:`WHOIS Lookup feature <whois>` is enabled, each device in the list
7676
response will also include a ``whois_info`` field with related brief WHOIS
7777
information.
7878

79+
.. _device_list_estimated_filters:
80+
81+
**Estimated Location Filters**
82+
83+
if :doc:`Estimated Location feature <estimated-location>` is enabled,
84+
devices can be filtered based on the estimated nature of their location
85+
using the ``geo_is_estimated``.
86+
7987
**Available filters**
8088

8189
You can filter a list of devices based on their configuration status using
@@ -544,13 +552,26 @@ of certificate's organization as show in the example below:
544552
545553
GET /api/v1/controller/cert/{common_name}/group/?org={org1_slug},{org2_slug}
546554
555+
.. |est_loc| replace:: Estimated Location feature
556+
557+
.. _est_loc: estimated-location.html
558+
559+
.. |estimated_details| replace:: If |est_loc|_ is enabled, the location
560+
response will also include ``is_estimated`` status field.
561+
547562
Get Device Location
548563
~~~~~~~~~~~~~~~~~~~
549564

550565
.. code-block:: text
551566
552567
GET /api/v1/controller/device/{id}/location/
553568
569+
.. _device_location_estimated:
570+
571+
**Estimated Status**
572+
573+
|estimated_details|
574+
554575
.. _create_device_location:
555576

556577
Create Device Location
@@ -787,6 +808,14 @@ List Locations
787808
788809
GET /api/v1/controller/location/
789810
811+
.. _location_list_estimated:
812+
813+
**Estimated Status**
814+
815+
|estimated_details|
816+
817+
Locations can also be filtered using the ``is_estimated``.
818+
790819
**Available filters**
791820

792821
You can filter using ``organization_id`` or ``organization_slug`` to get
@@ -868,6 +897,12 @@ Get Location Details
868897
869898
GET /api/v1/controller/location/{pk}/
870899
900+
.. _location_detail_estimated:
901+
902+
**Estimated Status**
903+
904+
|estimated_details|
905+
871906
Change Location Details
872907
~~~~~~~~~~~~~~~~~~~~~~~
873908

@@ -910,6 +945,14 @@ List Locations with Devices Deployed (in GeoJSON Format)
910945
911946
GET /api/v1/controller/location/geojson/
912947
948+
.. _location_geojson_estimated:
949+
950+
**Estimated Status**
951+
952+
|estimated_details|
953+
954+
Locations can also be filtered using the ``is_estimated``.
955+
913956
**Available filters**
914957

915958
You can filter using ``organization_id`` or ``organization_slug`` to get

docs/user/settings.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,3 +795,20 @@ Maxmind Account ID required for the :doc:`WHOIS Lookup feature <whois>`.
795795
============ =======
796796

797797
Maxmind License Key required for the :doc:`WHOIS Lookup feature <whois>`.
798+
799+
.. _openwisp_controller_whois_estimated_location_enabled:
800+
801+
``OPENWISP_CONTROLLER_WHOIS_ESTIMATED_LOCATION_ENABLED``
802+
--------------------------------------------------------
803+
804+
============ =========
805+
**type**: ``bool``
806+
**default**: ``False``
807+
============ =========
808+
809+
Allows enabling the optional :doc:`Estimated Location feature
810+
<estimated-location>`.
811+
812+
.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.3/estimated-location-setting.png
813+
:target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.3/estimated-location-setting.png
814+
:alt: Estimated Location setting

docs/user/whois.rst

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ associated with the device's public IP address and includes:
2828
- CIDR block assigned to the ASN
2929
- Physical address registered to the ASN
3030
- Timezone of the ASN's registered location
31+
- Coordinates (Latitude and Longitude)
3132

3233
Trigger Conditions
3334
------------------
@@ -40,25 +41,23 @@ A WHOIS lookup is triggered automatically when:
4041
However, the lookup will only run if **all** the following conditions are
4142
met:
4243

44+
- The device is either **newly created** or has a **changed last IP**.
4345
- The device's last IP address is **public**.
4446
- There is **no existing WHOIS record** for that IP.
4547
- WHOIS lookup is **enabled** for the device's organization.
4648

47-
Behavior with Shared IP Addresses
48-
---------------------------------
49+
Managing WHOIS Records
50+
----------------------
4951

50-
If multiple devices share the same public IP address and one of them
51-
switches to a different IP, the following occurs:
52-
53-
- A lookup is triggered for the **new IP**.
54-
- The WHOIS record for the **old IP** is deleted.
55-
- The next time a device still using the old IP fetches its checksum, a
56-
new lookup is triggered, ensuring up-to-date data.
52+
If a device updates its last IP address, lookup is triggered for the **new
53+
IP** and the **WHOIS record for the old IP** is deleted if no active
54+
devices are associated with that IP address.
5755

5856
.. note::
5957

6058
When a device with an associated WHOIS record is deleted, its WHOIS
61-
record is automatically removed.
59+
record is automatically removed only if no active devices are
60+
associated with that IP address.
6261

6362
.. _controller_setup_whois_lookup:
6463

@@ -79,6 +78,26 @@ Setup Instructions
7978
- Set :ref:`OPENWISP_CONTROLLER_WHOIS_GEOIP_ACCOUNT` to **Account ID**.
8079
- Set :ref:`OPENWISP_CONTROLLER_WHOIS_GEOIP_KEY` to **License Key**.
8180

81+
6. Restart the application/containers if using ansible-openwisp2 or
82+
docker.
83+
7. Run the ``clear_last_ip`` management command to clear the last IP
84+
address of **all active devices across organizations**.
85+
86+
- If using ansible-openwisp2 (default directory is /opt/openwisp2,
87+
unless changed in Ansible playbook configuration):
88+
89+
.. code-block:: bash
90+
91+
source /opt/openwisp2/env/bin/activate
92+
python /opt/openwisp2/src/manage.py clear_last_ip
93+
94+
- If using docker:
95+
96+
.. code-block:: bash
97+
98+
docker exec -it <openwisp_container_name> sh
99+
python manage.py clear_last_ip
100+
82101
Viewing WHOIS Lookup Data
83102
-------------------------
84103

openwisp_controller/config/admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1384,7 +1384,7 @@ def get_fields(self, request, obj=None):
13841384
if app_settings.REGISTRATION_ENABLED:
13851385
fields += ["registration_enabled", "shared_secret"]
13861386
if app_settings.WHOIS_CONFIGURED:
1387-
fields += ["whois_enabled"]
1387+
fields += ["whois_enabled", "estimated_location_enabled"]
13881388
fields += ["context"]
13891389
return fields
13901390

openwisp_controller/config/base/device.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ def save(self, *args, **kwargs):
287287
state_adding = self._state.adding
288288
super().save(*args, **kwargs)
289289
if app_settings.WHOIS_CONFIGURED:
290-
self._check_last_ip()
290+
self._check_last_ip(creating=state_adding)
291291
if state_adding and self.group and self.group.templates.exists():
292292
self.create_default_config()
293293
# The value of "self._state.adding" will always be "False"
@@ -510,9 +510,13 @@ def whois_service(self):
510510
"""
511511
return WHOISService(self)
512512

513-
def _check_last_ip(self):
514-
"""Trigger WHOIS lookup if last_ip is not deferred."""
513+
def _check_last_ip(self, creating=False):
514+
"""
515+
Process details and location related to last_ip if last_ip has
516+
changed or is being set for the first time.
517+
"""
515518
if self._initial_last_ip == models.DEFERRED:
516519
return
517-
self.whois_service.trigger_whois_lookup()
520+
if creating or self.last_ip != self._initial_last_ip:
521+
self.whois_service.process_ip_data_and_location()
518522
self._initial_last_ip = self.last_ip

openwisp_controller/config/base/multitenancy.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ class AbstractOrganizationConfigSettings(UUIDModel):
3939
fallback=app_settings.WHOIS_ENABLED,
4040
verbose_name=_("WHOIS Enabled"),
4141
)
42+
estimated_location_enabled = FallbackBooleanChoiceField(
43+
help_text=_("Whether the estimated location feature is enabled"),
44+
fallback=app_settings.ESTIMATED_LOCATION_ENABLED,
45+
verbose_name=_("Estimated Location Enabled"),
46+
)
4247
context = JSONField(
4348
blank=True,
4449
default=dict,
@@ -71,6 +76,15 @@ def clean(self):
7176
)
7277
}
7378
)
79+
if not self.whois_enabled and self.estimated_location_enabled:
80+
raise ValidationError(
81+
{
82+
"estimated_location_enabled": _(
83+
"Estimated Location feature requires "
84+
"WHOIS Lookup feature to be enabled."
85+
)
86+
}
87+
)
7488
return super().clean()
7589

7690
def save(

0 commit comments

Comments
 (0)