optional nfqueue fail-open option

This commit is contained in:
Domingo Dirutigliano
2025-02-18 17:36:15 +01:00
parent ece058d533
commit 59652fc697
11 changed files with 247 additions and 133 deletions

View File

@@ -0,0 +1,139 @@
import { Button, Group, Space, TextInput, Notification, Modal, Switch, SegmentedControl, Box, Tooltip } from '@mantine/core';
import { useForm } from '@mantine/form';
import { useEffect, useState } from 'react';
import { okNotify, regex_ipv4, regex_ipv6 } from '../../js/utils';
import { ImCross } from "react-icons/im"
import { nfregex, Service } from './utils';
import PortAndInterface from '../PortAndInterface';
import { IoMdInformationCircleOutline } from "react-icons/io";
import { ServiceAddForm as ServiceAddFormOriginal } from './utils';
type ServiceAddForm = ServiceAddFormOriginal & {autostart: boolean}
function AddEditService({ opened, onClose, edit }:{ opened:boolean, onClose:()=>void, edit?:Service }) {
const initialValues = {
name: "",
port:edit?.port??8080,
ip_int:edit?.ip_int??"",
proto:edit?.proto??"tcp",
fail_open: edit?.fail_open??false,
autostart: true
}
const form = useForm({
initialValues: initialValues,
validate:{
name: (value) => edit? null : value !== "" ? null : "Service name is required",
port: (value) => (value>0 && value<65536) ? null : "Invalid port",
proto: (value) => ["tcp","udp"].includes(value) ? null : "Invalid protocol",
ip_int: (value) => (value.match(regex_ipv6) || value.match(regex_ipv4)) ? null : "Invalid IP address",
}
})
useEffect(() => {
if (opened){
form.setInitialValues(initialValues)
form.reset()
}
}, [opened])
const close = () =>{
onClose()
form.reset()
setError(null)
}
const [submitLoading, setSubmitLoading] = useState(false)
const [error, setError] = useState<string|null>(null)
const submitRequest = ({ name, port, autostart, proto, ip_int, fail_open }:ServiceAddForm) =>{
setSubmitLoading(true)
if (edit){
nfregex.settings(edit.service_id, { port, proto, ip_int, fail_open }).then( res => {
if (!res){
setSubmitLoading(false)
close();
okNotify(`Service ${name} settings updated`, `Successfully updated settings for service ${name}`)
}
}).catch( err => {
setSubmitLoading(false)
setError("Request Failed! [ "+err+" ]")
})
}else{
nfregex.servicesadd({ name, port, proto, ip_int, fail_open }).then( res => {
if (res.status === "ok" && res.service_id){
setSubmitLoading(false)
close();
if (autostart) nfregex.servicestart(res.service_id)
okNotify(`Service ${name} has been added`, `Successfully added service with port ${port}`)
}else{
setSubmitLoading(false)
setError("Invalid request! [ "+res.status+" ]")
}
}).catch( err => {
setSubmitLoading(false)
setError("Request Failed! [ "+err+" ]")
})
}
}
return <Modal size="xl" title={edit?`Editing ${edit.name} service`:"Add a new service"} opened={opened} onClose={close} closeOnClickOutside={false} centered>
<form onSubmit={form.onSubmit(submitRequest)}>
{!edit?<TextInput
label="Service name"
placeholder="Challenge 01"
{...form.getInputProps('name')}
/>:null}
<Space h="md" />
<PortAndInterface form={form} int_name="ip_int" port_name="port" label={"Public IP Interface and port (ipv4/ipv6 + CIDR allowed)"} />
<Space h="md" />
<Box className='center-flex'>
<Box>
{!edit?<Switch
label="Auto-Start Service"
{...form.getInputProps('autostart', { type: 'checkbox' })}
/>:null}
<Space h="sm" />
<Switch
label={<Box className='center-flex'>
Enable fail-open nfqueue
<Space w="xs" />
<Tooltip label={<>
Firegex use internally nfqueue to handle packets<br />enabling this option will allow packets to pass through the firewall <br /> in case the filtering is too slow or too many traffic is coming<br />
</>}>
<IoMdInformationCircleOutline size={15} />
</Tooltip>
</Box>}
{...form.getInputProps('fail_open', { type: 'checkbox' })}
/>
</Box>
<Box className="flex-spacer"></Box>
<SegmentedControl
data={[
{ label: 'TCP', value: 'tcp' },
{ label: 'UDP', value: 'udp' },
]}
{...form.getInputProps('proto')}
/>
</Box>
<Group justify='flex-end' mt="md" mb="sm">
<Button loading={submitLoading} type="submit" disabled={edit?!form.isDirty():false}>{edit?"Edit Service":"Add Service"}</Button>
</Group>
{error?<>
<Space h="md" />
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
Error: {error}
</Notification><Space h="md" />
</>:null}
</form>
</Modal>
}
export default AddEditService;

