Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #59

Merged
merged 5 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
![goroutines](https://img.shields.io/badge/go%20routines-not%20leaking-success)
![file descriptors](https://img.shields.io/badge/file%20descriptors-not%20leaking-success)
[![Go Report Card](https://goreportcard.com/badge/github.com/siemens/ghostwire/v2)](https://goreportcard.com/report/github.com/siemens/ghostwire/v2)
![Coverage](https://img.shields.io/badge/Coverage-77.0%25-yellow)
![Coverage](https://img.shields.io/badge/Coverage-76.9%25-yellow)

**G(h)ostwire** discovers the virtual (or not) network configuration inside
_Linux_ hosts – and can be deployed as a REST service or consumed as a Go
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ replace github.com/mattn/go-sqlite3 => github.com/mattn/go-sqlite3 v1.14.12
require (
github.com/cenkalti/backoff/v4 v4.3.0
github.com/containernetworking/cni v1.2.3
github.com/docker/docker v27.1.0+incompatible
github.com/docker/docker v27.1.1+incompatible
github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0
github.com/getkin/kin-openapi v0.126.0
github.com/google/nftables v0.2.1-0.20240422065334-aa8348f7904c
Expand All @@ -29,7 +29,7 @@ require (
github.com/thediveo/go-plugger/v3 v3.1.0
github.com/thediveo/ioctl v0.9.3
github.com/thediveo/lxkns v0.36.0
github.com/thediveo/morbyd v0.13.0
github.com/thediveo/morbyd v0.13.1
github.com/thediveo/namspill v0.1.6
github.com/thediveo/netdb v1.1.2
github.com/thediveo/notwork v1.6.2
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v25.0.4+incompatible h1:DatRkJ+nrFoYL2HZUzjM5Z5sAmcA5XGp+AW0oEw2+cA=
github.com/docker/cli v25.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v27.1.0+incompatible h1:rEHVQc4GZ0MIQKifQPHSFGV/dVgaZafgRf8fCPtDYBs=
github.com/docker/docker v27.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
Expand Down Expand Up @@ -302,8 +302,8 @@ github.com/thediveo/ioctl v0.9.3 h1:DCxyUUY15z/Zezz+wf2nlbVf3yFh0nvfM7i7KnfgG8s=
github.com/thediveo/ioctl v0.9.3/go.mod h1:Ro3WW0UuPDh1QByEwNb/alva3ODM+GbRlb80u/LZU9o=
github.com/thediveo/lxkns v0.36.0 h1:2UrV8WKs2C9uKscHxAyw0M5u3y9eop8wsZrBAlqitbw=
github.com/thediveo/lxkns v0.36.0/go.mod h1:zYPNiNi6AK+ufDJYhivwn+OGj1hRHKF/uAEwuFpo+20=
github.com/thediveo/morbyd v0.13.0 h1:85K8vKU/Af/KnakjhOSGvdVJojxTSlFMfwO6vvlZ5t8=
github.com/thediveo/morbyd v0.13.0/go.mod h1:4g3wHYItuUdqIoIZmnOtifaI3X2PBqnCJpewBpLmlyY=
github.com/thediveo/morbyd v0.13.1 h1:mDQ27NzPXD5WIZ5t79QQazcj0tFat6HmQhbMveJ6s/A=
github.com/thediveo/morbyd v0.13.1/go.mod h1:4g3wHYItuUdqIoIZmnOtifaI3X2PBqnCJpewBpLmlyY=
github.com/thediveo/namspill v0.1.6 h1:eD8puqhwIkBS78vrzJtY46eurHX0o6JIAqzgkRmMLl0=
github.com/thediveo/namspill v0.1.6/go.mod h1:oRhr6rRg9z5pHuHckecgP4l9qN4YECZ22TtGs9Ma51E=
github.com/thediveo/netdb v1.1.2 h1:XdLx/YJPutxrSkPYtmCAIY5sgAvxtkS1Tz+Z0UX2I+U=
Expand Down
11 changes: 9 additions & 2 deletions network/portfwd/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ func PortForwardings(tables nufftables.TableMap, family nufftables.TableFamily)
if nattable == nil {
return nil
}
return grabPortForwardings(nattable)
}

// grabPortForwardings is a convenience helper to wire up the individual port
// forwarding detectors in a single place, making maintenance easier for both
// PROD and TEST.
func grabPortForwardings(nattable *nufftables.Table) []*portfinder.ForwardedPortRange {
forwardedPorts := forwardedPortsMk1(nattable)
forwardedPorts = append(forwardedPorts, forwardedPortsMk2(nattable)...)
forwardedPorts = append(forwardedPorts, forwardedPortsMk3(nattable)...)
Expand Down Expand Up @@ -69,7 +76,7 @@ func forwardedPortsInChainMk2(chain *nufftables.Chain) []*portfinder.ForwardedPo
family := chain.Table.Family
forwardedPorts := []*portfinder.ForwardedPortRange{}
for _, rule := range chain.Rules {
exprs, proto := nftget.L4ProtoTcpUdp(rule.Exprs)
exprs, proto := nftget.MetaL4ProtoTcpUdp(rule.Exprs)
exprs, origIP := nftget.OptionalIPv46(exprs, family)
exprs, port := nufftables.OfTypeTransformed(exprs, nftget.Port)
exprs, dnat := dsl.TargetDNAT(exprs)
Expand Down Expand Up @@ -112,7 +119,7 @@ func forwardedPortsInChainMk3(chain *nufftables.Chain) []*portfinder.ForwardedPo
forwardedPorts := []*portfinder.ForwardedPortRange{}
for _, rule := range chain.Rules {
exprs, origIP := nftget.OptionalDestIPv46(rule.Exprs, family)
exprs, proto := nftget.L4ProtoTcpUdp(exprs)
exprs, proto := nftget.PayloadL4ProtoTcpUdp(exprs)
exprs, port := nftget.PayloadPort(exprs)
exprs, dnat := dsl.TargetDNAT(exprs)
if exprs == nil || dnat.Flags&dnatWithIPsAndPorts != dnatWithIPsAndPorts || port == 0 {
Expand Down
17 changes: 6 additions & 11 deletions network/portfwd/docker/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,12 @@ import (
"github.com/thediveo/morbyd/session"
"github.com/thediveo/notwork/netns"
"github.com/thediveo/nufftables"
"github.com/thediveo/nufftables/portfinder"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/thediveo/success"
)

func fwports(nattable *nufftables.Table) []*portfinder.ForwardedPortRange {
forwardedPorts := forwardedPortsMk1(nattable)
forwardedPorts = append(forwardedPorts, forwardedPortsMk2(nattable)...)
forwardedPorts = append(forwardedPorts, forwardedPortsMk3(nattable)...)
return forwardedPorts
}

var _ = Describe("Docker port forwarding", Ordered, func() {

var cntrPID int
Expand Down Expand Up @@ -76,7 +68,7 @@ var _ = Describe("Docker port forwarding", Ordered, func() {
nattable := tables.Table("nat", nufftables.TableFamilyIPv4)
Expect(nattable).NotTo(BeNil())
Expect(nattable.ChainsByName).NotTo(BeEmpty())
forwardedPorts := fwports(nattable)
forwardedPorts := grabPortForwardings(nattable)
Expect(forwardedPorts).To(ContainElement(And(
HaveField("Protocol", "tcp"),
HaveField("IP", net.ParseIP("127.0.0.1").To4()),
Expand All @@ -103,8 +95,11 @@ var _ = Describe("Docker port forwarding", Ordered, func() {
nattable := tables.Table("nat", nufftables.TableFamilyIPv4)
Expect(nattable).NotTo(BeNil())
Expect(nattable.ChainsByName).NotTo(BeEmpty())
forwardedPorts := fwports(nattable)
Expect(forwardedPorts).To(ContainElements(
forwardedPorts := grabPortForwardings(nattable)
// Ensure to exactly match in order to catch any false positives; this
// is possible in this case because we're looking at the nft inside the
// container and thus know what should be there and what shouldn't.
Expect(forwardedPorts).To(ConsistOf(
And(
HaveField("Protocol", "tcp"),
HaveField("IP", net.ParseIP("127.0.0.11").To4()),
Expand Down
18 changes: 11 additions & 7 deletions network/portfwd/nftget/l4proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ import (
"golang.org/x/sys/unix"
)

// L4ProtoTcpUdp returns the transport layer protocol name checked for from
// either a Meta/Cmp twin-expression or a Payload/Cmp twin-expression, together
// with the remaining expressions; otherwise, it returns nil.
func L4ProtoTcpUdp(exprs nufftables.Expressions) (nufftables.Expressions, string) {
if exprs, proto := nufftables.PrefixedOfTypeTransformed(exprs, isMetaL4Proto, TcpUdp); exprs != nil {
return exprs, proto
}
// MetaL4ProtoTcpUdp returns the transport layer protocol name checked for from
// a Meta/Cmp twin-expression, together with the remaining expressions;
// otherwise, it returns nil.
func MetaL4ProtoTcpUdp(exprs nufftables.Expressions) (nufftables.Expressions, string) {
return nufftables.PrefixedOfTypeTransformed(exprs, isMetaL4Proto, TcpUdp)
}

// PayloadL4ProtoTcpUdp returns the transport layer protocol name checked for
// from a Payload/Cmp twin-expression, together with the remaining expressions;
// otherwise, it returns nil.
func PayloadL4ProtoTcpUdp(exprs nufftables.Expressions) (nufftables.Expressions, string) {
return nufftables.PrefixedOfTypeTransformed(exprs, isPayloadIPv4L4Proto, TcpUdp)
}

Expand Down
2 changes: 1 addition & 1 deletion network/portfwd/nftget/l4proto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ var _ = Describe("nftables L4 proto getter", func() {
if cmp != nil {
exprs = append(exprs, cmp)
}
exprs, protoname := L4ProtoTcpUdp(exprs)
exprs, protoname := MetaL4ProtoTcpUdp(exprs)
if expectedName == "" {
Expect(exprs).To(BeNil())
} else {
Expand Down
36 changes: 3 additions & 33 deletions webui/src/components/nifbadge/NifBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { Button, styled, SvgIconProps } from '@mui/material'
import HearingIcon from '@mui/icons-material/Hearing'

import { DormantIcon, DownIcon, LowerLayerDownIcon, UpIcon } from 'icons/operstates'
import { BridgeIcon, BridgeInternalIcon, DummyIcon, HardwareNicIcon, HardwareNicPFIcon, HardwareNicVFIcon, MacvlanIcon, MacvlanMasterIcon, NicIcon, OverlayIcon, TapIcon, TunIcon, VethIcon } from 'icons/nifs'

import { AddressFamily, AddressFamilySet, GHOSTWIRE_LABEL_ROOT, NetworkInterface, nifId, orderAddresses, SRIOVRole } from 'models/gw'
import { OperationalState } from 'models/gw'
Expand All @@ -19,6 +18,7 @@ import { relationClassName } from 'utils/relclassname'
import { rgba } from 'utils/rgba'
import { TargetCapture } from 'components/targetcapture'
import { NifCheckbox } from 'components/nifcheckbox'
import { NifIcon } from 'components/nificon'


// The outer span holding together an optional "hardware" NIC icon as well
Expand Down Expand Up @@ -174,33 +174,6 @@ const OperstateIndicator = styled('span')(({ theme }) => ({
[`&.${OperationalState.Dormant.toLowerCase()}`]: { color: theme.palette.operstate.dormant },
}))

const nifSRIOVIcons = {
[SRIOVRole.None]: HardwareNicIcon,
[SRIOVRole.PF]: HardwareNicPFIcon,
[SRIOVRole.VF]: HardwareNicVFIcon,
}

// Known network interface type icons, indexed by the kind property of network
// interface objects (and directly taken from what Linux' RTNETLINK tells us).
const nifTypeIcons: { [key: string]: (props: SvgIconProps) => JSX.Element } = {
'bridge': BridgeIcon,
'dummy': DummyIcon,
'macvlan': MacvlanIcon,
'tap': TapIcon,
'tun': TunIcon,
'veth': VethIcon,
'vxlan': OverlayIcon,
}

const nifIcon = (nif: NetworkInterface) => {
if (GHOSTWIRE_LABEL_ROOT + 'bridge/internal' in nif.labels) {
return BridgeInternalIcon
}
return (nif.tuntapDetails && nifTypeIcons[nif.tuntapDetails.mode]) ||
(nif.macvlans && MacvlanMasterIcon) ||
nifTypeIcons[nif.kind] || NicIcon
}

const operStateIcons: { [key: string]: (props: SvgIconProps) => JSX.Element } = {
[OperationalState.Unknown]: UpIcon,
[OperationalState.Dormant]: DormantIcon,
Expand Down Expand Up @@ -343,11 +316,10 @@ export const NifBadge = ({
const alias = (nif.alias && nif.alias !== "") && <> (~<span className="alias">{nif.alias}</span>)</>
const vid = (nif.vlanDetails) && <> VID&nbsp;{nif.vlanDetails.vid}</>

const NifIcon = nifIcon(nif)
const OperstateIcon = operStateIcons[nif.operstate]

const content = <>
<NifIcon />
<NifIcon nif={nif} />
<OperstateIndicator
as={OperstateIcon}
className={nif.operstate.toLowerCase()}
Expand Down Expand Up @@ -434,8 +406,6 @@ export const NifBadge = ({
}
}

const HWIcon = nifSRIOVIcons[nif.sriovrole || SRIOVRole.None]

// With lots of information prepared we can finally render the badge,
// optionally wrapped into a tooltip with some detail information about
// the network interface.
Expand All @@ -451,7 +421,7 @@ export const NifBadge = ({
: <Capture className="nifcaptureicon alignright" target={nif} />)}
{nif.isPhysical &&
<HWNif className="nifbagdeicon">
<HWIcon />
<NifIcon nif={nif} considerPhysical />
</HWNif>
}
{nif.isPromiscuous &&
Expand Down
30 changes: 30 additions & 0 deletions webui/src/components/nifhwicon/NifHWIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// (c) Siemens AG 2024
//
// SPDX-License-Identifier: MIT

import React from 'react'

import { SvgIconProps } from '@mui/material'

import HardwareNicIcon from 'icons/nifs/HardwareNic'
import HardwareNicPFIcon from 'icons/nifs/HardwareNicPF'
import HardwareNicVFIcon from 'icons/nifs/HardwareNicVF'
import { NetworkInterface, SRIOVRole } from 'models/gw/nif'

const nifSRIOVIcons = {
[SRIOVRole.None]: HardwareNicIcon,
[SRIOVRole.PF]: HardwareNicPFIcon,
[SRIOVRole.VF]: HardwareNicVFIcon,
}

export interface NifHWIconProps extends SvgIconProps {
/** network interface object describing a network interface in detail. */
nif: NetworkInterface
}

export const NifHWIcon = ({ nif, ...props }: NifHWIconProps) => {
const HWIcon = nifSRIOVIcons[nif.sriovrole || SRIOVRole.None]
return <HWIcon {...props} />
}

export default NifHWIcon
1 change: 1 addition & 0 deletions webui/src/components/nifhwicon/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { NifHWIcon } from './NifHWIcon'
55 changes: 55 additions & 0 deletions webui/src/components/nificon/NifIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// (c) Siemens AG 2024
//
// SPDX-License-Identifier: MIT

import React from 'react'

import { SvgIconProps } from '@mui/material'

import { NetworkInterface } from 'models/gw/nif'
import BridgeIcon from 'icons/nifs/Bridge'
import DummyIcon from 'icons/nifs/Dummy'
import MacvlanIcon from 'icons/nifs/Macvlan'
import TapIcon from 'icons/nifs/Tap'
import TunIcon from 'icons/nifs/Tun'
import VethIcon from 'icons/nifs/Veth'
import { BridgeInternalIcon, MacvlanMasterIcon, NicIcon, OverlayIcon } from 'icons/nifs'
import { GHOSTWIRE_LABEL_ROOT } from 'models/gw/model'
import { NifHWIcon } from 'components/nifhwicon'

// Known network interface type icons, indexed by the kind property of network
// interface objects (and directly taken from what Linux' RTNETLINK tells us).
const nifTypeIcons: { [key: string]: (props: SvgIconProps) => JSX.Element } = {
'bridge': BridgeIcon,
'dummy': DummyIcon,
'macvlan': MacvlanIcon,
'tap': TapIcon,
'tun': TunIcon,
'veth': VethIcon,
'vxlan': OverlayIcon,
}

export interface NifIconProps extends SvgIconProps {
/** network interface object describing a network interface in detail. */
nif: NetworkInterface
/** show HW NIC icon instead of generic icon if nic is "physical". */
considerPhysical?: boolean
}

export const NifIcon = ({ nif, considerPhysical, ...props }: NifIconProps) => {
if (!nif) {
return <></>
}
if (considerPhysical && nif.isPhysical) {
return <NifHWIcon nif={nif} {...props} />
}
if (nif.labels && GHOSTWIRE_LABEL_ROOT + 'bridge/internal' in nif.labels) {
return <BridgeInternalIcon {...props} />
}
const Icon = (nif.tuntapDetails && nifTypeIcons[nif.tuntapDetails.mode]) ||
(nif.macvlans && MacvlanMasterIcon) ||
nifTypeIcons[nif.kind] || NicIcon
return <Icon {...props} />
}

export default NifIcon
1 change: 1 addition & 0 deletions webui/src/components/nificon/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { NifIcon } from './NifIcon'
20 changes: 15 additions & 5 deletions webui/src/components/nifinfomodal/NifInfoModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@ import { styled } from '@mui/material'
import { NetworkInterface } from 'models/gw'
import { Alert, Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Snackbar, Tooltip } from '@mui/material'
import ClearIcon from '@mui/icons-material/Clear'
import LanIcon from '@mui/icons-material/Lan'
import { ContentCopy } from '@mui/icons-material'
import CloseIcon from '@mui/icons-material/Close'

const NifDialogTitle = styled(DialogTitle)(({theme}) => ({
import { NifIcon } from 'components/nificon'

const NifDialogTitle = styled(DialogTitle)(({ theme }) => ({
'& .close': {
position: 'relative',
right: theme.spacing(-1),
top: theme.spacing(-0.25),
top: theme.spacing(-0.5),
},
'& .nificon.MuiSvgIcon-root': {
position: 'relative',
top: theme.spacing(0.5),
},
}))

Expand Down Expand Up @@ -116,7 +121,12 @@ export const NifInfoModalProvider = ({ children }: NifInfoModalProviderProps) =>
onClose={handleClose}
>
<NifDialogTitle>
<LanIcon fontSize="inherit" />&nbsp;Network Interface Information
<NifIcon
nif={nif}
considerPhysical
fontSize="medium"
className="nificon"
/>&nbsp;Network Interface Information
<CloseButton
className="close"
aria-label="close"
Expand All @@ -128,7 +138,7 @@ export const NifInfoModalProvider = ({ children }: NifInfoModalProviderProps) =>
<Contents dividers>
<Details>
{prop('interface name', nif.name)}
{prop('type/kind', nif.kind || '(virtual) hardware')}
{prop('type/kind', nif.kind ? `virtual ${nif.kind}` : '(virtualized) hardware')}
{prop('driver', nif.driverinfo.driver)}
{prop('firmware version', nif.driverinfo.fwversion !== 'N/A' && nif.driverinfo.fwversion)}
{prop('ext ROM version', nif.driverinfo.eromversion)}
Expand Down
Loading