Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve port mapping warnings/errors #2020

Merged
merged 6 commits into from
Sep 2, 2024
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,22 @@ import {
PortProtocol
} from "@dappnode/types";

const maxPortNumber = 32768 - 1;
const maxEphemeralPortNumber = 65535;
const maxRegisteredPortNumber = 32768 - 1;
const maxPortNumber = 65535;
// Wireguard port is inside ephemeral range
const wireguardPort = 51820;

interface PortStatusSummary {
container: {
inEphemeralRange: PortMapping[];
overTheMax: PortMapping[];
};

host: {
inEphemeralRange: PortMapping[];
overTheMax: PortMapping[];
};
}

export function PortsByService({
dnpName,
Expand Down Expand Up @@ -120,59 +134,109 @@ export function PortsByService({
return conflictingPorts;
}

function getPortsOverTheMax(): PortMapping[] {
return ports.filter(
({ host, container, deletable }) =>
(deletable &&
(container > maxPortNumber || (host && host > maxPortNumber))) ||
(host && host > maxEphemeralPortNumber)
function getPortStatusSummary(): PortStatusSummary {

const isInEphemeralRange = (port: number) =>
port > maxRegisteredPortNumber && port <= maxPortNumber && port !== wireguardPort;

const isOverTheMax = (port: number) => port > maxPortNumber;

return ports.reduce<PortStatusSummary>(
(acc, portMapping) => {
const { container, host, deletable } = portMapping;

if (!deletable) return acc;

if (isOverTheMax(container)) {
acc.container.overTheMax.push(portMapping);

} else if (isInEphemeralRange(container)) {
acc.container.inEphemeralRange.push(portMapping);
}

if (host === undefined) return acc;

if (isOverTheMax(host)) {
acc.host.overTheMax.push(portMapping);

} else if (isInEphemeralRange(host)) {
acc.host.inEphemeralRange.push(portMapping);
}

return acc;
},
{
container: {
inEphemeralRange: [],
overTheMax: []
},
host: {
inEphemeralRange: [],
overTheMax: []
}
}
);
}

const areNewMappingsInvalid = ports.some(
({ container, protocol, deletable }) =>
deletable && (!container || !protocol)
);

const duplicatedContainerPorts = getDuplicatedContainerPorts();
const duplicatedHostPorts = getDuplicatedHostPorts();
const conflictingPorts = getConflictingPorts();
const portsOverTheMax = getPortsOverTheMax();
const portStatusSummary = getPortStatusSummary();
const arePortsTheSame = portsToId(portsFromDnp) === portsToId(ports);

// Aggregate error messages as an array of strings
// Aggregate error & warning messages as an array of strings
const errors: string[] = [];
const warnings: string[] = [];
for (const duplicatedHostPort of duplicatedHostPorts)
errors.push(
`Duplicated mapping for host port ${duplicatedHostPort.host}/${duplicatedHostPort.protocol}. Each host port can only be mapped once.`
`Duplicated mapping for host port ${duplicatedHostPort.host}/${duplicatedHostPort.protocol}. Each host port can only be mapped once.`
);

for (const duplicatedContainerPort of duplicatedContainerPorts)
errors.push(
`Duplicated mapping for package port ${duplicatedContainerPort.container}/${duplicatedContainerPort.protocol}. Each package port can only be mapped once.`
`Duplicated mapping for package port ${duplicatedContainerPort.container}/${duplicatedContainerPort.protocol}. Each package port can only be mapped once.`
);

for (const conflictingPort of conflictingPorts) {
const portName = `${conflictingPort.host}/${conflictingPort.protocol}`;
const ownerName = prettyDnpName(conflictingPort.owner);
errors.push(
`Port ${portName} is already mapped by the DAppNode Package ${ownerName}`
`Port ${portName} is already mapped by the DAppNode Package ${ownerName}`
);
}

for (const portOverTheMax of portsOverTheMax)
errors.push(
`Host port mapping ${portOverTheMax.host}/${portOverTheMax.protocol} is in the ephemeral port range (32768-65535). It must be avoided.`
);
// Adjusting the loops for error and warning outputs accordingly
portStatusSummary.container.overTheMax.forEach(port => {
errors.push(`❌ Container port mapping ${port.container}/${port.protocol} exceeds the maximum allowed port number of ${maxPortNumber} and can't be used.`);
});

portStatusSummary.host.overTheMax.forEach(port => {
errors.push(`❌ Host port mapping ${port.host}/${port.protocol} exceeds the maximum allowed port number of ${maxPortNumber} and can't be used.`);
});

portStatusSummary.container.inEphemeralRange.forEach(port => {
warnings.push(`⚠️ Package Port mapping ${port.container}/${port.protocol} is in the ephemeral port range (${maxRegisteredPortNumber + 1}-${maxPortNumber}).`);
});

portStatusSummary.host.inEphemeralRange.forEach(port => {
warnings.push(`⚠️ Host Port mapping ${port.host}/${port.protocol} is in the ephemeral port range (${maxRegisteredPortNumber + 1}-${maxPortNumber}).`);
});

// Aggregate conditions to disable the update
const disableUpdate = Boolean(
areNewMappingsInvalid ||
duplicatedContainerPorts.length > 0 ||
duplicatedHostPorts.length > 0 ||
conflictingPorts.length > 0 ||
portsOverTheMax.length > 0 ||
arePortsTheSame ||
updating
duplicatedContainerPorts.length > 0 ||
duplicatedHostPorts.length > 0 ||
conflictingPorts.length > 0 ||
arePortsTheSame ||
updating ||
portStatusSummary.container.overTheMax.length > 0 ||
portStatusSummary.host.overTheMax.length > 0
);

return (
Expand Down Expand Up @@ -210,7 +274,7 @@ export function PortsByService({
<Input
lock={true}
value={container}
onValueChange={() => {}}
onValueChange={() => { }}
/>
)}
</td>
Expand All @@ -230,7 +294,7 @@ export function PortsByService({
<Input
lock={true}
value={protocol}
onValueChange={() => {}}
onValueChange={() => { }}
/>
)}
</td>
Expand All @@ -256,6 +320,12 @@ export function PortsByService({
</div>
))}

{warnings.map(warning => (
<div key={warning}>
{warning}
</div>
))}

<div className="button-row">
<Button
variant={"dappnode"}
Expand Down
Loading