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