Skip to content

Conversation

@mvollmer
Copy link
Member

@mvollmer mvollmer commented Sep 23, 2025

Fixes #2186
Demo: https://youtu.be/L-EWj0dmgoo
Obsolete demo with choice, shows more about port forwarding: https://youtu.be/mhwTZ3CW8iI

  • Validate inputs, tests
  • Don't clear old port forwards when new ones will not be accepted by virt-xml. Done by first letting virt-xml override the old elements we want to keep, and then explicitly removing the old ones we don't want to keep.
  • coverage

Machines: Support for network port forwarding

Cockpit can now configure port forwarding for virtual machines on the user session.

image

Copy link
Member

@Venefilyn Venefilyn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking out the demos. I think the simple port forwarding is fine for now but the complex editing requires a lot more work IMO. It isn't intuitive and the alignment is all over the place.

I think we should experiment with what works for the complex one in a separate PR and keep simple port forwarding here.

Comment on lines 175 to 169
name: 'passt',
desc: 'Userspace PASST stack',
detailParagraph: _("Recommended method to provide a virtual LAN with NAT to the outside world. Supports port forwarding.")
},
{
name: 'user',
desc: 'Userspace SLIRP stack',
detailParagraph: _("Provides a virtual LAN with NAT to the outside world.")
desc: 'Userspace SLIRP stack (legacy)',
detailParagraph: _("Legacy method to provide a virtual LAN with NAT to the outside world.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we display "Userspace SLIRP stack" and such to begin with? Feels a bit weird to me to have it displayed in the select vs just writing "slirp" and "passt". We can in the select dropdown have a description for each entry to make it easier, which would look like this: https://www.patternfly.org/components/menus/menu#with-descriptions

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the latest version, there is no user choice anymore between slirp and passt. Cockpit switches from slirp to passt as required, but will never switch back.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant, why do we have "Userspace SLIRP stack" instead of just "slirp"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant, why do we have "Userspace SLIRP stack" instead of just "slirp"?

I don't know. Personally, I would like to avoid saying "slirp" or "passt" in the UI at all. The user should not need to make a decision between the two.

Comment on lines 314 to 326
for (let i = 0; i < pf.ranges.length; i++) {
const r = pf.ranges[i];
if (text != "")
text += ", ";
if (r.exclude == "yes")
text += _("excluding ");
text += r.start;
if (r.end)
text += "-" + r.end;
if (r.to)
text += " → " + r.to + (r.end ? "-" + (Number(r.to) + Number(r.end) - Number(r.start)).toString() : "");
if (pf.proto && pf.proto != "tcp")
text += "/" + pf.proto;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really dislike having one-letter variables and would much rather want to keep it expanded a bit

Comment on lines 420 to 437
} : {
id: string,
item: DialogSimplePortForward,
onChange: <K extends keyof DialogSimplePortForward>(idx: number, key: K, value: DialogSimplePortForward[K]) => void,
idx: number,
removeitem: (idx: number) => void,
}) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets split this out to an interface too

interface SimplePortForwardProps {
   // ...
}
const SimplePortForward = ({...}): SimplePortForwardProps {...}

@mvollmer mvollmer force-pushed the nics-passt branch 3 times, most recently from e5f91a8 to baa4847 Compare September 29, 2025 10:26
@mvollmer
Copy link
Member Author

Checking out the demos. I think the simple port forwarding is fine for now but the complex editing requires a lot more work IMO. It isn't intuitive and the alignment is all over the place.

Yes, I didn't try at all to make it nice.

I think we should experiment with what works for the complex one in a separate PR and keep simple port forwarding here.

I think we can postpone that indefinitely until there is clear demand.

@mvollmer mvollmer force-pushed the nics-passt branch 3 times, most recently from 9f7345f to 548bcc3 Compare September 30, 2025 07:37
@mvollmer mvollmer removed the blocked label Oct 10, 2025
@mvollmer mvollmer removed the no-test label Oct 13, 2025
@mvollmer mvollmer force-pushed the nics-passt branch 2 times, most recently from 5ca5232 to 4af3b62 Compare October 13, 2025 09:41
Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

@mvollmer mvollmer force-pushed the nics-passt branch 3 times, most recently from bd2aa21 to 57e3277 Compare October 13, 2025 12:38
Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

@mvollmer
Copy link
Member Author

  • 22 -> 22/tcp even if the port is the same.

  • change display of the ip addres to 127.0.0.1:22 -> 22/tcp

  • 8080-8090 → 80-90 this does not show it is tcp

Very good suggestions!

Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

@jelly
Copy link
Member

jelly commented Oct 30, 2025

minor validation issues

@jelly, there was and is no validation what so ever in that dialog, unfortunately. For example, invalid MACs also just cause ERROR XML error: unable to parse mac address '52:54:00:8c:a7:7cvvvvv'

Input validation is of course nice. So should I add it?

Aha, should we make this a follow up then if the whole dialog misses it?

@mvollmer
Copy link
Member Author

Ouch, when there is an error adding the new port forwards, we already have cleared out the old ones...

@mvollmer mvollmer marked this pull request as draft October 30, 2025 11:50
@Venefilyn
Copy link
Member

change display of the ip addres to 127.0.0.1:22 -> 22/tcp

I agree with this. Looks a lot better and it's following the expected IP:PORT

@mvollmer
Copy link
Member Author

minor validation issues

@jelly, there was and is no validation what so ever in that dialog, unfortunately. For example, invalid MACs also just cause ERROR XML error: unable to parse mac address '52:54:00:8c:a7:7cvvvvv'
Input validation is of course nice. So should I add it?

Aha, should we make this a follow up then if the whole dialog misses it?

Nah, I have started on it.

I find this whole topic annoying: We have no clear guidelines and no shareable code and every dialog turns into a spaghetti monster, and does validation slightly differently from any other. [dramatic exaggeration] But I am ready now to do something about this!

Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

@mvollmer mvollmer force-pushed the nics-passt branch 2 times, most recently from d9d932d to 86211d6 Compare October 31, 2025 12:21
@mvollmer mvollmer marked this pull request as ready for review October 31, 2025 12:25
@mvollmer mvollmer requested review from Venefilyn and jelly October 31, 2025 12:27
Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

/* Move 'Remove' button to the bottom of the line so as to align with the other form fields */
display: flex;
flex-direction: row;
align-items: flex-end;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the button alignment here is not quite right, it's more towrds bottom rather than being on center with the other groups.
You'll need to add the following css to the button element. Same as in podman.

Suggested change
align-items: flex-end;
align-items: flex-end;
+
+ button {
+ margin-block-end: var(--pf-t--global--spacer--xs);
+ }

Before:

Image

After:

Image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I also noticed that the trashcan is way out of alignment when there is helper text:

image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to align from the top, with a ugly computation:

        align-items: flex-start;
        button {
            margin-block-start: calc(var(--pf-v6-c-form__label--LineHeight) * var(--pf-v6-c-form__label--FontSize) +
                                     var(--pf-v6-c-form__group--Gap) +
                                     var(--pf-t--global--spacer--xs));
        }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

argh, yes. I forgot about the additional padding when helper text shows up. Good catch! And the same problem also needs to be fixed in c-podman's port forwarding.

}

