Merge changes I58f904d5,Ie0bf7804 am: 6717a6f930 am: 32efb6d2b3
am: d409459c5d

Change-Id: Ifce50d127c1b36805600df6c8c8626da59b6e813
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index 1a4119c..5accb45 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -218,6 +218,7 @@
                     .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
                     .setDnsServers(addr)
                     .setServerAddr(new LinkAddress(addr, prefixLen))
+                    .setMetered(true)
                     .build();
             // TODO: also advertise link MTU
         } catch (DhcpServingParams.InvalidParameterException e) {
diff --git a/services/net/java/android/net/dhcp/DhcpAckPacket.java b/services/net/java/android/net/dhcp/DhcpAckPacket.java
index df44b11..b2eb4e2 100644
--- a/services/net/java/android/net/dhcp/DhcpAckPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpAckPacket.java
@@ -30,8 +30,8 @@
     private final Inet4Address mSrcIp;
 
     DhcpAckPacket(int transId, short secs, boolean broadcast, Inet4Address serverAddress,
-                  Inet4Address clientIp, Inet4Address yourIp, byte[] clientMac) {
-        super(transId, secs, clientIp, yourIp, serverAddress, INADDR_ANY, clientMac, broadcast);
+            Inet4Address relayIp, Inet4Address clientIp, Inet4Address yourIp, byte[] clientMac) {
+        super(transId, secs, clientIp, yourIp, serverAddress, relayIp, clientMac, broadcast);
         mBroadcast = broadcast;
         mSrcIp = serverAddress;
     }
@@ -70,19 +70,8 @@
     void finishPacket(ByteBuffer buffer) {
         addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_ACK);
         addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
-        addTlv(buffer, DHCP_LEASE_TIME, mLeaseTime);
 
-        // the client should renew at 1/2 the lease-expiry interval
-        if (mLeaseTime != null) {
-            addTlv(buffer, DHCP_RENEWAL_TIME,
-                Integer.valueOf(mLeaseTime.intValue() / 2));
-        }
-
-        addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask);
-        addTlv(buffer, DHCP_ROUTER, mGateways);
-        addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName);
-        addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
-        addTlv(buffer, DHCP_DNS_SERVER, mDnsServers);
+        addCommonServerTlvs(buffer);
         addTlvEnd(buffer);
     }
 
diff --git a/services/net/java/android/net/dhcp/DhcpNakPacket.java b/services/net/java/android/net/dhcp/DhcpNakPacket.java
index ef9af52..1da0b73 100644
--- a/services/net/java/android/net/dhcp/DhcpNakPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpNakPacket.java
@@ -26,9 +26,10 @@
     /**
      * Generates a NAK packet with the specified parameters.
      */
-    DhcpNakPacket(int transId, short secs, Inet4Address nextIp, Inet4Address relayIp,
-            byte[] clientMac, boolean broadcast) {
-        super(transId, secs, INADDR_ANY, INADDR_ANY, nextIp, relayIp, clientMac, broadcast);
+    DhcpNakPacket(int transId, short secs, Inet4Address relayIp, byte[] clientMac,
+            boolean broadcast) {
+        super(transId, secs, INADDR_ANY /* clientIp */, INADDR_ANY /* yourIp */,
+                INADDR_ANY /* nextIp */, relayIp, clientMac, broadcast);
     }
 
     public String toString() {
diff --git a/services/net/java/android/net/dhcp/DhcpOfferPacket.java b/services/net/java/android/net/dhcp/DhcpOfferPacket.java
index 99154ef..0eba77e 100644
--- a/services/net/java/android/net/dhcp/DhcpOfferPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpOfferPacket.java
@@ -32,8 +32,8 @@
      * Generates a OFFER packet with the specified parameters.
      */
     DhcpOfferPacket(int transId, short secs, boolean broadcast, Inet4Address serverAddress,
-                    Inet4Address clientIp, Inet4Address yourIp, byte[] clientMac) {
-        super(transId, secs, clientIp, yourIp, INADDR_ANY, INADDR_ANY, clientMac, broadcast);
+            Inet4Address relayIp, Inet4Address clientIp, Inet4Address yourIp, byte[] clientMac) {
+        super(transId, secs, clientIp, yourIp, serverAddress, relayIp, clientMac, broadcast);
         mSrcIp = serverAddress;
     }
 
@@ -72,19 +72,8 @@
     void finishPacket(ByteBuffer buffer) {
         addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_OFFER);
         addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
-        addTlv(buffer, DHCP_LEASE_TIME, mLeaseTime);
 
-        // the client should renew at 1/2 the lease-expiry interval
-        if (mLeaseTime != null) {
-            addTlv(buffer, DHCP_RENEWAL_TIME,
-                Integer.valueOf(mLeaseTime.intValue() / 2));
-        }
-
-        addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask);
-        addTlv(buffer, DHCP_ROUTER, mGateways);
-        addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName);
-        addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
-        addTlv(buffer, DHCP_DNS_SERVER, mDnsServers);
+        addCommonServerTlvs(buffer);
         addTlvEnd(buffer);
     }
 }
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java
index 175e27e..595a129 100644
--- a/services/net/java/android/net/dhcp/DhcpPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpPacket.java
@@ -184,6 +184,11 @@
     protected String mVendorInfo;
 
     /**
+     * Value of the vendor specific option used to indicate that the network is metered
+     */
+    public static final String VENDOR_INFO_ANDROID_METERED = "ANDROID_METERED";
+
+    /**
      * DHCP Optional Type: DHCP Requested IP Address
      */
     protected static final byte DHCP_REQUESTED_IP = 50;
@@ -677,6 +682,23 @@
         if (!TextUtils.isEmpty(hn)) addTlv(buf, DHCP_HOST_NAME, hn);
     }
 
+    protected void addCommonServerTlvs(ByteBuffer buf) {
+        addTlv(buf, DHCP_LEASE_TIME, mLeaseTime);
+        if (mLeaseTime != null && mLeaseTime != INFINITE_LEASE) {
+            // The client should renew at 1/2 the lease-expiry interval
+            addTlv(buf, DHCP_RENEWAL_TIME, (int) (Integer.toUnsignedLong(mLeaseTime) / 2));
+            // Default rebinding time is set as below by RFC2131
+            addTlv(buf, DHCP_REBINDING_TIME,
+                    (int) (Integer.toUnsignedLong(mLeaseTime) * 875L / 1000L));
+        }
+        addTlv(buf, DHCP_SUBNET_MASK, mSubnetMask);
+        addTlv(buf, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
+        addTlv(buf, DHCP_ROUTER, mGateways);
+        addTlv(buf, DHCP_DNS_SERVER, mDnsServers);
+        addTlv(buf, DHCP_DOMAIN_NAME, mDomainName);
+        addTlv(buf, DHCP_VENDOR_INFO, mVendorInfo);
+    }
+
     /**
      * Converts a MAC from an array of octets to an ASCII string.
      */
@@ -1085,7 +1107,7 @@
                 break;
             case DHCP_MESSAGE_TYPE_OFFER:
                 newPacket = new DhcpOfferPacket(
-                    transactionId, secs, broadcast, ipSrc, clientIp, yourIp, clientMac);
+                    transactionId, secs, broadcast, ipSrc, relayIp, clientIp, yourIp, clientMac);
                 break;
             case DHCP_MESSAGE_TYPE_REQUEST:
                 newPacket = new DhcpRequestPacket(
@@ -1098,11 +1120,11 @@
                 break;
             case DHCP_MESSAGE_TYPE_ACK:
                 newPacket = new DhcpAckPacket(
-                    transactionId, secs, broadcast, ipSrc, clientIp, yourIp, clientMac);
+                    transactionId, secs, broadcast, ipSrc, relayIp, clientIp, yourIp, clientMac);
                 break;
             case DHCP_MESSAGE_TYPE_NAK:
                 newPacket = new DhcpNakPacket(
-                        transactionId, secs, nextIp, relayIp, clientMac, broadcast);
+                        transactionId, secs, relayIp, clientMac, broadcast);
                 break;
             case DHCP_MESSAGE_TYPE_RELEASE:
                 if (serverIdentifier == null) {
@@ -1234,12 +1256,13 @@
      * parameters.
      */
     public static ByteBuffer buildOfferPacket(int encap, int transactionId,
-        boolean broadcast, Inet4Address serverIpAddr, Inet4Address clientIpAddr,
-        byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr,
-        List<Inet4Address> gateways, List<Inet4Address> dnsServers,
-        Inet4Address dhcpServerIdentifier, String domainName) {
+        boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp,
+        Inet4Address yourIp, byte[] mac, Integer timeout, Inet4Address netMask,
+        Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
+        Inet4Address dhcpServerIdentifier, String domainName, boolean metered) {
         DhcpPacket pkt = new DhcpOfferPacket(
-            transactionId, (short) 0, broadcast, serverIpAddr, INADDR_ANY, clientIpAddr, mac);
+                transactionId, (short) 0, broadcast, serverIpAddr, relayIp,
+                INADDR_ANY /* clientIp */, yourIp, mac);
         pkt.mGateways = gateways;
         pkt.mDnsServers = dnsServers;
         pkt.mLeaseTime = timeout;
@@ -1247,6 +1270,9 @@
         pkt.mServerIdentifier = dhcpServerIdentifier;
         pkt.mSubnetMask = netMask;
         pkt.mBroadcastAddress = bcAddr;
+        if (metered) {
+            pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED;
+        }
         return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
     }
 
@@ -1254,12 +1280,13 @@
      * Builds a DHCP-ACK packet from the required specified parameters.
      */
     public static ByteBuffer buildAckPacket(int encap, int transactionId,
-        boolean broadcast, Inet4Address serverIpAddr, Inet4Address clientIpAddr,
+        boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp, Inet4Address yourIp,
         byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr,
         List<Inet4Address> gateways, List<Inet4Address> dnsServers,
-        Inet4Address dhcpServerIdentifier, String domainName) {
+        Inet4Address dhcpServerIdentifier, String domainName, boolean metered) {
         DhcpPacket pkt = new DhcpAckPacket(
-            transactionId, (short) 0, broadcast, serverIpAddr, INADDR_ANY, clientIpAddr, mac);
+                transactionId, (short) 0, broadcast, serverIpAddr, relayIp,
+                INADDR_ANY /* clientIp */, yourIp, mac);
         pkt.mGateways = gateways;
         pkt.mDnsServers = dnsServers;
         pkt.mLeaseTime = timeout;
@@ -1267,6 +1294,9 @@
         pkt.mSubnetMask = netMask;
         pkt.mServerIdentifier = dhcpServerIdentifier;
         pkt.mBroadcastAddress = bcAddr;
+        if (metered) {
+            pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED;
+        }
         return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
     }
 
@@ -1274,10 +1304,11 @@
      * Builds a DHCP-NAK packet from the required specified parameters.
      */
     public static ByteBuffer buildNakPacket(int encap, int transactionId, Inet4Address serverIpAddr,
-            byte[] mac, boolean broadcast, String message) {
+            Inet4Address relayIp, byte[] mac, boolean broadcast, String message) {
         DhcpPacket pkt = new DhcpNakPacket(
-                transactionId, (short) 0, serverIpAddr, serverIpAddr, mac, broadcast);
+                transactionId, (short) 0, relayIp, mac, broadcast);
         pkt.mMessage = message;
+        pkt.mServerIdentifier = serverIpAddr;
         return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
     }
 
diff --git a/services/net/java/android/net/dhcp/DhcpServer.java b/services/net/java/android/net/dhcp/DhcpServer.java
index 095a5eb72..da8c8bb 100644
--- a/services/net/java/android/net/dhcp/DhcpServer.java
+++ b/services/net/java/android/net/dhcp/DhcpServer.java
@@ -351,12 +351,10 @@
                 mServingParams.getServerInet4Addr(), mServingParams.serverAddr.getPrefixLength());
         final ByteBuffer offerPacket = DhcpPacket.buildOfferPacket(
                 ENCAP_BOOTP, request.mTransId, broadcastFlag, mServingParams.getServerInet4Addr(),
-                lease.getNetAddr(), request.mClientMac, timeout,
-                prefixMask,
-                broadcastAddr,
-                new ArrayList<>(mServingParams.defaultRouters),
+                request.mRelayIp, lease.getNetAddr(), request.mClientMac, timeout, prefixMask,
+                broadcastAddr, new ArrayList<>(mServingParams.defaultRouters),
                 new ArrayList<>(mServingParams.dnsServers),
-                mServingParams.getServerInet4Addr(), null /* domainName */);
+                mServingParams.getServerInet4Addr(), null /* domainName */, mServingParams.metered);
 
         return transmitOfferOrAckPacket(offerPacket, request, lease, clientMac, broadcastFlag);
     }
@@ -368,12 +366,12 @@
         final boolean broadcastFlag = getBroadcastFlag(request, lease);
         final int timeout = getLeaseTimeout(lease);
         final ByteBuffer ackPacket = DhcpPacket.buildAckPacket(ENCAP_BOOTP, request.mTransId,
-                broadcastFlag, mServingParams.getServerInet4Addr(), lease.getNetAddr(),
-                request.mClientMac, timeout, mServingParams.getPrefixMaskAsAddress(),
-                mServingParams.getBroadcastAddress(),
+                broadcastFlag, mServingParams.getServerInet4Addr(), request.mRelayIp,
+                lease.getNetAddr(), request.mClientMac, timeout,
+                mServingParams.getPrefixMaskAsAddress(), mServingParams.getBroadcastAddress(),
                 new ArrayList<>(mServingParams.defaultRouters),
                 new ArrayList<>(mServingParams.dnsServers),
-                mServingParams.getServerInet4Addr(), null /* domainName */);
+                mServingParams.getServerInet4Addr(), null /* domainName */, mServingParams.metered);
 
         return transmitOfferOrAckPacket(ackPacket, request, lease, clientMac, broadcastFlag);
     }
@@ -383,7 +381,7 @@
         // Always set broadcast flag for NAK: client may not have a correct IP
         final ByteBuffer nakPacket = DhcpPacket.buildNakPacket(
                 ENCAP_BOOTP, request.mTransId, mServingParams.getServerInet4Addr(),
-                request.mClientMac, true /* broadcast */, message);
+                request.mRelayIp, request.mClientMac, true /* broadcast */, message);
 
         final Inet4Address dst = isEmpty(request.mRelayIp)
                 ? (Inet4Address) Inet4Address.ALL
diff --git a/services/net/java/android/net/dhcp/DhcpServingParams.java b/services/net/java/android/net/dhcp/DhcpServingParams.java
index 6d58bc6..df15ba1 100644
--- a/services/net/java/android/net/dhcp/DhcpServingParams.java
+++ b/services/net/java/android/net/dhcp/DhcpServingParams.java
@@ -76,6 +76,11 @@
     public final int linkMtu;
 
     /**
+     * Indicates whether the DHCP server should send the ANDROID_METERED vendor-specific option.
+     */
+    public final boolean metered;
+
+    /**
      * Checked exception thrown when some parameters used to build {@link DhcpServingParams} are
      * missing or invalid.
      */
@@ -88,13 +93,14 @@
     private DhcpServingParams(@NonNull LinkAddress serverAddr,
             @NonNull Set<Inet4Address> defaultRouters,
             @NonNull Set<Inet4Address> dnsServers, @NonNull Set<Inet4Address> excludedAddrs,
-            long dhcpLeaseTimeSecs, int linkMtu) {
+            long dhcpLeaseTimeSecs, int linkMtu, boolean metered) {
         this.serverAddr = serverAddr;
         this.defaultRouters = defaultRouters;
         this.dnsServers = dnsServers;
         this.excludedAddrs = excludedAddrs;
         this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs;
         this.linkMtu = linkMtu;
+        this.metered = metered;
     }
 
     @NonNull
