test af_packet mode on bridges
All checks were successful
Build and Deploy MITM Webserver / traffic_target (push) Successful in 0s
Build and Deploy MITM Webserver / build (push) Successful in 10s

This commit is contained in:
2026-04-12 17:58:19 +02:00
parent 9ee47284e0
commit 4521b9d99e
5 changed files with 131 additions and 36 deletions

View File

@@ -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>