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