From 17fe0cdae38b2d020910dc599f431b5782172951 Mon Sep 17 00:00:00 2001 From: Kusakabe Si Date: Thu, 9 Dec 2021 20:23:02 +0000 Subject: [PATCH] fastgen static config, update readme --- README_zh.md | 2 - device/peer.go | 4 +- device/receive.go | 81 ++-- device/receivesendproc.go | 13 +- device/send.go | 6 +- .../static_mode/{n1.yaml => EgNet_edge1.yaml} | 14 +- .../static_mode/{n2.yaml => EgNet_edge2.yaml} | 22 +- .../static_mode/{n3.yaml => EgNet_edge3.yaml} | 30 +- .../static_mode/{n4.yaml => EgNet_edge4.yaml} | 22 +- .../static_mode/{n5.yaml => EgNet_edge5.yaml} | 14 +- .../static_mode/{n6.yaml => EgNet_edge6.yaml} | 14 +- example_config/static_mode/README_zh.md | 358 +++++++----------- example_config/static_mode/genstatic.yaml | 22 +- example_config/super_mode/EGS03.png | Bin 46645 -> 44182 bytes example_config/super_mode/EGS08.png | Bin 0 -> 23987 bytes .../super_mode/{n1.yaml => Node_edge001.yaml} | 38 +- .../super_mode/{n2.yaml => Node_edge002.yaml} | 40 +- .../{n100.yaml => Node_edge100.yaml} | 38 +- example_config/super_mode/Node_super.yaml | 55 +++ example_config/super_mode/README_zh.md | 308 +++++++++++---- example_config/super_mode/gensuper.yaml | 12 +- example_config/super_mode/s1.yaml | 47 --- .../super_mode/{ => testfd}/n1_fd.yaml | 0 .../{ => testfd}/n1_test_fd_mode.py | 0 .../{ => testfd}/n1_test_fd_mode2.go | 0 .../{ => testfd}/n1_test_fd_mode2.py | 0 gencfg/gencfgNM.go | 26 +- gencfg/gencfgSM.go | 3 +- gencfg/types.go | 1 - main_edge.go | 3 + main_httpserver.go | 6 +- main_super.go | 3 + mtypes/config.go | 10 +- path/header.go | 58 +++ path/path.go | 4 +- tap/tap_stdio.go | 5 +- 36 files changed, 719 insertions(+), 540 deletions(-) rename example_config/static_mode/{n1.yaml => EgNet_edge1.yaml} (87%) rename example_config/static_mode/{n2.yaml => EgNet_edge2.yaml} (79%) rename example_config/static_mode/{n3.yaml => EgNet_edge3.yaml} (79%) rename example_config/static_mode/{n4.yaml => EgNet_edge4.yaml} (79%) rename example_config/static_mode/{n5.yaml => EgNet_edge5.yaml} (87%) rename example_config/static_mode/{n6.yaml => EgNet_edge6.yaml} (87%) create mode 100644 example_config/super_mode/EGS08.png rename example_config/super_mode/{n1.yaml => Node_edge001.yaml} (67%) rename example_config/super_mode/{n2.yaml => Node_edge002.yaml} (66%) rename example_config/super_mode/{n100.yaml => Node_edge100.yaml} (67%) create mode 100644 example_config/super_mode/Node_super.yaml delete mode 100644 example_config/super_mode/s1.yaml rename example_config/super_mode/{ => testfd}/n1_fd.yaml (100%) rename example_config/super_mode/{ => testfd}/n1_test_fd_mode.py (100%) rename example_config/super_mode/{ => testfd}/n1_test_fd_mode2.go (100%) rename example_config/super_mode/{ => testfd}/n1_test_fd_mode2.py (100%) diff --git a/README_zh.md b/README_zh.md index 2620478..8efc3d6 100644 --- a/README_zh.md +++ b/README_zh.md @@ -82,8 +82,6 @@ VPN起來以後,自己手動加ip也行 $ ./etherguard-go -mode gencfg -cfgmode super -config example_config/super_mode/gensuper.yaml ``` - - 把一個super,2個edge分別搬去三台機器 或是2台機器,super和edge可以是同一台 diff --git a/device/peer.go b/device/peer.go index 88e6624..4e87250 100644 --- a/device/peer.go +++ b/device/peer.go @@ -154,7 +154,7 @@ type Peer struct { LastPacketReceivedAdd1Sec atomic.Value // *time.Time - SingleWayLatency float64 + SingleWayLatency atomic.Value stopping sync.WaitGroup // routines pending stop ID mtypes.Vertex @@ -245,7 +245,7 @@ func (device *Device) NewPeer(pk NoisePublicKey, id mtypes.Vertex, isSuper bool, peer.cookieGenerator.Init(pk) peer.device = device peer.endpoint_trylist = NewEndpoint_trylist(peer, mtypes.S2TD(device.EdgeConfig.DynamicRoute.PeerAliveTimeout)) - peer.SingleWayLatency = mtypes.Infinity + peer.SingleWayLatency.Store(mtypes.Infinity) peer.queue.outbound = newAutodrainingOutboundQueue(device) peer.queue.inbound = newAutodrainingInboundQueue(device) peer.queue.staged = make(chan *QueueOutboundElement, QueueStagedSize) diff --git a/device/receive.go b/device/receive.go index 3e7b584..52584b2 100644 --- a/device/receive.go +++ b/device/receive.go @@ -470,39 +470,76 @@ func (peer *Peer) RoutineSequentialReceiver() { packet_type = elem.Type if device.IsSuperNode { - switch dst_nodeID { - case mtypes.NodeID_AllPeer: + if packet_type.IsControl_Edge2Super() { should_process = true + } else { + device.log.Errorf("received unsupported packet_type %v from %v %v", packet_type, src_nodeID, peer.endpoint.DstToString()) + goto skip + } + switch dst_nodeID { case mtypes.NodeID_SuperNode: should_process = true default: - device.log.Errorf("Invalid dst_nodeID received. Check your code for bug") + device.log.Errorf("received invalid dst_nodeID %v from %v %v", dst_nodeID, src_nodeID, peer.endpoint.DstToString()) + goto skip } } else { - switch dst_nodeID { - case mtypes.NodeID_Boardcast: - should_receive = true - should_transfer = true - case mtypes.NodeID_SuperNode: - should_process = true - case mtypes.NodeID_AllPeer: - packet := elem.packet[path.EgHeaderLen:] //true packet - if device.CheckNoDup(packet) { + // Set should_receive and should_process + if packet_type.IsNormal() { + switch dst_nodeID { + case device.ID: + should_receive = true + case mtypes.NodeID_Broadcast: + should_receive = true + case mtypes.NodeID_AllPeer: + should_receive = true + } + } + if packet_type.IsControl_Edge2Edge() { + switch dst_nodeID { + case device.ID: should_process = true + case mtypes.NodeID_Broadcast: + should_process = true + case mtypes.NodeID_AllPeer: + should_process = true + } + } + if packet_type.IsControl_Super2Edge() { + if peer.ID == mtypes.NodeID_SuperNode { + switch dst_nodeID { + case device.ID: + should_process = true + case mtypes.NodeID_SuperNode: + should_process = true + } + + } else { + device.log.Errorf("received ServerUpdate packet from non supernode %v %v", src_nodeID, peer.endpoint.DstToString()) + goto skip + } + } + + // Set should_transfer + switch dst_nodeID { + case mtypes.NodeID_Broadcast: + should_transfer = true + case mtypes.NodeID_AllPeer: + packet := elem.packet[path.EgHeaderLen:] //packet body + if device.CheckNoDup(packet) { should_transfer = true } else { - should_process = false - should_transfer = false if device.LogLevel.LogTransit { - fmt.Printf("Transit: Duplicate packet received from %d through %d , src_nodeID = %d . Dropeed.\n", peer.ID, device.ID, src_nodeID) + fmt.Printf("Transit: Duplicate packet received from %d through %d , src_nodeID = %d . Dropped.\n", peer.ID, device.ID, src_nodeID) } + goto skip } case device.ID: - if packet_type == path.NormalPacket { - should_receive = true - } else { - should_process = true - } + should_transfer = false + case mtypes.NodeID_SuperNode: + should_transfer = false + case mtypes.NodeID_Invalid: + should_transfer = false default: if device.graph.Next(device.ID, dst_nodeID) != mtypes.NodeID_Invalid { should_transfer = true @@ -517,7 +554,7 @@ func (peer *Peer) RoutineSequentialReceiver() { device.log.Verbosef("TTL is 0 %v", dst_nodeID) } else { EgHeader.SetTTL(l2ttl - 1) - if dst_nodeID == mtypes.NodeID_Boardcast { //Regular transfer algorithm + if dst_nodeID == mtypes.NodeID_Broadcast { //Regular transfer algorithm device.TransitBoardcastPacket(src_nodeID, peer.ID, elem.Type, elem.packet, MessageTransportOffsetContent) } else if dst_nodeID == mtypes.NodeID_AllPeer { // Control Message will try send to every know node regardless the connectivity skip_list := make(map[mtypes.Vertex]bool) @@ -534,7 +571,7 @@ func (peer *Peer) RoutineSequentialReceiver() { if device.LogLevel.LogTransit { fmt.Printf("Transit: Transfer packet from %d through %d to %d\n", peer.ID, device.ID, peer_out.ID) } - device.SendPacket(peer_out, elem.Type, elem.packet, MessageTransportOffsetContent) + go device.SendPacket(peer_out, elem.Type, elem.packet, MessageTransportOffsetContent) } } } diff --git a/device/receivesendproc.go b/device/receivesendproc.go index 3f6fe2e..7de3eed 100644 --- a/device/receivesendproc.go +++ b/device/receivesendproc.go @@ -309,17 +309,20 @@ func (device *Device) server_process_Pong(peer *Peer, content mtypes.PongMsg) er func (device *Device) process_ping(peer *Peer, content mtypes.PingMsg) error { Timediff := device.graph.GetCurrentTime().Sub(content.Time).Seconds() - peer.SingleWayLatency = Timediff + NewTimediff := peer.SingleWayLatency.Load().(float64) + DR := NewTimediff * device.EdgeConfig.DynamicRoute.P2P.GraphRecalculateSetting.DampingResistance + NewTimediff = NewTimediff*DR + Timediff*(1-DR) + peer.SingleWayLatency.Store(NewTimediff) PongMSG := mtypes.PongMsg{ Src_nodeID: content.Src_nodeID, Dst_nodeID: device.ID, - Timediff: Timediff, + Timediff: NewTimediff, TimeToAlive: device.EdgeConfig.DynamicRoute.PeerAliveTimeout, AdditionalCost: device.EdgeConfig.DynamicRoute.AdditionalCost, } if device.EdgeConfig.DynamicRoute.P2P.UseP2P && time.Now().After(device.graph.NhTableExpire) { - device.graph.UpdateLatency(content.Src_nodeID, device.ID, PongMSG.Timediff, device.EdgeConfig.DynamicRoute.PeerAliveTimeout, device.EdgeConfig.DynamicRoute.AdditionalCost, true, false) + device.graph.UpdateLatencyMulti([]mtypes.PongMsg{PongMSG}, true, false) } body, err := mtypes.GetByte(&PongMSG) if err != nil { @@ -887,7 +890,7 @@ func (device *Device) RoutinePostPeerInfo(startchan <-chan struct{}) { RequestID: 0, Src_nodeID: device.ID, Dst_nodeID: id, - Timediff: peer.SingleWayLatency, + Timediff: peer.SingleWayLatency.Load().(float64), TimeToAlive: time.Since(*peer.LastPacketReceivedAdd1Sec.Load().(*time.Time)).Seconds() + device.EdgeConfig.DynamicRoute.PeerAliveTimeout, } pongs = append(pongs, pong) @@ -989,7 +992,7 @@ func (device *Device) RoutineSpreadAllMyNeighbor() { timeout := mtypes.S2TD(device.EdgeConfig.DynamicRoute.P2P.SendPeerInterval) for { device.process_RequestPeerMsg(mtypes.QueryPeerMsg{ - Request_ID: uint32(mtypes.NodeID_Boardcast), + Request_ID: uint32(mtypes.NodeID_Broadcast), }) time.Sleep(timeout) } diff --git a/device/send.go b/device/send.go index 5a51f00..0705036 100644 --- a/device/send.go +++ b/device/send.go @@ -256,9 +256,9 @@ func (device *Device) RoutineReadFromTUN() { dstMacAddr := tap.GetDstMacAddr(elem.packet[path.EgHeaderLen:]) // lookup peer if tap.IsNotUnicast(dstMacAddr) { - dst_nodeID = mtypes.NodeID_Boardcast + dst_nodeID = mtypes.NodeID_Broadcast } else if val, ok := device.l2fib.Load(dstMacAddr); !ok { //Lookup failed - dst_nodeID = mtypes.NodeID_Boardcast + dst_nodeID = mtypes.NodeID_Broadcast } else { dst_nodeID = val.(*IdAndTime).ID } @@ -275,7 +275,7 @@ func (device *Device) RoutineReadFromTUN() { continue } - if dst_nodeID != mtypes.NodeID_Boardcast { + if dst_nodeID != mtypes.NodeID_Broadcast { var peer *Peer next_id := device.graph.Next(device.ID, dst_nodeID) if next_id != mtypes.NodeID_Invalid { diff --git a/example_config/static_mode/n1.yaml b/example_config/static_mode/EgNet_edge1.yaml similarity index 87% rename from example_config/static_mode/n1.yaml rename to example_config/static_mode/EgNet_edge1.yaml index 5236178..bde9a9a 100644 --- a/example_config/static_mode/n1.yaml +++ b/example_config/static_mode/EgNet_edge1.yaml @@ -3,7 +3,7 @@ Interface: Name: tap1 VPPIFaceID: 1 VPPBridgeID: 4242 - MacAddrPrefix: 8E:AA:C8:4B + MacAddrPrefix: DA:21:10:81 IPv4CIDR: 192.168.76.0/24 IPv6CIDR: fd95:71cb:a3df:e586::/64 IPv6LLPrefix: fe80::a3df:0/112 @@ -12,17 +12,17 @@ Interface: SendAddr: 127.0.0.1:5001 L2HeaderMode: kbdbg NodeID: 1 -NodeName: Node1 +NodeName: EgNet1 PostScript: "" DefaultTTL: 200 L2FIBTimeout: 3600 -PrivKey: Fe9Q6K5L6xTVbx/zFc07tnQuo+pkeyrhfoTQ3BF5GRM= +PrivKey: LFFQqPBQ84x2AQ9BCI+0wG8nr+Y6yXHqhXkMCb4HCmg= ListenPort: 3001 LogLevel: LogLevel: error LogTransit: true - LogControl: true LogNormal: true + LogControl: true LogInternal: true LogNTP: true DynamicRoute: @@ -100,8 +100,8 @@ NextHopTable: ResetConnInterval: 86400 Peers: - NodeID: 2 - PubKey: MC8dHNj8u/RrK9iL6ln+AaGZWkMrDl+8aUoyxbsBZC4= - PSKey: kWDwCaC11UvYjfiXBMwYpR6Pujo1vaVW8JTusp1Kkrw= + PubKey: 0r2o9Hb36gYVgD3VSKCH18MVOZw0BvzcJ6TOTo6Cc1g= + PSKey: lFq67qp9LXL3PtlEJ3SGg3c/6++Ljy2I8i/k0Xcibvk= EndPoint: 127.0.0.1:3002 - PersistentKeepalive: 30 + PersistentKeepalive: 0 Static: true diff --git a/example_config/static_mode/n2.yaml b/example_config/static_mode/EgNet_edge2.yaml similarity index 79% rename from example_config/static_mode/n2.yaml rename to example_config/static_mode/EgNet_edge2.yaml index 85fa7bf..7293c34 100644 --- a/example_config/static_mode/n2.yaml +++ b/example_config/static_mode/EgNet_edge2.yaml @@ -3,7 +3,7 @@ Interface: Name: tap1 VPPIFaceID: 1 VPPBridgeID: 4242 - MacAddrPrefix: 8E:AA:C8:4B + MacAddrPrefix: DA:21:10:81 IPv4CIDR: 192.168.76.0/24 IPv6CIDR: fd95:71cb:a3df:e586::/64 IPv6LLPrefix: fe80::a3df:0/112 @@ -12,17 +12,17 @@ Interface: SendAddr: 127.0.0.1:5001 L2HeaderMode: kbdbg NodeID: 2 -NodeName: Node2 +NodeName: EgNet2 PostScript: "" DefaultTTL: 200 L2FIBTimeout: 3600 -PrivKey: WaeR98bFhy0rxw7unsaTo7aR2RmySo9l185IZqPWl1c= +PrivKey: M8SxYCTHCPES/yPqcKP4mr+AoMAx9sgUmk64FCJIv6k= ListenPort: 3002 LogLevel: LogLevel: error LogTransit: true - LogControl: true LogNormal: true + LogControl: true LogInternal: true LogNTP: true DynamicRoute: @@ -100,20 +100,20 @@ NextHopTable: ResetConnInterval: 86400 Peers: - NodeID: 1 - PubKey: iDhQA9pgL01Gb0MrPzbiV80P4Uv3+uY/s+wNTQye0Qo= - PSKey: kWDwCaC11UvYjfiXBMwYpR6Pujo1vaVW8JTusp1Kkrw= + PubKey: fnc7bfFTf7B23hwtARPpX14tCeZwNfDhGCJctBpMfA8= + PSKey: lFq67qp9LXL3PtlEJ3SGg3c/6++Ljy2I8i/k0Xcibvk= EndPoint: 127.0.0.1:3001 PersistentKeepalive: 0 Static: true - NodeID: 3 - PubKey: 1vg9bSyqjDL8oUdqpKXn/RR96cjTHPCBga0vaw86WzU= - PSKey: f2/34zUTLx8RP9cYisESoQlxz55oGZlTemqxv25VVa8= + PubKey: ymNpm430tiph3rMSVnQzDAxmK9+3jdcRFi6e96xmQUI= + PSKey: Y86PZY1ldgoyzPJxEqei5Vg5zlzkJkoO77LJfxV8alE= EndPoint: 127.0.0.1:3003 PersistentKeepalive: 0 Static: true - NodeID: 4 - PubKey: WkBYMUcQwWwUlDYh7uLu4YefiH1yXb3Tkf3wGEtE6HY= - PSKey: zkG1ywPqS4MiGofuWmxHHBs8YBlnYd04B4T9IhkbXYM= + PubKey: 9YSIkihv/+aeukOaWwsR9EKvO1DM9+5kN53a/osTpmA= + PSKey: QHyrrls0KT2dvBKtoWkZvq5NQ+Xyjm0YgbMUzCCRw34= EndPoint: 127.0.0.1:3004 - PersistentKeepalive: 30 + PersistentKeepalive: 0 Static: true diff --git a/example_config/static_mode/n3.yaml b/example_config/static_mode/EgNet_edge3.yaml similarity index 79% rename from example_config/static_mode/n3.yaml rename to example_config/static_mode/EgNet_edge3.yaml index 0ff078e..dbca261 100644 --- a/example_config/static_mode/n3.yaml +++ b/example_config/static_mode/EgNet_edge3.yaml @@ -3,7 +3,7 @@ Interface: Name: tap1 VPPIFaceID: 1 VPPBridgeID: 4242 - MacAddrPrefix: 8E:AA:C8:4B + MacAddrPrefix: DA:21:10:81 IPv4CIDR: 192.168.76.0/24 IPv6CIDR: fd95:71cb:a3df:e586::/64 IPv6LLPrefix: fe80::a3df:0/112 @@ -12,17 +12,17 @@ Interface: SendAddr: 127.0.0.1:5001 L2HeaderMode: kbdbg NodeID: 3 -NodeName: Node3 +NodeName: EgNet3 PostScript: "" DefaultTTL: 200 L2FIBTimeout: 3600 -PrivKey: vNUFWbZXySk2dTl5R9UEG73p+4IQmywvC1uQStf0vao= +PrivKey: cTO/GUSYHoj5mKczCyQu/ckPiDMEygkUbXcY3RafGEE= ListenPort: 3003 LogLevel: LogLevel: error LogTransit: true - LogControl: true LogNormal: true + LogControl: true LogInternal: true LogNTP: true DynamicRoute: @@ -99,21 +99,21 @@ NextHopTable: 5: 4 ResetConnInterval: 86400 Peers: -- NodeID: 5 - PubKey: AarP7cL6TVmFyHRZLXuxFGr5uQghsp57ydsJdj+lGzs= - PSKey: +TU8Zi8dr/UBqNyW/MYMgJiqkLnRbHW4ExQYQ2eNEag= - EndPoint: 127.0.0.1:3005 - PersistentKeepalive: 0 - Static: true - NodeID: 2 - PubKey: MC8dHNj8u/RrK9iL6ln+AaGZWkMrDl+8aUoyxbsBZC4= - PSKey: f2/34zUTLx8RP9cYisESoQlxz55oGZlTemqxv25VVa8= + PubKey: 0r2o9Hb36gYVgD3VSKCH18MVOZw0BvzcJ6TOTo6Cc1g= + PSKey: Y86PZY1ldgoyzPJxEqei5Vg5zlzkJkoO77LJfxV8alE= EndPoint: 127.0.0.1:3002 PersistentKeepalive: 0 Static: true - NodeID: 4 - PubKey: WkBYMUcQwWwUlDYh7uLu4YefiH1yXb3Tkf3wGEtE6HY= - PSKey: 8HN+PBGDKmBFCDlWIyNUFFYS4X6ONGXI32AIKXSnUqU= + PubKey: 9YSIkihv/+aeukOaWwsR9EKvO1DM9+5kN53a/osTpmA= + PSKey: SQyLomXDeknBIFezUKardOviAmNS+YZ1XTvZtYVlOtE= EndPoint: 127.0.0.1:3004 - PersistentKeepalive: 30 + PersistentKeepalive: 0 + Static: true +- NodeID: 5 + PubKey: x66hOjYXoZjL1FI3OVtf/CWittsnvmezKV6sW0v5FXQ= + PSKey: ChWBQurfGNZE5xIlhi9EUvZrQPBvkFsfrPaD6tyqSYg= + EndPoint: 127.0.0.1:3005 + PersistentKeepalive: 0 Static: true diff --git a/example_config/static_mode/n4.yaml b/example_config/static_mode/EgNet_edge4.yaml similarity index 79% rename from example_config/static_mode/n4.yaml rename to example_config/static_mode/EgNet_edge4.yaml index 13c7c14..4ff6ae7 100644 --- a/example_config/static_mode/n4.yaml +++ b/example_config/static_mode/EgNet_edge4.yaml @@ -3,7 +3,7 @@ Interface: Name: tap1 VPPIFaceID: 1 VPPBridgeID: 4242 - MacAddrPrefix: 8E:AA:C8:4B + MacAddrPrefix: DA:21:10:81 IPv4CIDR: 192.168.76.0/24 IPv6CIDR: fd95:71cb:a3df:e586::/64 IPv6LLPrefix: fe80::a3df:0/112 @@ -12,17 +12,17 @@ Interface: SendAddr: 127.0.0.1:5001 L2HeaderMode: kbdbg NodeID: 4 -NodeName: Node4 +NodeName: EgNet4 PostScript: "" DefaultTTL: 200 L2FIBTimeout: 3600 -PrivKey: sn0BUwyNxfmgePYi6O6a8pFDKiLaCz6jk3ws+covRTA= +PrivKey: ai9XXdezwamYHf7EvzmLGlMp7mUg+hZwoqLefzwHWVM= ListenPort: 3004 LogLevel: LogLevel: error LogTransit: true - LogControl: true LogNormal: true + LogControl: true LogInternal: true LogNTP: true DynamicRoute: @@ -100,20 +100,20 @@ NextHopTable: ResetConnInterval: 86400 Peers: - NodeID: 2 - PubKey: MC8dHNj8u/RrK9iL6ln+AaGZWkMrDl+8aUoyxbsBZC4= - PSKey: zkG1ywPqS4MiGofuWmxHHBs8YBlnYd04B4T9IhkbXYM= + PubKey: 0r2o9Hb36gYVgD3VSKCH18MVOZw0BvzcJ6TOTo6Cc1g= + PSKey: QHyrrls0KT2dvBKtoWkZvq5NQ+Xyjm0YgbMUzCCRw34= EndPoint: 127.0.0.1:3002 PersistentKeepalive: 0 Static: true - NodeID: 3 - PubKey: 1vg9bSyqjDL8oUdqpKXn/RR96cjTHPCBga0vaw86WzU= - PSKey: 8HN+PBGDKmBFCDlWIyNUFFYS4X6ONGXI32AIKXSnUqU= + PubKey: ymNpm430tiph3rMSVnQzDAxmK9+3jdcRFi6e96xmQUI= + PSKey: SQyLomXDeknBIFezUKardOviAmNS+YZ1XTvZtYVlOtE= EndPoint: 127.0.0.1:3003 PersistentKeepalive: 0 Static: true - NodeID: 6 - PubKey: GH6ra6xhDezskkJrr/DXPi93vtmhi96DFBJ3s8U20EA= - PSKey: f50pI53AQ6RoTGEnTSjl5YJhuMS/xiJjwtPuiP0xMUM= + PubKey: f6kHXq3qPLocdM0CZEkoumOHevrabzqOBFG5vg9cjDc= + PSKey: crsb9pbTY/Bei7TugjPNtg4dVKVzjwC/A6AjlSVoqbQ= EndPoint: 127.0.0.1:3006 - PersistentKeepalive: 30 + PersistentKeepalive: 0 Static: true diff --git a/example_config/static_mode/n5.yaml b/example_config/static_mode/EgNet_edge5.yaml similarity index 87% rename from example_config/static_mode/n5.yaml rename to example_config/static_mode/EgNet_edge5.yaml index 55fa33f..4dfd665 100644 --- a/example_config/static_mode/n5.yaml +++ b/example_config/static_mode/EgNet_edge5.yaml @@ -3,7 +3,7 @@ Interface: Name: tap1 VPPIFaceID: 1 VPPBridgeID: 4242 - MacAddrPrefix: 8E:AA:C8:4B + MacAddrPrefix: DA:21:10:81 IPv4CIDR: 192.168.76.0/24 IPv6CIDR: fd95:71cb:a3df:e586::/64 IPv6LLPrefix: fe80::a3df:0/112 @@ -12,17 +12,17 @@ Interface: SendAddr: 127.0.0.1:5001 L2HeaderMode: kbdbg NodeID: 5 -NodeName: Node5 +NodeName: EgNet5 PostScript: "" DefaultTTL: 200 L2FIBTimeout: 3600 -PrivKey: Blk9f1+NiC2fVCZpUBG34G9hrcLThemk/1Zd/dET6AA= +PrivKey: hBNK6Wu/Cl2MOXVA/F8yZsqIQ5eeIYCKPJeLu7i/190= ListenPort: 3005 LogLevel: LogLevel: error LogTransit: true - LogControl: true LogNormal: true + LogControl: true LogInternal: true LogNTP: true DynamicRoute: @@ -100,8 +100,8 @@ NextHopTable: ResetConnInterval: 86400 Peers: - NodeID: 3 - PubKey: 1vg9bSyqjDL8oUdqpKXn/RR96cjTHPCBga0vaw86WzU= - PSKey: +TU8Zi8dr/UBqNyW/MYMgJiqkLnRbHW4ExQYQ2eNEag= + PubKey: ymNpm430tiph3rMSVnQzDAxmK9+3jdcRFi6e96xmQUI= + PSKey: ChWBQurfGNZE5xIlhi9EUvZrQPBvkFsfrPaD6tyqSYg= EndPoint: 127.0.0.1:3003 - PersistentKeepalive: 30 + PersistentKeepalive: 0 Static: true diff --git a/example_config/static_mode/n6.yaml b/example_config/static_mode/EgNet_edge6.yaml similarity index 87% rename from example_config/static_mode/n6.yaml rename to example_config/static_mode/EgNet_edge6.yaml index 46c8184..794cf15 100644 --- a/example_config/static_mode/n6.yaml +++ b/example_config/static_mode/EgNet_edge6.yaml @@ -3,7 +3,7 @@ Interface: Name: tap1 VPPIFaceID: 1 VPPBridgeID: 4242 - MacAddrPrefix: 8E:AA:C8:4B + MacAddrPrefix: DA:21:10:81 IPv4CIDR: 192.168.76.0/24 IPv6CIDR: fd95:71cb:a3df:e586::/64 IPv6LLPrefix: fe80::a3df:0/112 @@ -12,17 +12,17 @@ Interface: SendAddr: 127.0.0.1:5001 L2HeaderMode: kbdbg NodeID: 6 -NodeName: Node6 +NodeName: EgNet6 PostScript: "" DefaultTTL: 200 L2FIBTimeout: 3600 -PrivKey: h03OJ1d8cVGF8TemiWWTla7RCIMtInhUnjs2CBb8AT8= +PrivKey: ao6wfUYOG3FBYYPlb+VtXk2ZiK6j/6ac75Y9VuKd2Vs= ListenPort: 3006 LogLevel: LogLevel: error LogTransit: true - LogControl: true LogNormal: true + LogControl: true LogInternal: true LogNTP: true DynamicRoute: @@ -100,8 +100,8 @@ NextHopTable: ResetConnInterval: 86400 Peers: - NodeID: 4 - PubKey: WkBYMUcQwWwUlDYh7uLu4YefiH1yXb3Tkf3wGEtE6HY= - PSKey: f50pI53AQ6RoTGEnTSjl5YJhuMS/xiJjwtPuiP0xMUM= + PubKey: 9YSIkihv/+aeukOaWwsR9EKvO1DM9+5kN53a/osTpmA= + PSKey: crsb9pbTY/Bei7TugjPNtg4dVKVzjwC/A6AjlSVoqbQ= EndPoint: 127.0.0.1:3004 - PersistentKeepalive: 30 + PersistentKeepalive: 0 Static: true diff --git a/example_config/static_mode/README_zh.md b/example_config/static_mode/README_zh.md index ac56c26..647be8f 100644 --- a/example_config/static_mode/README_zh.md +++ b/example_config/static_mode/README_zh.md @@ -1,84 +1,105 @@ # Etherguard -[English](README.md) - -Static Mode的[範例配置檔](./)的說明文件 +[English](README.md) | [中文](#) ## Static Mode 沒有自動選路,沒有握手伺服器 +類似原本的wireguard,一切都要提前配置好 +設定檔裡面的`NextHopTable`部分,只有此模式會生效 -十分類似原本的wireguard,一切都要提前配置好 - -但是除了peer以外,還要額外配置轉發表,所有人共用一份轉發表 - -設定檔裡面的`nexthoptable`部分,只有此模式會生效 - -這個模式下,不存在任何的Control Message,斷線偵測甚麼的也不會有 +這個模式下,不存在任何的Control Message,斷線偵測什麼的也不會有 請務必保持提前定義好的拓樸。不然如果存在中轉,中轉節點斷了,部分連線就會中斷 +## Quick Start +首先,按照需求修改`genstatic.yaml` + +```yaml +Config output dir: /tmp/eg_gen_static # 設定檔輸出位置 +ConfigTemplate for edge node: "" # 設定檔Template +Network name: "EgNet" +Edge Node: + MacAddress prefix: "" # 留空隨機產生 + IPv4 range: 192.168.76.0/24 # 順帶一提,IP的部分可以直接省略沒關係 + IPv6 range: fd95:71cb:a3df:e586::/64 # 這個欄位唯一的目的只是在啟動以後,調用ip命令,幫tap接口加個ip + IPv6 LL range: fe80::a3df:0/112 # 和VPN本身運作完全無關 +Edge Nodes: # 所有的節點相關設定 + 1: + Endpoint(optional): 127.0.0.1:3001 + 2: + Endpoint(optional): 127.0.0.1:3002 + 3: + Endpoint(optional): 127.0.0.1:3003 + 4: + Endpoint(optional): 127.0.0.1:3004 + 5: + Endpoint(optional): 127.0.0.1:3005 + 6: + Endpoint(optional): 127.0.0.1:3006 +Distance matrix for all nodes: |- # 左邊是起點,上面是終點,Inf代表此二節點不相連 ,數值代表相連。數值大小代表通過成本(通常是延遲) + X 1 2 3 4 5 6 + 1 0 1.0 Inf Inf Inf Inf + 2 1.0 0 1.0 1.0 Inf Inf + 3 Inf 1.0 0 1 1.0 Inf + 4 Inf 1.0 1.0 0 Inf 1.0 + 5 Inf Inf 1.0 Inf 1.0 Inf + 6 Inf Inf Inf 1.0 Inf 1.0 +``` +接著執行這個,就會生成所需設定檔了。 +``` +./etherguard-go -mode gencfg -cfgmode static -config example_config/static_mode/genstatic.yaml +``` + +把這些設定檔不捨去對應節點,然後再執行 +``` +./etherguard-go -config [設定檔位置] -mode edge +``` +就可以了 + +確認運作以後,可以關閉不必要的log增加性能 + +## Documentation + +Static Mode的說明文件 + 這份[範例配置檔](./)的網路拓樸如圖所示 !["Topology"](https://raw.githubusercontent.com/KusakabeSi/EtherGuard-VPN/master/example_config/static_mode/Example_static.png) 發出封包時,會設定起始ID=自己的Node ID,終點ID則是看Dst Mac Address。 -如果Dst MacAddr是廣播地址,或是不在自己的對應表裡面,就會設定終點=Boardcast +如果Dst MacAddr是廣播地址,或是不在自己的對應表裡面,就會設定終點=Broadcast 收到封包的時候,如果`dst==自己ID`,就會收下,不轉給任何人。 同時還會看它的 Src Mac Address 和 Src NodeID ,並加入對應表 這樣下次傳給他就可以直接傳給目標,而不用廣播給全節點了 所以設定檔中的轉發表如下表。格式是yaml的巢狀dictionary -轉發/發送封包時,直接查詢 `NhTable[起點][終點]=下一跳` +轉發/發送封包時,直接查詢`NhTable` 就知道下面一個封包要轉給誰了 +NextHopTable 是長這樣的資料結構,`NhTable[起點][終點]=下一跳` + ```yaml -nexthoptable: +NextHopTable: 1: 2: 2 3: 2 - 4: 2 - 5: 2 - 6: 2 2: 1: 1 3: 3 - 4: 4 - 5: 3 - 6: 4 + 3: 1: 2 2: 2 - 4: 4 - 5: 5 - 6: 4 - 4: - 1: 2 - 2: 2 - 3: 3 - 5: 3 - 6: 6 - 5: - 1: 3 - 2: 3 - 3: 3 - 4: 3 - 6: 3 - 6: - 1: 4 - 2: 4 - 3: 4 - 4: 4 - 5: 4 ``` -### Boardcast -比較特別的是`終點ID=Boardcast`的情況。 +### Broadcast +比較特別的是`終點ID=Broadcast`的情況。 -假設今天的狀況:我是4號,我收到`起點ID = 1,終點ID=boardcast`的封包 +假設今天的狀況:我是4號,我收到`起點ID = 1,終點ID=Broadcast`的封包 我應該只轉給6號就好,而不會轉給3號。 因為3號會收到來自2號的封包,自己就不用重複遞送了 -因此我有設計,如果`終點ID = Boardcast`,就會檢查Src到自己的所有鄰居,會不會經過自己 +因此我有設計,如果`終點ID = Broadcast`,就會檢查Src到自己的所有鄰居,會不會經過自己 **1 -> 6** 會經過自己: [1 2 4 6] **1 -> 3** 不會: [1 2 3] 2號是封包來源跳過檢查 @@ -94,189 +115,97 @@ nexthoptable: ``` X 1 2 3 4 5 6 -1 0 0.5 Inf Inf Inf Inf -2 0.5 0 0.5 0.5 Inf Inf -3 Inf 0.5 0 0.5 0.5 Inf -4 Inf 0.5 0.5 0 Inf 0.5 -5 Inf Inf 0.5 Inf 0 Inf -6 Inf Inf Inf 0.5 Inf 0 +1 0 1.0 Inf Inf Inf Inf +2 1.0 0 1.0 1.0 Inf Inf +3 Inf 1.0 0 1 1.0 Inf +4 Inf 1.0 1.0 0 Inf 1.0 +5 Inf Inf 1.0 Inf 1.0 Inf +6 Inf Inf Inf 1.0 Inf 1.0 ``` 之後用這個指令就能輸出用Floyd Warshall算好的轉發表了,填入設定檔即可 -``` -./etherguard-go -config example_config/static_mode/path.txt -mode slove -NextHopTable: - 1: - 2: 2 - 3: 2 - 4: 2 - 5: 2 - 6: 2 - 2: - 1: 1 - 3: 3 - 4: 4 - 5: 3 - 6: 4 - 3: - 1: 2 - 2: 2 - 4: 4 - 5: 5 - 6: 4 - 4: - 1: 2 - 2: 2 - 3: 3 - 5: 3 - 6: 6 - 5: - 1: 3 - 2: 3 - 3: 3 - 4: 3 - 6: 3 - 6: - 1: 4 - 2: 4 - 3: 4 - 4: 4 - 5: 4 -``` +### EdgeNode Config Parameter -程式還會額外輸出一些資訊,像是路徑表。 -會標示所有的起點終點組合的封包路徑,還有行經距離 -``` -Human readable: -src dist path -1 -> 2 0.500000 [1 2] -1 -> 3 1.000000 [1 2 3] -1 -> 4 1.000000 [1 2 4] -1 -> 5 1.500000 [1 2 3 5] -1 -> 6 1.500000 [1 2 4 6] -2 -> 1 0.500000 [2 1] -2 -> 3 0.500000 [2 3] -2 -> 4 0.500000 [2 4] -2 -> 5 1.000000 [2 3 5] -2 -> 6 1.000000 [2 4 6] -3 -> 1 1.000000 [3 2 1] -3 -> 2 0.500000 [3 2] -3 -> 4 0.500000 [3 4] -3 -> 5 0.500000 [3 5] -3 -> 6 1.000000 [3 4 6] -4 -> 1 1.000000 [4 2 1] -4 -> 2 0.500000 [4 2] -4 -> 3 0.500000 [4 3] -4 -> 5 1.000000 [4 3 5] -4 -> 6 0.500000 [4 6] -5 -> 1 1.500000 [5 3 2 1] -5 -> 2 1.000000 [5 3 2] -5 -> 3 0.500000 [5 3] -5 -> 4 1.000000 [5 3 4] -5 -> 6 1.500000 [5 3 4 6] -6 -> 1 1.500000 [6 4 2 1] -6 -> 2 1.000000 [6 4 2] -6 -> 3 1.000000 [6 4 3] -6 -> 4 0.500000 [6 4] -6 -> 5 1.500000 [6 4 3 5] -``` +Key | Description +-------------- |:----- +[Interface](#Interface)| 接口相關設定。VPN有兩端,一端是VPN網路,另一端則是本地接口 +NodeID | 節點ID。節點之間辨識身分用的,同一網路內節點ID不能重複 +NodeName | 節點名稱 +PostScript | 初始化完畢之後要跑的腳本 +DefaultTTL | TTL,etherguard層使用,和乙太層不共通 +L2FIBTimeout | MacAddr-> NodeID 查找表的 timeout(秒) ,類似ARP table +PrivKey | 私鑰,和wireguard規格一樣 +ListenPort | 監聽的udp埠 +[LogLevel](#LogLevel)| 紀錄log +[DynamicRoute](../super_mode/README_zh.md#DynamicRoute) | 動態路由相關設定,Static模式用不到 +NextHopTable | 轉發表, 下一跳 = `NhTable[起點][終點]` +ResetConnInterval | 如果對方是動態ip就要用這個。每隔一段時間就會重置連線,重新解析域名 +[Peers](#Peers) | 鄰居節點,和wireguard相同 -有些設定檔對應某些運作模式,這邊針對共同部分的設定做說明 +Interface | Description +---------------|:----- +[IType](#IType)| 接口類型,意味著從VPN網路收到的封包要丟去哪邊 +Name | 裝置名稱 +VPPIFaceID | VPP 的 interface ID。同一個VPP runtime內不能重複 +VPPBridgeID | VPP 的網橋ID。不使用VPP網橋功能的話填0 +MacAddrPrefix | MAC地址前綴。真正的 MAC 地址=[前綴]:[NodeID] +IPv4CIDR | 啟動以後,調用ip命令,幫tap接口加個ip。僅限tap有效 +IPv4CIDR | 啟動以後,調用ip命令,幫tap接口加個ip。僅限tap有效 +IPv6LLPrefix | 啟動以後,調用ip命令,幫tap接口加個ip。僅限tap有效 +MTU | 裝置MTU,僅限`tap` , `vpp` 模式有效 +RecvAddr | listen地址,收到的東西丟去 VPN 網路。僅限`*sock`生效 +SendAddr | 連線地址,VPN網路收到的東西丟去這個地址。僅限`*sock`生效 +[L2HeaderMode](#L2HeaderMode) | 僅限 `stdio` 生效。debug用途,有三種模式 -### Edge config +IType | Description +-----------|:----- +dummy | 收到的封包直接丟棄,但幫忙轉發。作為中繼節點,本身不加入網路使用 +stdio | 收到的封包丟stdout,stdin進來的資料丟入vpn網路,debug用途
需要參數: `MacAddrPrefix` && `L2HeaderMode` +udpsock | 收到的封包丟去一個udp socket
需要參數: `RecvAddr` && `SendAddr` +tcpsock | 收到的封包丟去一個tcp socket
需要參數: `RecvAddr` \|\| `SendAddr` +unixsock | 收到的封包丟去一個unix socket(SOCK_STREAM 模式)
需要參數: `RecvAddr` \|\| `SendAddr` +udpsock | 收到的封包丟去一個unix socket(SOCK_DGRAM 模式)
需要參數: `RecvAddr` \|\| `SendAddr` +udpsock | 收到的封包丟去一個unix socket(SOCK_SEQPACKET 模式)
需要參數: `RecvAddr` \|\| `SendAddr` +fd | 收到的封包丟去一個特定的file descriptor
需要參數: 無. 但是使用環境變數 `EG_FD_RX` && `EG_FD_TX` 來指定 +vpp | 使用libmemif使vpp加入VPN網路
需要參數: `Name` && `VPPIFaceID` && `VPPBridgeID` && `MacAddrPrefix` && `MTU` +tap | Linux的tap設備。讓linux加入VPN網路
需要參數: `Name` && `MacAddrPrefix` && `MTU`
可選參數:`IPv4CIDR` , `IPv6CIDR` , `IPv6LLPrefix` -1. `interface` - 1. `itype`: 裝置類型,意味著從VPN網路收到的封包要丟去哪個硬體 - 1. `dummy`: 收到的封包直接丟棄,也不發出任何封包。作為中繼節點使用 - 2. `stdio`: 收到的封包丟stdout,stdin進來的資料丟入vpn網路 - 需要參數: `macaddrprefix`,`l2headermode` - 3. `udpsock`: 把VPN網路收到的layer2封包讀寫去一個udp socket. - Paramaters: `recvaddr`,`sendaddr` - 3. `tcpsock`: 把VPN網路收到的layer2封包讀寫去一個tcp socket. - Paramaters: `recvaddr`,`sendaddr` - 3. `unixsock`: 把VPN網路收到的layer2封包讀寫去一個unix socket(SOCK_STREAM 模式). - Paramaters: `recvaddr`,`sendaddr` - 3. `unixgramsock`: 把VPN網路收到的layer2封包讀寫去一個unix socket(SOCK_DGRAM 模式). - Paramaters: `recvaddr`,`sendaddr` - 3. `unixpacketsock`: 把VPN網路收到的layer2封包讀寫去一個unix socket(SOCK_SEQPACKET 模式). - Paramaters: `recvaddr`,`sendaddr` - 3. `fd`: 把VPN網路收到的layer2封包讀寫去一個特定的file descriptor. - Paramaters: 無. 但是使用環境變數 `EG_FD_RX` 和 `EG_FD_TX` 來指定 - 4. `vpp`: 使用libmemif使vpp加入VPN網路 - 需要參數: `name`,`vppifaceid`,`vppbridgeid`,`macaddrprefix`,`mtu` - 5. `tap`: Linux的tap設備。讓linux加入VPN網路 - 需要參數: `name`,`macaddrprefix`,`mtu` - 2. `name` : 裝置名稱 - 3. `vppifaceid`: VPP 的 interface ID。一個VPP runtime內不能重複 - 4. `vppbridgeid`: VPP 的網橋ID。不使用VPP網橋功能的話填0 - 5. `macaddrprefix`: MAC地址前綴。真正的 MAC 地址=[前綴]:[NodeID]。 - 如果這邊填了完整6格長度,就忽略`NodeID` - 6. `recvaddr`: 僅限`XXXsock`生效。listen地址,收到的東西丟去 VPN 網路 - 7. `sendaddr`: 僅限`XXXsock`生效。連線地址,VPN網路收到的東西丟去這個地址 - 8. `l2headermode`: 僅限 `stdio` 生效。debug用途,有三種模式: - 1. `nochg`: 從 VPN 網路收到什麼,就往tap裝置發送什麼。不對封包作任何更動 - 2. `kbdbg`: 鍵盤bebug模式。搭配 `stdio` 模式,讓我 debug 用 - 因為前 12 byte 會用來做選路判斷,但是只是要debug,構造完整的封包就不是很方便 - 這個模式下,如果輸入b2content,就會幫你把b轉換成`FF:FF:FF:FF:FF:FF`, `2` 轉換成 `AA:BB:CC:DD:EE:02` 。封包內容變成 `b"0xffffffffffffaabbccddee02content"`。 - 用鍵盤就能輕鬆產生L2 header,查看選路的行為 - 3. `noL2`: 拔掉L2 Header的模式。 - 但是本VPN會查詢L2用作選路,所以會變成一律廣播 -2. `nodeid`: 節點ID。節點之間辨識身分用的,同一網路內節點ID不能重複 -3. `postscript`: etherguard初始化完畢之後要跑的腳本. -3. `nodename`: 節點名稱 -4. `defaultttl`: 預設ttl(etherguard層使用,和乙太層不共通) -5. `l2fibtimeout`: MacAddr-> NodeID 查找表的 timeout(秒) -5. `privkey`: 私鑰,和wireguard規格一樣 -5. `listenport`: 監聽的udp埠 -6. `loglevel`: 紀錄log - 1. `loglevel`: wireguard原本的log紀錄器的loglevel。 - 有`debug`,`error`,`slient`三種程度 - 2. `logtransit`: 轉送封包,也就是起點/終點都不是自己的封包的log - 3. `logcontrol`: Control Message的log - 4. `lognormal`: 收發普通封包,起點是自己or終點是自己的log - 5. `logntp`: NTP 同步時鐘相關的log -7. `dynamicroute`: 動態路由相關的設定。時間類設定單位都是秒 - 1. `sendpinginterval`: 發送Ping訊息的間隔 - 2. `dupchecktimeout`: 重複封包檢查的timeout。完全相同的封包收第二次會被丟棄 - 1. `peeralivetimeout`: 每次收到封包就重置,超過時間沒收到就標記該peer離線 - 3. `conntimeout`: 檢查peer離線的間格,如果標記離線,就切換下一個endpoint(supernode可能傳了多個endpoint過來) - 4. `savenewpeers`: 是否把下載來的鄰居資訊存到本地設定檔裡面 - 5. `supernode`: 參見[Super模式](example_config/super_mode/README_zh.md) - 6. `p2p` 參見 [P2P模式](example_config/p2p_mode/README_zh.md) - 7. `ntpconfig`: NTP 相關的設定 - 1. `usentp`: 是否使用ntp同步時鐘 - 2. `maxserveruse`: 一次對多連線幾個NTP伺服器 - 第一次會全部連一遍測延遲,之後每次都取延遲前n低的來用 - 3. `synctimeinterval`: 多久同步一次 - 4. `ntptimeout`: 多久算是超時 - 5. `servers`: NTP伺服器列表 -8. `nexthoptable`: 轉發表。只有Static模式會用到,參見 [Static模式](example_config/super_mode/README_zh.md) -9. `resetconninterval`: 如果對方是動態ip就要用這個。每隔一段時間就會重新解析domain。 -10. `peers`: 和wireguard一樣的peer資訊 - 1. `nodeid`: 對方的節點ID - 2. `pubkey`: 對方的公鑰 - 3. `pskey`: 對方的預共享金鑰。但是目前沒用(因為不能設定自己的),之後會加 - 4. `endpoint`: 對方的連線地址。如果roaming會覆寫設定檔 - 5. `static`: 設定成true的話,每隔`resetconninterval`秒就會重新解析一次domain,與此同時也不會被roaming覆寫 +L2HeaderMode | Description +---------------|:----- +nochg | 收到的封包丟stdout,stdin進來的資料丟入vpn網路,不對封包作任何更動 +kbdbg | 前 12byte 會用來做選路判斷
但是stdio模式下,使用鍵盤輸入一個Ethernet frame不太方便
此模式讓我快速產生Ethernet frame,debug更方便
`b`轉換成`FF:FF:FF:FF:FF:FF`
`2`轉換成 `AA:BB:CC:DD:EE:02`
輸入`b2aaaaa`就會變成`b"0xffffffffffffaabbccddee02aaaaa"` +noL2 | 讀取時拔掉L2 Header的模式
寫入時時一律使用廣播MacAddress -### Super config +LogLevel | Description +------------|:----- +LogLevel | wireguard原本的log紀錄器的loglevel
接受參數: `debug`,`error`,`slient` +LogTransit | 轉送封包,也就是起點/終點都不是自己的封包的log +LogNormal | 收發普通封包,起點是自己or終點是自己的log +LogControl | Control Message的log +LogInternal | 一些內部事件的log +LogNTP | NTP 同步時鐘相關的log - 參見 [example_config/super_mode/README_zh.md](example_config/super_mode/README_zh.md) - -### Quick start +Peers | Description +--------------------|:----- +NodeID | 對方的節點ID +PubKey | 對方的公鑰 +PSKey | 對方的預共享金鑰 +EndPoint | 對方的連線地址。如果漫遊,而且`Static=false`會覆寫設定檔 +PersistentKeepalive | wireguard的PersistentKeepalive參數 +Static | 關閉漫遊功能,每隔`ResetConnInterval`秒,重置回初始ip #### Run example config 在**不同terminal**分別執行以下命令 ``` -./etherguard-go -config example_config/super_mode/n1.yaml -mode edge -./etherguard-go -config example_config/super_mode/n2.yaml -mode edge -./etherguard-go -config example_config/super_mode/n3.yaml -mode edge -./etherguard-go -config example_config/super_mode/n4.yaml -mode edge -./etherguard-go -config example_config/super_mode/n5.yaml -mode edge -./etherguard-go -config example_config/super_mode/n6.yaml -mode edge +./etherguard-go -config example_config/super_mode/EgNet_edge1.yaml -mode edge +./etherguard-go -config example_config/super_mode/EgNet_edge2.yaml -mode edge +./etherguard-go -config example_config/super_mode/EgNet_edge3.yaml -mode edge +./etherguard-go -config example_config/super_mode/EgNet_edge4.yaml -mode edge +./etherguard-go -config example_config/super_mode/EgNet_edge5.yaml -mode edge +./etherguard-go -config example_config/super_mode/EgNet_edge6.yaml -mode edge ``` 因為本範例配置是stdio的kbdbg模式,stdin會讀入VPN網路 @@ -284,12 +213,7 @@ src dist path ``` b1message ``` -因為`l2headermode`是`kbdbg`,所以b1會被轉換成 12byte 的layer 2 header,b是廣播地址`FF:FF:FF:FF:FF:FF`,1是普通地址`AA:BB:CC:DD:EE:01`,message是後面的payload,然後再丟入VPN +因為`L2HeaderMode`是`kbdbg`,所以b1會被轉換成 12byte 的layer 2 header,b是廣播地址`FF:FF:FF:FF:FF:FF`,1是普通地址`AA:BB:CC:DD:EE:01`,message是後面的payload,然後再丟入VPN 此時應該要能夠在另一個視窗上看見字串b1message。前12byte被轉換回來了 -#### Run your own etherguard - -要正式使用,請將itype改成`tap`,並且修改各節點的公鑰私鑰和連線地址 -再關閉不必要的log增加性能,最後部屬到不同節點即可 - ## 下一篇: [Super Mode的運作](../super_mode/README_zh.md) \ No newline at end of file diff --git a/example_config/static_mode/genstatic.yaml b/example_config/static_mode/genstatic.yaml index 8aca6ed..fe78c05 100644 --- a/example_config/static_mode/genstatic.yaml +++ b/example_config/static_mode/genstatic.yaml @@ -1,6 +1,6 @@ Config output dir: /tmp/eg_gen_static -ConfigTemplate for edge node: "n1.yaml" -Network name: "Node" +ConfigTemplate for edge node: "" # "EgNet_edge1.yaml" +Network name: "EgNet" Edge Node: MacAddress prefix: "" IPv4 range: 192.168.76.0/24 @@ -9,27 +9,21 @@ Edge Node: Edge Nodes: 1: Endpoint(optional): 127.0.0.1:3001 - PersistentKeepalive: 30 2: Endpoint(optional): 127.0.0.1:3002 - PersistentKeepalive: 30 3: Endpoint(optional): 127.0.0.1:3003 - PersistentKeepalive: 30 4: Endpoint(optional): 127.0.0.1:3004 - PersistentKeepalive: 30 5: Endpoint(optional): 127.0.0.1:3005 - PersistentKeepalive: 30 6: Endpoint(optional): 127.0.0.1:3006 - PersistentKeepalive: 30 Distance matrix for all nodes: |- X 1 2 3 4 5 6 - 1 0 1 Inf Inf Inf Inf - 2 1 0 1 1 Inf Inf - 3 Inf 1 0 1 1 Inf - 4 Inf 1 1 0 Inf 1 - 5 Inf Inf 1 Inf 0 Inf - 6 Inf Inf Inf 1 Inf 0 \ No newline at end of file + 1 0 1.0 Inf Inf Inf Inf + 2 1.0 0 1.0 1.0 Inf Inf + 3 Inf 1.0 0 1 1.0 Inf + 4 Inf 1.0 1.0 0 Inf 1.0 + 5 Inf Inf 1.0 Inf 1.0 Inf + 6 Inf Inf Inf 1.0 Inf 1.0 \ No newline at end of file diff --git a/example_config/super_mode/EGS03.png b/example_config/super_mode/EGS03.png index 63e742b44a19300b7a9d4b9324e995f79a62927d..00b7c565fd76345f9b1ebf4e110dd46e091a9443 100644 GIT binary patch literal 44182 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 NvA(%pwXS2-{{YfB8~FeL literal 46645 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