function validateAddress(addr: string): string | undefined {
if (addr && !ipaddr.isValid(addr))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be careful about ipaddr.isValid as it uses ipaddr.IPv4.isValid which also considers "short" variants of IPv4 to be valid. Is libvirt OK with it when it receives IPv4 address that isn't in the typical 4 octets format? AFAIK there is no RFC for such format and you might want to use ipaddr.IPv4.isValidFourPartDecimal instead.

ipaddr.IPv6.isValid which is used in the background should be fine from what I remember from cockpit networking page.

I'd change this to (ipaddr.IPv4.isValidFourPartDecimal(addr) || ipaddr.IPv6.isValid(addr)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after testing it it looks like it's fine or some part of JS code expands the format. Inputting 10.10 into the address field and clicking save changes it to 10.0.0.10.

Copy link
Member Author

@mvollmer mvollmer Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thanks for checking!

Copy link
Contributor

@cockpituous cockpituous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than 10 code coverage comments, see the full report here.

this.state = {
dialogError: undefined,
networkType: "network",
networkType: this.props.vm.connectionName == "session" ? "user" : "network",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

}

if (this.state.doOnlineValidation)
this.setState(state => ({ validation: validateDialogBodyValues(state) }));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

Comment on lines +219 to +221
if (validation) {
this.setState({ validation, doOnlineValidation: true });
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 3 added lines are not executed by any test. Details

type: this.state.networkType,
backend: { type: backend },
source,
portForward: this.state.networkType == "user" ? dialogPortForwardsToInterface(this.state.portForwards) : null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

onValueChanged={this.onValueChanged} />}
</Form>
<>
<Form onSubmit={e => e.preventDefault()} isHorizontal>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

if (pf.address)
text += pf.address + ":";
if (pf.dev)
text += pf.dev + ":";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

if (pf.dev)
text += pf.dev + ":";
if (!pf.range.some(r => r.exclude != "yes"))
text += _("all");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

if (r.end)
text += "-" + r.end;
if (!r.exclude)
text += " → " + (r.to || r.start) + (r.end ? "-" + (Number(r.to || r.start) + Number(r.end) - Number(r.start)).toString() : "");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

return {
kind: "simple",
address: pf.address || "",
proto: pf.proto || "",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

address: pf.address || "",
proto: pf.proto || "",
host: r.start + (r.end ? "-" + r.end : ""),
guest: r.to || "",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test. Details

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature request] Add Passt and portForward to the create/edit VM page

5 participants