From eefda802d7516d4567c2731b532f623c05afb154 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:39:37 +0000 Subject: [PATCH 1/4] Initial plan From 9989d12086761d2bc7ea1c31742c61dee92fc500 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:46:54 +0000 Subject: [PATCH 2/4] fix: format HAProxy peer entries with name, address, and port Add HAPROXY_PEER_PORT constant (10000) and _format_peer_entries helper to render peer entries as '
:' instead of just '
'. Also add 'bind *:' to the peers section in all templates. This fixes HAProxy discarding peers due to invalid config. Agent-Logs-Url: https://github.com/canonical/haproxy-operator/sessions/71b67230-85a7-4437-ba07-80b49a93c036 Co-authored-by: Thanhphan1147 <42444001+Thanhphan1147@users.noreply.github.com> --- haproxy-operator/src/haproxy.py | 28 +++++++++++++- .../templates/haproxy_ingress.cfg.j2 | 5 ++- .../templates/haproxy_ingress_per_unit.cfg.j2 | 5 ++- .../templates/haproxy_route.cfg.j2 | 5 ++- .../tests/unit/test_haproxy_service.py | 37 ++++++++++++++++++- 5 files changed, 71 insertions(+), 9 deletions(-) diff --git a/haproxy-operator/src/haproxy.py b/haproxy-operator/src/haproxy.py index 8da4067af..c6b022d23 100644 --- a/haproxy-operator/src/haproxy.py +++ b/haproxy-operator/src/haproxy.py @@ -16,6 +16,7 @@ from charms.operator_libs_linux.v0 import apt from charms.operator_libs_linux.v1 import systemd from jinja2 import Environment, FileSystemLoader, select_autoescape +from pydantic import IPvAnyAddress from state.charm_state import CharmState from state.ddos_protection import DDosProtection @@ -25,6 +26,7 @@ from state.spoe_auth import SpoeAuthInformation APT_PACKAGE_NAME = "haproxy" +HAPROXY_PEER_PORT = 10000 HAPROXY_CONFIG_DIR = Path("/etc/haproxy") HAPROXY_CONFIG = Path(HAPROXY_CONFIG_DIR / "haproxy.cfg") SPOE_AUTH_CONFIG = Path(HAPROXY_CONFIG_DIR / "spoe_auth.conf") @@ -141,7 +143,8 @@ def reconcile_ingress( "config_external_hostname": external_hostname, "haproxy_crt_dir": HAPROXY_CERTS_DIR, "ddos_protection_config": ddos_protection_config, - "peer_units_address": ingress_requirers_information.peers, + "peer_units_address": _format_peer_entries(ingress_requirers_information.peers), + "peer_tcp_port": HAPROXY_PEER_PORT, "ip_allow_list_file": IP_ALLOW_LIST_FILE, "deny_paths_file": DENY_PATHS_FILE, } @@ -191,7 +194,8 @@ def reconcile_haproxy_route( if backend.application_data.external_grpc_port ], "stick_table_entries": haproxy_route_requirers_information.stick_table_entries, - "peer_units_address": haproxy_route_requirers_information.peers, + "peer_units_address": _format_peer_entries(haproxy_route_requirers_information.peers), + "peer_tcp_port": HAPROXY_PEER_PORT, "haproxy_crt_dir": HAPROXY_CERTS_DIR, "acls_for_allow_http": haproxy_route_requirers_information.acls_for_allow_http, "spoe_auth_info_list": spoe_oauth_info_list, @@ -283,6 +287,26 @@ def _validate_haproxy_config(self) -> None: raise HaproxyValidateConfigError("Failed validating the HAProxy config.") from exc +def _format_peer_entries(peers: list[IPvAnyAddress]) -> list[str]: + """Format peer IP addresses into HAProxy peer entry strings. + + Each entry is formatted as ``
:`` where ```` + is derived from the IP address with non-alphanumeric characters replaced + by hyphens. + + Args: + peers: List of peer IP addresses. + + Returns: + list[str]: Formatted peer entry strings. + """ + entries: list[str] = [] + for addr in peers: + name = str(addr).replace(".", "-").replace(":", "-") + entries.append(f"{name} {addr}:{HAPROXY_PEER_PORT}") + return entries + + def render_file(path: Path, content: str, mode: int) -> None: """Write a content rendered from a template to a file. diff --git a/haproxy-operator/templates/haproxy_ingress.cfg.j2 b/haproxy-operator/templates/haproxy_ingress.cfg.j2 index 635d153f0..62fe1c7d2 100644 --- a/haproxy-operator/templates/haproxy_ingress.cfg.j2 +++ b/haproxy-operator/templates/haproxy_ingress.cfg.j2 @@ -25,8 +25,9 @@ frontend ingress {% if ddos_protection_config.has_rate_limiting %} peers haproxy_peers -{% for address in peer_units_address %} - peer {{ address }} + bind *:{{ peer_tcp_port }} +{% for entry in peer_units_address %} + peer {{ entry }} {% endfor %} table ddos_protection_ip type ip size 100k expire 2m store http_req_rate(1m),conn_rate(1m),conn_cur {% endif %} diff --git a/haproxy-operator/templates/haproxy_ingress_per_unit.cfg.j2 b/haproxy-operator/templates/haproxy_ingress_per_unit.cfg.j2 index 120f0a923..1e155d161 100644 --- a/haproxy-operator/templates/haproxy_ingress_per_unit.cfg.j2 +++ b/haproxy-operator/templates/haproxy_ingress_per_unit.cfg.j2 @@ -26,8 +26,9 @@ frontend ingress_per_unit {% if ddos_protection_config.has_rate_limiting %} peers haproxy_peers -{% for address in peer_units_address %} - peer {{ address }} + bind *:{{ peer_tcp_port }} +{% for entry in peer_units_address %} + peer {{ entry }} {% endfor %} table ddos_protection_ip type ip size 100k expire 2m store http_req_rate(1m),conn_rate(1m),http_err_rate(1m),conn_cur {% endif %} diff --git a/haproxy-operator/templates/haproxy_route.cfg.j2 b/haproxy-operator/templates/haproxy_route.cfg.j2 index b708d5fbc..0a0bc79e3 100644 --- a/haproxy-operator/templates/haproxy_route.cfg.j2 +++ b/haproxy-operator/templates/haproxy_route.cfg.j2 @@ -48,8 +48,9 @@ frontend haproxy default_backend default peers haproxy_peers -{% for address in peer_units_address %} - peer {{ address }} + bind *:{{ peer_tcp_port }} +{% for entry in peer_units_address %} + peer {{ entry }} {% endfor %} {% for entry in stick_table_entries %} table {{ entry }} type ip size 100k expire 2m store http_req_rate(1m) diff --git a/haproxy-operator/tests/unit/test_haproxy_service.py b/haproxy-operator/tests/unit/test_haproxy_service.py index 930a1412f..c61306f17 100644 --- a/haproxy-operator/tests/unit/test_haproxy_service.py +++ b/haproxy-operator/tests/unit/test_haproxy_service.py @@ -3,11 +3,19 @@ """Unit tests for charm file.""" +from typing import cast from unittest.mock import MagicMock import pytest +from pydantic import IPvAnyAddress -from haproxy import HAPROXY_DH_PARAM, HAPROXY_DHCONFIG, HAProxyService +from haproxy import ( + HAPROXY_DH_PARAM, + HAPROXY_DHCONFIG, + HAPROXY_PEER_PORT, + HAProxyService, + _format_peer_entries, +) @pytest.mark.usefixtures("systemd_mock") @@ -28,3 +36,30 @@ def test_deploy(monkeypatch: pytest.MonkeyPatch): apt_add_package_mock.assert_called_once() render_file_mock.assert_called_once_with(HAPROXY_DHCONFIG, HAPROXY_DH_PARAM, 0o644) + + +def test_format_peer_entries_ipv4(): + """ + arrange: A list of IPv4 peer addresses. + act: Call _format_peer_entries. + assert: Each entry has the format '
:'. + """ + peers = [cast(IPvAnyAddress, "10.68.79.144"), cast(IPvAnyAddress, "192.168.1.10")] + + result = _format_peer_entries(peers) + + assert result == [ + f"10-68-79-144 10.68.79.144:{HAPROXY_PEER_PORT}", + f"192-168-1-10 192.168.1.10:{HAPROXY_PEER_PORT}", + ] + + +def test_format_peer_entries_empty(): + """ + arrange: An empty list of peer addresses. + act: Call _format_peer_entries. + assert: Returns an empty list. + """ + result = _format_peer_entries([]) + + assert result == [] From 1b4920cd2d5166a08fda2a322d470a501d671678 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:48:50 +0000 Subject: [PATCH 3/4] test: add IPv6 test case for _format_peer_entries and use proper IP objects Agent-Logs-Url: https://github.com/canonical/haproxy-operator/sessions/71b67230-85a7-4437-ba07-80b49a93c036 Co-authored-by: Thanhphan1147 <42444001+Thanhphan1147@users.noreply.github.com> --- .../tests/unit/test_haproxy_service.py | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/haproxy-operator/tests/unit/test_haproxy_service.py b/haproxy-operator/tests/unit/test_haproxy_service.py index c61306f17..500087963 100644 --- a/haproxy-operator/tests/unit/test_haproxy_service.py +++ b/haproxy-operator/tests/unit/test_haproxy_service.py @@ -3,6 +3,7 @@ """Unit tests for charm file.""" +from ipaddress import IPv4Address, IPv6Address from typing import cast from unittest.mock import MagicMock @@ -44,7 +45,10 @@ def test_format_peer_entries_ipv4(): act: Call _format_peer_entries. assert: Each entry has the format '
:'. """ - peers = [cast(IPvAnyAddress, "10.68.79.144"), cast(IPvAnyAddress, "192.168.1.10")] + peers: list[IPvAnyAddress] = [ + cast(IPvAnyAddress, IPv4Address("10.68.79.144")), + cast(IPvAnyAddress, IPv4Address("192.168.1.10")), + ] result = _format_peer_entries(peers) @@ -54,6 +58,23 @@ def test_format_peer_entries_ipv4(): ] +def test_format_peer_entries_ipv6(): + """ + arrange: A list of IPv6 peer addresses. + act: Call _format_peer_entries. + assert: Each entry has colons replaced by hyphens in the name. + """ + peers: list[IPvAnyAddress] = [ + cast(IPvAnyAddress, IPv6Address("fd42:bc01:a5e3:f4e2::1")), + ] + + result = _format_peer_entries(peers) + + assert result == [ + f"fd42-bc01-a5e3-f4e2--1 fd42:bc01:a5e3:f4e2::1:{HAPROXY_PEER_PORT}", + ] + + def test_format_peer_entries_empty(): """ arrange: An empty list of peer addresses. From d1ac83e482eb5ab42ca479ce32d9fc06398872cd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 11:26:34 +0000 Subject: [PATCH 4/4] refactor: move peer formatting into PeersInformation mixin on state dataclasses Move HAPROXY_PEER_PORT and peer formatting logic from src/haproxy.py into a PeersInformation mixin class in state/peers.py. All three state dataclasses (IngressRequirersInformation, IngressPerUnitRequirersInformation, HaproxyRouteRequirersInformation) now subclass PeersInformation and expose formatted_peer_entries and peer_tcp_port as @property methods. Agent-Logs-Url: https://github.com/canonical/haproxy-operator/sessions/29068634-9a26-492a-a765-4ae185a92429 Co-authored-by: Thanhphan1147 <42444001+Thanhphan1147@users.noreply.github.com> --- haproxy-operator/src/haproxy.py | 30 ++------ haproxy-operator/src/state/haproxy_route.py | 3 +- haproxy-operator/src/state/ingress.py | 3 +- .../src/state/ingress_per_unit.py | 3 +- haproxy-operator/src/state/peers.py | 47 ++++++++++++ .../tests/unit/test_haproxy_service.py | 58 +-------------- .../tests/unit/test_peers_information.py | 72 +++++++++++++++++++ 7 files changed, 130 insertions(+), 86 deletions(-) create mode 100644 haproxy-operator/src/state/peers.py create mode 100644 haproxy-operator/tests/unit/test_peers_information.py diff --git a/haproxy-operator/src/haproxy.py b/haproxy-operator/src/haproxy.py index c6b022d23..55beb1b2f 100644 --- a/haproxy-operator/src/haproxy.py +++ b/haproxy-operator/src/haproxy.py @@ -16,7 +16,6 @@ from charms.operator_libs_linux.v0 import apt from charms.operator_libs_linux.v1 import systemd from jinja2 import Environment, FileSystemLoader, select_autoescape -from pydantic import IPvAnyAddress from state.charm_state import CharmState from state.ddos_protection import DDosProtection @@ -26,7 +25,6 @@ from state.spoe_auth import SpoeAuthInformation APT_PACKAGE_NAME = "haproxy" -HAPROXY_PEER_PORT = 10000 HAPROXY_CONFIG_DIR = Path("/etc/haproxy") HAPROXY_CONFIG = Path(HAPROXY_CONFIG_DIR / "haproxy.cfg") SPOE_AUTH_CONFIG = Path(HAPROXY_CONFIG_DIR / "spoe_auth.conf") @@ -143,8 +141,8 @@ def reconcile_ingress( "config_external_hostname": external_hostname, "haproxy_crt_dir": HAPROXY_CERTS_DIR, "ddos_protection_config": ddos_protection_config, - "peer_units_address": _format_peer_entries(ingress_requirers_information.peers), - "peer_tcp_port": HAPROXY_PEER_PORT, + "peer_units_address": ingress_requirers_information.formatted_peer_entries, + "peer_tcp_port": ingress_requirers_information.peer_tcp_port, "ip_allow_list_file": IP_ALLOW_LIST_FILE, "deny_paths_file": DENY_PATHS_FILE, } @@ -194,8 +192,8 @@ def reconcile_haproxy_route( if backend.application_data.external_grpc_port ], "stick_table_entries": haproxy_route_requirers_information.stick_table_entries, - "peer_units_address": _format_peer_entries(haproxy_route_requirers_information.peers), - "peer_tcp_port": HAPROXY_PEER_PORT, + "peer_units_address": haproxy_route_requirers_information.formatted_peer_entries, + "peer_tcp_port": haproxy_route_requirers_information.peer_tcp_port, "haproxy_crt_dir": HAPROXY_CERTS_DIR, "acls_for_allow_http": haproxy_route_requirers_information.acls_for_allow_http, "spoe_auth_info_list": spoe_oauth_info_list, @@ -287,26 +285,6 @@ def _validate_haproxy_config(self) -> None: raise HaproxyValidateConfigError("Failed validating the HAProxy config.") from exc -def _format_peer_entries(peers: list[IPvAnyAddress]) -> list[str]: - """Format peer IP addresses into HAProxy peer entry strings. - - Each entry is formatted as ``
:`` where ```` - is derived from the IP address with non-alphanumeric characters replaced - by hyphens. - - Args: - peers: List of peer IP addresses. - - Returns: - list[str]: Formatted peer entry strings. - """ - entries: list[str] = [] - for addr in peers: - name = str(addr).replace(".", "-").replace(":", "-") - entries.append(f"{name} {addr}:{HAPROXY_PEER_PORT}") - return entries - - def render_file(path: Path, content: str, mode: int) -> None: """Write a content rendered from a template to a file. diff --git a/haproxy-operator/src/state/haproxy_route.py b/haproxy-operator/src/state/haproxy_route.py index 964f842d6..68afc39be 100644 --- a/haproxy-operator/src/state/haproxy_route.py +++ b/haproxy-operator/src/state/haproxy_route.py @@ -33,6 +33,7 @@ HAProxyRouteTcpFrontend, HAProxyRouteTcpFrontendValidationError, ) +from .peers import PeersInformation HAPROXY_ROUTE_RELATION = "haproxy-route" HAPROXY_PEER_INTEGRATION = "haproxy-peers" @@ -272,7 +273,7 @@ def enable_http_check(self) -> bool: # pylint: disable=too-many-locals @dataclass(frozen=True) -class HaproxyRouteRequirersInformation: +class HaproxyRouteRequirersInformation(PeersInformation): """A component of charm state containing haproxy-route requirers information. Attrs: diff --git a/haproxy-operator/src/state/ingress.py b/haproxy-operator/src/state/ingress.py index 4592cdc75..baed52cf1 100644 --- a/haproxy-operator/src/state/ingress.py +++ b/haproxy-operator/src/state/ingress.py @@ -10,6 +10,7 @@ from pydantic import IPvAnyAddress from .exception import CharmStateValidationBaseError +from .peers import PeersInformation INGRESS_RELATION = "ingress" @@ -49,7 +50,7 @@ class HAProxyBackend: @dataclasses.dataclass(frozen=True) -class IngressRequirersInformation: +class IngressRequirersInformation(PeersInformation): """A component of charm state containing ingress requirers information. Attrs: diff --git a/haproxy-operator/src/state/ingress_per_unit.py b/haproxy-operator/src/state/ingress_per_unit.py index ad71f99f9..551438946 100644 --- a/haproxy-operator/src/state/ingress_per_unit.py +++ b/haproxy-operator/src/state/ingress_per_unit.py @@ -14,6 +14,7 @@ from pydantic.dataclasses import dataclass from .exception import CharmStateValidationBaseError +from .peers import PeersInformation INGRESS_PER_UNIT_RELATION = "ingress_per_unit" @@ -42,7 +43,7 @@ class HAProxyBackend: @dataclass(frozen=True) -class IngressPerUnitRequirersInformation: +class IngressPerUnitRequirersInformation(PeersInformation): """A component of charm state containing ingress per unit requirers information. Attrs: diff --git a/haproxy-operator/src/state/peers.py b/haproxy-operator/src/state/peers.py new file mode 100644 index 000000000..4026a8c21 --- /dev/null +++ b/haproxy-operator/src/state/peers.py @@ -0,0 +1,47 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +"""HAProxy peers information mixin for charm state components.""" + +from pydantic import IPvAnyAddress + +HAPROXY_PEER_PORT = 10000 + + +class PeersInformation: + """Mixin providing HAProxy peer formatting properties. + + Subclasses must define a ``peers: list[IPvAnyAddress]`` field. + + Attrs: + formatted_peer_entries: Pre-rendered peer entry strings for the HAProxy config. + peer_tcp_port: The TCP port used for HAProxy peer communication. + """ + + peers: list[IPvAnyAddress] + + @property + def formatted_peer_entries(self) -> list[str]: + """Format peer IP addresses into HAProxy peer entry strings. + + Each entry is formatted as ``
:`` where ```` + is derived from the IP address with non-alphanumeric characters replaced + by hyphens. + + Returns: + list[str]: Formatted peer entry strings. + """ + entries: list[str] = [] + for addr in self.peers: + name = str(addr).replace(".", "-").replace(":", "-") + entries.append(f"{name} {addr}:{HAPROXY_PEER_PORT}") + return entries + + @property + def peer_tcp_port(self) -> int: + """Return the TCP port used for HAProxy peer communication. + + Returns: + int: The peer TCP port. + """ + return HAPROXY_PEER_PORT diff --git a/haproxy-operator/tests/unit/test_haproxy_service.py b/haproxy-operator/tests/unit/test_haproxy_service.py index 500087963..930a1412f 100644 --- a/haproxy-operator/tests/unit/test_haproxy_service.py +++ b/haproxy-operator/tests/unit/test_haproxy_service.py @@ -3,20 +3,11 @@ """Unit tests for charm file.""" -from ipaddress import IPv4Address, IPv6Address -from typing import cast from unittest.mock import MagicMock import pytest -from pydantic import IPvAnyAddress -from haproxy import ( - HAPROXY_DH_PARAM, - HAPROXY_DHCONFIG, - HAPROXY_PEER_PORT, - HAProxyService, - _format_peer_entries, -) +from haproxy import HAPROXY_DH_PARAM, HAPROXY_DHCONFIG, HAProxyService @pytest.mark.usefixtures("systemd_mock") @@ -37,50 +28,3 @@ def test_deploy(monkeypatch: pytest.MonkeyPatch): apt_add_package_mock.assert_called_once() render_file_mock.assert_called_once_with(HAPROXY_DHCONFIG, HAPROXY_DH_PARAM, 0o644) - - -def test_format_peer_entries_ipv4(): - """ - arrange: A list of IPv4 peer addresses. - act: Call _format_peer_entries. - assert: Each entry has the format '
:'. - """ - peers: list[IPvAnyAddress] = [ - cast(IPvAnyAddress, IPv4Address("10.68.79.144")), - cast(IPvAnyAddress, IPv4Address("192.168.1.10")), - ] - - result = _format_peer_entries(peers) - - assert result == [ - f"10-68-79-144 10.68.79.144:{HAPROXY_PEER_PORT}", - f"192-168-1-10 192.168.1.10:{HAPROXY_PEER_PORT}", - ] - - -def test_format_peer_entries_ipv6(): - """ - arrange: A list of IPv6 peer addresses. - act: Call _format_peer_entries. - assert: Each entry has colons replaced by hyphens in the name. - """ - peers: list[IPvAnyAddress] = [ - cast(IPvAnyAddress, IPv6Address("fd42:bc01:a5e3:f4e2::1")), - ] - - result = _format_peer_entries(peers) - - assert result == [ - f"fd42-bc01-a5e3-f4e2--1 fd42:bc01:a5e3:f4e2::1:{HAPROXY_PEER_PORT}", - ] - - -def test_format_peer_entries_empty(): - """ - arrange: An empty list of peer addresses. - act: Call _format_peer_entries. - assert: Returns an empty list. - """ - result = _format_peer_entries([]) - - assert result == [] diff --git a/haproxy-operator/tests/unit/test_peers_information.py b/haproxy-operator/tests/unit/test_peers_information.py new file mode 100644 index 000000000..a4d907836 --- /dev/null +++ b/haproxy-operator/tests/unit/test_peers_information.py @@ -0,0 +1,72 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +"""Unit tests for the PeersInformation mixin.""" + +from ipaddress import IPv4Address, IPv6Address +from typing import cast + +from pydantic import IPvAnyAddress + +from state.peers import HAPROXY_PEER_PORT, PeersInformation + + +class _PeersInfoStub(PeersInformation): + """Minimal concrete class for testing the mixin.""" + + def __init__(self, peers: list[IPvAnyAddress]): + self.peers = peers + + +def test_formatted_peer_entries_ipv4(): + """ + arrange: A PeersInformation instance with IPv4 peer addresses. + act: Access the formatted_peer_entries property. + assert: Each entry has the format '
:'. + """ + info = _PeersInfoStub( + [ + cast(IPvAnyAddress, IPv4Address("10.68.79.144")), + cast(IPvAnyAddress, IPv4Address("192.168.1.10")), + ] + ) + + assert info.formatted_peer_entries == [ + f"10-68-79-144 10.68.79.144:{HAPROXY_PEER_PORT}", + f"192-168-1-10 192.168.1.10:{HAPROXY_PEER_PORT}", + ] + + +def test_formatted_peer_entries_ipv6(): + """ + arrange: A PeersInformation instance with an IPv6 peer address. + act: Access the formatted_peer_entries property. + assert: Colons are replaced by hyphens in the peer name. + """ + info = _PeersInfoStub([cast(IPvAnyAddress, IPv6Address("fd42:bc01:a5e3:f4e2::1"))]) + + assert info.formatted_peer_entries == [ + f"fd42-bc01-a5e3-f4e2--1 fd42:bc01:a5e3:f4e2::1:{HAPROXY_PEER_PORT}", + ] + + +def test_formatted_peer_entries_empty(): + """ + arrange: A PeersInformation instance with no peers. + act: Access the formatted_peer_entries property. + assert: Returns an empty list. + """ + info = _PeersInfoStub([]) + + assert info.formatted_peer_entries == [] + + +def test_peer_tcp_port(): + """ + arrange: A PeersInformation instance. + act: Access the peer_tcp_port property. + assert: Returns the constant HAPROXY_PEER_PORT. + """ + info = _PeersInfoStub([]) + + assert info.peer_tcp_port == HAPROXY_PEER_PORT