View File

@@ -1,105 +0,0 @@
import { Button, Group, Space, TextInput, Notification, Modal, Switch, SegmentedControl, Box } from '@mantine/core';
import { useForm } from '@mantine/form';
import { useState } from 'react';
import { okNotify, regex_ipv4, regex_ipv6 } from '../../js/utils';
import { ImCross } from "react-icons/im"
import { nfregex } from './utils';
import PortAndInterface from '../PortAndInterface';
type ServiceAddForm = {
name:string,
port:number,
proto:string,
ip_int:string,
autostart: boolean,
}
function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void }) {
const form = useForm({
initialValues: {
name:"",
port:8080,
ip_int:"",
proto:"tcp",
autostart: true
},
validate:{
name: (value) => value !== "" ? null : "Service name is required",
port: (value) => (value>0 && value<65536) ? null : "Invalid port",
proto: (value) => ["tcp","udp"].includes(value) ? null : "Invalid protocol",
ip_int: (value) => (value.match(regex_ipv6) || value.match(regex_ipv4)) ? null : "Invalid IP address",
}
})
const close = () =>{
onClose()
form.reset()
setError(null)
}
const [submitLoading, setSubmitLoading] = useState(false)
const [error, setError] = useState<string|null>(null)
const submitRequest = ({ name, port, autostart, proto, ip_int }:ServiceAddForm) =>{
setSubmitLoading(true)
nfregex.servicesadd({name, port, proto, ip_int }).then( res => {
if (res.status === "ok" && res.service_id){
setSubmitLoading(false)
close();
if (autostart) nfregex.servicestart(res.service_id)
okNotify(`Service ${name} has been added`, `Successfully added service with port ${port}`)
}else{
setSubmitLoading(false)
setError("Invalid request! [ "+res.status+" ]")
}
}).catch( err => {
setSubmitLoading(false)
setError("Request Failed! [ "+err+" ]")
})
}
return <Modal size="xl" title="Add a new service" opened={opened} onClose={close} closeOnClickOutside={false} centered>
<form onSubmit={form.onSubmit(submitRequest)}>
<TextInput
label="Service name"
placeholder="Challenge 01"
{...form.getInputProps('name')}
/>
<Space h="md" />
<PortAndInterface form={form} int_name="ip_int" port_name="port" label={"Public IP Interface and port (ipv4/ipv6 + CIDR allowed)"} />
<Space h="md" />
<Box className='center-flex'>
<Switch
label="Auto-Start Service"
{...form.getInputProps('autostart', { type: 'checkbox' })}
/>
<Box className="flex-spacer"></Box>
<SegmentedControl
data={[
{ label: 'TCP', value: 'tcp' },
{ label: 'UDP', value: 'udp' },
]}
{...form.getInputProps('proto')}
/>
</Box>
<Group justify='flex-end' mt="md" mb="sm">
<Button loading={submitLoading} type="submit">Add Service</Button>
</Group>
{error?<>
<Space h="md" />
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
Error: {error}
</Notification><Space h="md" />
</>:null}
</form>
</Modal>
}
export default AddNewService;

View File

