systemdネットワーク化および/または仮想ネットワークインターフェイスで(生)パケット受信ブロックの問題を解決しますか?

systemdネットワーク化および/または仮想ネットワークインターフェイスで(生)パケット受信ブロックの問題を解決しますか?

デフォルトでは、あるMACVLANから別のMACVLAN(仮想)ネットワークインターフェイスにrawイーサネットパケットを送信するいくつかのユニットテストコードを操作していますが、テストコードが最初のMACVLANから2番目に送信されたパケットを受信できないことがほとんどです。を発見しました。 MACVLAN のパケットです。 Wireshark を使用すると、パケットが最初の MACVLAN を離れるのを確認できますが、2 番目の MACVLAN に到達しないか、raw ソケットでリッスンしません。いくつかの奇妙な場合にのみパケットが通過し、テストコードは変更されません。

ホストシステムはUbuntu 22.10(カーネル5.19.0-38-typ)です。システムそしてネットワーク管理者

しばらくしてから、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、実際には何をしますか?これWebリンク文書説明する:

ハードウェアに永続的なMACアドレス(ほとんどのハードウェアがある)があり、それをカーネルで使用している場合、何もしません。それ以外の場合、新しいMACアドレスが生成されます。このアドレスは、指定されたシステムとデバイスを起動するたびに同じですが、それ以外はランダムです。

したがってnetworkd(または実際にはそうですかudevd?)新しいMACVLAN netdevは元のMACアドレスを別のアドレスに置き換えます。

MACVLANが表示されてから少し時間がかかったため、単体テストでは元のMACアドレスを照会し、テストがイーサネットパケットの送信を開始した頃は2番目の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

おすすめ記事