Skip to content

Commit 3bc7bae

Browse files
committed
nics: Allow managing source mode of directly attached network interfaces
Virtual functions from SR-IOV devices might require "passthrough" to work, so let's surface the mode property of directly attached devices fully. See https://issues.redhat.com/browse/RHEL-88407
1 parent 776c8fd commit 3bc7bae

File tree

7 files changed

+147
-26
lines changed

7 files changed

+147
-26
lines changed

src/components/common/needsShutdown.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ export function needsShutdownIfaceSource(vm: VM, iface: VMInterface) {
7070
getIfaceSourceName(inactiveIface) !== getIfaceSourceName(iface);
7171
}
7272

73+
export function needsShutdownIfaceSourceMode(vm: VM, iface: VMInterface) {
74+
const inactiveIface = nicLookupByMAC(vm.inactiveXML.interfaces, iface.mac);
75+
76+
return inactiveIface && inactiveIface.type == "direct" && iface.type == "direct" &&
77+
inactiveIface.source.mode !== iface.source.mode;
78+
}
79+
7380
export function needsShutdownVcpu(vm: VM) {
7481
return ((vm.vcpus.count !== vm.inactiveXML.vcpus.count) ||
7582
(vm.vcpus.max !== vm.inactiveXML.vcpus.max) ||
@@ -122,7 +129,8 @@ export function getDevicesRequiringShutdown(vm: VM) {
122129
for (const iface of vm.interfaces) {
123130
if (needsShutdownIfaceType(vm, iface) ||
124131
needsShutdownIfaceModel(vm, iface) ||
125-
needsShutdownIfaceSource(vm, iface)) {
132+
needsShutdownIfaceSource(vm, iface) ||
133+
needsShutdownIfaceSourceMode(vm, iface)) {
126134
devices.push(_("Network interface"));
127135
break;
128136
}

src/components/vm/nics/nicAdd.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export class AddNIC extends React.Component {
109109
dialogError: undefined,
110110
networkType: "network",
111111
networkSource: props.availableSources.network.length > 0 ? props.availableSources.network[0] : undefined,
112+
networkSourceMode: "bridge",
112113
networkModel: "virtio",
113114
setNetworkMac: false,
114115
networkMac: "",
@@ -162,6 +163,7 @@ export class AddNIC extends React.Component {
162163
model: this.state.networkModel,
163164
sourceType: this.state.networkType,
164165
source: this.state.networkSource,
166+
sourceMode: this.state.networkSourceMode,
165167
// Generate our own random MAC address because virt-xml has bug which generates different MAC for online and offline XML
166168
// https://github.com/virt-manager/virt-manager/issues/305
167169
mac: this.state.setNetworkMac ? this.state.networkMac : getRandomMac(vms),

src/components/vm/nics/nicBody.jsx

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,12 @@
1919

2020
import React from 'react';
2121
import PropTypes from 'prop-types';
22-
import { Button } from "@patternfly/react-core/dist/esm/components/Button";
2322
import { Flex } from "@patternfly/react-core/dist/esm/layouts/Flex";
2423
import { FormGroup } from "@patternfly/react-core/dist/esm/components/Form";
2524
import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/esm/components/FormSelect";
25+
import { Radio } from "@patternfly/react-core/dist/esm/components/Radio";
2626
import { PopoverPosition } from "@patternfly/react-core/dist/esm/components/Popover";
2727
import { Content, ContentVariants } from "@patternfly/react-core/dist/esm/components/Content";
28-
import { ExternalLinkSquareAltIcon } from '@patternfly/react-icons';
2928

3029
import { InfoPopover } from '../../common/infoPopover.jsx';
3130

@@ -83,7 +82,7 @@ export const NetworkTypeAndSourceRow = ({ idPrefix, onValueChanged, dialogValues
8382
const virtualNetwork = [{
8483
name: 'network',
8584
desc: 'Virtual network',
86-
detailHeadline: _("This is the recommended config for general guest connectivity on hosts with dynamic / wireless networking configs."),
85+
detailHeadline: _("This is the recommended type for general guest connectivity on hosts with dynamic / wireless networking configs."),
8786
detailParagraph: _("Provides a connection whose details are described by the named network definition.")
8887
}];
8988
if (connectionName !== 'session') {
@@ -92,26 +91,13 @@ export const NetworkTypeAndSourceRow = ({ idPrefix, onValueChanged, dialogValues
9291
{
9392
name: 'bridge',
9493
desc: 'Bridge to LAN',
95-
detailHeadline: _("This is the recommended config for general guest connectivity on hosts with static wired networking configs."),
94+
detailHeadline: _("This is the recommended type for general guest connectivity on hosts with static wired networking configs."),
9695
detailParagraph: _("Provides a bridge from the guest virtual machine directly onto the LAN. This needs a bridge device on the host with one or more physical NICs.")
9796
},
9897
{
9998
name: 'direct',
10099
desc: 'Direct attachment',
101-
detailHeadline: _("This is the recommended config for high performance or enhanced security."),
102-
detailParagraph: _("In the default 'vepa' mode, switching is offloaded to the external switch. If the switch is not VEPA-capable, communication between guest virtual machines, or between a guests and the host is not possible."),
103-
externalDocs: (
104-
<Button isInline
105-
className='ct-external-docs-link'
106-
variant="link"
107-
component="a"
108-
icon={<ExternalLinkSquareAltIcon />}
109-
iconPosition="right"
110-
target="__blank"
111-
href="https://libvirt.org/formatdomain.html#direct-attachment-to-physical-interface">
112-
{_("more info")}
113-
</Button>
114-
)
100+
detailParagraph: _("This is the recommended type for high performance or enhanced security."),
115101
},
116102
];
117103
} else {
@@ -170,10 +156,7 @@ export const NetworkTypeAndSourceRow = ({ idPrefix, onValueChanged, dialogValues
170156
{availableNetworkTypes.map(type => (<Content key={type.name}>
171157
<Content component={ContentVariants.h4}>{type.desc}</Content>
172158
<strong>{type.detailHeadline}</strong>
173-
<p>
174-
{type.detailParagraph}
175-
{type.externalDocs}
176-
</p>
159+
<p>{type.detailParagraph}</p>
177160
</Content>))}
178161
</Flex>}
179162
/>
@@ -203,6 +186,65 @@ export const NetworkTypeAndSourceRow = ({ idPrefix, onValueChanged, dialogValues
203186
</FormSelect>
204187
</FormGroup>
205188
)}
189+
{dialogValues.networkType == "direct" && (
190+
<FormGroup id={`${idPrefix}-source-mode`} label={_("Mode")} hasNoPaddingTop isInline
191+
data-value={dialogValues.networkSourceMode}
192+
labelHelp={
193+
<InfoPopover
194+
aria-label={_("Mode help")}
195+
position={PopoverPosition.bottom}
196+
enableFlip={false}
197+
bodyContent={
198+
<Content>
199+
<Content component={ContentVariants.h4}>
200+
VEPA
201+
</Content>
202+
<Content component={ContentVariants.p}>
203+
{_("All VMs' packets are sent to the external bridge. Packets whose destination is a VM on the same host as where the packet originates from are sent back to the host by the VEPA capable bridge (today's bridges are typically not VEPA capable)")}.
204+
</Content>
205+
<Content component={ContentVariants.h4}>
206+
{_("Bridge")}
207+
</Content>
208+
<Content component={ContentVariants.p}>
209+
{_("Packets whose destination is on the same host as where they originate from are directly delivered to the target macvtap device. Both origin and destination devices need to be in bridge mode for direct delivery. If either one of them is in \"VEPA\" mode, a VEPA capable bridge is required.")}
210+
</Content>
211+
<Content component={ContentVariants.h4}>
212+
{_("Private")}
213+
</Content>
214+
<Content component={ContentVariants.p}>
215+
{_("All packets are sent to the external bridge and will only be delivered to a target VM on the same host if they are sent through an external router or gateway and that device sends them back to the host. This procedure is followed if either the source or destination device is in \"Private\" mode.")}
216+
</Content>
217+
<Content component={ContentVariants.h4}>
218+
{_("Passthrough")}
219+
</Content>
220+
<Content component={ContentVariants.p}>
221+
{_("This feature attaches a virtual function of a SRIOV capable NIC directly to a VM without losing the migration capability. All packets are sent to the VF/IF of the configured network device. Depending on the capabilities of the device additional prerequisites or limitations may apply.")}
222+
</Content>
223+
</Content>}
224+
/>
225+
}>
226+
<Radio id={`${idPrefix}-source-mode-vepa`}
227+
name="mode-vepa"
228+
isChecked={dialogValues.networkSourceMode == "vepa"}
229+
label="VEPA"
230+
onChange={() => onValueChanged('networkSourceMode', "vepa")} />
231+
<Radio id={`${idPrefix}-source-mode-bridge`}
232+
name="mode-bridge"
233+
isChecked={dialogValues.networkSourceMode == "bridge"}
234+
label={_("Bridge")}
235+
onChange={() => onValueChanged('networkSourceMode', "bridge")} />
236+
<Radio id={`${idPrefix}-source-mode-private`}
237+
name="mode-private"
238+
isChecked={dialogValues.networkSourceMode == "private"}
239+
label={_("Private")}
240+
onChange={() => onValueChanged('networkSourceMode', "private")} />
241+
<Radio id={`${idPrefix}-source-mode-passthrough`}
242+
name="mode-passthrough"
243+
isChecked={dialogValues.networkSourceMode == "passthrough"}
244+
label={_("Passthrough")}
245+
onChange={() => onValueChanged('networkSourceMode', "passthrough")} />
246+
</FormGroup>
247+
)}
206248
</>
207249
);
208250
};

src/components/vm/nics/nicEdit.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export class EditNICModal extends React.Component {
7878
dialogError: undefined,
7979
networkType: props.network.type,
8080
networkSource: defaultNetworkSource,
81+
networkSourceMode: props.network.type == "direct" ? props.network.source.mode : "bridge",
8182
networkModel: props.network.model,
8283
networkMac: props.network.mac,
8384
saveDisabled: false,
@@ -141,6 +142,7 @@ export class EditNICModal extends React.Component {
141142
networkModel: this.state.networkModel,
142143
networkType: this.state.networkType,
143144
networkSource: this.state.networkSource,
145+
networkSourceMode: this.state.networkSourceMode,
144146
})
145147
.then(() => {
146148
domainGet({ connectionName: vm.connectionName, id: vm.id });

src/components/vm/nics/vmNicsCard.jsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@ const NetworkSource = ({ network, networkId, vm, hostDevices }) => {
145145
{needsShutdownIfaceSource(vm, network) && <NeedsShutdownTooltip iconId={`${id}-network-${networkId}-source-tooltip`} tooltipId="tip-network" />}
146146
</Flex>
147147
</DescriptionListDescription>
148+
{ network.source.mode &&
149+
<>
150+
<DescriptionListTerm>
151+
{_("Mode")}
152+
</DescriptionListTerm>
153+
<DescriptionListDescription id={`${id}-network-${networkId}-source-mode`}>
154+
{network.source.mode}
155+
</DescriptionListDescription>
156+
</>
157+
}
148158
</DescriptionListGroup>
149159
);
150160
};

src/libvirtApi/domain.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ interface InterfaceSpec {
226226
hotplug: boolean,
227227
sourceType: string,
228228
source: string,
229+
sourceMode: string,
229230
model: string,
230231
}
231232

@@ -237,10 +238,11 @@ export function domainAttachIface({
237238
hotplug,
238239
sourceType,
239240
source,
241+
sourceMode,
240242
model
241243
}: { connectionName: ConnectionName, vmName: string } & InterfaceSpec): cockpit.Spawn<string> {
242244
const macArg = mac ? "mac=" + mac + "," : "";
243-
const args = ['virt-xml', '-c', `qemu:///${connectionName}`, vmName, '--add-device', '--network', `${macArg}type=${sourceType},source=${source},source.mode=bridge,model=${model}`];
245+
const args = ['virt-xml', '-c', `qemu:///${connectionName}`, vmName, '--add-device', '--network', `${macArg}type=${sourceType},source=${source},source.mode=${sourceMode},model=${model}`];
244246

245247
if (hotplug) {
246248
args.push("--update");
@@ -258,6 +260,7 @@ interface InterfaceChangeSpec {
258260
newMacAddress?: string,
259261
networkType?: string,
260262
networkSource?: string,
263+
networkSourceMode?: string,
261264
networkModel?: string,
262265
state?: string,
263266
}
@@ -271,6 +274,7 @@ export function domainChangeInterfaceSettings({
271274
newMacAddress,
272275
networkType,
273276
networkSource,
277+
networkSourceMode,
274278
networkModel,
275279
state,
276280
}: { connectionName: ConnectionName, vmName: string } & InterfaceChangeSpec): cockpit.Spawn<string> {
@@ -287,6 +291,8 @@ export function domainChangeInterfaceSettings({
287291
}
288292
if (networkSource)
289293
networkParams += `source=${networkSource},`;
294+
if (networkSourceMode)
295+
networkParams += `source.mode=${networkSourceMode},`;
290296
if (networkModel)
291297
networkParams += `model=${networkModel},`;
292298
}

0 commit comments

Comments
 (0)