1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.net.module.util.ip;
18 
19 import static com.android.net.module.util.netlink.ConntrackMessage.DYING_MASK;
20 import static com.android.net.module.util.netlink.ConntrackMessage.ESTABLISHED_MASK;
21 
22 import android.annotation.NonNull;
23 import android.os.Handler;
24 import android.system.OsConstants;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.net.module.util.SharedLog;
28 import com.android.net.module.util.netlink.ConntrackMessage;
29 import com.android.net.module.util.netlink.NetlinkConstants;
30 import com.android.net.module.util.netlink.NetlinkMessage;
31 
32 import java.util.Objects;
33 
34 
35 /**
36  * ConntrackMonitor.
37  *
38  * Monitors the netfilter conntrack notifications and presents to callers
39  * ConntrackEvents describing each event.
40  *
41  * @hide
42  */
43 public class ConntrackMonitor extends NetlinkMonitor {
44     private static final String TAG = ConntrackMonitor.class.getSimpleName();
45     private static final boolean DBG = false;
46     private static final boolean VDBG = false;
47 
48     // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h
49     public static final int NF_NETLINK_CONNTRACK_NEW = 1;
50     public static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
51     public static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
52 
53     // The socket receive buffer size in bytes. If too many conntrack messages are sent too
54     // quickly, the conntrack messages can overflow the socket receive buffer. This can happen
55     // if too many connections are disconnected by losing network and so on. Use a large-enough
56     // buffer to avoid the error ENOBUFS while listening to the conntrack messages.
57     private static final int SOCKET_RECV_BUFSIZE = 6 * 1024 * 1024;
58 
59     /**
60      * A class for describing parsed netfilter conntrack events.
61      */
62     public static class ConntrackEvent {
63         /**
64          * Conntrack event type.
65          */
66         public final short msgType;
67         /**
68          * Original direction conntrack tuple.
69          */
70         public final ConntrackMessage.Tuple tupleOrig;
71         /**
72          * Reply direction conntrack tuple.
73          */
74         public final ConntrackMessage.Tuple tupleReply;
75         /**
76          * Connection status. A bitmask of ip_conntrack_status enum flags.
77          */
78         public final int status;
79         /**
80          * Conntrack timeout.
81          */
82         public final int timeoutSec;
83 
ConntrackEvent(ConntrackMessage msg)84         public ConntrackEvent(ConntrackMessage msg) {
85             this.msgType = msg.getHeader().nlmsg_type;
86             this.tupleOrig = msg.tupleOrig;
87             this.tupleReply = msg.tupleReply;
88             this.status = msg.status;
89             this.timeoutSec = msg.timeoutSec;
90         }
91 
92         @VisibleForTesting
ConntrackEvent(short msgType, ConntrackMessage.Tuple tupleOrig, ConntrackMessage.Tuple tupleReply, int status, int timeoutSec)93         public ConntrackEvent(short msgType, ConntrackMessage.Tuple tupleOrig,
94                 ConntrackMessage.Tuple tupleReply, int status, int timeoutSec) {
95             this.msgType = msgType;
96             this.tupleOrig = tupleOrig;
97             this.tupleReply = tupleReply;
98             this.status = status;
99             this.timeoutSec = timeoutSec;
100         }
101 
102         @Override
103         @VisibleForTesting
equals(Object o)104         public boolean equals(Object o) {
105             if (!(o instanceof ConntrackEvent)) return false;
106             ConntrackEvent that = (ConntrackEvent) o;
107             return this.msgType == that.msgType
108                     && Objects.equals(this.tupleOrig, that.tupleOrig)
109                     && Objects.equals(this.tupleReply, that.tupleReply)
110                     && this.status == that.status
111                     && this.timeoutSec == that.timeoutSec;
112         }
113 
114         @Override
hashCode()115         public int hashCode() {
116             return Objects.hash(msgType, tupleOrig, tupleReply, status, timeoutSec);
117         }
118 
119         @Override
toString()120         public String toString() {
121             return "ConntrackEvent{"
122                     + "msg_type{"
123                     + NetlinkConstants.stringForNlMsgType(msgType, OsConstants.NETLINK_NETFILTER)
124                     + "}, "
125                     + "tuple_orig{" + tupleOrig + "}, "
126                     + "tuple_reply{" + tupleReply + "}, "
127                     + "status{"
128                     + status + "(" + ConntrackMessage.stringForIpConntrackStatus(status) + ")"
129                     + "}, "
130                     + "timeout_sec{" + Integer.toUnsignedLong(timeoutSec) + "}"
131                     + "}";
132         }
133 
134         /**
135          * Check the established NAT session conntrack message.
136          *
137          * @param msg the conntrack message to check.
138          * @return true if an established NAT message, false if not.
139          */
isEstablishedNatSession(@onNull ConntrackMessage msg)140         public static boolean isEstablishedNatSession(@NonNull ConntrackMessage msg) {
141             if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_NEW) return false;
142             if (msg.tupleOrig == null) return false;
143             if (msg.tupleReply == null) return false;
144             if (msg.timeoutSec == 0) return false;
145             if ((msg.status & ESTABLISHED_MASK) != ESTABLISHED_MASK) return false;
146 
147             return true;
148         }
149 
150         /**
151          * Check the dying NAT session conntrack message.
152          * Note that IPCTNL_MSG_CT_DELETE event has no CTA_TIMEOUT attribute.
153          *
154          * @param msg the conntrack message to check.
155          * @return true if a dying NAT message, false if not.
156          */
isDyingNatSession(@onNull ConntrackMessage msg)157         public static boolean isDyingNatSession(@NonNull ConntrackMessage msg) {
158             if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_DELETE) return false;
159             if (msg.tupleOrig == null) return false;
160             if (msg.tupleReply == null) return false;
161             if (msg.timeoutSec != 0) return false;
162             if ((msg.status & DYING_MASK) != DYING_MASK) return false;
163 
164             return true;
165         }
166     }
167 
168     /**
169      * A callback to caller for conntrack event.
170      */
171     public interface ConntrackEventConsumer {
172         /**
173          * Every conntrack event received on the netlink socket is passed in
174          * here.
175          */
accept(@onNull ConntrackEvent event)176         void accept(@NonNull ConntrackEvent event);
177     }
178 
179     private final ConntrackEventConsumer mConsumer;
180 
ConntrackMonitor(@onNull Handler h, @NonNull SharedLog log, @NonNull ConntrackEventConsumer cb)181     public ConntrackMonitor(@NonNull Handler h, @NonNull SharedLog log,
182             @NonNull ConntrackEventConsumer cb) {
183         super(h, log, TAG, OsConstants.NETLINK_NETFILTER, NF_NETLINK_CONNTRACK_NEW
184                 | NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY, SOCKET_RECV_BUFSIZE);
185         mConsumer = cb;
186     }
187 
188     @Override
processNetlinkMessage(NetlinkMessage nlMsg, final long whenMs)189     public void processNetlinkMessage(NetlinkMessage nlMsg, final long whenMs) {
190         if (!(nlMsg instanceof ConntrackMessage)) {
191             mLog.e("non-conntrack msg: " + nlMsg);
192             return;
193         }
194 
195         final ConntrackMessage conntrackMsg = (ConntrackMessage) nlMsg;
196         if (!(ConntrackEvent.isEstablishedNatSession(conntrackMsg)
197                 || ConntrackEvent.isDyingNatSession(conntrackMsg))) {
198             return;
199         }
200 
201         mConsumer.accept(new ConntrackEvent(conntrackMsg));
202     }
203 }
204