Skip to content

Web Panel can assign already-used IPs to new AWG clients #79

Description

@truevapor

Bug: Web Panel reassigns IP addresses already in use by natively-created AmneziaWG clients.

Environment

  • Panel image: prvtpro/amnezia-panel:1.5.0
  • Protocol: AWG2 (AmneziaWG)
  • Server was originally configured through the official app AmneziaVPN, Web Panel was installed afterward on top of the existing deployment

Summary

All clients were created directly through the native AmneziaVPN app before the panel was installed, so they never got an entry in the panel's own client tracking. After installing the panel and creating new clients through it, the panel repeatedly assigned IP addresses that were already in use by these pre-existing peers. This caused the existing peer to lose its AllowedIPs on the live WireGuard interface, effectively disconnecting a real, actively-used device without warning.

This happened multiple times across different new-client-creation attempts, colliding with a different pre-existing client each time.

Root cause

In managers/awg_manager.py, _get_next_ip takes the last IP found by file line order and simply increments the last octet by 1 — it doesn't check whether the resulting IP is already assigned to another peer.

Problematic code
def _get_next_ip(self, protocol_type):
    """Calculate the next available IP for a new client."""
    used_ips = self._get_used_ips(protocol_type)
    if not used_ips:
        base = self._get_subnet_base(protocol_type)
        parts = base.split('.')
        parts[3] = '2'
        return '.'.join(parts)
    # Get the last used IP and increment
    last_ip = used_ips[-1]
    parts = last_ip.split('.')
    last_octet = int(parts[3])
    if last_octet == 254:
        next_octet = last_octet + 3
    elif last_octet == 255:
        next_octet = last_octet + 2
    else:
        next_octet = last_octet + 1
    parts[3] = str(next_octet)
    return '.'.join(parts)

If the last [Peer] block in the config file (by line order — which changes any time a peer is added/removed) has a lower octet than peers listed earlier, this returns an IP that's already taken.

Workaround I used

Patched the function to scan all occupied octets in the subnet and pick the lowest genuinely free one, instead of trusting file-line order. After this change, new clients get valid, genuinely free IPs with no further collisions.

Fix
def _get_next_ip(self, protocol_type):
    """Calculate the next available IP for a new client."""
    used_ips = self._get_used_ips(protocol_type)
    base = self._get_subnet_base(protocol_type)
    base_parts = base.split('.')
    if not used_ips:
        base_parts[3] = '2'
        return '.'.join(base_parts)
    used_octets = set()
    for ip in used_ips:
        parts = ip.split('.')
        if parts[0:3] == base_parts[0:3]:
            used_octets.add(int(parts[3]))
    next_octet = 2
    while next_octet in used_octets and next_octet < 254:
        next_octet += 1
    base_parts[3] = str(next_octet)
    return '.'.join(base_parts)

Ran into this issue and tried to fix it — not sure if it's the right approach, but wanted to share in case it helps. If the maintainer gets a chance to weigh in on whether this is the correct way to handle it, that could help others hitting the same problem.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions