Merge "APF: filter unwanted ARP replies" into nyc-mr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
b91c1232ee
@@ -180,6 +180,7 @@ public class ApfFilter {
|
|||||||
private static final int IPV4_FRAGMENT_OFFSET_MASK = 0x1fff;
|
private static final int IPV4_FRAGMENT_OFFSET_MASK = 0x1fff;
|
||||||
private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
|
private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
|
||||||
private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
|
private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
|
||||||
|
private static final int IPV4_ANY_HOST_ADDRESS = 0;
|
||||||
|
|
||||||
private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
|
private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
|
||||||
private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8;
|
private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8;
|
||||||
@@ -201,12 +202,14 @@ public class ApfFilter {
|
|||||||
private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28;
|
private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28;
|
||||||
|
|
||||||
private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
|
private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
|
||||||
private static final byte[] ARP_IPV4_REQUEST_HEADER = new byte[]{
|
private static final int ARP_OPCODE_OFFSET = ARP_HEADER_OFFSET + 6;
|
||||||
|
private static final short ARP_OPCODE_REQUEST = 1;
|
||||||
|
private static final short ARP_OPCODE_REPLY = 2;
|
||||||
|
private static final byte[] ARP_IPV4_HEADER = new byte[]{
|
||||||
0, 1, // Hardware type: Ethernet (1)
|
0, 1, // Hardware type: Ethernet (1)
|
||||||
8, 0, // Protocol type: IP (0x0800)
|
8, 0, // Protocol type: IP (0x0800)
|
||||||
6, // Hardware size: 6
|
6, // Hardware size: 6
|
||||||
4, // Protocol size: 4
|
4, // Protocol size: 4
|
||||||
0, 1 // Opcode: request (1)
|
|
||||||
};
|
};
|
||||||
private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24;
|
private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24;
|
||||||
|
|
||||||
@@ -667,23 +670,48 @@ public class ApfFilter {
|
|||||||
private void generateArpFilterLocked(ApfGenerator gen) throws IllegalInstructionException {
|
private void generateArpFilterLocked(ApfGenerator gen) throws IllegalInstructionException {
|
||||||
// Here's a basic summary of what the ARP filter program does:
|
// Here's a basic summary of what the ARP filter program does:
|
||||||
//
|
//
|
||||||
// if interface has IPv4 address:
|
// if not ARP IPv4
|
||||||
// if it's not an ARP IPv4 request:
|
// pass
|
||||||
// pass
|
// if not ARP IPv4 reply or request
|
||||||
// if it's not a request for our IPv4 address:
|
// pass
|
||||||
// drop
|
// if unicast ARP reply
|
||||||
|
// pass
|
||||||
|
// if interface has no IPv4 address
|
||||||
|
// if target ip is 0.0.0.0
|
||||||
|
// drop
|
||||||
|
// else
|
||||||
|
// if target ip is not the interface ip
|
||||||
|
// drop
|
||||||
// pass
|
// pass
|
||||||
|
|
||||||
if (mIPv4Address != null) {
|
final String checkTargetIPv4 = "checkTargetIPv4";
|
||||||
// if it's not an ARP IPv4 request, pass
|
|
||||||
gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET);
|
// Pass if not ARP IPv4.
|
||||||
gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_REQUEST_HEADER, gen.PASS_LABEL);
|
gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET);
|
||||||
// if it's not a request for our IPv4 address, drop
|
gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, gen.PASS_LABEL);
|
||||||
|
|
||||||
|
// Pass if unknown ARP opcode.
|
||||||
|
gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET);
|
||||||
|
gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkTargetIPv4); // Skip to unicast check
|
||||||
|
gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, gen.PASS_LABEL);
|
||||||
|
|
||||||
|
// Pass if unicast reply.
|
||||||
|
gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
|
||||||
|
gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, gen.PASS_LABEL);
|
||||||
|
|
||||||
|
// Either a unicast request, a unicast reply, or a broadcast reply.
|
||||||
|
gen.defineLabel(checkTargetIPv4);
|
||||||
|
if (mIPv4Address == null) {
|
||||||
|
// When there is no IPv4 address, drop GARP replies (b/29404209).
|
||||||
|
gen.addLoad32(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
|
||||||
|
gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, gen.DROP_LABEL);
|
||||||
|
} else {
|
||||||
|
// When there is an IPv4 address, drop unicast/broadcast requests
|
||||||
|
// and broadcast replies with a different target IPv4 address.
|
||||||
gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
|
gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
|
||||||
gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, gen.DROP_LABEL);
|
gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, gen.DROP_LABEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, pass
|
|
||||||
gen.addJump(gen.PASS_LABEL);
|
gen.addJump(gen.PASS_LABEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -580,7 +580,7 @@ public class ApfTest extends AndroidTestCase {
|
|||||||
|
|
||||||
public TestApfFilter(IpManager.Callback ipManagerCallback, boolean multicastFilter,
|
public TestApfFilter(IpManager.Callback ipManagerCallback, boolean multicastFilter,
|
||||||
IpConnectivityLog log) throws Exception {
|
IpConnectivityLog log) throws Exception {
|
||||||
super(new ApfCapabilities(2, 1000, ARPHRD_ETHER), NetworkInterface.getByName("lo"),
|
super(new ApfCapabilities(2, 1536, ARPHRD_ETHER), NetworkInterface.getByName("lo"),
|
||||||
ipManagerCallback, multicastFilter, log);
|
ipManagerCallback, multicastFilter, log);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -618,6 +618,7 @@ public class ApfTest extends AndroidTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final int ETH_HEADER_LEN = 14;
|
private static final int ETH_HEADER_LEN = 14;
|
||||||
|
private static final int ETH_DEST_ADDR_OFFSET = 0;
|
||||||
private static final int ETH_ETHERTYPE_OFFSET = 12;
|
private static final int ETH_ETHERTYPE_OFFSET = 12;
|
||||||
private static final byte[] ETH_BROADCAST_MAC_ADDRESS = new byte[]{
|
private static final byte[] ETH_BROADCAST_MAC_ADDRESS = new byte[]{
|
||||||
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
|
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
|
||||||
@@ -676,9 +677,18 @@ public class ApfTest extends AndroidTestCase {
|
|||||||
4, // Protocol size: 4
|
4, // Protocol size: 4
|
||||||
0, 1 // Opcode: request (1)
|
0, 1 // Opcode: request (1)
|
||||||
};
|
};
|
||||||
|
private static final byte[] ARP_IPV4_REPLY_HEADER = new byte[]{
|
||||||
|
0, 1, // Hardware type: Ethernet (1)
|
||||||
|
8, 0, // Protocol type: IP (0x0800)
|
||||||
|
6, // Hardware size: 6
|
||||||
|
4, // Protocol size: 4
|
||||||
|
0, 2 // Opcode: reply (2)
|
||||||
|
};
|
||||||
private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24;
|
private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24;
|
||||||
|
|
||||||
private static final byte[] MOCK_IPV4_ADDR = new byte[]{10, 0, 0, 1};
|
private static final byte[] MOCK_IPV4_ADDR = new byte[]{10, 0, 0, 1};
|
||||||
|
private static final byte[] ANOTHER_IPV4_ADDR = new byte[]{10, 0, 0, 2};
|
||||||
|
private static final byte[] IPV4_ANY_HOST_ADDR = new byte[]{0, 0, 0, 0};
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
public void testApfFilterIPv4() throws Exception {
|
public void testApfFilterIPv4() throws Exception {
|
||||||
@@ -801,51 +811,81 @@ public class ApfTest extends AndroidTestCase {
|
|||||||
apfFilter.shutdown();
|
apfFilter.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyArpFilter(MockIpManagerCallback ipManagerCallback, ApfFilter apfFilter,
|
private byte[] getProgram(MockIpManagerCallback cb, ApfFilter filter, LinkProperties lp) {
|
||||||
LinkProperties linkProperties, int filterResult) {
|
cb.resetApfProgramWait();
|
||||||
ipManagerCallback.resetApfProgramWait();
|
filter.setLinkProperties(lp);
|
||||||
apfFilter.setLinkProperties(linkProperties);
|
return cb.getApfProgram();
|
||||||
byte[] program = ipManagerCallback.getApfProgram();
|
}
|
||||||
ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
|
|
||||||
packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
|
private void verifyArpFilter(byte[] program, int filterResult) {
|
||||||
assertPass(program, packet.array(), 0);
|
// Verify ARP request packet
|
||||||
packet.position(ARP_HEADER_OFFSET);
|
assertPass(program, arpRequestBroadcast(MOCK_IPV4_ADDR), 0);
|
||||||
packet.put(ARP_IPV4_REQUEST_HEADER);
|
assertVerdict(filterResult, program, arpRequestBroadcast(ANOTHER_IPV4_ADDR), 0);
|
||||||
assertVerdict(filterResult, program, packet.array(), 0);
|
assertDrop(program, arpRequestBroadcast(IPV4_ANY_HOST_ADDR), 0);
|
||||||
packet.position(ARP_TARGET_IP_ADDRESS_OFFSET);
|
|
||||||
packet.put(MOCK_IPV4_ADDR);
|
// Verify unicast ARP reply packet is always accepted.
|
||||||
assertPass(program, packet.array(), 0);
|
assertPass(program, arpReplyUnicast(MOCK_IPV4_ADDR), 0);
|
||||||
|
assertPass(program, arpReplyUnicast(ANOTHER_IPV4_ADDR), 0);
|
||||||
|
assertPass(program, arpReplyUnicast(IPV4_ANY_HOST_ADDR), 0);
|
||||||
|
|
||||||
|
// Verify GARP reply packets are always filtered
|
||||||
|
assertDrop(program, garpReply(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
public void testApfFilterArp() throws Exception {
|
public void testApfFilterArp() throws Exception {
|
||||||
MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
|
MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
|
||||||
ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, mLog);
|
ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, mLog);
|
||||||
byte[] program = ipManagerCallback.getApfProgram();
|
|
||||||
|
|
||||||
// Verify initially ARP filter is off
|
// Verify initially ARP request filter is off, and GARP filter is on.
|
||||||
ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
|
verifyArpFilter(ipManagerCallback.getApfProgram(), PASS);
|
||||||
packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
|
|
||||||
assertPass(program, packet.array(), 0);
|
|
||||||
packet.position(ARP_HEADER_OFFSET);
|
|
||||||
packet.put(ARP_IPV4_REQUEST_HEADER);
|
|
||||||
assertPass(program, packet.array(), 0);
|
|
||||||
packet.position(ARP_TARGET_IP_ADDRESS_OFFSET);
|
|
||||||
packet.put(MOCK_IPV4_ADDR);
|
|
||||||
assertPass(program, packet.array(), 0);
|
|
||||||
|
|
||||||
// Inform ApfFilter of our address and verify ARP filtering is on
|
// Inform ApfFilter of our address and verify ARP filtering is on
|
||||||
|
LinkAddress linkAddress = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24);
|
||||||
LinkProperties lp = new LinkProperties();
|
LinkProperties lp = new LinkProperties();
|
||||||
assertTrue(lp.addLinkAddress(
|
assertTrue(lp.addLinkAddress(linkAddress));
|
||||||
new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24)));
|
verifyArpFilter(getProgram(ipManagerCallback, apfFilter, lp), DROP);
|
||||||
verifyArpFilter(ipManagerCallback, apfFilter, lp, DROP);
|
|
||||||
|
|
||||||
// Inform ApfFilter of loss of IP and verify ARP filtering is off
|
// Inform ApfFilter of loss of IP and verify ARP filtering is off
|
||||||
verifyArpFilter(ipManagerCallback, apfFilter, new LinkProperties(), PASS);
|
verifyArpFilter(getProgram(ipManagerCallback, apfFilter, new LinkProperties()), PASS);
|
||||||
|
|
||||||
apfFilter.shutdown();
|
apfFilter.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static byte[] arpRequestBroadcast(byte[] tip) {
|
||||||
|
ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
|
||||||
|
packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
|
||||||
|
packet.position(ETH_DEST_ADDR_OFFSET);
|
||||||
|
packet.put(ETH_BROADCAST_MAC_ADDRESS);
|
||||||
|
packet.position(ARP_HEADER_OFFSET);
|
||||||
|
packet.put(ARP_IPV4_REQUEST_HEADER);
|
||||||
|
packet.position(ARP_TARGET_IP_ADDRESS_OFFSET);
|
||||||
|
packet.put(tip);
|
||||||
|
return packet.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] arpReplyUnicast(byte[] tip) {
|
||||||
|
ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
|
||||||
|
packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
|
||||||
|
packet.position(ARP_HEADER_OFFSET);
|
||||||
|
packet.put(ARP_IPV4_REPLY_HEADER);
|
||||||
|
packet.position(ARP_TARGET_IP_ADDRESS_OFFSET);
|
||||||
|
packet.put(tip);
|
||||||
|
return packet.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] garpReply() {
|
||||||
|
ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
|
||||||
|
packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
|
||||||
|
packet.position(ETH_DEST_ADDR_OFFSET);
|
||||||
|
packet.put(ETH_BROADCAST_MAC_ADDRESS);
|
||||||
|
packet.position(ARP_HEADER_OFFSET);
|
||||||
|
packet.put(ARP_IPV4_REPLY_HEADER);
|
||||||
|
packet.position(ARP_TARGET_IP_ADDRESS_OFFSET);
|
||||||
|
packet.put(IPV4_ANY_HOST_ADDR);
|
||||||
|
return packet.array();
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that the last program pushed to the IpManager.Callback properly filters the
|
// Verify that the last program pushed to the IpManager.Callback properly filters the
|
||||||
// given packet for the given lifetime.
|
// given packet for the given lifetime.
|
||||||
private void verifyRaLifetime(MockIpManagerCallback ipManagerCallback, ByteBuffer packet,
|
private void verifyRaLifetime(MockIpManagerCallback ipManagerCallback, ByteBuffer packet,
|
||||||
|
|||||||
Reference in New Issue
Block a user