systemd 네트워크화 및/또는 가상 네트워크 인터페이스에서 (원시) 패킷 수신 차단 문제를 해결하시겠습니까?

systemd 네트워크화 및/또는 가상 네트워크 인터페이스에서 (원시) 패킷 수신 차단 문제를 해결하시겠습니까?

기본적으로 하나의 MACVLAN에서 다른 MACVLAN(가상) 네트워크 인터페이스로 원시 이더넷 패킷을 보내는 일부 단위 테스트 코드를 작업하는 동안 테스트 코드가 첫 번째 MACVLAN에서 두 번째로 전송된 패킷을 수신할 수 없는 경우가 대부분이라는 사실을 발견했습니다. MACVLAN의 패킷입니다. Wireshark를 사용하면 패킷이 첫 번째 MACVLAN을 떠나는 것을 볼 수 있지만 두 번째 MACVLAN에 도달하지 않거나 원시 소켓에서 수신 대기하지 않습니다. 몇 가지 이상한 경우에만 패킷이 통과하며 테스트 코드는 변경되지 않습니다.

호스트 시스템은 Ubuntu 22.10(커널 5.19.0-38-일반)입니다.체계그리고네트워크 관리자.

얼마 후부터 systemd-resolved, systemd-networkd 및 네트워크 관리자가 나의 의심을 불러일으켰습니다. 자체 격리된 임시 네트워크 네임스페이스에서 테스트를 실행함으로써 이러한 호스트 서비스에서 연결할 수 없는 범위에서 테스트가 항상 올바르게 성공했는지 확인할 수 있었습니다.

네트워크 관리자를 의심합니다. nmcli device status가상 가상 인터페이스와 MACVLAN 인터페이스가 "관리되지 않는다"고 들었음에도 불구하고 저는 발견했습니다.https://developer-old.gnome.org/NetworkManager/stable/NetworkManager.conf.html그런 다음 관리되지 않는 장치에 와일드카드를 추가합니다.

[keyfile]
unmanaged-devices=interface-name:docker*;interface-name:br-*;interface-name:veth*;interface-name:mcvl-*;interface-name:dumy-*

불행하게도 이 방법은 상황을 개선하지 못했고 테스트를 실행할 때마다 테스트가 여전히 실패했습니다(Network Manager를 여러 번 다시 시작하고 구성 파일이 올바른지 확인한 후에도).

Wireshark에서 MDNS가 브로드캐스트해서는 안 되는 MACVLAN 네트워크 인터페이스에서 브로드캐스트하고 있음을 발견했습니다. 가상 네트워크 인터페이스, 특히 더미, MACVLAN 및 VETH 네트워크 인터페이스에서 더러운 발을 얻지 않도록 systemd의 네트워크 및 해결에 어떻게 알릴 수 있습니까? 구성 옵션을 검색했지만 적합한 옵션을 찾을 수 없습니다.

처음에 노출되어서는 안 되는 것들로부터 시스템 구성요소를 멀리하는 방법에 대해 알고 계시나요?

다음은 이러한 상황을 재현한 Ginkgo/Gomega 기반의 단위 테스트입니다.

package pingpong

