Repository navigation
ipv6: take a global source when the outgoing interface is link-local only #1256
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1006,9 +1006,33 @@ void Ipv6::fragmentPostRouting(Packet *packet, const NetworkInterface *ie, const | |
| !ipv6Header->getDestAddress().isSolicitedNodeMulticastAddress()) | ||
| { | ||
| // source address can be unspecified during DAD | ||
| const Ipv6Address& srcAddr = ie->getProtocolData<Ipv6InterfaceData>()->getPreferredAddress(); | ||
| Ipv6Address srcAddr = ie->getProtocolData<Ipv6InterfaceData>()->getPreferredAddress(); | ||
| ASSERT(!srcAddr.isUnspecified()); // FIXME what if we don't have an address yet? | ||
|
|
||
| // A link-local source must not leave its link (RFC 4291 Section 2.5.6), but an | ||
| // outgoing interface with only a link-local address, such as an | ||
| // Ipv6TunnelInterface, has no other address. For a global destination, take the | ||
| // source from another interface, as Linux also does: RFC 6724 Section 4 only | ||
| // recommends the outgoing interface's addresses as the candidates, and its Rule | ||
| // 2 prefers a source whose scope is at least the destination's. A tentative | ||
| // address is not assigned yet (RFC 4862 Section 5.4), unless Optimistic | ||
| // Duplicate Address Detection (RFC 4429) allows its use. | ||
| const Ipv6Address& destAddr = ipv6Header->getDestAddress(); | ||
| if (srcAddr.isLinkLocal() && destAddr.getScope() == Ipv6Address::GLOBAL) { | ||
| for (int i = 0; i < ift->getNumInterfaces(); i++) { | ||
| const NetworkInterface *candidateIE = ift->getInterface(i); | ||
| auto candidateData = candidateIE->findProtocolData<Ipv6InterfaceData>(); | ||
| if (candidateIE == ie || !candidateIE->isUp() || candidateData == nullptr) | ||
| continue; | ||
| const Ipv6Address& candidate = candidateData->getPreferredAddress(); | ||
| bool tentative = candidateData->isTentativeAddress(candidate) && !candidateData->isOptimisticDad(); | ||
| if (candidate.getScope() == Ipv6Address::GLOBAL && !tentative) { | ||
|
Comment on lines
+1027
to
+1029
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Tentative preference hides usable global source When an alternative interface prefers a tentative global address, Learn moreAn interface can have several IPv6 addresses. choosePreferredAddress selects a tentative global address before a non-tentative link-local address, but an existing non-tentative global address can also be present with different preference ordering. If Example: Let eth0 hold an assigned global address with a shorter lifetime and a tentative global address with a longer lifetime, while tun0 holds only Recommended fix: Search each candidate interface's address list for an assigned, unexpired global unicast address before falling back to its tentative preferred address. Add a tunnel test with both tentative and assigned global addresses on the same candidate interface. Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| srcAddr = candidate; | ||
|
Comment on lines
+1027
to
+1030
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Expired global address becomes tunnel source After a candidate's valid lifetime expires, Learn moreAn IPv6 address assigned with a finite valid lifetime remains cached in Example: eth0 has a global address valid until simulation time 10s; tun0 has only Recommended fix: Validate candidate address expiry against Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // TODO factor out | ||
| ipv6Header = nullptr; | ||
| auto newIpv6Header = removeNetworkProtocolHeader<Ipv6Header>(packet); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Disconnected interface supplies tunnel source
When an alternative interface loses carrier but remains up,
isUp()still accepts its global address. The fallback can choose it before a connected interface, sending replies toward a disconnected link.Learn more
Interface administrative state and carrier state are separate. NetworkInterface exposes both
isUp()andhasCarrier(), and a carrier loss does not clear its addresses. The fallback chooses the first candidate in interface order, so a carrierless interface can displace a connected one with a working global address.Example: A host has tun0 with
fe80::, eth0 with a global address but no carrier, and eth1 with a reachable global address. A packet sent through tun0 chooses eth0's address because eth0 remains administratively up. Replies routed to eth0's disconnected network fail rather than reaching eth1.Recommended fix: Require both
isUp()andhasCarrier()for cross-interface source candidates, consistent with interface availability checks in Ipv6RoutingTable. Add a test with a carrierless first candidate and a connected second candidate.Was this helpful? React with 👍 or 👎 to provide feedback.