@@ -134,6 +140,7 @@
         private Set<Inet4Address> excludedAddrs;
         private long dhcpLeaseTimeSecs;
         private int linkMtu = MTU_UNSET;
+        private boolean metered;
 
         /**
          * Set the server address and served prefix for the DHCP server.
@@ -248,6 +255,16 @@
         }
 
         /**
+         * Set whether the DHCP server should send the ANDROID_METERED vendor-specific option.
+         *
+         * <p>If not set, the default value is false.
+         */
+        public Builder setMetered(boolean metered) {
+            this.metered = metered;
+            return this;
+        }
+
+        /**
          * Create a new {@link DhcpServingParams} instance based on parameters set in the builder.
          *
          * <p>This method has no side-effects. If it does not throw, a valid
@@ -301,7 +318,7 @@
                     Collections.unmodifiableSet(new HashSet<>(defaultRouters)),
                     Collections.unmodifiableSet(new HashSet<>(dnsServers)),
                     Collections.unmodifiableSet(excl),
-                    dhcpLeaseTimeSecs, linkMtu);
+                    dhcpLeaseTimeSecs, linkMtu, metered);
         }
     }
 
diff --git a/tests/net/java/android/net/dhcp/DhcpPacketTest.java b/tests/net/java/android/net/dhcp/DhcpPacketTest.java
index 050183c..312b3d1 100644
--- a/tests/net/java/android/net/dhcp/DhcpPacketTest.java
+++ b/tests/net/java/android/net/dhcp/DhcpPacketTest.java
@@ -16,6 +16,8 @@
 
 package android.net.dhcp;
 
+import static android.net.NetworkUtils.getBroadcastAddress;
+import static android.net.NetworkUtils.getPrefixMaskAsInet4Address;
 import static android.net.dhcp.DhcpPacket.*;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -29,14 +31,15 @@
 import android.net.metrics.DhcpErrorEvent;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.filters.SmallTest;
-import android.system.OsConstants;
 
 import com.android.internal.util.HexDump;
 
+import java.io.ByteArrayOutputStream;
 import java.net.Inet4Address;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Random;
 
 import org.junit.Before;
@@ -47,13 +50,17 @@
 @SmallTest
 public class DhcpPacketTest {
 
-    private static Inet4Address SERVER_ADDR = v4Address("192.0.2.1");
-    private static Inet4Address CLIENT_ADDR = v4Address("192.0.2.234");
+    private static final Inet4Address SERVER_ADDR = v4Address("192.0.2.1");
+    private static final Inet4Address CLIENT_ADDR = v4Address("192.0.2.234");
+    private static final int PREFIX_LENGTH = 22;
+    private static final Inet4Address NETMASK = getPrefixMaskAsInet4Address(PREFIX_LENGTH);
+    private static final Inet4Address BROADCAST_ADDR = getBroadcastAddress(
+            SERVER_ADDR, PREFIX_LENGTH);
     // Use our own empty address instead of Inet4Address.ANY or INADDR_ANY to ensure that the code
     // doesn't use == instead of equals when comparing addresses.
-    private static Inet4Address ANY = (Inet4Address) v4Address("0.0.0.0");
+    private static final Inet4Address ANY = (Inet4Address) v4Address("0.0.0.0");
 
-    private static byte[] CLIENT_MAC = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+    private static final byte[] CLIENT_MAC = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
 
     private static final Inet4Address v4Address(String addrString) throws IllegalArgumentException {
         return (Inet4Address) NetworkUtils.numericToInetAddress(addrString);
@@ -952,4 +959,96 @@
                 "\nActual:\n  " + Arrays.toString(actual);
         assertTrue(msg, Arrays.equals(expected, actual));
     }
+
+    public void checkBuildOfferPacket(int leaseTimeSecs) throws Exception {
+        final int renewalTime = (int) (Integer.toUnsignedLong(leaseTimeSecs) / 2);
+        final int rebindingTime = (int) (Integer.toUnsignedLong(leaseTimeSecs) * 875 / 1000);
+        final int transactionId = 0xdeadbeef;
+
+        final ByteBuffer packet = DhcpPacket.buildOfferPacket(
+                DhcpPacket.ENCAP_BOOTP, transactionId, false /* broadcast */,
+                SERVER_ADDR, INADDR_ANY /* relayIp */, CLIENT_ADDR /* yourIp */,
+                CLIENT_MAC, leaseTimeSecs, NETMASK /* netMask */,
+                BROADCAST_ADDR /* bcAddr */, Collections.singletonList(SERVER_ADDR) /* gateways */,
+                Collections.singletonList(SERVER_ADDR) /* dnsServers */,
+                SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, false /* metered */);
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        // BOOTP headers
+        bos.write(new byte[] {
+                (byte) 0x02, (byte) 0x01, (byte) 0x06, (byte) 0x00,
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // ciaddr
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        });
+        // yiaddr
+        bos.write(CLIENT_ADDR.getAddress());
+        // siaddr
+        bos.write(SERVER_ADDR.getAddress());
+        // giaddr
+        bos.write(INADDR_ANY.getAddress());
+        // chaddr
+        bos.write(CLIENT_MAC);
+
+        // Padding
+        bos.write(new byte[202]);
+
+        // Options
+        bos.write(new byte[]{
+                // Magic cookie 0x63825363.
+                (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63,
+                // Message type OFFER.
+                (byte) 0x35, (byte) 0x01, (byte) 0x02,
+        });
+        // Server ID
+        bos.write(new byte[] { (byte) 0x36, (byte) 0x04 });
+        bos.write(SERVER_ADDR.getAddress());
+        // Lease time
+        bos.write(new byte[] { (byte) 0x33, (byte) 0x04 });
+        bos.write(intToByteArray(leaseTimeSecs));
+        if (leaseTimeSecs != INFINITE_LEASE) {
+            // Renewal time
+            bos.write(new byte[]{(byte) 0x3a, (byte) 0x04});
+            bos.write(intToByteArray(renewalTime));
+            // Rebinding time
+            bos.write(new byte[]{(byte) 0x3b, (byte) 0x04});
+            bos.write(intToByteArray(rebindingTime));
+        }
+        // Subnet mask
+        bos.write(new byte[] { (byte) 0x01, (byte) 0x04 });
+        bos.write(NETMASK.getAddress());
+        // Broadcast address
+        bos.write(new byte[] { (byte) 0x1c, (byte) 0x04 });
+        bos.write(BROADCAST_ADDR.getAddress());
+        // Router
+        bos.write(new byte[] { (byte) 0x03, (byte) 0x04 });
+        bos.write(SERVER_ADDR.getAddress());
+        // Nameserver
+        bos.write(new byte[] { (byte) 0x06, (byte) 0x04 });
+        bos.write(SERVER_ADDR.getAddress());
+        // End options.
+        bos.write(0xff);
+
+        final byte[] expected = bos.toByteArray();
+        assertTrue((expected.length & 1) == 0);
+
+        final byte[] actual = new byte[packet.limit()];
+        packet.get(actual);
+        final String msg = "Expected:\n  " + HexDump.dumpHexString(expected) +
+                "\nActual:\n  " + HexDump.dumpHexString(actual);
+        assertTrue(msg, Arrays.equals(expected, actual));
+    }
+
+    @Test
+    public void testOfferPacket() throws Exception {
+        checkBuildOfferPacket(3600);
+        checkBuildOfferPacket(Integer.MAX_VALUE);
+        checkBuildOfferPacket(0x80000000);
+        checkBuildOfferPacket(INFINITE_LEASE);
+    }
+
+    private static byte[] intToByteArray(int val) {
+        return ByteBuffer.allocate(4).putInt(val).array();
+    }
 }