기본적으로 하나의 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