XQYV7g~5ZkJ+HE3VXn-eCWViC)q6{_~e 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 e*FHgL{R-ErUkXvaL_diG{#2FJ6)WT|0{clB diff --git a/example_config/super_mode/EGS08.png b/example_config/super_mode/EGS08.png new file mode 100644 index 0000000000000000000000000000000000000000..6b363eb6d9d399cb01284bf8cfdbcaba974a2aae GIT binary patch literal 23987 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 x%X*lQrJC9Kn+AdditionalCost +有了各個節點的延遲以後,還不會立刻計算`Floyd-Warshall`,而是要先加上`AdditionalCost` + +以這張圖片的情境為例: +![EGS08](https://raw.githubusercontent.com/KusakabeSi/EtherGuard-VPN/master/example_config/super_mode/EGS08.png) +Path | Latency |Cost|Win +--------|:--------|:---|:-- +A->B->C | 3ms | 3 | +A->C | 4ms | 4 | O + +但是這個情境,3ms 4ms 只相差1ms +為了這1ms而多繞一趟實在浪費,而且轉發本身也要時間 + +每個節點有了`AdditionalCost`參數,就能設定經過這個節點轉發,所需額外增加的成本 + +假如ABC全部設定了`AdditionalCost=10` +Path | Latency |AdditionalCost|Cost|Win +--------|:--------|:-------------|:---|:-- +A->B->C | 3ms | 20 | 23 | +A->C | 4ms | 10 | 14 | O + +A->C 就換選擇直連,不會為了省下1ms而繞路 +這邊`AdditionalCost=10`可以解釋為: 必須能省下10ms,才會繞這條路 + +這個參數也有別的用途 +針對流量比較貴的節點,可以設定`AdditionalCost=10000` +別人就不會走他中轉了,而是盡量繞別的路,或是直連 +除非別條路線全掛,只剩這挑Cost 10000的路線 + +還有一個用法,全部節點都設定`AdditionalCost=10000` +無視延遲,全節點都盡量直連,打動失敗才繞路 + ### UpdateNhTable Super node收到節點們傳來的Pong以後,就知道他們的單向延遲了。接下來的運作方式類似這張圖 ![image](https://raw.githubusercontent.com/KusakabeSi/EtherGuard-VPN/master/example_config/super_mode/EGS03.png) Super node收到Pong以後,就會更新它裡面的`Distance matrix`,並且重新計算轉發表 如果有變動,就發布`UpdateNhTableMsg` -其他edge node收到以後就用HTTP API去下載完整的轉發表 +其他edge node收到以後就用HTTP EdgeAPI去下載完整的轉發表 -### UpdateError -通知edges有錯誤發生,關閉egde端程式 -發生在版本號不匹被,該edge的NodeID配置錯誤,還有該Edge被刪除時觸發 +### ServerUpdate +通知EdgeNode有事情發生 +1. 關閉EdgeNode程式 + * 版本號不匹配 + * 該edge的NodeID配置錯誤 + * 該Edge被刪除 +2. 通知EdgeNode有更新 + * UpdateNhTable + * UpdatePeer + * UpdateSuperParams -### HTTP API + +## HTTP EdgeAPI 為什麼要用HTTP額外下載呢?直接`UpdateXXX`夾帶資訊不好嗎? 因為udp是不可靠協議,能攜帶的內容量也有上限。 但是peer list包含了全部的peer資訊,長度不是固定的,可能超過 @@ -68,12 +147,15 @@ Super node收到Pong以後,就會更新它裡面的`Distance matrix`,並且 這樣super node收到HTTP API看到`state hash`就知道這個edge node確實有收到`UpdateXXX`了。 不然每隔一段時間就會重新發送`UpdateXXX`給該節點 +預設配置是走HTTP。但為**了你的安全著想,建議使用nginx反代理成https** +有想過SuperNode開發成直接支援https,但是證書動態更新太麻煩就沒有做了 + ## HTTP Manage API -HTTP還有一些個API,給前端使用,幫助管理整個網路 +HTTP還有5個Manage API,給前端使用,幫助管理整個網路 ### super/state ```bash -curl "http://127.0.0.1:3000/eg_api/manage/super/state?Password=passwd_showstate" +curl "http://127.0.0.1:3456/eg_net/eg_api/manage/super/state?Password=passwd_showstate" ``` 可以給前端看的,用來顯示現在各節點之間的單向延遲狀況 之後可以用來畫力導向圖。 @@ -145,10 +227,10 @@ curl "http://127.0.0.1:3000/eg_api/manage/super/state?Password=passwd_showstate" 再來是新增peer,可以不用重啟Supernode就新增Peer 範例: -``` -curl -X POST "http://127.0.0.1:3000/eg_api/manage/peer/add?Password=passwd_addpeer" \ +```bash +curl -X POST "http://127.0.0.1:3456/eg_net/eg_api/manage/peer/add?Password=passwd_addpeer" \ -H "Content-Type: application/x-www-form-urlencoded" \ - -d "NodeID=100&Name=Node_100&PubKey=6SuqwPH9pxGigtZDNp3PABZYfSEzDaBSwuThsUUAcyM=&AdditionalCost=1000&PSKey=j9dS%2FlYvL16svSeC5lh%2Bldlq2iZX2MWwZfM3NNWpULI%3D&SkipLocalIP=false" + -d "NodeID=100&Name=Node_100&PubKey=Bax6wOJpisSVJtrU92ujn8D%2F2oGUyhyPrKTXkHbGamM%3D&AdditionalCost=1000&PSKey=Gfp2RkPNrKTeGKrCJNEvSyiBqYYRmzVnVG6CBuUKUNc%3D&SkipLocalIP=false" ``` 參數: 1. URL query: Password: 新增peer用的密碼,在設定檔配置 @@ -172,13 +254,13 @@ curl -X POST "http://127.0.0.1:3000/eg_api/manage/peer/add?Password=passwd_addpe 設計上分別是給管理員使用,或是給加入網路的人,想離開網路使用 使用Password刪除可以刪除任意節點,以上面新增的節點為例,使用這個API即可刪除剛剛新增的節點 -``` -curl "http://127.0.0.1:3000/eg_api/manage/peer/del?Password=passwd_delpeer&NodeID=100" +```bash +curl "http://127.0.0.1:3456/eg_net/eg_api/manage/peer/del?Password=passwd_delpeer&NodeID=100" ``` 也可以使用privkey刪除,同上,但是只要附上privkey參數就好 -``` -curl "http://127.0.0.1:3000/eg_api/manage/peer/del?PrivKey=IJtpnkm9ytbuCukx4VBMENJKuLngo9KSsS1D60BqonQ=" +```bash +curl "http://127.0.0.1:3456/eg_net/eg_api/manage/peer/del?PrivKey=a04BVvT%2BYbrX1ejjvMQVI6k5VRFlBkEX8tuLGWNyNrY%3D" ``` 參數: @@ -193,65 +275,121 @@ curl "http://127.0.0.1:3000/eg_api/manage/peer/del?PrivKey=IJtpnkm9ytbuCukx4VBME ### peer/update 更新節點的一些參數 -``` -curl -X POST "http://127.0.0.1:12369/eg_net/eg_api/manage/peer/update?Password=e05znou1_updatepeer&NodeID=1" \ +```bash +curl -X POST "http://127.0.0.1:3456/eg_net/eg_api/eg_api/manage/peer/update?Password=e05znou1_updatepeer&NodeID=1" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "AdditionalCost=10&SkipLocalIP=false" ``` ### super/update 更新SuperNode的一些參數 -``` -curl -X POST "http://127.0.0.1:12369/eg_net/eg_api/manage/super/update?Password=e05znou1_updatesuper" \ +```bash +curl -X POST "http://127.0.0.1:3456/eg_net/eg_api/eg_api/manage/super/update?Password=e05znou1_updatesuper" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "SendPingInterval=15&HttpPostInterval=60&PeerAliveTimeout=70" ``` +### SuperNode Config Parameter -## Config Parameters +Key | Description +-------------- |:----- +NodeName| 節點名稱 +PostScript | 初始化完畢之後要跑的腳本 +PrivKeyV4 | IPv4通訊使用的私鑰 +PrivKeyV6 | IPv6通訊使用的私鑰 +ListenPort | udp監聽埠 +ListenPort_EdgeAPI | HTTP EdgeAPI 的監聽埠 +ListenPort_ManageAPI| HTTP ManageAPI 的監聽埠 +API_Prefix | HTTP API prefix +RePushConfigInterval| 重新push`UpdateXXX`的間格 +HttpPostInterval | EdgeNode 使用EdgeAPI回報狀態的頻率 +PeerAliveTimeout | 判定斷線Timeout +SendPingInterval | EdgeNode 之間使用Ping/Pong測量延遲的間格 +[LogLevel](../static_mode/README_zh.md#LogLevel)| 紀錄log +[Passwords](#Passwords) | HTTP ManageAPI 的密碼,5個API密碼是獨立的 +[GraphRecalculateSetting](#GraphRecalculateSetting) | 一些和[Floyd-Warshall演算法](https://zh.wikipedia.org/zh-tw/Floyd-Warshall算法)相關的參數 +[NextHopTable](../static_mode/README_zh.md#NextHopTable) | StaticMode 模式下使用的轉發表 +EdgeTemplate | HTTP ManageAPI `peer/add` 返回的edge的參考設定檔 +UsePSKForInterEdge | 幫Edge生成PreSharedKey,供edge之間直接連線使用 +[Peers](#EdgeNodes) | EdgeNode資訊 -### Super mode的edge node有幾個參數 -1. `usesupernode`: 是否啟用Super mode -1. `pskey`: 和supernode建立連線用的Pre shared Key -1. `connurlv4`: Super node的IPv4連線地址 -1. `pubkeyv4`: Super node的IPv4工鑰 -1. `connurlv6`: Super node的IPv6連線地址 -1. `pubkeyv6`: Super node的IPv6工鑰 -1. `apiurl`: Super node的HTTP(S) API連線地址 -1. `supernodeinfotimeout`: Supernode Timeout -1. `httppostinterval`: 15 -1. `skiplocalip`: 打洞時,一律使用supernode蒐集到的外部ip,不使用edge自行回報的local ip +Passwords | Description +--------------------|:----- +ShowState | HTTP ManageAPI `super/state` 的密碼 +AddPeer | HTTP ManageAPI `peer/add` 的密碼 +DelPeer | HTTP ManageAPI `peer/del` 的密碼 +UpdatePeer | HTTP ManageAPI `peer/update` 的密碼 +UpdateSuper | HTTP ManageAPI `super/update` 的密碼 -### Super node本身的設定檔 +GraphRecalculateSetting | Description +--------------------|:----- +StaticMode | 關閉`Floyd-Warshall`演算法,只使用設定檔提供的NextHopTable`。SuperNode單純用來輔助打洞 +ManualLatency | 手動設定延遲,不採用EdgeNode回報的延遲(單位: 毫秒) +JitterTolerance | 抖動容許誤差,收到Pong以後,一個37ms,一個39ms,不會觸發重新計算
比較對象是上次更新使用的值。如果37 37 41 43 .. 100 ,每次變動一點點,總變動量超過域值還是會更新 +JitterToleranceMultiplier | 抖動容許誤差的放大係數,高ping的話允許更多誤差
https://www.desmos.com/calculator/raoti16r5n +DampingResistance | 防抖阻尼系數,`latency = latency_old * resistance + latency_in * (1-resistance)` +TimeoutCheckInterval | 週期性檢查節點的連線狀況,是否斷線需要重新規劃線路 +RecalculateCoolDown | Floyd-Warshal是O(n^3)時間複雜度,不能太常算。
設個冷卻時間
有節點加入/斷線觸發的重新計算,無視這個CoolDown -1. nodename: 節點名稱 -1. privkeyv4: ipv4用的私鑰 -1. privkeyv6: ipv6用的私鑰 -1. listenport: 監聽udp埠號 -1. loglevel: 參考 [README_zh.md](../README_zh.md) -1. repushconfiginterval: 重新push`UpdateXXX`的間格 -1. passwords: HTTP API 密碼 - 1. showstate: 節點資訊 - 1. addpeer: 新增peer - 1. delpeer: 刪除peer -1. graphrecalculatesetting: 一些和[Floyd-Warshall演算法](https://zh.wikipedia.org/zh-tw/Floyd-Warshall算法)相關的參數 - 1. staticmode: 關閉Floyd-Warshall演算法,只使用一開始載入的nexthoptable。Supernode單純用來輔助打洞 - 1. recalculatecooldown: Floyd-Warshal是O(n^3)時間複雜度,不能太常算。設個冷卻時間 - 1. jittertolerance: 抖動容許誤差,收到Pong以後,一個37ms,一個39ms,不會觸發重新計算 - 1. jittertolerancemultiplier: 一樣是抖動容許誤差,但是高ping的話允許更多誤差 - https://www.desmos.com/calculator/raoti16r5n - 1. nodereporttimeout: 收到的`Pong`封包的有效期限。太久沒收到就變回Infinity - 1. timeoutcheckinterval: 固定間格檢查,有沒有人的Pong封包超過有效期限,要重算轉發表 -1. nexthoptable: 僅在`staticmode==true` 有效,手動設定的nexthoptable -1. edgetemplate: 給`addpeer`API用的。參考這個設定檔,顯示一個範例設定檔給edge -1. usepskforinteredge: 是否啟用edge間pre shares key通信。若啟用則幫edge們自動生成PSK -1. peers: Peer列表,參考 [README_zh.md](../README_zh.md) - 1. nodeid: Peer的節點ID - 1. name: Peer名稱(顯示在前端) - 1. pubkey: peer 公鑰 - 1. pskey: preshared key 該peer和本Supernode連線的PSK - 1. additionalcost: 此節點進行封包轉發的額外成本。單位: 毫秒 +Peers | Description +--------------------|:----- +NodeID | 節點ID +PubKey | 公鑰 +PSKey | 預共享金鑰 +[AdditionalCost](#AdditionalCost) | 繞路成本(單位: 毫秒)
設定-1代表使用EdgeNode自身設定 +SkipLocalIP | 打洞時,不使用EdgeNode回報的本地IP,僅使用SuperNode蒐集到的外部IP +### EdgeNode Config Parameter + +Key | Description +-------------- |:----- +[Interface](../static_mode/README_zh.md#Interface)| 接口相關設定。VPN有兩端,一端是VPN網路,另一端則是本地接口 +NodeID | 節點ID。節點之間辨識身分用的,同一網路內節點ID不能重複 +NodeName | 節點名稱 +PostScript | 初始化完畢之後要跑的腳本 +DefaultTTL | TTL,etherguard層使用,和乙太層不共通 +L2FIBTimeout | MacAddr-> NodeID 查找表的 timeout(秒) ,類似ARP table +PrivKey | 私鑰,和wireguard規格一樣 +ListenPort | 監聽的udp埠 +[LogLevel](../static_mode/README_zh.md#LogLevel)| 紀錄log +[DynamicRoute](#DynamicRoute) | 動態路由相關設定 +NextHopTable | 轉發表, SuperMode由SuperNode計算,EdgeNode用不到 +ResetConnInterval | 如果對方是動態ip就要用這個。每隔一段時間就會重置連線,重新解析域名 +[Peers](#Peers) | 鄰居節點,SuperMode從SuperNode計算,EdgeNode用不到 + +DynamicRoute | Description +--------------------|:----- +SendPingInterval | 發送Ping訊息的間隔(秒) +PeerAliveTimeout | 每次收到封包就重置,超過時間(秒)沒收到就標記該peer離線 +DupCheckTimeout | 重複封包檢查的timeout(秒)
完全相同的封包收第二次會被丟棄 +ConnTimeOut | 檢查peer離線的時間間格
如果標記離線,就切換下一個endpoint
SuperNode可能傳了多個endpoint過來 +ConnNextTry | 切換下一個endpoint的間隔 +[AdditionalCost](#AdditionalCost) | 繞路成本(毫秒)。僅限SuperNode設定-1時生效 +SaveNewPeers | 是否把下載來的鄰居資訊存到本地設定檔裡面 +[SuperNode](#SuperNode) | SuperNode相關設定 +P2P | P2P相關設定,SuperMode用不到 +[NTPConfig](#NTPConfig) | NTP時間同步相關設定 + +SuperNode | Description +---------------------|:----- +UseSuperNode | 是否啟用SuperNode +PSKey | 和SuperNode通訊用的PreShared Key +EndpointV4 | SuperNode的IPv4 Endpoint +PubKeyV4 | SuperNode的IPv4公鑰 +EndpointV6 | SuperNode的IPv6 Endpoint +PubKeyV6 | SuperNode的IPv6公鑰 +EndpointEdgeAPIUrl | SuperNode的EdgeAPI存取路徑 +SkipLocalIP | 不回報本地IP,避免和其他Edge內網直連 +SuperNodeInfoTimeout | 實驗性選項,SuperNode離線超時,切換成P2P模式
需先打開P2P模式
`UseP2P=false`本選項無效
P2P模式尚未測試,穩定性未知,不推薦使用 + + +NTPConfig | Description +--------------------|:----- +UseNTP | 是否使用NTP同步時間 +MaxServerUse | 向多少NTP伺服器發送請求 +SyncTimeInterval | 多久同步一次時間 +NTPTimeout | NTP伺服器連線Timeout +Servers | NTP伺服器列表 ## V4 V6 兩個公鑰 為什麼要分開IPv4和IPv6呢? @@ -284,12 +422,14 @@ Relay node其實也是一個edge node,只不過被設定成為interface=dummy 因為如果用127.0.0.1連接supernode,supernode看到封包的src IP就是127.0.0.1,就會把127.0.0.1分發給`Node_1`和`Node_2` `Node_1`和`Node_2`看到`Node_R`的連線地址是`127.0.0.1`,就連不上了 -## Quick start -執行此範例設定檔(請開三個terminal): +#### Run example config + +在**不同terminal**分別執行以下命令 + ```bash -./etherguard-go -config example_config/super_mode/s1.yaml -mode super -./etherguard-go -config example_config/super_mode/n1.yaml -mode edge -./etherguard-go -config example_config/super_mode/n2.yaml -mode edge +./etherguard-go -config example_config/super_mode/Node_super.yaml -mode super +./etherguard-go -config example_config/super_mode/Node_edge001.yaml -mode edge +./etherguard-go -config example_config/super_mode/Node_edge002.yaml -mode edge ``` 因為是stdio模式,stdin會讀入VPN網路 請在其中一個edge視窗中鍵入 diff --git a/example_config/super_mode/gensuper.yaml b/example_config/super_mode/gensuper.yaml index 54489f8..e3e0793 100644 --- a/example_config/super_mode/gensuper.yaml +++ b/example_config/super_mode/gensuper.yaml @@ -1,15 +1,15 @@ -Config output dir: /tmp/eg_gen +Config output dir: /tmp/eg_gen_super ConfigTemplate for super node: "" ConfigTemplate for edge node: "" -Network name: eg_net +Network name: EgNet Super Node: Listen port: 3456 EdgeAPI prefix: /eg_net/eg_api - Endpoint(IPv4)(optional): example.com - Endpoint(IPv6)(optional): example.com - Endpoint(EdgeAPI): http://example.com:3456/eg_net/eg_api + Endpoint(IPv4)(optional): 127.0.0.1 + Endpoint(IPv6)(optional): + Endpoint(EdgeAPI): http://127.0.0.1:3456/eg_net/eg_api Edge Node: - Node IDs: "[1~10,11,19,23,29,31,55~66,88~99]" + Node IDs: "[1~2,100]" MacAddress prefix: "" IPv4 range: 192.168.76.0/24 IPv6 range: fd95:71cb:a3df:e586::/64 diff --git a/example_config/super_mode/s1.yaml b/example_config/super_mode/s1.yaml deleted file mode 100644 index 2ee7e03..0000000 --- a/example_config/super_mode/s1.yaml +++ /dev/null @@ -1,47 +0,0 @@ -NodeName: NodeSuper -PostScript: "" -PrivKeyV4: mL5IW0GuqbjgDeOJuPHBU2iJzBPNKhaNEXbIGwwYWWk= -PrivKeyV6: +EdOKIoBp/EvIusHDsvXhV1RJYbyN3Qr8nxlz35wl3I= -ListenPort: 3000 -ListenPort_EdgeAPI: "3000" -ListenPort_ManageAPI: "3000" -API_Prefix: /eg_api -RePushConfigInterval: 30 -HttpPostInterval: 50 -PeerAliveTimeout: 70 -SendPingInterval: 15 -LogLevel: - LogLevel: normal - LogTransit: true - LogControl: true - LogNormal: false - LogInternal: true - LogNTP: false -Passwords: - ShowState: passwd_showstate - AddPeer: passwd_addpeer - DelPeer: passwd_delpeer - UpdatePeer: passwd_updatepeer - UpdateSuper: passwd_updatesuper -GraphRecalculateSetting: - StaticMode: false - JitterTolerance: 5 - JitterToleranceMultiplier: 1.01 - TimeoutCheckInterval: 5 - RecalculateCoolDown: 5 -NextHopTable: {} -EdgeTemplate: example_config/super_mode/n1.yaml -UsePSKForInterEdge: true -Peers: -- NodeID: 1 - Name: Node_01 - PubKey: ZqzLVSbXzjppERslwbf2QziWruW3V/UIx9oqwU8Fn3I= - PSKey: iPM8FXfnHVzwjguZHRW9bLNY+h7+B1O2oTJtktptQkI= - AdditionalCost: 10 - SkipLocalIP: false -- NodeID: 2 - Name: Node_02 - PubKey: dHeWQtlTPQGy87WdbUARS4CtwVaR2y7IQ1qcX4GKSXk= - PSKey: juJMQaGAaeSy8aDsXSKNsPZv/nFiPj4h/1G70tGYygs= - AdditionalCost: 10 - SkipLocalIP: false diff --git a/example_config/super_mode/n1_fd.yaml b/example_config/super_mode/testfd/n1_fd.yaml similarity index 100% rename from example_config/super_mode/n1_fd.yaml rename to example_config/super_mode/testfd/n1_fd.yaml diff --git a/example_config/super_mode/n1_test_fd_mode.py b/example_config/super_mode/testfd/n1_test_fd_mode.py similarity index 100% rename from example_config/super_mode/n1_test_fd_mode.py rename to example_config/super_mode/testfd/n1_test_fd_mode.py diff --git a/example_config/super_mode/n1_test_fd_mode2.go b/example_config/super_mode/testfd/n1_test_fd_mode2.go similarity index 100% rename from example_config/super_mode/n1_test_fd_mode2.go rename to example_config/super_mode/testfd/n1_test_fd_mode2.go diff --git a/example_config/super_mode/n1_test_fd_mode2.py b/example_config/super_mode/testfd/n1_test_fd_mode2.py similarity index 100% rename from example_config/super_mode/n1_test_fd_mode2.py rename to example_config/super_mode/testfd/n1_test_fd_mode2.py diff --git a/gencfg/gencfgNM.go b/gencfg/gencfgNM.go index 111a81f..763d184 100644 --- a/gencfg/gencfgNM.go +++ b/gencfg/gencfgNM.go @@ -92,17 +92,18 @@ func GenNMCfg(NMCinfigPath string, printExample bool) (err error) { if !all_verts[NodeID] { return fmt.Errorf("duplicate definition: NodeID %v ", NodeID) } - _, err = conn.LookupIP(endpoint, 0) - if err != nil { - return err + if endpoint != "" { + _, err = conn.LookupIP(endpoint, 0) + if err != nil { + return err + } } pri, pub := device.RandomKeyPair() edge_infos[NodeID] = edge_info{ - Endpoint: endpoint, - PrivKey: pri.ToString(), - PubKey: pub.ToString(), - PersistentKeepalive: edgeinfo.PersistentKeepalive, - ConnectedEdge: make(map[mtypes.Vertex]bool), + Endpoint: endpoint, + PrivKey: pri.ToString(), + PubKey: pub.ToString(), + ConnectedEdge: make(map[mtypes.Vertex]bool), } all_verts[NodeID] = false if NodeID > MaxNodeID { @@ -118,7 +119,7 @@ func GenNMCfg(NMCinfigPath string, printExample bool) (err error) { s := edge.Src_nodeID d := edge.Dst_nodeID if len(edge_infos[s].Endpoint)+len(edge_infos[d].Endpoint) == 0 { - return fmt.Errorf("there are an edge between node %v and %v, but non of them have endpoint", s, d) + return fmt.Errorf("there are an edge between node [%v , %v], but non of them have endpoint", s, d) } edge_infos[s].ConnectedEdge[d] = true edge_infos[d].ConnectedEdge[s] = true @@ -132,7 +133,7 @@ func GenNMCfg(NMCinfigPath string, printExample bool) (err error) { pbyte := mtypes.RandomBytes(4, []byte{0xaa, 0xbb, 0xcc, 0xdd}) pbyte[0] &^= 0b00000001 pbyte[0] |= 0b00000010 - NMCfg.EdgeNode.MacPrefix = fmt.Sprintf("%X:%X:%X:%X", pbyte[0], pbyte[1], pbyte[2], pbyte[3]) + NMCfg.EdgeNode.MacPrefix = fmt.Sprintf("%02X:%02X:%02X:%02X", pbyte[0], pbyte[1], pbyte[2], pbyte[3]) } dist, next, err := g.FloydWarshall(false) @@ -177,6 +178,8 @@ func GenNMCfg(NMCinfigPath string, printExample bool) (err error) { econfig.NodeID = NodeID idstr := fmt.Sprintf("%0"+strconv.Itoa(len(MaxNodeID.ToString()))+"d", NodeID) econfig.NodeName = NMCfg.NetworkName + idstr + PersistentKeepalive := uint32(30) + econfig.ListenPort = 0 if Edge.Endpoint != "" { ps := strings.Split(Edge.Endpoint, ":") pss := ps[len(ps)-1] @@ -185,6 +188,7 @@ func GenNMCfg(NMCinfigPath string, printExample bool) (err error) { return err } econfig.ListenPort = int(port) + PersistentKeepalive = 0 } econfig.Peers = make([]mtypes.PeerInfo, 0) for CNodeID, _ := range Edge.ConnectedEdge { @@ -193,7 +197,7 @@ func GenNMCfg(NMCinfigPath string, printExample bool) (err error) { PubKey: edge_infos[CNodeID].PubKey, PSKey: pskdb.GetPSK(NodeID, CNodeID).ToString(), EndPoint: edge_infos[CNodeID].Endpoint, - PersistentKeepalive: Edge.PersistentKeepalive, + PersistentKeepalive: PersistentKeepalive, Static: true, }) } diff --git a/gencfg/gencfgSM.go b/gencfg/gencfgSM.go index f90ef39..7a19753 100644 --- a/gencfg/gencfgSM.go +++ b/gencfg/gencfgSM.go @@ -220,7 +220,7 @@ func GenSuperCfg(SMCinfigPath string, printExample bool) (err error) { pbyte := mtypes.RandomBytes(4, []byte{0xaa, 0xbb, 0xcc, 0xdd}) pbyte[0] &^= 0b00000001 pbyte[0] |= 0b00000010 - MacPrefix = fmt.Sprintf("%X:%X:%X:%X", pbyte[0], pbyte[1], pbyte[2], pbyte[3]) + MacPrefix = fmt.Sprintf("%02X:%02X:%02X:%02X", pbyte[0], pbyte[1], pbyte[2], pbyte[3]) } IPv4Block := SMCfg.EdgeNode.IPv4Range @@ -264,6 +264,7 @@ func GenSuperCfg(SMCinfigPath string, printExample bool) (err error) { peerceconf.DynamicRoute.SuperNode.EndpointV4 = EndpointV4 + ":" + ListenPort peerceconf.DynamicRoute.SuperNode.EndpointV6 = EndpointV6 + ":" + ListenPort peerceconf.DynamicRoute.SuperNode.EndpointEdgeAPIUrl = EndpointEdgeAPIUrl + peerceconf.DynamicRoute.P2P.GraphRecalculateSetting.DampingResistance = 0 peerceconf.Interface.MacAddrPrefix = MacPrefix peerceconf.Interface.IPv4CIDR = IPv4Block peerceconf.Interface.IPv6CIDR = IPv6Block diff --git a/gencfg/types.go b/gencfg/types.go index 46dd708..f315d56 100644 --- a/gencfg/types.go +++ b/gencfg/types.go @@ -68,5 +68,4 @@ type edge_info struct { ConnectedEdge map[mtypes.Vertex]bool PrivKey string PubKey string - PersistentKeepalive uint32 } diff --git a/main_edge.go b/main_edge.go index 011bbd9..1ece5de 100644 --- a/main_edge.go +++ b/main_edge.go @@ -51,6 +51,9 @@ func Edge(configPath string, useUAPI bool, printExample bool, bindmode string) ( if len(NodeName) > 32 { return errors.New("Node name can't longer than 32 :" + NodeName) } + if econfig.DynamicRoute.P2P.GraphRecalculateSetting.DampingResistance < 0 || econfig.DynamicRoute.P2P.GraphRecalculateSetting.DampingResistance >= 1 { + return fmt.Errorf("DampingResistance must in range [0,1) : %v", econfig.DynamicRoute.P2P.GraphRecalculateSetting.DampingResistance) + } var logLevel int switch econfig.LogLevel.LogLevel { case "verbose", "debug": diff --git a/main_httpserver.go b/main_httpserver.go index 1e7c38a..9e8303e 100644 --- a/main_httpserver.go +++ b/main_httpserver.go @@ -684,7 +684,7 @@ func manage_peeradd(w http.ResponseWriter, r *http.Request) { func manage_peerupdate(w http.ResponseWriter, r *http.Request) { params := r.URL.Query() - toUpdate := mtypes.NodeID_Boardcast + toUpdate := mtypes.NodeID_Broadcast var err error var NodeID mtypes.Vertex @@ -856,7 +856,7 @@ func manage_superupdate(w http.ResponseWriter, r *http.Request) { func manage_peerdel(w http.ResponseWriter, r *http.Request) { params := r.URL.Query() - toDelete := mtypes.NodeID_Boardcast + toDelete := mtypes.NodeID_Broadcast var err error var NodeID mtypes.Vertex @@ -900,7 +900,7 @@ func manage_peerdel(w http.ResponseWriter, r *http.Request) { toDelete = peerinfo.NodeID } } - if toDelete == mtypes.NodeID_Boardcast { + if toDelete == mtypes.NodeID_Broadcast { w.WriteHeader(http.StatusNotFound) w.Write([]byte(fmt.Sprintf("Paramater PrivKey: \"%v\" not found", PubKey))) return diff --git a/main_super.go b/main_super.go index 8294f99..95214cb 100644 --- a/main_super.go +++ b/main_super.go @@ -100,6 +100,9 @@ func Super(configPath string, useUAPI bool, printExample bool, bindmode string) if sconfig.RePushConfigInterval <= 0 { return fmt.Errorf("RePushConfigInterval must > 0 : %v", sconfig.RePushConfigInterval) } + if sconfig.GraphRecalculateSetting.DampingResistance < 0 || sconfig.GraphRecalculateSetting.DampingResistance >= 1 { + return fmt.Errorf("DampingResistance must in range [0,1) : %v", sconfig.GraphRecalculateSetting.DampingResistance) + } var logLevel int switch sconfig.LogLevel.LogLevel { diff --git a/mtypes/config.go b/mtypes/config.go index 4fea539..d70092f 100644 --- a/mtypes/config.go +++ b/mtypes/config.go @@ -10,11 +10,11 @@ import ( type Vertex uint16 const ( - NodeID_Boardcast Vertex = math.MaxUint16 - iota // Normal boardcast, boardcast with route table + NodeID_Broadcast Vertex = math.MaxUint16 - iota // Normal boardcast, boardcast with route table NodeID_AllPeer Vertex = math.MaxUint16 - iota // p2p mode: boardcast to every know peer and prevent dup. super mode: send to supernode NodeID_SuperNode Vertex = math.MaxUint16 - iota - NodeID_Invalid Vertex = math.MaxUint16 - iota - NodeID_Special Vertex = NodeID_Invalid + NodeID_Invalid Vertex = math.MaxUint16 - iota + NodeID_Special Vertex = NodeID_Invalid ) type EdgeConfig struct { @@ -99,15 +99,15 @@ type SuperPeerInfo struct { type LoggerInfo struct { LogLevel string `yaml:"LogLevel"` LogTransit bool `yaml:"LogTransit"` - LogControl bool `yaml:"LogControl"` LogNormal bool `yaml:"LogNormal"` + LogControl bool `yaml:"LogControl"` LogInternal bool `yaml:"LogInternal"` LogNTP bool `yaml:"LogNTP"` } func (v *Vertex) ToString() string { switch *v { - case NodeID_Boardcast: + case NodeID_Broadcast: return "Boardcast" case NodeID_AllPeer: return "Control" diff --git a/path/header.go b/path/header.go index 63e50dc..926a54a 100644 --- a/path/header.go +++ b/path/header.go @@ -32,6 +32,64 @@ const ( BroadcastPeer ) +func (v Usage) IsNormal() bool { + return v == NormalPacket +} + +func (v Usage) IsControl() bool { + switch v { + case Register: + return true + case ServerUpdate: + return true + case PingPacket: + return true + case PongPacket: + return true + case QueryPeer: + return true + case BroadcastPeer: + return true + default: + return false + } +} + +func (v Usage) IsControl_Super2Edge() bool { + switch v { + case ServerUpdate: + return true + default: + return false + } +} + +func (v Usage) IsControl_Edge2Super() bool { + switch v { + case Register: + return true + case PongPacket: + return true + default: + return false + } +} + +func (v Usage) IsControl_Edge2Edge() bool { + switch v { + case PingPacket: + return true + case PongPacket: + return true + case QueryPeer: + return true + case BroadcastPeer: + return true + default: + return false + } +} + func NewEgHeader(pac []byte) (e EgHeader, err error) { if len(pac) != EgHeaderLen { err = errors.New("invalid packet size") diff --git a/path/path.go b/path/path.go index 19da8c0..c97d189 100644 --- a/path/path.go +++ b/path/path.go @@ -189,7 +189,7 @@ func (g *IG) UpdateLatencyMulti(pong_info []mtypes.PongMsg, recalculate bool, ch newval := pong_msg.Timediff if _, ok := g.gsetting.ManualLatency[u]; ok { if _, ok := g.gsetting.ManualLatency[u][v]; ok { - newval = g.gsetting.ManualLatency[u][v] + newval = g.gsetting.ManualLatency[u][v] / 1000 // s to ms } } w := newval @@ -206,7 +206,7 @@ func (g *IG) UpdateLatencyMulti(pong_info []mtypes.PongMsg, recalculate bool, ch g.edgelock.Unlock() oldval := g.OldWeight(u, v, false) g.edgelock.Lock() - if oldval != mtypes.Infinity { + if oldval != mtypes.Infinity && g.IsSuperMode && g.gsetting.DampingResistance > 0 { w = oldval*g.gsetting.DampingResistance + newval*(1-g.gsetting.DampingResistance) } should_update = should_update || g.ShouldUpdate(oldval, w, false) diff --git a/tap/tap_stdio.go b/tap/tap_stdio.go index 37fc722..02d35bd 100644 --- a/tap/tap_stdio.go +++ b/tap/tap_stdio.go @@ -100,10 +100,11 @@ func (tap *StdIOTap) Read(buf []byte, offset int) (int, error) { } } // read a packet from the device (without any additional headers) func (tap *StdIOTap) Write(buf []byte, offset int) (size int, err error) { - packet := make([]byte, len(buf[offset:])) - copy(packet, buf[offset:]) + packet := buf[offset:] switch tap.L2mode { case KeyboardDebug: + packet = make([]byte, len(buf[offset:])) + copy(packet, buf[offset:]) src := Mac2charForm(packet[6:12]) dst := Mac2charForm(packet[0:6]) packet[10] = dst