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 android.net.netlink.ConntrackMessage.DYING_MASK;
20 import static android.net.netlink.ConntrackMessage.ESTABLISHED_MASK;
21 
22 import android.net.netlink.ConntrackMessage;
23 import android.net.netlink.NetlinkConstants;
24 import android.net.netlink.NetlinkMessage;
25 import android.net.util.SharedLog;
26 import android.os.Handler;
27 import android.system.OsConstants;
28 
29 import androidx.annotation.NonNull;
30 
31 import com.android.internal.annotations.VisibleForTesting;
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     /**
55      * A class for describing parsed netfilter conntrack events.
56      */
57     public static class ConntrackEvent {
58         /**
59          * Conntrack event type.
60          */
61         public final short msgType;
62         /**
63          * Original direction conntrack tuple.
64          */
65         public final ConntrackMessage.Tuple tupleOrig;
66         /**
67          * Reply direction conntrack tuple.
68          */
69         public final ConntrackMessage.Tuple tupleReply;
70         /**
71          * Connection status. A bitmask of ip_conntrack_status enum flags.
72          */
73         public final int status;
74         /**
75          * Conntrack timeout.
76          */
77         public final int timeoutSec;
78 
ConntrackEvent(ConntrackMessage msg)79         public ConntrackEvent(ConntrackMessage msg) {
80             this.msgType = msg.getHeader().nlmsg_type;
81             this.tupleOrig = msg.tupleOrig;
82             this.tupleReply = msg.tupleReply;
83             this.status = msg.status;
84             this.timeoutSec = msg.timeoutSec;
85         }
86 
87         @VisibleForTesting
ConntrackEvent(short msgType, ConntrackMessage.Tuple tupleOrig, ConntrackMessage.Tuple tupleReply, int status, int timeoutSec)88         public ConntrackEvent(short msgType, ConntrackMessage.Tuple tupleOrig,
89                 ConntrackMessage.Tuple tupleReply, int status, int timeoutSec) {
90             this.msgType = msgType;
91             this.tupleOrig = tupleOrig;
92             this.tupleReply = tupleReply;
93             this.status = status;
94             this.timeoutSec = timeoutSec;
95         }
96 
97         @Override
98         @VisibleForTesting
equals(Object o)99         public boolean equals(Object o) {
100             if (!(o instanceof ConntrackEvent)) return false;
101             ConntrackEvent that = (ConntrackEvent) o;
102             return this.msgType == that.msgType
103                     && Objects.equals(this.tupleOrig, that.tupleOrig)
104                     && Objects.equals(this.tupleReply, that.tupleReply)
105                     && this.status == that.status
106                     && this.timeoutSec == that.timeoutSec;
107         }
108 
109         @Override
hashCode()110         public int hashCode() {
111             return Objects.hash(msgType, tupleOrig, tupleReply, status, timeoutSec);
112         }
113 
114         @Override
toString()115         public String toString() {
116             return "ConntrackEvent{"
117                     + "msg_type{"
118                     + NetlinkConstants.stringForNlMsgType(msgType, OsConstants.NETLINK_NETFILTER)
119                     + "}, "
120                     + "tuple_orig{" + tupleOrig + "}, "
121                     + "tuple_reply{" + tupleReply + "}, "
122                     + "status{"
123                     + status + "(" + ConntrackMessage.stringForIpConntrackStatus(status) + ")"
124                     + "}, "
125                     + "timeout_sec{" + Integer.toUnsignedLong(timeoutSec) + "}"
126                     + "}";
127         }
128 
129         /**
130          * Check the established NAT session conntrack message.
131          *
132          * @param msg the conntrack message to check.
133          * @return true if an established NAT message, false if not.
134          */
isEstablishedNatSession(@onNull ConntrackMessage msg)135         public static boolean isEstablishedNatSession(@NonNull ConntrackMessage msg) {
136             if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_NEW) return false;
137             if (msg.tupleOrig == null) return false;
138             if (msg.tupleReply == null) return false;
139             if (msg.timeoutSec == 0) return false;
140             if ((msg.status & ESTABLISHED_MASK) != ESTABLISHED_MASK) return false;
141 
142             return true;
143         }
144 
145         /**
146          * Check the dying NAT session conntrack message.
147          * Note that IPCTNL_MSG_CT_DELETE event has no CTA_TIMEOUT attribute.
148          *
149          * @param msg the conntrack message to check.
150          * @return true if a dying NAT message, false if not.
151          */
isDyingNatSession(@onNull ConntrackMessage msg)152         public static boolean isDyingNatSession(@NonNull ConntrackMessage msg) {
153             if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_DELETE) return false;
154             if (msg.tupleOrig == null) return false;
155             if (msg.tupleReply == null) return false;
156             if (msg.timeoutSec != 0) return false;
157             if ((msg.status & DYING_MASK) != DYING_MASK) return false;
158 
159             return true;
160         }
161     }
162 
163     /**
164      * A callback to caller for conntrack event.
165      */
166     public interface ConntrackEventConsumer {
167         /**
168          * Every conntrack event received on the netlink socket is passed in
169          * here.
170          */
accept(@onNull ConntrackEvent event)171         void accept(@NonNull ConntrackEvent event);
172     }
173 
174     private final ConntrackEventConsumer mConsumer;
175 
ConntrackMonitor(@onNull Handler h, @NonNull SharedLog log, @NonNull ConntrackEventConsumer cb)176     public ConntrackMonitor(@NonNull Handler h, @NonNull SharedLog log,
177             @NonNull ConntrackEventConsumer cb) {
178         super(h, log, TAG, OsConstants.NETLINK_NETFILTER, NF_NETLINK_CONNTRACK_NEW
179                 | NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
180         mConsumer = cb;
181     }
182 
183     @Override
processNetlinkMessage(NetlinkMessage nlMsg, final long whenMs)184     public void processNetlinkMessage(NetlinkMessage nlMsg, final long whenMs) {
185         if (!(nlMsg instanceof ConntrackMessage)) {
186             mLog.e("non-conntrack msg: " + nlMsg);
187             return;
188         }
189 
190         final ConntrackMessage conntrackMsg = (ConntrackMessage) nlMsg;
191         if (!(ConntrackEvent.isEstablishedNatSession(conntrackMsg)
192                 || ConntrackEvent.isDyingNatSession(conntrackMsg))) {
193             return;
194         }
195 
196         mConsumer.accept(new ConntrackEvent(conntrackMsg));
197     }
198 }
199