blob: 1705f1c803dc09693a5a4961754a0f37e28f7756 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.net.module.util.netlink;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY;
import android.annotation.SuppressLint;
import android.net.IpPrefix;
import android.system.OsConstants;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
/**
* A NetlinkMessage subclass for rtnetlink route messages.
*
* RtNetlinkRouteMessage.parse() must be called with a ByteBuffer that contains exactly one
* netlink message.
*
* see also:
*
* include/uapi/linux/rtnetlink.h
*
* @hide
*/
public class RtNetlinkRouteMessage extends NetlinkMessage {
public static final short RTA_DST = 1;
public static final short RTA_OIF = 4;
public static final short RTA_GATEWAY = 5;
private int mIfindex;
@NonNull
private StructRtMsg mRtmsg;
@NonNull
private IpPrefix mDestination;
@Nullable
private InetAddress mGateway;
private RtNetlinkRouteMessage(StructNlMsgHdr header) {
super(header);
mRtmsg = null;
mDestination = null;
mGateway = null;
mIfindex = 0;
}
public int getInterfaceIndex() {
return mIfindex;
}
@NonNull
public StructRtMsg getRtMsgHeader() {
return mRtmsg;
}
@NonNull
public IpPrefix getDestination() {
return mDestination;
}
@Nullable
public InetAddress getGateway() {
return mGateway;
}
/**
* Check whether the address families of destination and gateway match rtm_family in
* StructRtmsg.
*
* For example, IPv4-mapped IPv6 addresses as an IPv6 address will be always converted to IPv4
* address, that's incorrect when upper layer creates a new {@link RouteInfo} class instance
* for IPv6 route with the converted IPv4 gateway.
*/
private static boolean matchRouteAddressFamily(@NonNull final InetAddress address,
int family) {
return ((address instanceof Inet4Address) && (family == AF_INET))
|| ((address instanceof Inet6Address) && (family == AF_INET6));
}
/**
* Parse rtnetlink route message from {@link ByteBuffer}. This method must be called with a
* ByteBuffer that contains exactly one netlink message.
*
* @param header netlink message header.
* @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes.
*/
@SuppressLint("NewApi")
@Nullable
public static RtNetlinkRouteMessage parse(@NonNull final StructNlMsgHdr header,
@NonNull final ByteBuffer byteBuffer) {
final RtNetlinkRouteMessage routeMsg = new RtNetlinkRouteMessage(header);
routeMsg.mRtmsg = StructRtMsg.parse(byteBuffer);
if (routeMsg.mRtmsg == null) return null;
int rtmFamily = routeMsg.mRtmsg.family;
// RTA_DST
final int baseOffset = byteBuffer.position();
StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(RTA_DST, byteBuffer);
if (nlAttr != null) {
final InetAddress destination = nlAttr.getValueAsInetAddress();
// If the RTA_DST attribute is malformed, return null.
if (destination == null) return null;
// If the address family of destination doesn't match rtm_family, return null.
if (!matchRouteAddressFamily(destination, rtmFamily)) return null;
routeMsg.mDestination = new IpPrefix(destination, routeMsg.mRtmsg.dstLen);
} else if (rtmFamily == AF_INET) {
routeMsg.mDestination = new IpPrefix(IPV4_ADDR_ANY, 0);
} else if (rtmFamily == AF_INET6) {
routeMsg.mDestination = new IpPrefix(IPV6_ADDR_ANY, 0);
} else {
return null;
}
// RTA_GATEWAY
byteBuffer.position(baseOffset);
nlAttr = StructNlAttr.findNextAttrOfType(RTA_GATEWAY, byteBuffer);
if (nlAttr != null) {
routeMsg.mGateway = nlAttr.getValueAsInetAddress();
// If the RTA_GATEWAY attribute is malformed, return null.
if (routeMsg.mGateway == null) return null;
// If the address family of gateway doesn't match rtm_family, return null.
if (!matchRouteAddressFamily(routeMsg.mGateway, rtmFamily)) return null;
}
// RTA_OIF
byteBuffer.position(baseOffset);
nlAttr = StructNlAttr.findNextAttrOfType(RTA_OIF, byteBuffer);
if (nlAttr != null) {
// Any callers that deal with interface names are responsible for converting
// the interface index to a name themselves. This may not succeed or may be
// incorrect, because the interface might have been deleted, or even deleted
// and re-added with a different index, since the netlink message was sent.
routeMsg.mIfindex = nlAttr.getValueAsInt(0 /* 0 isn't a valid ifindex */);
}
return routeMsg;
}
/**
* Write a rtnetlink address message to {@link ByteBuffer}.
*/
@VisibleForTesting
protected void pack(ByteBuffer byteBuffer) {
getHeader().pack(byteBuffer);
mRtmsg.pack(byteBuffer);
final StructNlAttr destination = new StructNlAttr(RTA_DST, mDestination.getAddress());
destination.pack(byteBuffer);
if (mGateway != null) {
final StructNlAttr gateway = new StructNlAttr(RTA_GATEWAY, mGateway.getAddress());
gateway.pack(byteBuffer);
}
if (mIfindex != 0) {
final StructNlAttr ifindex = new StructNlAttr(RTA_OIF, mIfindex);
ifindex.pack(byteBuffer);
}
}
@Override
public String toString() {
return "RtNetlinkRouteMessage{ "
+ "nlmsghdr{" + mHeader.toString(OsConstants.NETLINK_ROUTE) + "}, "
+ "Rtmsg{" + mRtmsg.toString() + "}, "
+ "destination{" + mDestination.getAddress().getHostAddress() + "}, "
+ "gateway{" + (mGateway == null ? "" : mGateway.getHostAddress()) + "}, "
+ "ifindex{" + mIfindex + "} "
+ "}";
}
}