@@ -12,6 +12,8 @@ import { MenuDropDownWithButton } from '../../MainLayout';
import { useQueryClient } from '@tanstack/react-query';
import { FaFilter } from "react-icons/fa";
import { VscRegex } from "react-icons/vsc";
import { IoSettingsSharp } from 'react-icons/io5';
import AddEditService from '../AddEditService';
export default function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) {
@@ -26,6 +28,7 @@ export default function ServiceRow({ service, onClick }:{ service:Service, onCli
const [tooltipStopOpened, setTooltipStopOpened] = useState(false);
const [deleteModal, setDeleteModal] = useState(false)
const [renameModal, setRenameModal] = useState(false)
const [editModal, setEditModal] = useState(false)
const isMedium = isMediumScreen()
const stopService = async () => {
@@ -104,12 +107,13 @@ export default function ServiceRow({ service, onClick }:{ service:Service, onCli
{isMedium?<Space w="xl" />:<Space h="lg" />}
<Box className="center-flex">
<MenuDropDownWithButton>
<Menu.Label><b>Rename service</b></Menu.Label>
<Menu.Item><b>Edit service</b></Menu.Item>
<Menu.Item leftSection={<IoSettingsSharp size={18} />} onClick={()=>setEditModal(true)}>Service Settings</Menu.Item>
<Menu.Item leftSection={<BiRename size={18} />} onClick={()=>setRenameModal(true)}>Change service name</Menu.Item>
<Divider />
<Menu.Label><b>Danger zone</b></Menu.Label>
<Menu.Item color="red" leftSection={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item>
</MenuDropDownWithButton>
</MenuDropDownWithButton>
<Space w="md"/>
<Tooltip label="Stop service" zIndex={0} color="red" opened={tooltipStopOpened}>
<ActionIcon color="red" loading={buttonLoading}
@@ -148,5 +152,10 @@ export default function ServiceRow({ service, onClick }:{ service:Service, onCli
opened={renameModal}
service={service}
/>
<AddEditService
opened={editModal}
onClose={()=>setEditModal(false)}
edit={service}
/>
</>
}

View File

@@ -12,6 +12,7 @@ export type Service = {
ip_int: string,
n_packets:number,
n_regex:number,
fail_open:boolean,
}
export type ServiceAddForm = {
@@ -19,6 +20,14 @@ export type ServiceAddForm = {
port:number,
proto:string,
ip_int:string,
fail_open: boolean,
}
export type ServiceSettings = {
port?:number,
proto?:string,
ip_int?:string,
fail_open?: boolean,
}
export type ServiceAddResponse = {
@@ -79,5 +88,9 @@ export const nfregex = {
},
serviceregexes: async (service_id:string) => {
return await getapi(`nfregex/services/${service_id}/regexes`) as RegexFilter[];
}
},
settings: async (service_id:string, data:ServiceSettings) => {
const { status } = await putapi(`nfregex/services/${service_id}/settings`,data) as ServerResponse;
return status === "ok"?undefined:status
},
}

View File

@@ -3,7 +3,7 @@ import { Navigate, useNavigate, useParams } from 'react-router-dom';
import RegexView from '../../components/RegexView';
import AddNewRegex from '../../components/AddNewRegex';
import { BsPlusLg } from "react-icons/bs";
import { nfregexServiceQuery, nfregexServiceRegexesQuery } from '../../components/NFRegex/utils';
import { nfregexServiceQuery, nfregexServiceRegexesQuery, Service } from '../../components/NFRegex/utils';
import { Badge, Divider, Menu } from '@mantine/core';
import { useState } from 'react';
import { FaFilter, FaPlay, FaStop } from 'react-icons/fa';
@@ -18,6 +18,8 @@ import { MenuDropDownWithButton } from '../../components/MainLayout';
import { useQueryClient } from '@tanstack/react-query';
import { FaArrowLeft } from "react-icons/fa";
import { VscRegex } from 'react-icons/vsc';
import { IoSettingsSharp } from 'react-icons/io5';
import AddEditService from '../../components/NFRegex/AddEditService';
export default function ServiceDetailsNFRegex() {
@@ -29,6 +31,7 @@ export default function ServiceDetailsNFRegex() {
const regexesList = nfregexServiceRegexesQuery(srv??"")
const [deleteModal, setDeleteModal] = useState(false)
const [renameModal, setRenameModal] = useState(false)
const [editModal, setEditModal] = useState(false)
const [buttonLoading, setButtonLoading] = useState(false)
const queryClient = useQueryClient()
const [tooltipStopOpened, setTooltipStopOpened] = useState(false);
@@ -108,7 +111,8 @@ export default function ServiceDetailsNFRegex() {
</Badge>
<MenuDropDownWithButton>
<Menu.Label><b>Rename service</b></Menu.Label>
<Menu.Item><b>Edit service</b></Menu.Item>
<Menu.Item leftSection={<IoSettingsSharp size={18} />} onClick={()=>setEditModal(true)}>Service Settings</Menu.Item>
<Menu.Item leftSection={<BiRename size={18} />} onClick={()=>setRenameModal(true)}>Change service name</Menu.Item>
<Divider />
<Menu.Label><b>Danger zone</b></Menu.Label>
@@ -190,5 +194,10 @@ export default function ServiceDetailsNFRegex() {
opened={renameModal}
service={serviceInfo}
/>
<AddEditService
opened={editModal}
onClose={()=>setEditModal(false)}
edit={serviceInfo}
/>
</>
}

View File

@@ -5,7 +5,7 @@ import { useNavigate, useParams } from 'react-router-dom';
import ServiceRow from '../../components/NFRegex/ServiceRow';
import { nfregexServiceQuery } from '../../components/NFRegex/utils';
import { errorNotify, getErrorMessage, isMediumScreen } from '../../js/utils';
import AddNewService from '../../components/NFRegex/AddNewService';
import AddEditService from '../../components/NFRegex/AddEditService';
import AddNewRegex from '../../components/AddNewRegex';
import { useQueryClient } from '@tanstack/react-query';
import { TbReload } from 'react-icons/tb';
@@ -81,13 +81,12 @@ function NFRegex({ children }: { children: any }) {
</Tooltip>
</Box>
</>}
<AddNewService opened={open} onClose={closeModal} />
</>}
</Box>
{srv?children:null}
{srv?
<AddNewRegex opened={open} onClose={closeModal} service={srv} />:
<AddNewService opened={open} onClose={closeModal} />
<AddEditService opened={open} onClose={closeModal} />
}
</>
}