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