|
@@ -35,7 +35,8 @@ import (
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
type noticeLogger struct {
|
|
type noticeLogger struct {
|
|
|
- logDiagnostics int32
|
|
|
|
|
|
|
+ emitDiagnostics int32
|
|
|
|
|
+ emitNetworkParameters int32
|
|
|
mutex sync.Mutex
|
|
mutex sync.Mutex
|
|
|
writer io.Writer
|
|
writer io.Writer
|
|
|
homepageFilename string
|
|
homepageFilename string
|
|
@@ -53,23 +54,40 @@ var singletonNoticeLogger = noticeLogger{
|
|
|
writer: os.Stderr,
|
|
writer: os.Stderr,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// SetEmitDiagnosticNotices toggles whether diagnostic notices
|
|
|
|
|
-// are emitted. Diagnostic notices contain potentially sensitive
|
|
|
|
|
-// circumvention network information; only enable this in environments
|
|
|
|
|
-// where notices are handled securely (for example, don't include these
|
|
|
|
|
-// notices in log files which users could post to public forums).
|
|
|
|
|
-func SetEmitDiagnosticNotices(enable bool) {
|
|
|
|
|
- if enable {
|
|
|
|
|
- atomic.StoreInt32(&singletonNoticeLogger.logDiagnostics, 1)
|
|
|
|
|
|
|
+// SetEmitDiagnosticNotices toggles whether diagnostic notices are emitted;
|
|
|
|
|
+// and whether to include circumvention network parameters in diagnostics.
|
|
|
|
|
+//
|
|
|
|
|
+// Diagnostic notices contain potentially sensitive user information; and
|
|
|
|
|
+// sensitive circumvention network parameters, when enabled. Only enable this
|
|
|
|
|
+// in environments where notices are handled securely (for example, don't
|
|
|
|
|
+// include these notices in log files which users could post to public
|
|
|
|
|
+// forums).
|
|
|
|
|
+func SetEmitDiagnosticNotices(
|
|
|
|
|
+ emitDiagnostics bool, emitNetworkParameters bool) {
|
|
|
|
|
+
|
|
|
|
|
+ if emitDiagnostics {
|
|
|
|
|
+ atomic.StoreInt32(&singletonNoticeLogger.emitDiagnostics, 1)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ atomic.StoreInt32(&singletonNoticeLogger.emitDiagnostics, 0)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if emitNetworkParameters {
|
|
|
|
|
+ atomic.StoreInt32(&singletonNoticeLogger.emitNetworkParameters, 1)
|
|
|
} else {
|
|
} else {
|
|
|
- atomic.StoreInt32(&singletonNoticeLogger.logDiagnostics, 0)
|
|
|
|
|
|
|
+ atomic.StoreInt32(&singletonNoticeLogger.emitNetworkParameters, 0)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// GetEmitDiagnoticNotices returns the current state
|
|
// GetEmitDiagnoticNotices returns the current state
|
|
|
// of emitting diagnostic notices.
|
|
// of emitting diagnostic notices.
|
|
|
func GetEmitDiagnoticNotices() bool {
|
|
func GetEmitDiagnoticNotices() bool {
|
|
|
- return atomic.LoadInt32(&singletonNoticeLogger.logDiagnostics) == 1
|
|
|
|
|
|
|
+ return atomic.LoadInt32(&singletonNoticeLogger.emitDiagnostics) == 1
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// GetEmitNetworkParameters returns the current state
|
|
|
|
|
+// of emitting network parameters.
|
|
|
|
|
+func GetEmitNetworkParameters() bool {
|
|
|
|
|
+ return atomic.LoadInt32(&singletonNoticeLogger.emitNetworkParameters) == 1
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// SetNoticeWriter sets a target writer to receive notices. By default,
|
|
// SetNoticeWriter sets a target writer to receive notices. By default,
|
|
@@ -188,7 +206,7 @@ const (
|
|
|
// outputNotice encodes a notice in JSON and writes it to the output writer.
|
|
// outputNotice encodes a notice in JSON and writes it to the output writer.
|
|
|
func (nl *noticeLogger) outputNotice(noticeType string, noticeFlags uint32, args ...interface{}) {
|
|
func (nl *noticeLogger) outputNotice(noticeType string, noticeFlags uint32, args ...interface{}) {
|
|
|
|
|
|
|
|
- if (noticeFlags¬iceIsDiagnostic != 0) && atomic.LoadInt32(&nl.logDiagnostics) != 1 {
|
|
|
|
|
|
|
+ if (noticeFlags¬iceIsDiagnostic != 0) && !GetEmitDiagnoticNotices() {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -218,6 +236,11 @@ func (nl *noticeLogger) outputNotice(noticeType string, noticeFlags uint32, args
|
|
|
fmt.Sprintf("marshal notice failed: %s", common.ContextError(err)))
|
|
fmt.Sprintf("marshal notice failed: %s", common.ContextError(err)))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Ensure direct server IPs are not exposed in notices. The "net" package,
|
|
|
|
|
+ // and possibly other 3rd party packages, will include destination addresses
|
|
|
|
|
+ // in I/O error messages.
|
|
|
|
|
+ output = StripIPAddresses(output)
|
|
|
|
|
+
|
|
|
nl.mutex.Lock()
|
|
nl.mutex.Lock()
|
|
|
defer nl.mutex.Unlock()
|
|
defer nl.mutex.Unlock()
|
|
|
|
|
|
|
@@ -404,78 +427,81 @@ func NoticeAvailableEgressRegions(regions []string) {
|
|
|
func noticeWithDialParameters(noticeType string, dialParams *DialParameters) {
|
|
func noticeWithDialParameters(noticeType string, dialParams *DialParameters) {
|
|
|
|
|
|
|
|
args := []interface{}{
|
|
args := []interface{}{
|
|
|
- "ipAddress", dialParams.ServerEntry.IpAddress,
|
|
|
|
|
|
|
+ "serverID", dialParams.ServerEntry.GetDiagnosticID(),
|
|
|
"region", dialParams.ServerEntry.Region,
|
|
"region", dialParams.ServerEntry.Region,
|
|
|
"protocol", dialParams.TunnelProtocol,
|
|
"protocol", dialParams.TunnelProtocol,
|
|
|
"isReplay", dialParams.IsReplay,
|
|
"isReplay", dialParams.IsReplay,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if dialParams.SelectedSSHClientVersion {
|
|
|
|
|
- args = append(args, "SSHClientVersion", dialParams.SSHClientVersion)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if GetEmitNetworkParameters() {
|
|
|
|
|
|
|
|
- if dialParams.UpstreamProxyType != "" {
|
|
|
|
|
- args = append(args, "upstreamProxyType", dialParams.UpstreamProxyType)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if dialParams.SelectedSSHClientVersion {
|
|
|
|
|
+ args = append(args, "SSHClientVersion", dialParams.SSHClientVersion)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if dialParams.UpstreamProxyCustomHeaderNames != nil {
|
|
|
|
|
- args = append(args, "upstreamProxyCustomHeaderNames", strings.Join(dialParams.UpstreamProxyCustomHeaderNames, ","))
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if dialParams.UpstreamProxyType != "" {
|
|
|
|
|
+ args = append(args, "upstreamProxyType", dialParams.UpstreamProxyType)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if dialParams.MeekDialAddress != "" {
|
|
|
|
|
- args = append(args, "meekDialAddress", dialParams.MeekDialAddress)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if dialParams.UpstreamProxyCustomHeaderNames != nil {
|
|
|
|
|
+ args = append(args, "upstreamProxyCustomHeaderNames", strings.Join(dialParams.UpstreamProxyCustomHeaderNames, ","))
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- meekResolvedIPAddress := dialParams.MeekResolvedIPAddress.Load().(string)
|
|
|
|
|
- if meekResolvedIPAddress != "" {
|
|
|
|
|
- args = append(args, "meekResolvedIPAddress", meekResolvedIPAddress)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if dialParams.MeekDialAddress != "" {
|
|
|
|
|
+ args = append(args, "meekDialAddress", dialParams.MeekDialAddress)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if dialParams.MeekSNIServerName != "" {
|
|
|
|
|
- args = append(args, "meekSNIServerName", dialParams.MeekSNIServerName)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ meekResolvedIPAddress := dialParams.MeekResolvedIPAddress.Load().(string)
|
|
|
|
|
+ if meekResolvedIPAddress != "" {
|
|
|
|
|
+ args = append(args, "meekResolvedIPAddress", meekResolvedIPAddress)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if dialParams.MeekHostHeader != "" {
|
|
|
|
|
- args = append(args, "meekHostHeader", dialParams.MeekHostHeader)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if dialParams.MeekSNIServerName != "" {
|
|
|
|
|
+ args = append(args, "meekSNIServerName", dialParams.MeekSNIServerName)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // MeekTransformedHostName is meaningful when meek is used, which is when MeekDialAddress != ""
|
|
|
|
|
- if dialParams.MeekDialAddress != "" {
|
|
|
|
|
- args = append(args, "meekTransformedHostName", dialParams.MeekTransformedHostName)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if dialParams.MeekHostHeader != "" {
|
|
|
|
|
+ args = append(args, "meekHostHeader", dialParams.MeekHostHeader)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if dialParams.SelectedUserAgent {
|
|
|
|
|
- args = append(args, "userAgent", dialParams.UserAgent)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // MeekTransformedHostName is meaningful when meek is used, which is when MeekDialAddress != ""
|
|
|
|
|
+ if dialParams.MeekDialAddress != "" {
|
|
|
|
|
+ args = append(args, "meekTransformedHostName", dialParams.MeekTransformedHostName)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if dialParams.SelectedTLSProfile {
|
|
|
|
|
- args = append(args, "TLSProfile", dialParams.TLSProfile)
|
|
|
|
|
- args = append(args, "TLSVersion", dialParams.TLSVersion)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if dialParams.SelectedUserAgent {
|
|
|
|
|
+ args = append(args, "userAgent", dialParams.UserAgent)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if dialParams.DialPortNumber != "" {
|
|
|
|
|
- args = append(args, "dialPortNumber", dialParams.DialPortNumber)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if dialParams.SelectedTLSProfile {
|
|
|
|
|
+ args = append(args, "TLSProfile", dialParams.TLSProfile)
|
|
|
|
|
+ args = append(args, "TLSVersion", dialParams.TLSVersion)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if dialParams.QUICVersion != "" {
|
|
|
|
|
- args = append(args, "QUICVersion", dialParams.QUICVersion)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if dialParams.DialPortNumber != "" {
|
|
|
|
|
+ args = append(args, "dialPortNumber", dialParams.DialPortNumber)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if dialParams.QUICDialSNIAddress != "" {
|
|
|
|
|
- args = append(args, "QUICDialSNIAddress", dialParams.QUICDialSNIAddress)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if dialParams.QUICVersion != "" {
|
|
|
|
|
+ args = append(args, "QUICVersion", dialParams.QUICVersion)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if dialParams.DialConnMetrics != nil {
|
|
|
|
|
- metrics := dialParams.DialConnMetrics.GetMetrics()
|
|
|
|
|
- for name, value := range metrics {
|
|
|
|
|
- args = append(args, name, value)
|
|
|
|
|
|
|
+ if dialParams.QUICDialSNIAddress != "" {
|
|
|
|
|
+ args = append(args, "QUICDialSNIAddress", dialParams.QUICDialSNIAddress)
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- if dialParams.ObfuscatedSSHConnMetrics != nil {
|
|
|
|
|
- metrics := dialParams.ObfuscatedSSHConnMetrics.GetMetrics()
|
|
|
|
|
- for name, value := range metrics {
|
|
|
|
|
- args = append(args, name, value)
|
|
|
|
|
|
|
+ if dialParams.DialConnMetrics != nil {
|
|
|
|
|
+ metrics := dialParams.DialConnMetrics.GetMetrics()
|
|
|
|
|
+ for name, value := range metrics {
|
|
|
|
|
+ args = append(args, name, value)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if dialParams.ObfuscatedSSHConnMetrics != nil {
|
|
|
|
|
+ metrics := dialParams.ObfuscatedSSHConnMetrics.GetMetrics()
|
|
|
|
|
+ for name, value := range metrics {
|
|
|
|
|
+ args = append(args, name, value)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -505,10 +531,10 @@ func NoticeRequestedTactics(dialParams *DialParameters) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// NoticeActiveTunnel is a successful connection that is used as an active tunnel for port forwarding
|
|
// NoticeActiveTunnel is a successful connection that is used as an active tunnel for port forwarding
|
|
|
-func NoticeActiveTunnel(ipAddress, protocol string, isTCS bool) {
|
|
|
|
|
|
|
+func NoticeActiveTunnel(serverID, protocol string, isTCS bool) {
|
|
|
singletonNoticeLogger.outputNotice(
|
|
singletonNoticeLogger.outputNotice(
|
|
|
"ActiveTunnel", noticeIsDiagnostic,
|
|
"ActiveTunnel", noticeIsDiagnostic,
|
|
|
- "ipAddress", ipAddress,
|
|
|
|
|
|
|
+ "serverID", serverID,
|
|
|
"protocol", protocol,
|
|
"protocol", protocol,
|
|
|
"isTCS", isTCS)
|
|
"isTCS", isTCS)
|
|
|
}
|
|
}
|
|
@@ -642,25 +668,24 @@ func NoticeClientUpgradeDownloaded(filename string) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// NoticeBytesTransferred reports how many tunneled bytes have been
|
|
// NoticeBytesTransferred reports how many tunneled bytes have been
|
|
|
-// transferred since the last NoticeBytesTransferred, for the tunnel
|
|
|
|
|
-// to the server at ipAddress. This is not a diagnostic notice: the
|
|
|
|
|
-// user app has requested this notice with EmitBytesTransferred for
|
|
|
|
|
-// functionality such as traffic display; and this frequent notice
|
|
|
|
|
-// is not intended to be included with feedback.
|
|
|
|
|
-func NoticeBytesTransferred(ipAddress string, sent, received int64) {
|
|
|
|
|
|
|
+// transferred since the last NoticeBytesTransferred. This is not a diagnostic
|
|
|
|
|
+// notice: the user app has requested this notice with EmitBytesTransferred
|
|
|
|
|
+// for functionality such as traffic display; and this frequent notice is not
|
|
|
|
|
+// intended to be included with feedback.
|
|
|
|
|
+func NoticeBytesTransferred(serverID string, sent, received int64) {
|
|
|
singletonNoticeLogger.outputNotice(
|
|
singletonNoticeLogger.outputNotice(
|
|
|
"BytesTransferred", 0,
|
|
"BytesTransferred", 0,
|
|
|
|
|
+ "serverID", serverID,
|
|
|
"sent", sent,
|
|
"sent", sent,
|
|
|
"received", received)
|
|
"received", received)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// NoticeTotalBytesTransferred reports how many tunneled bytes have been
|
|
// NoticeTotalBytesTransferred reports how many tunneled bytes have been
|
|
|
-// transferred in total up to this point, for the tunnel to the server
|
|
|
|
|
-// at ipAddress. This is a diagnostic notice.
|
|
|
|
|
-func NoticeTotalBytesTransferred(ipAddress string, sent, received int64) {
|
|
|
|
|
|
|
+// transferred in total up to this point. This is a diagnostic notice.
|
|
|
|
|
+func NoticeTotalBytesTransferred(serverID string, sent, received int64) {
|
|
|
singletonNoticeLogger.outputNotice(
|
|
singletonNoticeLogger.outputNotice(
|
|
|
"TotalBytesTransferred", noticeIsDiagnostic,
|
|
"TotalBytesTransferred", noticeIsDiagnostic,
|
|
|
- "ipAddress", ipAddress,
|
|
|
|
|
|
|
+ "serverID", serverID,
|
|
|
"sent", sent,
|
|
"sent", sent,
|
|
|
"received", received)
|
|
"received", received)
|
|
|
}
|
|
}
|
|
@@ -701,6 +726,9 @@ func NoticeExiting() {
|
|
|
|
|
|
|
|
// NoticeRemoteServerListResourceDownloadedBytes reports remote server list download progress.
|
|
// NoticeRemoteServerListResourceDownloadedBytes reports remote server list download progress.
|
|
|
func NoticeRemoteServerListResourceDownloadedBytes(url string, bytes int64) {
|
|
func NoticeRemoteServerListResourceDownloadedBytes(url string, bytes int64) {
|
|
|
|
|
+ if !GetEmitNetworkParameters() {
|
|
|
|
|
+ url = "<url>"
|
|
|
|
|
+ }
|
|
|
singletonNoticeLogger.outputNotice(
|
|
singletonNoticeLogger.outputNotice(
|
|
|
"RemoteServerListResourceDownloadedBytes", noticeIsDiagnostic,
|
|
"RemoteServerListResourceDownloadedBytes", noticeIsDiagnostic,
|
|
|
"url", url,
|
|
"url", url,
|
|
@@ -710,6 +738,9 @@ func NoticeRemoteServerListResourceDownloadedBytes(url string, bytes int64) {
|
|
|
// NoticeRemoteServerListResourceDownloaded indicates that a remote server list download
|
|
// NoticeRemoteServerListResourceDownloaded indicates that a remote server list download
|
|
|
// completed successfully.
|
|
// completed successfully.
|
|
|
func NoticeRemoteServerListResourceDownloaded(url string) {
|
|
func NoticeRemoteServerListResourceDownloaded(url string) {
|
|
|
|
|
+ if !GetEmitNetworkParameters() {
|
|
|
|
|
+ url = "<url>"
|
|
|
|
|
+ }
|
|
|
singletonNoticeLogger.outputNotice(
|
|
singletonNoticeLogger.outputNotice(
|
|
|
"RemoteServerListResourceDownloaded", noticeIsDiagnostic,
|
|
"RemoteServerListResourceDownloaded", noticeIsDiagnostic,
|
|
|
"url", url)
|
|
"url", url)
|
|
@@ -757,10 +788,10 @@ func NoticeNetworkID(networkID string) {
|
|
|
"NetworkID", 0, "ID", networkID)
|
|
"NetworkID", 0, "ID", networkID)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func NoticeLivenessTest(ipAddress string, metrics *livenessTestMetrics, success bool) {
|
|
|
|
|
|
|
+func NoticeLivenessTest(serverID string, metrics *livenessTestMetrics, success bool) {
|
|
|
singletonNoticeLogger.outputNotice(
|
|
singletonNoticeLogger.outputNotice(
|
|
|
"LivenessTest", noticeIsDiagnostic,
|
|
"LivenessTest", noticeIsDiagnostic,
|
|
|
- "ipAddress", ipAddress,
|
|
|
|
|
|
|
+ "serverID", serverID,
|
|
|
"metrics", metrics,
|
|
"metrics", metrics,
|
|
|
"success", success)
|
|
"success", success)
|
|
|
}
|
|
}
|
|
@@ -779,6 +810,13 @@ func NoticeEstablishTunnelTimeout(timeout time.Duration) {
|
|
|
"timeout", timeout)
|
|
"timeout", timeout)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func NoticeFragmentor(serverID string, message string) {
|
|
|
|
|
+ singletonNoticeLogger.outputNotice(
|
|
|
|
|
+ "Fragmentor", noticeIsDiagnostic,
|
|
|
|
|
+ "serverID", serverID,
|
|
|
|
|
+ "message", message)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
type repetitiveNoticeState struct {
|
|
type repetitiveNoticeState struct {
|
|
|
message string
|
|
message string
|
|
|
repeats int
|
|
repeats int
|