import (
    "bytes"
    "context"
    "fmt"
    "net"
    "os"
    "strings"
    "time"

    "github.com/mdlayher/ethernet"
    "github.com/mdlayher/packet"
    "github.com/thediveo/notwork/dummy"
    "github.com/thediveo/notwork/link"
    "github.com/thediveo/notwork/macvlan"
    "github.com/thediveo/notwork/netns"
    "github.com/vishvananda/netlink"

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

func TestPingPong(t *testing.T) {
    RegisterFailHandler(Fail)
    RunSpecs(t, "pingpong package")
}

const (
    experimentalEthType = 0xffee // something (hopefully) unused
    pings               = 10
    pingInterval        = 100 * time.Millisecond
)

var payload = bytes.Repeat([]byte("HELO"), 100)

var _ = Describe("pingponging netdevs", Ordered, func() {

    BeforeAll(func() {
        if os.Geteuid() != 0 {
            Skip("needs root")
        }
    })

    DescribeTable("virtual network pingpong",
        func(ctx context.Context, dropall bool) {
            // By("creating a new network namespace")
            // defer netns.EnterTransientNetns()()

            By("creating two MACVLANs connected via a dummy network interface")
            dummy := dummy.NewTransientUp()
            macvlan1 := macvlan.NewTransient(dummy)
            netlink.LinkSetUp(macvlan1)
            macvlan2 := macvlan.NewTransient(dummy)
            netlink.LinkSetUp(macvlan2)

            macvlan1 = Successful(netlink.LinkByIndex(macvlan1.Attrs().Index))
            mac1 := macvlan1.Attrs().HardwareAddr

            macvlan2 = Successful(netlink.LinkByIndex(macvlan2.Attrs().Index))
            mac2 := macvlan2.Attrs().HardwareAddr

            Expect(mac1).NotTo(Equal(mac2))

            By(fmt.Sprintf("waiting for MACVLANs (%s-%s, %s-%s) to become operationally UP",
                macvlan1.Attrs().Name, macvlan1.Attrs().HardwareAddr.String(),
                macvlan2.Attrs().Name, macvlan2.Attrs().HardwareAddr.String()))
            link.EnsureUp(macvlan1)
            link.EnsureUp(macvlan2)

            By("opening data-link layer sockets")
            txconn := Successful(packet.Listen(
                &net.Interface{Index: macvlan1.Attrs().Index}, packet.Raw, experimentalEthType, nil))
            defer txconn.Close()
            rxconn := Successful(packet.Listen(
                &net.Interface{Index: macvlan2.Attrs().Index}, packet.Raw, experimentalEthType, nil))
            defer rxconn.Close()

            ctx, cancel := context.WithCancel(ctx)
            defer cancel()

            By("sending data-link layer PDUs")
            go func() {
                defer cancel()
                defer GinkgoRecover()
                f := ethernet.Frame{
                    Destination: mac2,
                    Source:      mac1,
                    EtherType:   experimentalEthType,
                    Payload:     payload,
                }
                frame := Successful(f.MarshalBinary())
                toAddr := packet.Addr{HardwareAddr: mac2}
                for i := 0; i < pings; i++ {
                    By("sending something...")
                    _, err := txconn.WriteTo(frame, &toAddr)
                    Expect(err).NotTo(HaveOccurred())
                    select {
                    case <-ctx.Done():
                        return
                    case <-time.After(pingInterval):
                    }
                }
            }()

            By("receiving data-link layer PDUs (or not)")
            received := 0
        receive:
            for {
                buffer := make([]byte, 1500)
                rxconn.SetReadDeadline(time.Now().Add(1 * time.Second))
                n, fromAddr, err := rxconn.ReadFrom(buffer)
                select {
                case <-ctx.Done():
                    break receive
                default:
                }
                if err != nil && dropall && strings.Contains(err.Error(), "i/o timeout") {
                    continue
                }
                Expect(err).NotTo(HaveOccurred())
                By("...received something")
                f := ethernet.Frame{}
                Expect(f.UnmarshalBinary(buffer[:n])).To(Succeed())
                Expect(f.EtherType).To(Equal(ethernet.EtherType(experimentalEthType)))
                Expect(fromAddr.(*packet.Addr).HardwareAddr).To(Equal(mac1))
                Expect(f.EtherType).To(Equal(ethernet.EtherType(experimentalEthType)))
                Expect(len(f.Payload)).To(BeNumerically(">=", len(payload)))
                Expect(f.Payload[:len(payload)]).To(Equal(payload))
                received++
            }

            if !dropall {
                Expect(received).To(BeNumerically(">=", (2*pings)/3), "too much packet loss")
            } else {
                Expect(received).To(BeZero())
            }

        },
        Entry("receives passed-on packets", false),
    )

})

답변1

MAC 주소에 문제가 있을 수 있다는 올바른 생각을 얻는 데 시간이 좀 걸렸으므로 다음 보안 기대치를 추가한 후 즉시 실행했습니다.

mac2 := Successful(netlink.LinkByIndex(macvlan2.Attrs().Index)).
    Attrs().HardwareAddr

// ... something going on here

mac2now := Successful(netlink.LinkByIndex(macvlan2.Attrs().Index)).
    Attrs().HardwareAddr
Expect(mac2now).To(Equal(mac2))

특히 일부 구성을 살펴보세요 *.link. 특히 다음과 같은 /usr/lib/systemd/network/포괄적인 기본 구성이 있습니다 99-default.link.

[Match]
OriginalName=*

[Link]
NamePolicy=keep kernel database onboard slot path
AlternativeNamesPolicy=database onboard slot path
MACAddressPolicy=persistent

그렇다면 MACAddressPolicy=persistent실제로는 무엇을 합니까? 이것웹 링크 문서설명하다:

하드웨어에 영구 MAC 주소(대부분의 하드웨어가 있음)가 있고 이를 커널에서 사용하는 경우 아무 작업도 수행되지 않습니다. 그렇지 않으면 새로운 MAC 주소가 생성됩니다. 이 주소는 지정된 시스템과 장치를 부팅할 때마다 동일하지만 그 외에는 무작위입니다.

따라서 networkd(또는 실제로 그렇습니까 udevd?) 새로운 MACVLAN netdev는 원래 MAC 주소를 다른 주소로 대체합니다.

MACVLAN이 나타난 후 시간이 좀 걸리기 때문에 단위 테스트에서는 원래 MAC 주소를 쿼리했으며 테스트가 이더넷 패킷 전송을 시작할 때쯤에는 두 번째 MACVLAN의 MAC 주소가 변경되었으므로 원래 대상 MAC이 이것을 조금도 모르고 테스트를 통해 변경되었습니다.

수정 사항은 최소한 선택적으로 로 되돌리는 것입니다 MACAddressPolicy=. none예를 들면 다음과 같습니다.

# /etc/systemd/network/00-notwork.link
[Match]
Kind=macvlan
OriginalName=mcvl-*

[Link]
Description="keep systemd's sticky fingers off test netdevs"
MACAddressPolicy=none

관련 정보