test af_packet mode on bridges
This commit is contained in:
@@ -43,6 +43,7 @@ interface SnifferManagerProps {
|
||||
export default function SnifferManager(props: SnifferManagerProps): ReactElement {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [startMode, setStartMode] = useState<'interface' | 'bridge'>('interface');
|
||||
const [bridgeCaptureMode, setBridgeCaptureMode] = useState<'tc_ebpf' | 'af_packet'>('tc_ebpf');
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const statusEntries = useMemo(
|
||||
@@ -53,6 +54,7 @@ export default function SnifferManager(props: SnifferManagerProps): ReactElement
|
||||
const onOpenStartModal = () => {
|
||||
form.resetFields();
|
||||
setStartMode('interface');
|
||||
setBridgeCaptureMode('tc_ebpf');
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
@@ -60,27 +62,30 @@ export default function SnifferManager(props: SnifferManagerProps): ReactElement
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
|
||||
const handleStartSubmit = async (values: { target?: string }) => {
|
||||
const handleStartSubmit = async (values: { target?: string; bridgeCaptureMode?: 'tc_ebpf' | 'af_packet' }) => {
|
||||
const target = values.target;
|
||||
if (!target) {
|
||||
notification.warning({ message: 'Warning', description: 'Please select a target to start sniffing on.' });
|
||||
notification.warning({ message: 'Warning', description: 'Please select a target to start capture on.' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = startMode === 'interface' ? { interface: target } : { bridge: target };
|
||||
const payload =
|
||||
startMode === 'interface'
|
||||
? { interface: target }
|
||||
: { bridge: target, bridge_capture_mode: values.bridgeCaptureMode ?? bridgeCaptureMode };
|
||||
const result = await startSniffer(payload);
|
||||
notification.success({
|
||||
message: 'Sniffer started',
|
||||
description: `Sniffer started on ${target} (session ${result.session_id})`,
|
||||
message: 'Capture started',
|
||||
description: `Capture started on ${target} via ${result.capture_mode} (session ${result.session_id})`,
|
||||
});
|
||||
await props.refreshAll();
|
||||
setIsModalOpen(false);
|
||||
} catch (error: any) {
|
||||
console.error('startSniffer error', error);
|
||||
notification.error({
|
||||
message: 'Failed to start sniffer',
|
||||
description: error?.message ?? 'Failed to start sniffer',
|
||||
message: 'Failed to start capture',
|
||||
description: error?.message ?? 'Failed to start capture',
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -88,13 +93,13 @@ export default function SnifferManager(props: SnifferManagerProps): ReactElement
|
||||
const handleStopAll = async () => {
|
||||
try {
|
||||
await stopSniffer();
|
||||
notification.success({ message: 'All sniffers stopped' });
|
||||
notification.success({ message: 'All capture sessions stopped' });
|
||||
await props.refreshStatus();
|
||||
} catch (error: any) {
|
||||
console.error('stopSniffer error', error);
|
||||
notification.error({
|
||||
message: 'Failed to stop sniffers',
|
||||
description: error?.message ?? 'Failed to stop sniffers',
|
||||
message: 'Failed to stop capture sessions',
|
||||
description: error?.message ?? 'Failed to stop capture sessions',
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -103,8 +108,8 @@ export default function SnifferManager(props: SnifferManagerProps): ReactElement
|
||||
try {
|
||||
await stopSnifferByInterface(ifaceName);
|
||||
notification.success({
|
||||
message: 'Sniffer stopped',
|
||||
description: `Sniffer stopped on interface ${ifaceName}`,
|
||||
message: 'Capture stopped',
|
||||
description: `Capture stopped on interface ${ifaceName}`,
|
||||
});
|
||||
await props.refreshStatus();
|
||||
return;
|
||||
@@ -116,8 +121,8 @@ export default function SnifferManager(props: SnifferManagerProps): ReactElement
|
||||
try {
|
||||
await stopSniffer({ session_id: sessionId });
|
||||
notification.success({
|
||||
message: 'Sniffer stopped',
|
||||
description: `Sniffer stopped on interface ${ifaceName} (session ${sessionId})`,
|
||||
message: 'Capture stopped',
|
||||
description: `Capture stopped on interface ${ifaceName} (session ${sessionId})`,
|
||||
});
|
||||
await props.refreshStatus();
|
||||
return;
|
||||
@@ -127,8 +132,8 @@ export default function SnifferManager(props: SnifferManagerProps): ReactElement
|
||||
}
|
||||
|
||||
notification.error({
|
||||
message: 'Failed to stop sniffer',
|
||||
description: 'Could not stop sniffer for this interface.',
|
||||
message: 'Failed to stop capture',
|
||||
description: 'Could not stop capture for this interface.',
|
||||
});
|
||||
};
|
||||
|
||||
@@ -145,7 +150,7 @@ export default function SnifferManager(props: SnifferManagerProps): ReactElement
|
||||
<Tooltip title="Refresh status">
|
||||
<Button icon={<ReloadOutlined />} onClick={() => props.refreshStatus()} />
|
||||
</Tooltip>
|
||||
<Tooltip title="Stop all sniffers">
|
||||
<Tooltip title="Stop all capture sessions">
|
||||
<Button danger icon={<StopOutlined />} onClick={handleStopAll} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
@@ -189,15 +194,15 @@ export default function SnifferManager(props: SnifferManagerProps): ReactElement
|
||||
try {
|
||||
const result = await startSniffer({ interface: name });
|
||||
notification.success({
|
||||
message: 'Sniffer started',
|
||||
description: `Sniffer started on ${name} (session ${result.session_id})`,
|
||||
message: 'Capture started',
|
||||
description: `Capture started on ${name} via ${result.capture_mode} (session ${result.session_id})`,
|
||||
});
|
||||
await props.refreshStatus();
|
||||
} catch (error: any) {
|
||||
console.error('startSniffer quick', error);
|
||||
notification.error({
|
||||
message: 'Failed to start sniffer',
|
||||
description: error?.message ?? 'Failed to start sniffer',
|
||||
message: 'Failed to start capture',
|
||||
description: error?.message ?? 'Failed to start capture',
|
||||
});
|
||||
}
|
||||
}}
|
||||
@@ -231,6 +236,11 @@ export default function SnifferManager(props: SnifferManagerProps): ReactElement
|
||||
</Text>
|
||||
</Tag>
|
||||
)}
|
||||
{status.capture_mode && (
|
||||
<Tag color={status.capture_mode === 'af_packet' ? 'geekblue' : 'purple'}>
|
||||
{status.capture_mode === 'af_packet' ? 'AF_PACKET' : 'tc/eBPF'}
|
||||
</Tag>
|
||||
)}
|
||||
</Space>
|
||||
}
|
||||
description={<Text type="secondary">interface: {name}</Text>}
|
||||
@@ -256,7 +266,8 @@ export default function SnifferManager(props: SnifferManagerProps): ReactElement
|
||||
value={startMode}
|
||||
onChange={(event) => {
|
||||
setStartMode(event.target.value);
|
||||
form.setFieldsValue({ target: undefined });
|
||||
setBridgeCaptureMode('tc_ebpf');
|
||||
form.setFieldsValue({ target: undefined, bridgeCaptureMode: 'tc_ebpf' });
|
||||
}}
|
||||
>
|
||||
<Radio value="interface">Interface</Radio>
|
||||
@@ -290,6 +301,28 @@ export default function SnifferManager(props: SnifferManagerProps): ReactElement
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
{startMode === 'bridge' && (
|
||||
<Form.Item
|
||||
label="Bridge Capture Path"
|
||||
name="bridgeCaptureMode"
|
||||
initialValue="tc_ebpf"
|
||||
extra="Choose between tc/eBPF bridge telemetry or direct AF_PACKET capture on the bridge member interfaces."
|
||||
>
|
||||
<Radio.Group
|
||||
value={bridgeCaptureMode}
|
||||
onChange={(event) => {
|
||||
setBridgeCaptureMode(event.target.value);
|
||||
form.setFieldsValue({ bridgeCaptureMode: event.target.value });
|
||||
}}
|
||||
>
|
||||
<Space direction="vertical">
|
||||
<Radio value="tc_ebpf">tc/eBPF telemetry capture</Radio>
|
||||
<Radio value="af_packet">AF_PACKET on bridge member interfaces</Radio>
|
||||
</Space>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function Sniffing(): ReactElement {
|
||||
setStatusMap(status.interfaces ?? {});
|
||||
} catch (error: any) {
|
||||
console.error('fetchSnifferStatus error', error);
|
||||
message.error(error?.message ?? 'Failed to fetch sniffer status');
|
||||
message.error(error?.message ?? 'Failed to fetch capture status');
|
||||
} finally {
|
||||
setStatusLoading(false);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
export interface SnifferStartRequest {
|
||||
bridge?: string;
|
||||
interface?: string;
|
||||
bridge_capture_mode?: 'tc_ebpf' | 'af_packet';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -15,6 +16,7 @@ export interface SnifferStartResponse {
|
||||
session_id: string;
|
||||
target: string;
|
||||
target_type: 'bridge' | 'interface';
|
||||
capture_mode: 'tc_ebpf' | 'af_packet';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,7 +28,7 @@ export interface SnifferStopRequest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response returned when stopping sniffer(s).
|
||||
* Response returned when stopping capture session(s).
|
||||
*/
|
||||
export interface SnifferStopResponse {
|
||||
stopped: boolean;
|
||||
@@ -36,7 +38,7 @@ export interface SnifferStopResponse {
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-interface sniffer status (now includes owning session info).
|
||||
* Per-interface capture status (now includes owning session info).
|
||||
*/
|
||||
export interface InterfaceSnifferStatus {
|
||||
running: boolean;
|
||||
@@ -44,6 +46,7 @@ export interface InterfaceSnifferStatus {
|
||||
up: boolean;
|
||||
session_id?: string | null;
|
||||
session_label?: string | null;
|
||||
capture_mode?: 'tc_ebpf' | 'af_packet' | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user